wbcompliance 2.2.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wbcompliance/__init__.py +1 -0
- wbcompliance/admin/__init__.py +16 -0
- wbcompliance/admin/compliance_form.py +56 -0
- wbcompliance/admin/compliance_task.py +135 -0
- wbcompliance/admin/compliance_type.py +8 -0
- wbcompliance/admin/risk_management/__init__.py +3 -0
- wbcompliance/admin/risk_management/checks.py +7 -0
- wbcompliance/admin/risk_management/incidents.py +50 -0
- wbcompliance/admin/risk_management/rules.py +63 -0
- wbcompliance/admin/utils.py +46 -0
- wbcompliance/apps.py +14 -0
- wbcompliance/factories/__init__.py +21 -0
- wbcompliance/factories/compliance.py +246 -0
- wbcompliance/factories/risk_management/__init__.py +12 -0
- wbcompliance/factories/risk_management/backends.py +42 -0
- wbcompliance/factories/risk_management/checks.py +12 -0
- wbcompliance/factories/risk_management/incidents.py +84 -0
- wbcompliance/factories/risk_management/rules.py +100 -0
- wbcompliance/filters/__init__.py +2 -0
- wbcompliance/filters/compliances.py +189 -0
- wbcompliance/filters/risk_management/__init__.py +3 -0
- wbcompliance/filters/risk_management/checks.py +22 -0
- wbcompliance/filters/risk_management/incidents.py +113 -0
- wbcompliance/filters/risk_management/rules.py +110 -0
- wbcompliance/filters/risk_management/tables.py +112 -0
- wbcompliance/filters/risk_management/utils.py +3 -0
- wbcompliance/management/__init__.py +10 -0
- wbcompliance/migrations/0001_initial_squashed_squashed_0010_alter_checkedobjectincidentrelationship_resolved_by_and_more.py +1744 -0
- wbcompliance/migrations/0011_alter_riskrule_parameters.py +21 -0
- wbcompliance/migrations/0012_alter_compliancetype_options.py +20 -0
- wbcompliance/migrations/0013_alter_riskrule_unique_together.py +16 -0
- wbcompliance/migrations/0014_alter_reviewcompliancetask_year.py +27 -0
- wbcompliance/migrations/0015_auto_20240103_0957.py +43 -0
- wbcompliance/migrations/0016_checkedobjectincidentrelationship_report_details_and_more.py +37 -0
- wbcompliance/migrations/0017_alter_rulebackend_incident_report_template.py +20 -0
- wbcompliance/migrations/0018_alter_rulecheckedobjectrelationship_unique_together.py +39 -0
- wbcompliance/migrations/0019_rulegroup_riskrule_activation_date_and_more.py +60 -0
- wbcompliance/migrations/__init__.py +0 -0
- wbcompliance/models/__init__.py +20 -0
- wbcompliance/models/compliance_form.py +626 -0
- wbcompliance/models/compliance_task.py +800 -0
- wbcompliance/models/compliance_type.py +133 -0
- wbcompliance/models/enums.py +13 -0
- wbcompliance/models/risk_management/__init__.py +4 -0
- wbcompliance/models/risk_management/backend.py +139 -0
- wbcompliance/models/risk_management/checks.py +194 -0
- wbcompliance/models/risk_management/dispatch.py +41 -0
- wbcompliance/models/risk_management/incidents.py +619 -0
- wbcompliance/models/risk_management/mixins.py +115 -0
- wbcompliance/models/risk_management/rules.py +654 -0
- wbcompliance/permissions.py +32 -0
- wbcompliance/serializers/__init__.py +30 -0
- wbcompliance/serializers/compliance_form.py +320 -0
- wbcompliance/serializers/compliance_task.py +463 -0
- wbcompliance/serializers/compliance_type.py +26 -0
- wbcompliance/serializers/risk_management/__init__.py +19 -0
- wbcompliance/serializers/risk_management/checks.py +53 -0
- wbcompliance/serializers/risk_management/incidents.py +227 -0
- wbcompliance/serializers/risk_management/rules.py +158 -0
- wbcompliance/tasks.py +112 -0
- wbcompliance/tests/__init__.py +0 -0
- wbcompliance/tests/conftest.py +63 -0
- wbcompliance/tests/disable_signals.py +82 -0
- wbcompliance/tests/mixins.py +17 -0
- wbcompliance/tests/risk_management/__init__.py +0 -0
- wbcompliance/tests/risk_management/models/__init__.py +0 -0
- wbcompliance/tests/risk_management/models/test_backends.py +0 -0
- wbcompliance/tests/risk_management/models/test_checks.py +55 -0
- wbcompliance/tests/risk_management/models/test_incidents.py +327 -0
- wbcompliance/tests/risk_management/models/test_rules.py +255 -0
- wbcompliance/tests/signals.py +89 -0
- wbcompliance/tests/test_filters.py +23 -0
- wbcompliance/tests/test_models.py +57 -0
- wbcompliance/tests/test_serializers.py +48 -0
- wbcompliance/tests/test_views.py +377 -0
- wbcompliance/tests/tests.py +21 -0
- wbcompliance/urls.py +238 -0
- wbcompliance/viewsets/__init__.py +40 -0
- wbcompliance/viewsets/buttons/__init__.py +9 -0
- wbcompliance/viewsets/buttons/compliance_form.py +78 -0
- wbcompliance/viewsets/buttons/compliance_task.py +149 -0
- wbcompliance/viewsets/buttons/risk_managment/__init__.py +3 -0
- wbcompliance/viewsets/buttons/risk_managment/checks.py +11 -0
- wbcompliance/viewsets/buttons/risk_managment/incidents.py +51 -0
- wbcompliance/viewsets/buttons/risk_managment/rules.py +35 -0
- wbcompliance/viewsets/compliance_form.py +425 -0
- wbcompliance/viewsets/compliance_task.py +513 -0
- wbcompliance/viewsets/compliance_type.py +38 -0
- wbcompliance/viewsets/display/__init__.py +22 -0
- wbcompliance/viewsets/display/compliance_form.py +317 -0
- wbcompliance/viewsets/display/compliance_task.py +453 -0
- wbcompliance/viewsets/display/compliance_type.py +22 -0
- wbcompliance/viewsets/display/risk_managment/__init__.py +11 -0
- wbcompliance/viewsets/display/risk_managment/checks.py +46 -0
- wbcompliance/viewsets/display/risk_managment/incidents.py +155 -0
- wbcompliance/viewsets/display/risk_managment/rules.py +146 -0
- wbcompliance/viewsets/display/risk_managment/tables.py +51 -0
- wbcompliance/viewsets/endpoints/__init__.py +27 -0
- wbcompliance/viewsets/endpoints/compliance_form.py +207 -0
- wbcompliance/viewsets/endpoints/compliance_task.py +193 -0
- wbcompliance/viewsets/endpoints/compliance_type.py +9 -0
- wbcompliance/viewsets/endpoints/risk_managment/__init__.py +12 -0
- wbcompliance/viewsets/endpoints/risk_managment/checks.py +16 -0
- wbcompliance/viewsets/endpoints/risk_managment/incidents.py +36 -0
- wbcompliance/viewsets/endpoints/risk_managment/rules.py +32 -0
- wbcompliance/viewsets/endpoints/risk_managment/tables.py +14 -0
- wbcompliance/viewsets/menu/__init__.py +17 -0
- wbcompliance/viewsets/menu/compliance_form.py +49 -0
- wbcompliance/viewsets/menu/compliance_task.py +130 -0
- wbcompliance/viewsets/menu/compliance_type.py +17 -0
- wbcompliance/viewsets/menu/risk_management.py +56 -0
- wbcompliance/viewsets/risk_management/__init__.py +21 -0
- wbcompliance/viewsets/risk_management/checks.py +49 -0
- wbcompliance/viewsets/risk_management/incidents.py +204 -0
- wbcompliance/viewsets/risk_management/mixins.py +52 -0
- wbcompliance/viewsets/risk_management/rules.py +179 -0
- wbcompliance/viewsets/risk_management/tables.py +96 -0
- wbcompliance/viewsets/titles/__init__.py +17 -0
- wbcompliance/viewsets/titles/compliance_form.py +101 -0
- wbcompliance/viewsets/titles/compliance_task.py +60 -0
- wbcompliance/viewsets/titles/compliance_type.py +13 -0
- wbcompliance/viewsets/titles/risk_managment/__init__.py +1 -0
- wbcompliance/viewsets/titles/risk_managment/checks.py +0 -0
- wbcompliance/viewsets/titles/risk_managment/incidents.py +0 -0
- wbcompliance/viewsets/titles/risk_managment/rules.py +0 -0
- wbcompliance/viewsets/titles/risk_managment/tables.py +7 -0
- wbcompliance-2.2.1.dist-info/METADATA +7 -0
- wbcompliance-2.2.1.dist-info/RECORD +129 -0
- wbcompliance-2.2.1.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
|
|
3
|
+
from django.utils.translation import gettext as _
|
|
4
|
+
from psycopg.types.range import NumericRange
|
|
5
|
+
from rest_framework import serializers
|
|
6
|
+
from rest_framework.reverse import reverse
|
|
7
|
+
from wbcompliance.models.risk_management import (
|
|
8
|
+
CheckedObjectIncidentRelationship,
|
|
9
|
+
RiskIncident,
|
|
10
|
+
RiskIncidentType,
|
|
11
|
+
RuleThreshold,
|
|
12
|
+
)
|
|
13
|
+
from wbcore import serializers as wb_serializers
|
|
14
|
+
from wbcore.content_type.serializers import ContentTypeRepresentationSerializer
|
|
15
|
+
from wbcore.contrib.authentication.serializers import GroupRepresentationSerializer
|
|
16
|
+
from wbcore.contrib.directory.serializers import PersonRepresentationSerializer
|
|
17
|
+
|
|
18
|
+
from .checks import RiskCheckRepresentationSerializer
|
|
19
|
+
from .rules import RiskRuleRepresentationSerializer
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RiskIncidentTypeRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
23
|
+
class Meta:
|
|
24
|
+
model = RiskIncidentType
|
|
25
|
+
fields = ("id", "name")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RiskIncidentRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
29
|
+
class Meta:
|
|
30
|
+
model = RiskIncident
|
|
31
|
+
fields = ("id", "computed_str")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CheckedObjectIncidentRelationshipRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
35
|
+
class Meta:
|
|
36
|
+
model = CheckedObjectIncidentRelationship
|
|
37
|
+
fields = ("id", "computed_str")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class RuleThresholdModelSerializer(wb_serializers.ModelSerializer):
|
|
41
|
+
_rule = RiskRuleRepresentationSerializer(source="rule")
|
|
42
|
+
_notifiable_users = PersonRepresentationSerializer(source="notifiable_users", many=True)
|
|
43
|
+
_notifiable_groups = GroupRepresentationSerializer(source="notifiable_groups", many=True)
|
|
44
|
+
_severity = RiskIncidentTypeRepresentationSerializer(source="severity")
|
|
45
|
+
range_lower = wb_serializers.FloatField(source="range.lower", allow_null=True, required=False, precision=4)
|
|
46
|
+
range_upper = wb_serializers.FloatField(source="range.upper", allow_null=True, required=False, precision=4)
|
|
47
|
+
range = wb_serializers.DecimalRangeField(required=False)
|
|
48
|
+
|
|
49
|
+
def validate(self, data):
|
|
50
|
+
range_upper, range_lower = None, None
|
|
51
|
+
if (range_dict := data.get("range", None)) and isinstance(range_dict, dict):
|
|
52
|
+
range_upper = range_dict.pop("upper", self.instance.range.upper if self.instance else None)
|
|
53
|
+
range_lower = range_dict.pop("lower", self.instance.range.lower if self.instance else None)
|
|
54
|
+
if range_upper and range_lower and range_upper < range_lower:
|
|
55
|
+
raise serializers.ValidationError({"range": "Lower needs to be strictly lower than upper bound"})
|
|
56
|
+
data["range"] = NumericRange(lower=range_lower, upper=range_upper)
|
|
57
|
+
return data
|
|
58
|
+
|
|
59
|
+
class Meta:
|
|
60
|
+
model = RuleThreshold
|
|
61
|
+
read_only_fields = ("computed_str",)
|
|
62
|
+
fields = (
|
|
63
|
+
"id",
|
|
64
|
+
"rule",
|
|
65
|
+
"_rule",
|
|
66
|
+
"range_lower",
|
|
67
|
+
"range_upper",
|
|
68
|
+
"range",
|
|
69
|
+
"severity",
|
|
70
|
+
"notifiable_users",
|
|
71
|
+
"_notifiable_users",
|
|
72
|
+
"notifiable_groups",
|
|
73
|
+
"_notifiable_groups",
|
|
74
|
+
"computed_str",
|
|
75
|
+
"upgradable_after_days",
|
|
76
|
+
"_severity",
|
|
77
|
+
"severity",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class RiskIncidentModelSerializer(wb_serializers.ModelSerializer):
|
|
82
|
+
# Extra fields to accommodate the multi level tree view with the CheckedIncidentRelationship class
|
|
83
|
+
_group_key = wb_serializers.CharField(read_only=True)
|
|
84
|
+
checked_date = wb_serializers.DateField(read_only=True)
|
|
85
|
+
object_repr = wb_serializers.CharField(read_only=True)
|
|
86
|
+
threshold_repr = wb_serializers.CharField(read_only=True, required=False)
|
|
87
|
+
breached_value = wb_serializers.TextField(read_only=True, default="Open to see details")
|
|
88
|
+
report = wb_serializers.TextField(read_only=True, default="Open to see details")
|
|
89
|
+
|
|
90
|
+
_resolved_by = PersonRepresentationSerializer(source="resolved_by")
|
|
91
|
+
_breached_content_type = ContentTypeRepresentationSerializer(source="breached_content_type")
|
|
92
|
+
_rule = RiskRuleRepresentationSerializer(source="rule")
|
|
93
|
+
_severity = RiskIncidentTypeRepresentationSerializer(source="severity")
|
|
94
|
+
date_range = wb_serializers.DateRangeField(outward_bounds_transform="[]")
|
|
95
|
+
ignore_until = wb_serializers.DateField(
|
|
96
|
+
read_only=True, label="Ignore Until (Included)", help_text=_("Ignore until this date (included)")
|
|
97
|
+
)
|
|
98
|
+
ignore_duration_in_days = wb_serializers.IntegerField(
|
|
99
|
+
required=False,
|
|
100
|
+
label=_("Ignore for X days"),
|
|
101
|
+
help_text=_(
|
|
102
|
+
"If set to a value different than 0, will ignore the forthcoming incidents for the specified number of days"
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
@wb_serializers.register_resource()
|
|
107
|
+
def additional_resources(self, instance, request, user):
|
|
108
|
+
res = {}
|
|
109
|
+
if instance.checked_object_relationships.exists():
|
|
110
|
+
res["relationships"] = reverse(
|
|
111
|
+
"wbcompliance:riskincident-relationship-list",
|
|
112
|
+
args=[instance.id],
|
|
113
|
+
request=request,
|
|
114
|
+
)
|
|
115
|
+
return res
|
|
116
|
+
|
|
117
|
+
def validate(self, data):
|
|
118
|
+
if (ignore_duration_in_days := data.get("ignore_duration_in_days", None)) is not None:
|
|
119
|
+
data["ignore_duration"] = timedelta(days=ignore_duration_in_days)
|
|
120
|
+
return data
|
|
121
|
+
|
|
122
|
+
class Meta:
|
|
123
|
+
model = RiskIncident
|
|
124
|
+
only_fsm_transition_on_instance = True
|
|
125
|
+
|
|
126
|
+
fields = (
|
|
127
|
+
"id",
|
|
128
|
+
"date_range",
|
|
129
|
+
"last_ignored_date",
|
|
130
|
+
"ignore_until",
|
|
131
|
+
"rule",
|
|
132
|
+
"_rule",
|
|
133
|
+
"breached_content_type",
|
|
134
|
+
"_breached_content_type",
|
|
135
|
+
"breached_object_id",
|
|
136
|
+
"breached_object_repr",
|
|
137
|
+
"ignore_duration",
|
|
138
|
+
"ignore_duration_in_days",
|
|
139
|
+
"status",
|
|
140
|
+
"severity",
|
|
141
|
+
"comment",
|
|
142
|
+
"resolved_by",
|
|
143
|
+
"_resolved_by",
|
|
144
|
+
"_severity",
|
|
145
|
+
"severity",
|
|
146
|
+
"is_notified",
|
|
147
|
+
"_additional_resources",
|
|
148
|
+
"_group_key",
|
|
149
|
+
"checked_date",
|
|
150
|
+
"object_repr",
|
|
151
|
+
"threshold_repr",
|
|
152
|
+
"breached_value",
|
|
153
|
+
"report",
|
|
154
|
+
)
|
|
155
|
+
read_only_fields = (
|
|
156
|
+
"id",
|
|
157
|
+
"date_range",
|
|
158
|
+
"last_ignored_date",
|
|
159
|
+
"ignore_until",
|
|
160
|
+
"rule",
|
|
161
|
+
"breached_content_type",
|
|
162
|
+
"breached_object_id",
|
|
163
|
+
"breached_object_repr",
|
|
164
|
+
"status",
|
|
165
|
+
"severity",
|
|
166
|
+
"resolved_by",
|
|
167
|
+
"_severity",
|
|
168
|
+
"severity",
|
|
169
|
+
"is_notified",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class CheckedObjectIncidentRelationshipModelSerializer(wb_serializers.ModelSerializer):
|
|
174
|
+
_resolved_by = PersonRepresentationSerializer(source="resolved_by")
|
|
175
|
+
_incident = RiskIncidentRepresentationSerializer(source="incident")
|
|
176
|
+
_rule_check = RiskCheckRepresentationSerializer(source="rule_check")
|
|
177
|
+
_severity = RiskIncidentTypeRepresentationSerializer(source="severity")
|
|
178
|
+
breached_value = wb_serializers.TextField()
|
|
179
|
+
# extra annotation to play properly with the tree table
|
|
180
|
+
checked_date = wb_serializers.DateField(read_only=True, required=False)
|
|
181
|
+
# rule = wb_serializers.PrimaryKeyRelatedField()
|
|
182
|
+
# _rule = RiskRuleRepresentationSerializer(source="rule")
|
|
183
|
+
object_repr = wb_serializers.CharField(read_only=True, required=False)
|
|
184
|
+
date_range = wb_serializers.DateRangeField(read_only=True, outward_bounds_transform="[]", required=False)
|
|
185
|
+
threshold_repr = wb_serializers.CharField(read_only=True, required=False)
|
|
186
|
+
|
|
187
|
+
class Meta:
|
|
188
|
+
model = CheckedObjectIncidentRelationship
|
|
189
|
+
read_only_fields = (
|
|
190
|
+
"computed_str",
|
|
191
|
+
"incident",
|
|
192
|
+
"status",
|
|
193
|
+
"severity",
|
|
194
|
+
"rule_check",
|
|
195
|
+
"report",
|
|
196
|
+
"_severity",
|
|
197
|
+
"resolved_by",
|
|
198
|
+
"checked_date",
|
|
199
|
+
"rule",
|
|
200
|
+
"_rule",
|
|
201
|
+
"object_repr",
|
|
202
|
+
"date_range",
|
|
203
|
+
"breached_value",
|
|
204
|
+
)
|
|
205
|
+
fields = (
|
|
206
|
+
"id",
|
|
207
|
+
"computed_str",
|
|
208
|
+
"rule_check",
|
|
209
|
+
"_rule_check",
|
|
210
|
+
"incident",
|
|
211
|
+
"_incident",
|
|
212
|
+
"report",
|
|
213
|
+
"status",
|
|
214
|
+
"comment",
|
|
215
|
+
"resolved_by",
|
|
216
|
+
"_resolved_by",
|
|
217
|
+
"_severity",
|
|
218
|
+
"severity",
|
|
219
|
+
"_additional_resources",
|
|
220
|
+
"checked_date",
|
|
221
|
+
# "rule",
|
|
222
|
+
# "_rule",
|
|
223
|
+
"object_repr",
|
|
224
|
+
"date_range",
|
|
225
|
+
"threshold_repr",
|
|
226
|
+
"breached_value",
|
|
227
|
+
)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from django.contrib.contenttypes.models import ContentType
|
|
2
|
+
from rest_framework.reverse import reverse
|
|
3
|
+
from wbcompliance.models.risk_management import (
|
|
4
|
+
RiskRule,
|
|
5
|
+
RuleBackend,
|
|
6
|
+
RuleCheckedObjectRelationship,
|
|
7
|
+
RuleThreshold,
|
|
8
|
+
)
|
|
9
|
+
from wbcompliance.models.risk_management.rules import RuleGroup
|
|
10
|
+
from wbcore import serializers as wb_serializers
|
|
11
|
+
from wbcore.content_type.serializers import (
|
|
12
|
+
ContentTypeRepresentationSerializer,
|
|
13
|
+
DynamicObjectIDRepresentationSerializer,
|
|
14
|
+
)
|
|
15
|
+
from wbcore.contrib.authentication.serializers import UserRepresentationSerializer
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RuleGroupRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
19
|
+
class Meta:
|
|
20
|
+
model = RuleGroup
|
|
21
|
+
fields = ("id", "name")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RuleCheckedObjectRelationshipRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
25
|
+
class Meta:
|
|
26
|
+
model = RuleCheckedObjectRelationship
|
|
27
|
+
fields = ("id", "computed_str", "checked_object_repr")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RuleBackendRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
31
|
+
class Meta:
|
|
32
|
+
model = RuleBackend
|
|
33
|
+
fields = ("id", "name")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RuleThresholdRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
37
|
+
class Meta:
|
|
38
|
+
model = RuleThreshold
|
|
39
|
+
fields = ("id", "computed_str")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RiskRuleRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
43
|
+
class Meta:
|
|
44
|
+
model = RiskRule
|
|
45
|
+
fields = ("id", "name")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class GetContentTypeFromKwargs:
|
|
49
|
+
requires_context = True
|
|
50
|
+
|
|
51
|
+
def __call__(self, serializer_instance):
|
|
52
|
+
if (view := serializer_instance.view) and (rule_id := view.kwargs.get("rule_id", None)):
|
|
53
|
+
rule = RiskRule.objects.get(id=rule_id)
|
|
54
|
+
if content_type := rule.rule_backend.allowed_checked_object_content_type:
|
|
55
|
+
return content_type.id
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class RuleCheckedObjectRelationshipModelSerializer(wb_serializers.ModelSerializer):
|
|
60
|
+
checked_object_content_type = wb_serializers.PrimaryKeyRelatedField(
|
|
61
|
+
queryset=ContentType.objects.all(), default=GetContentTypeFromKwargs()
|
|
62
|
+
)
|
|
63
|
+
_checked_object_content_type = ContentTypeRepresentationSerializer(source="checked_object_content_type")
|
|
64
|
+
_checked_object_id = DynamicObjectIDRepresentationSerializer(
|
|
65
|
+
content_type_field_name="checked_object_content_type",
|
|
66
|
+
source="checked_object_id",
|
|
67
|
+
optional_get_parameters={"checked_object_content_type": "content_type"},
|
|
68
|
+
depends_on=[{"field": "checked_object_content_type", "options": {}}],
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
class Meta:
|
|
72
|
+
model = RuleCheckedObjectRelationship
|
|
73
|
+
# dependency_map = {
|
|
74
|
+
# "checked_object_content_type": ["checked_object_id"],
|
|
75
|
+
# }
|
|
76
|
+
read_only_fields = ("computed_str", "checked_object_repr")
|
|
77
|
+
fields = (
|
|
78
|
+
"id",
|
|
79
|
+
"rule",
|
|
80
|
+
"checked_object_content_type",
|
|
81
|
+
"_checked_object_content_type",
|
|
82
|
+
"checked_object_id",
|
|
83
|
+
"_checked_object_id",
|
|
84
|
+
"checked_object_repr",
|
|
85
|
+
"computed_str",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class RuleBackendModelSerializer(wb_serializers.ModelSerializer):
|
|
90
|
+
class Meta:
|
|
91
|
+
model = RuleBackend
|
|
92
|
+
fields = ("id", "name", "backend_class_path", "backend_class_name", "allowed_checked_object_content_type")
|
|
93
|
+
read_only_fields = fields
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class RiskRuleModelSerializer(wb_serializers.ModelSerializer):
|
|
97
|
+
parameters__group_by = wb_serializers.CharField(read_only=True)
|
|
98
|
+
_rule_backend = RuleBackendRepresentationSerializer(source="rule_backend")
|
|
99
|
+
_creator = UserRepresentationSerializer(source="creator")
|
|
100
|
+
parameters = wb_serializers.JSONTableField()
|
|
101
|
+
open_incidents_count = wb_serializers.IntegerField(default=0, read_only=True)
|
|
102
|
+
in_breach = wb_serializers.ChoiceField(
|
|
103
|
+
default=False,
|
|
104
|
+
read_only=True,
|
|
105
|
+
choices=[("BREACH", "In Breach"), ("PASSED", "Passed"), ("INACTIVE", "Inactive")],
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def validate(self, data):
|
|
109
|
+
if (not self.instance or not self.instance.creator) and (request := self.context.get("request")):
|
|
110
|
+
data["creator"] = request.user
|
|
111
|
+
return super().validate(data)
|
|
112
|
+
|
|
113
|
+
@wb_serializers.register_resource()
|
|
114
|
+
def additional_resources(self, instance, request, user):
|
|
115
|
+
return {
|
|
116
|
+
"relationships": reverse(
|
|
117
|
+
"wbcompliance:riskrule-relationship-list",
|
|
118
|
+
args=[instance.id],
|
|
119
|
+
request=request,
|
|
120
|
+
),
|
|
121
|
+
"thresholds": reverse(
|
|
122
|
+
"wbcompliance:riskrule-threshold-list",
|
|
123
|
+
args=[instance.id],
|
|
124
|
+
request=request,
|
|
125
|
+
),
|
|
126
|
+
"incidents": reverse(
|
|
127
|
+
"wbcompliance:riskrule-incident-list",
|
|
128
|
+
args=[instance.id],
|
|
129
|
+
request=request,
|
|
130
|
+
),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class Meta:
|
|
134
|
+
model = RiskRule
|
|
135
|
+
read_only_fields = ("creator",)
|
|
136
|
+
fields = (
|
|
137
|
+
"id",
|
|
138
|
+
"parameters__group_by",
|
|
139
|
+
"permission_type",
|
|
140
|
+
"creator",
|
|
141
|
+
"_creator",
|
|
142
|
+
"name",
|
|
143
|
+
"description",
|
|
144
|
+
"rule_backend",
|
|
145
|
+
"_rule_backend",
|
|
146
|
+
"is_enable",
|
|
147
|
+
"only_passive_check_allowed",
|
|
148
|
+
"automatically_close_incident",
|
|
149
|
+
"is_silent",
|
|
150
|
+
"is_mandatory",
|
|
151
|
+
"apply_to_all_active_relationships",
|
|
152
|
+
"parameters",
|
|
153
|
+
"open_incidents_count",
|
|
154
|
+
"in_breach",
|
|
155
|
+
"frequency",
|
|
156
|
+
"activation_date",
|
|
157
|
+
"_additional_resources",
|
|
158
|
+
)
|
wbcompliance/tasks.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from datetime import date, datetime
|
|
3
|
+
|
|
4
|
+
from celery import shared_task
|
|
5
|
+
from django.contrib.contenttypes.models import ContentType
|
|
6
|
+
from django.db.models import Q
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
from wbcompliance.models import ComplianceTask, ReviewComplianceTask
|
|
9
|
+
from wbcompliance.models.risk_management.rules import (
|
|
10
|
+
RiskRule,
|
|
11
|
+
RuleCheckedObjectRelationship,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@shared_task
|
|
16
|
+
def check_passive_rules(
|
|
17
|
+
from_date: date | None = None,
|
|
18
|
+
to_date: date | None = None,
|
|
19
|
+
override_incident: bool | None = False,
|
|
20
|
+
extra_process_kwargs: dict | None = None,
|
|
21
|
+
silent_notification: bool = False,
|
|
22
|
+
debug: bool = False,
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
Periodic function that call all active passive rules and trigger the check workflow
|
|
26
|
+
"""
|
|
27
|
+
if not to_date:
|
|
28
|
+
to_date = datetime.today().date()
|
|
29
|
+
|
|
30
|
+
# cleanup relationship before continuing
|
|
31
|
+
clean_dynamic_rule_relationships(debug=debug)
|
|
32
|
+
|
|
33
|
+
process_kwargs = {
|
|
34
|
+
"override_incident": override_incident,
|
|
35
|
+
}
|
|
36
|
+
if isinstance(extra_process_kwargs, dict):
|
|
37
|
+
process_kwargs.update(extra_process_kwargs)
|
|
38
|
+
|
|
39
|
+
res = []
|
|
40
|
+
for rule in RiskRule.objects.filter(is_enable=True):
|
|
41
|
+
for relationship in rule.checked_object_relationships.iterator():
|
|
42
|
+
res.append((rule, relationship))
|
|
43
|
+
gen = res
|
|
44
|
+
if debug:
|
|
45
|
+
gen = tqdm(gen, total=len(res))
|
|
46
|
+
date_to_notify = defaultdict(set)
|
|
47
|
+
for rule, relationship in gen:
|
|
48
|
+
for evaluation_date in relationship.get_unchecked_dates(from_date=from_date, to_date=to_date):
|
|
49
|
+
if relationship.process_rule(evaluation_date, **process_kwargs):
|
|
50
|
+
date_to_notify[rule].add(evaluation_date)
|
|
51
|
+
|
|
52
|
+
if not silent_notification:
|
|
53
|
+
for rule, dates in date_to_notify.items():
|
|
54
|
+
for checked_date in dates:
|
|
55
|
+
rule.notify(checked_date)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@shared_task
|
|
59
|
+
def clean_dynamic_rule_relationships(debug: bool = False):
|
|
60
|
+
"""
|
|
61
|
+
Periodic function to check reverse generic object that don't exist anymore. Furthermore, get the queryset representing all the active relationship for all backend and ensure that every object have a relationship (e.g. new object creation)
|
|
62
|
+
"""
|
|
63
|
+
gen = RiskRule.objects.filter(apply_to_all_active_relationships=True)
|
|
64
|
+
if debug:
|
|
65
|
+
gen = tqdm(gen, total=gen.count())
|
|
66
|
+
|
|
67
|
+
for rule in gen:
|
|
68
|
+
leftover_relationships = rule.checked_object_relationships.all()
|
|
69
|
+
for content_object in rule.rule_backend.get_all_active_relationships():
|
|
70
|
+
rel, _ = RuleCheckedObjectRelationship.objects.get_or_create(
|
|
71
|
+
rule=rule,
|
|
72
|
+
checked_object_content_type=ContentType.objects.get_for_model(content_object),
|
|
73
|
+
checked_object_id=content_object.id,
|
|
74
|
+
)
|
|
75
|
+
leftover_relationships = leftover_relationships.exclude(id=rel.id)
|
|
76
|
+
|
|
77
|
+
for leftover_relationship in leftover_relationships.iterator():
|
|
78
|
+
leftover_relationship.delete()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@shared_task
|
|
82
|
+
def periodic_quaterly_or_monthly_compliance_task():
|
|
83
|
+
today = datetime.now()
|
|
84
|
+
qs = ComplianceTask.objects.filter(active=True)
|
|
85
|
+
qs_review = ReviewComplianceTask.objects.filter(
|
|
86
|
+
Q(status=ReviewComplianceTask.Status.VALIDATED) & Q(is_instance=False)
|
|
87
|
+
)
|
|
88
|
+
if today.month == 1 and today.day == 1:
|
|
89
|
+
qs = qs.filter(
|
|
90
|
+
Q(occurrence=ComplianceTask.Occurrence.YEARLY) | Q(occurrence=ComplianceTask.Occurrence.MONTHLY)
|
|
91
|
+
)
|
|
92
|
+
qs_review = qs_review.filter(
|
|
93
|
+
Q(occurrence=ReviewComplianceTask.Occurrence.YEARLY)
|
|
94
|
+
| Q(occurrence=ReviewComplianceTask.Occurrence.MONTHLY)
|
|
95
|
+
)
|
|
96
|
+
elif today.month % 3 == 0:
|
|
97
|
+
qs = qs.filter(
|
|
98
|
+
Q(occurrence=ComplianceTask.Occurrence.QUARTERLY) | Q(occurrence=ComplianceTask.Occurrence.MONTHLY)
|
|
99
|
+
)
|
|
100
|
+
qs_review = qs_review.filter(
|
|
101
|
+
Q(occurrence=ReviewComplianceTask.Occurrence.QUARTERLY)
|
|
102
|
+
| Q(occurrence=ReviewComplianceTask.Occurrence.MONTHLY)
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
qs = qs.filter(occurrence=ComplianceTask.Occurrence.MONTHLY)
|
|
106
|
+
qs_review = qs_review.filter(occurrence=ReviewComplianceTask.Occurrence.MONTHLY)
|
|
107
|
+
|
|
108
|
+
for review_task in qs_review:
|
|
109
|
+
review_task.generate_review_compliance_task_instance(notify_admin=True)
|
|
110
|
+
|
|
111
|
+
for task in qs:
|
|
112
|
+
task.generate_compliance_task_instance(link_instance_review=True)
|
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from django.apps import apps
|
|
2
|
+
from django.db.models.signals import pre_migrate
|
|
3
|
+
from pytest_factoryboy import register
|
|
4
|
+
from wbcompliance.factories import (
|
|
5
|
+
ComplianceActionFactory,
|
|
6
|
+
ComplianceEventFactory,
|
|
7
|
+
ComplianceFormFactory,
|
|
8
|
+
ComplianceFormRuleFactory,
|
|
9
|
+
ComplianceFormSectionFactory,
|
|
10
|
+
ComplianceFormSignatureFactory,
|
|
11
|
+
ComplianceFormTypeFactory,
|
|
12
|
+
ComplianceTaskFactory,
|
|
13
|
+
ComplianceTaskGroupFactory,
|
|
14
|
+
ComplianceTaskInstanceFactory,
|
|
15
|
+
ComplianceTypeFactory,
|
|
16
|
+
UnsignedComplianceFormSignatureFactory,
|
|
17
|
+
)
|
|
18
|
+
from wbcompliance.factories.risk_management import (
|
|
19
|
+
CheckedObjectIncidentRelationshipFactory,
|
|
20
|
+
RiskCheckFactory,
|
|
21
|
+
RiskIncidentFactory,
|
|
22
|
+
RiskIncidentTypeFactory,
|
|
23
|
+
RiskRuleFactory,
|
|
24
|
+
RuleBackendFactory,
|
|
25
|
+
RuleCheckedObjectRelationshipFactory,
|
|
26
|
+
RuleThresholdFactory,
|
|
27
|
+
)
|
|
28
|
+
from wbcore.contrib.authentication.factories import (
|
|
29
|
+
AuthenticatedPersonFactory,
|
|
30
|
+
UserFactory,
|
|
31
|
+
)
|
|
32
|
+
from wbcore.contrib.directory.factories import PersonFactory
|
|
33
|
+
|
|
34
|
+
from wbcore.tests.conftest import * # isort:skip
|
|
35
|
+
|
|
36
|
+
register(ComplianceFormRuleFactory)
|
|
37
|
+
register(ComplianceFormSectionFactory)
|
|
38
|
+
register(ComplianceFormTypeFactory)
|
|
39
|
+
register(ComplianceFormFactory)
|
|
40
|
+
register(ComplianceFormSignatureFactory)
|
|
41
|
+
register(UnsignedComplianceFormSignatureFactory)
|
|
42
|
+
register(ComplianceTaskFactory)
|
|
43
|
+
register(ComplianceTaskInstanceFactory)
|
|
44
|
+
register(ComplianceActionFactory)
|
|
45
|
+
register(ComplianceEventFactory)
|
|
46
|
+
register(ComplianceTypeFactory)
|
|
47
|
+
register(ComplianceTaskGroupFactory)
|
|
48
|
+
register(RiskCheckFactory)
|
|
49
|
+
register(RiskIncidentFactory)
|
|
50
|
+
register(CheckedObjectIncidentRelationshipFactory)
|
|
51
|
+
register(RuleBackendFactory)
|
|
52
|
+
register(RiskRuleFactory)
|
|
53
|
+
register(RuleThresholdFactory)
|
|
54
|
+
register(RuleCheckedObjectRelationshipFactory)
|
|
55
|
+
register(RiskIncidentTypeFactory)
|
|
56
|
+
register(PersonFactory)
|
|
57
|
+
register(UserFactory)
|
|
58
|
+
register(AuthenticatedPersonFactory)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
from .signals import *
|
|
62
|
+
|
|
63
|
+
pre_migrate.connect(app_pre_migration, sender=apps.get_app_config("wbcompliance"))
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
|
|
3
|
+
from django.db.models.signals import *
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DisableSignals(object):
|
|
7
|
+
def __init__(self, disabled_signals=None):
|
|
8
|
+
self.stashed_signals = defaultdict(list)
|
|
9
|
+
self.disabled_signals = disabled_signals or [
|
|
10
|
+
pre_init,
|
|
11
|
+
post_init,
|
|
12
|
+
pre_save,
|
|
13
|
+
post_save,
|
|
14
|
+
pre_delete,
|
|
15
|
+
post_delete,
|
|
16
|
+
pre_migrate,
|
|
17
|
+
post_migrate,
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
def __enter__(self):
|
|
21
|
+
for signal in self.disabled_signals:
|
|
22
|
+
self.disconnect(signal)
|
|
23
|
+
|
|
24
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
25
|
+
for signal in list(self.stashed_signals):
|
|
26
|
+
self.reconnect(signal)
|
|
27
|
+
|
|
28
|
+
def disconnect(self, signal):
|
|
29
|
+
self.stashed_signals[signal] = signal.receivers
|
|
30
|
+
signal.receivers = []
|
|
31
|
+
|
|
32
|
+
def reconnect(self, signal):
|
|
33
|
+
signal.receivers = self.stashed_signals.get(signal, [])
|
|
34
|
+
del self.stashed_signals[signal]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# https://stackoverflow.com/questions/55578230/django-how-to-visualize-signals-and-save-overrides
|
|
38
|
+
# RECEIVER_MODELS = re.compile(r"sender=(\w+)\W")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# class DisableSignalsNotification_OLD_VERSION(DisableSignals):
|
|
42
|
+
# def __enter__(self):
|
|
43
|
+
# for signal in self.disabled_signals:
|
|
44
|
+
# if not isinstance(signal, ModelSignal):
|
|
45
|
+
# continue
|
|
46
|
+
# for _, receiver in signal.receivers:
|
|
47
|
+
# rcode = inspect.getsource(receiver())
|
|
48
|
+
# rmodel = RECEIVER_MODELS.findall(rcode)
|
|
49
|
+
# if "Notification" in rmodel:
|
|
50
|
+
# self.disconnect(signal)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class temp_disconnect_signal:
|
|
54
|
+
"""Temporarily disconnect a model from a signal"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, signal, receiver, sender, dispatch_uid=None):
|
|
57
|
+
self.signal = signal
|
|
58
|
+
self.receiver = receiver
|
|
59
|
+
self.sender = sender
|
|
60
|
+
self.dispatch_uid = dispatch_uid
|
|
61
|
+
|
|
62
|
+
def __enter__(self):
|
|
63
|
+
self.signal.disconnect(
|
|
64
|
+
receiver=self.receiver,
|
|
65
|
+
sender=self.sender,
|
|
66
|
+
dispatch_uid=self.dispatch_uid,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def __exit__(self, type, value, traceback):
|
|
70
|
+
self.signal.connect(
|
|
71
|
+
receiver=self.receiver,
|
|
72
|
+
sender=self.sender,
|
|
73
|
+
dispatch_uid=self.dispatch_uid,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class DisableSignalsNotification(temp_disconnect_signal):
|
|
78
|
+
def __init__(self, dispatch_uid=None):
|
|
79
|
+
self.signal = post_save
|
|
80
|
+
self.receiver = post_create_notification
|
|
81
|
+
self.sender = Notification
|
|
82
|
+
self.dispatch_uid = dispatch_uid
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from django.contrib.auth import get_user_model
|
|
3
|
+
from django.contrib.auth.models import Group
|
|
4
|
+
from faker import Faker
|
|
5
|
+
from wbcore.contrib.authentication.factories import InternalUserFactory
|
|
6
|
+
|
|
7
|
+
fake = Faker()
|
|
8
|
+
User = get_user_model()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UserTestMixin:
|
|
12
|
+
@pytest.fixture()
|
|
13
|
+
def user(self):
|
|
14
|
+
user = InternalUserFactory.create()
|
|
15
|
+
group = Group.objects.create(name="Compliance Position")
|
|
16
|
+
user.groups.add(group)
|
|
17
|
+
return user
|
|
File without changes
|
|
File without changes
|
|
File without changes
|