Skip to content

Commit 9651a21

Browse files
Merge pull request #1108 from hinashi/feature/type_checking
Added type checking to trigger model
2 parents 8df9939 + a8fd401 commit 9651a21

File tree

3 files changed

+89
-78
lines changed

3 files changed

+89
-78
lines changed

trigger/models.py

+87-75
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import TYPE_CHECKING
2+
13
from django.db import models
24

35
from acl.models import ACLBase
@@ -8,6 +10,9 @@
810
from entry.api_v2.serializers import EntryUpdateSerializer
911
from entry.models import Attribute, Entry
1012

13+
if TYPE_CHECKING:
14+
from django.db.models import Manager
15+
1116

1217
## These are internal classes for AirOne trigger and action
1318
class InputTriggerCondition(object):
@@ -158,82 +163,13 @@ def _do_get_value(input_value, attr_type):
158163
return _do_get_value(raw_input_value, self.attr.type)
159164

160165

161-
class TriggerAction(models.Model):
162-
condition = models.ForeignKey("TriggerParent", on_delete=models.CASCADE, related_name="actions")
163-
attr = models.ForeignKey("entity.EntityAttr", on_delete=models.CASCADE)
164-
165-
def save_actions(self, input: InputTriggerAction):
166-
for input_action_value in input.values:
167-
params = {
168-
"action": self,
169-
"str_cond": input_action_value.str_cond,
170-
"ref_cond": input_action_value.ref_cond,
171-
"bool_cond": input_action_value.bool_cond,
172-
}
173-
TriggerActionValue.objects.create(**params)
174-
175-
def get_serializer_acceptable_value(self, value=None, attr_type=None):
176-
"""
177-
This converts TriggerActionValue to the value that EntryUpdateSerializer can accept.
178-
"""
179-
if not attr_type:
180-
attr_type = self.attr.type
181-
182-
value = value or self.values.first()
183-
if attr_type & AttrTypeValue["array"]:
184-
return [
185-
self.get_serializer_acceptable_value(x, attr_type ^ AttrTypeValue["array"])
186-
for x in self.values.all()
187-
]
188-
elif attr_type == AttrTypeValue["boolean"]:
189-
return value.bool_cond
190-
elif attr_type == AttrTypeValue["named_object"]:
191-
value.ref_cond.id if isinstance(value.ref_cond, Entry) else None
192-
return {
193-
"name": value.str_cond,
194-
"id": value.ref_cond.id,
195-
}
196-
elif attr_type == AttrTypeValue["string"]:
197-
return value.str_cond
198-
elif attr_type == AttrTypeValue["text"]:
199-
return value.str_cond
200-
elif attr_type == AttrTypeValue["object"]:
201-
return value.ref_cond.id if isinstance(value.ref_cond, Entry) else None
202-
203-
def run(self, user, entry, call_stacks=[]):
204-
# When self.id contains in call_stacks, it means that this action is already invoked.
205-
# This prevents infinite loop.
206-
if self.id in call_stacks:
207-
return
208-
209-
# update specified Entry by configured attribute value
210-
setting_data = {
211-
"id": entry.id,
212-
"name": entry.name,
213-
"attrs": [{"id": self.attr.id, "value": self.get_serializer_acceptable_value()}],
214-
"delay_trigger": False,
215-
"call_stacks": [*call_stacks, self.id],
216-
}
217-
serializer = EntryUpdateSerializer(
218-
instance=entry, data=setting_data, context={"request": DRFRequest(user)}
219-
)
220-
if serializer:
221-
serializer.is_valid(raise_exception=True)
222-
serializer.save()
223-
224-
225-
class TriggerActionValue(models.Model):
226-
action = models.ForeignKey(TriggerAction, on_delete=models.CASCADE, related_name="values")
227-
str_cond = models.TextField(blank=True, null=True)
228-
ref_cond = models.ForeignKey("entry.Entry", on_delete=models.SET_NULL, null=True, blank=True)
229-
bool_cond = models.BooleanField(default=False)
230-
231-
# TODO: Add method to register value to Attribute when action is invoked
232-
233-
234166
class TriggerParent(models.Model):
235167
entity = models.ForeignKey("entity.Entity", on_delete=models.CASCADE)
236168

169+
if TYPE_CHECKING:
170+
conditions: Manager["TriggerCondition"]
171+
actions: Manager["TriggerAction"]
172+
237173
def is_match_condition(self, inputs: list[InputTriggerCondition]):
238174
if all([c.is_same_condition(inputs) for c in self.conditions.all()]):
239175
return True
@@ -251,7 +187,7 @@ def save_conditions(self, inputs: list[InputTriggerCondition]):
251187
if not TriggerCondition.objects.filter(**params).exists():
252188
TriggerCondition.objects.create(**params)
253189

254-
def get_actions(self, recv_attrs: list) -> list[TriggerAction]:
190+
def get_actions(self, recv_attrs: list) -> list["TriggerAction"]:
255191
"""
256192
This method checks whether specified entity's Trigger is invoked by recv_attrs context.
257193
The recv_attrs format should be compatible with APIv2 standard.
@@ -261,7 +197,7 @@ def get_actions(self, recv_attrs: list) -> list[TriggerAction]:
261197
context to reduce DB query to get it from Attribute instance.
262198
"""
263199

264-
def _is_match(condition):
200+
def _is_match(condition: TriggerCondition):
265201
for attr_info in [x for x in recv_attrs if x["attr_id"] == condition.attr.id]:
266202
if condition.is_match_condition(attr_info["value"]):
267203
return True
@@ -446,3 +382,79 @@ def get_invoked_actions(cls, entity: Entity, recv_data: list):
446382
actions += parent_condition.get_actions(params)
447383

448384
return actions
385+
386+
387+
class TriggerAction(models.Model):
388+
condition = models.ForeignKey(TriggerParent, on_delete=models.CASCADE, related_name="actions")
389+
attr = models.ForeignKey("entity.EntityAttr", on_delete=models.CASCADE)
390+
391+
if TYPE_CHECKING:
392+
values: Manager["TriggerActionValue"]
393+
394+
def save_actions(self, input: InputTriggerAction):
395+
for input_action_value in input.values:
396+
params = {
397+
"action": self,
398+
"str_cond": input_action_value.str_cond,
399+
"ref_cond": input_action_value.ref_cond,
400+
"bool_cond": input_action_value.bool_cond,
401+
}
402+
TriggerActionValue.objects.create(**params)
403+
404+
def get_serializer_acceptable_value(self, value=None, attr_type=None):
405+
"""
406+
This converts TriggerActionValue to the value that EntryUpdateSerializer can accept.
407+
"""
408+
if not attr_type:
409+
attr_type = self.attr.type
410+
411+
value = value or self.values.first()
412+
if attr_type & AttrTypeValue["array"]:
413+
return [
414+
self.get_serializer_acceptable_value(x, attr_type ^ AttrTypeValue["array"])
415+
for x in self.values.all()
416+
]
417+
elif attr_type == AttrTypeValue["boolean"]:
418+
return value.bool_cond
419+
elif attr_type == AttrTypeValue["named_object"]:
420+
value.ref_cond.id if isinstance(value.ref_cond, Entry) else None
421+
return {
422+
"name": value.str_cond,
423+
"id": value.ref_cond.id,
424+
}
425+
elif attr_type == AttrTypeValue["string"]:
426+
return value.str_cond
427+
elif attr_type == AttrTypeValue["text"]:
428+
return value.str_cond
429+
elif attr_type == AttrTypeValue["object"]:
430+
return value.ref_cond.id if isinstance(value.ref_cond, Entry) else None
431+
432+
def run(self, user, entry, call_stacks=[]):
433+
# When self.id contains in call_stacks, it means that this action is already invoked.
434+
# This prevents infinite loop.
435+
if self.id in call_stacks:
436+
return
437+
438+
# update specified Entry by configured attribute value
439+
setting_data = {
440+
"id": entry.id,
441+
"name": entry.name,
442+
"attrs": [{"id": self.attr.id, "value": self.get_serializer_acceptable_value()}],
443+
"delay_trigger": False,
444+
"call_stacks": [*call_stacks, self.id],
445+
}
446+
serializer = EntryUpdateSerializer(
447+
instance=entry, data=setting_data, context={"request": DRFRequest(user)}
448+
)
449+
if serializer:
450+
serializer.is_valid(raise_exception=True)
451+
serializer.save()
452+
453+
454+
class TriggerActionValue(models.Model):
455+
action = models.ForeignKey(TriggerAction, on_delete=models.CASCADE, related_name="values")
456+
str_cond = models.TextField(blank=True, null=True)
457+
ref_cond = models.ForeignKey("entry.Entry", on_delete=models.SET_NULL, null=True, blank=True)
458+
bool_cond = models.BooleanField(default=False)
459+
460+
# TODO: Add method to register value to Attribute when action is invoked

trigger/tests/test_api_v2.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@
88
TriggerCondition,
99
TriggerParent,
1010
)
11-
from user.models import User
1211

1312

1413
class APITest(AironeViewTest):
1514
def setUp(self):
1615
super(APITest, self).setUp()
1716

18-
self.user: User = self.guest_login()
17+
self.user = self.guest_login()
1918

2019
# create Entities that are used for each tests
2120
self.entity_people = self.create_entity(

trigger/tests/test_models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class ModelTest(AironeTestCase):
2020
def setUp(self):
2121
super(ModelTest, self).setUp()
2222

23-
self.user: User = User.objects.create(username="test")
23+
self.user = User.objects.create(username="test")
2424
self.entity_ref = self.create_entity(self.user, "test_entity_ref")
2525
self.entity = self.create_entity(
2626
self.user,

0 commit comments

Comments
 (0)