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.
Files changed (129) hide show
  1. wbcompliance/__init__.py +1 -0
  2. wbcompliance/admin/__init__.py +16 -0
  3. wbcompliance/admin/compliance_form.py +56 -0
  4. wbcompliance/admin/compliance_task.py +135 -0
  5. wbcompliance/admin/compliance_type.py +8 -0
  6. wbcompliance/admin/risk_management/__init__.py +3 -0
  7. wbcompliance/admin/risk_management/checks.py +7 -0
  8. wbcompliance/admin/risk_management/incidents.py +50 -0
  9. wbcompliance/admin/risk_management/rules.py +63 -0
  10. wbcompliance/admin/utils.py +46 -0
  11. wbcompliance/apps.py +14 -0
  12. wbcompliance/factories/__init__.py +21 -0
  13. wbcompliance/factories/compliance.py +246 -0
  14. wbcompliance/factories/risk_management/__init__.py +12 -0
  15. wbcompliance/factories/risk_management/backends.py +42 -0
  16. wbcompliance/factories/risk_management/checks.py +12 -0
  17. wbcompliance/factories/risk_management/incidents.py +84 -0
  18. wbcompliance/factories/risk_management/rules.py +100 -0
  19. wbcompliance/filters/__init__.py +2 -0
  20. wbcompliance/filters/compliances.py +189 -0
  21. wbcompliance/filters/risk_management/__init__.py +3 -0
  22. wbcompliance/filters/risk_management/checks.py +22 -0
  23. wbcompliance/filters/risk_management/incidents.py +113 -0
  24. wbcompliance/filters/risk_management/rules.py +110 -0
  25. wbcompliance/filters/risk_management/tables.py +112 -0
  26. wbcompliance/filters/risk_management/utils.py +3 -0
  27. wbcompliance/management/__init__.py +10 -0
  28. wbcompliance/migrations/0001_initial_squashed_squashed_0010_alter_checkedobjectincidentrelationship_resolved_by_and_more.py +1744 -0
  29. wbcompliance/migrations/0011_alter_riskrule_parameters.py +21 -0
  30. wbcompliance/migrations/0012_alter_compliancetype_options.py +20 -0
  31. wbcompliance/migrations/0013_alter_riskrule_unique_together.py +16 -0
  32. wbcompliance/migrations/0014_alter_reviewcompliancetask_year.py +27 -0
  33. wbcompliance/migrations/0015_auto_20240103_0957.py +43 -0
  34. wbcompliance/migrations/0016_checkedobjectincidentrelationship_report_details_and_more.py +37 -0
  35. wbcompliance/migrations/0017_alter_rulebackend_incident_report_template.py +20 -0
  36. wbcompliance/migrations/0018_alter_rulecheckedobjectrelationship_unique_together.py +39 -0
  37. wbcompliance/migrations/0019_rulegroup_riskrule_activation_date_and_more.py +60 -0
  38. wbcompliance/migrations/__init__.py +0 -0
  39. wbcompliance/models/__init__.py +20 -0
  40. wbcompliance/models/compliance_form.py +626 -0
  41. wbcompliance/models/compliance_task.py +800 -0
  42. wbcompliance/models/compliance_type.py +133 -0
  43. wbcompliance/models/enums.py +13 -0
  44. wbcompliance/models/risk_management/__init__.py +4 -0
  45. wbcompliance/models/risk_management/backend.py +139 -0
  46. wbcompliance/models/risk_management/checks.py +194 -0
  47. wbcompliance/models/risk_management/dispatch.py +41 -0
  48. wbcompliance/models/risk_management/incidents.py +619 -0
  49. wbcompliance/models/risk_management/mixins.py +115 -0
  50. wbcompliance/models/risk_management/rules.py +654 -0
  51. wbcompliance/permissions.py +32 -0
  52. wbcompliance/serializers/__init__.py +30 -0
  53. wbcompliance/serializers/compliance_form.py +320 -0
  54. wbcompliance/serializers/compliance_task.py +463 -0
  55. wbcompliance/serializers/compliance_type.py +26 -0
  56. wbcompliance/serializers/risk_management/__init__.py +19 -0
  57. wbcompliance/serializers/risk_management/checks.py +53 -0
  58. wbcompliance/serializers/risk_management/incidents.py +227 -0
  59. wbcompliance/serializers/risk_management/rules.py +158 -0
  60. wbcompliance/tasks.py +112 -0
  61. wbcompliance/tests/__init__.py +0 -0
  62. wbcompliance/tests/conftest.py +63 -0
  63. wbcompliance/tests/disable_signals.py +82 -0
  64. wbcompliance/tests/mixins.py +17 -0
  65. wbcompliance/tests/risk_management/__init__.py +0 -0
  66. wbcompliance/tests/risk_management/models/__init__.py +0 -0
  67. wbcompliance/tests/risk_management/models/test_backends.py +0 -0
  68. wbcompliance/tests/risk_management/models/test_checks.py +55 -0
  69. wbcompliance/tests/risk_management/models/test_incidents.py +327 -0
  70. wbcompliance/tests/risk_management/models/test_rules.py +255 -0
  71. wbcompliance/tests/signals.py +89 -0
  72. wbcompliance/tests/test_filters.py +23 -0
  73. wbcompliance/tests/test_models.py +57 -0
  74. wbcompliance/tests/test_serializers.py +48 -0
  75. wbcompliance/tests/test_views.py +377 -0
  76. wbcompliance/tests/tests.py +21 -0
  77. wbcompliance/urls.py +238 -0
  78. wbcompliance/viewsets/__init__.py +40 -0
  79. wbcompliance/viewsets/buttons/__init__.py +9 -0
  80. wbcompliance/viewsets/buttons/compliance_form.py +78 -0
  81. wbcompliance/viewsets/buttons/compliance_task.py +149 -0
  82. wbcompliance/viewsets/buttons/risk_managment/__init__.py +3 -0
  83. wbcompliance/viewsets/buttons/risk_managment/checks.py +11 -0
  84. wbcompliance/viewsets/buttons/risk_managment/incidents.py +51 -0
  85. wbcompliance/viewsets/buttons/risk_managment/rules.py +35 -0
  86. wbcompliance/viewsets/compliance_form.py +425 -0
  87. wbcompliance/viewsets/compliance_task.py +513 -0
  88. wbcompliance/viewsets/compliance_type.py +38 -0
  89. wbcompliance/viewsets/display/__init__.py +22 -0
  90. wbcompliance/viewsets/display/compliance_form.py +317 -0
  91. wbcompliance/viewsets/display/compliance_task.py +453 -0
  92. wbcompliance/viewsets/display/compliance_type.py +22 -0
  93. wbcompliance/viewsets/display/risk_managment/__init__.py +11 -0
  94. wbcompliance/viewsets/display/risk_managment/checks.py +46 -0
  95. wbcompliance/viewsets/display/risk_managment/incidents.py +155 -0
  96. wbcompliance/viewsets/display/risk_managment/rules.py +146 -0
  97. wbcompliance/viewsets/display/risk_managment/tables.py +51 -0
  98. wbcompliance/viewsets/endpoints/__init__.py +27 -0
  99. wbcompliance/viewsets/endpoints/compliance_form.py +207 -0
  100. wbcompliance/viewsets/endpoints/compliance_task.py +193 -0
  101. wbcompliance/viewsets/endpoints/compliance_type.py +9 -0
  102. wbcompliance/viewsets/endpoints/risk_managment/__init__.py +12 -0
  103. wbcompliance/viewsets/endpoints/risk_managment/checks.py +16 -0
  104. wbcompliance/viewsets/endpoints/risk_managment/incidents.py +36 -0
  105. wbcompliance/viewsets/endpoints/risk_managment/rules.py +32 -0
  106. wbcompliance/viewsets/endpoints/risk_managment/tables.py +14 -0
  107. wbcompliance/viewsets/menu/__init__.py +17 -0
  108. wbcompliance/viewsets/menu/compliance_form.py +49 -0
  109. wbcompliance/viewsets/menu/compliance_task.py +130 -0
  110. wbcompliance/viewsets/menu/compliance_type.py +17 -0
  111. wbcompliance/viewsets/menu/risk_management.py +56 -0
  112. wbcompliance/viewsets/risk_management/__init__.py +21 -0
  113. wbcompliance/viewsets/risk_management/checks.py +49 -0
  114. wbcompliance/viewsets/risk_management/incidents.py +204 -0
  115. wbcompliance/viewsets/risk_management/mixins.py +52 -0
  116. wbcompliance/viewsets/risk_management/rules.py +179 -0
  117. wbcompliance/viewsets/risk_management/tables.py +96 -0
  118. wbcompliance/viewsets/titles/__init__.py +17 -0
  119. wbcompliance/viewsets/titles/compliance_form.py +101 -0
  120. wbcompliance/viewsets/titles/compliance_task.py +60 -0
  121. wbcompliance/viewsets/titles/compliance_type.py +13 -0
  122. wbcompliance/viewsets/titles/risk_managment/__init__.py +1 -0
  123. wbcompliance/viewsets/titles/risk_managment/checks.py +0 -0
  124. wbcompliance/viewsets/titles/risk_managment/incidents.py +0 -0
  125. wbcompliance/viewsets/titles/risk_managment/rules.py +0 -0
  126. wbcompliance/viewsets/titles/risk_managment/tables.py +7 -0
  127. wbcompliance-2.2.1.dist-info/METADATA +7 -0
  128. wbcompliance-2.2.1.dist-info/RECORD +129 -0
  129. wbcompliance-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,204 @@
1
+ from django.contrib.auth import get_user_model
2
+ from django.contrib.auth.models import Permission
3
+ from django.contrib.contenttypes.models import ContentType
4
+ from django.contrib.messages.api import warning
5
+ from django.contrib.postgres.fields.ranges import DateRangeField
6
+ from django.db.models import (
7
+ Case,
8
+ CharField,
9
+ DateField,
10
+ Exists,
11
+ ExpressionWrapper,
12
+ F,
13
+ IntegerField,
14
+ OuterRef,
15
+ Subquery,
16
+ Value,
17
+ When,
18
+ )
19
+ from django.db.models.functions import Cast, Concat
20
+ from django.http import HttpResponse
21
+ from django.utils.functional import cached_property
22
+ from rest_framework import status
23
+ from rest_framework.decorators import action
24
+ from wbcompliance.filters import (
25
+ CheckedObjectIncidentRelationshipFilterSet,
26
+ RiskIncidentFilterSet,
27
+ )
28
+ from wbcompliance.models import RuleThreshold
29
+ from wbcompliance.models.risk_management import (
30
+ CheckedObjectIncidentRelationship,
31
+ RiskIncident,
32
+ RiskIncidentType,
33
+ RiskRule,
34
+ )
35
+ from wbcompliance.models.risk_management.incidents import resolve_all_incidents_as_task
36
+ from wbcompliance.permissions import RulePermission
37
+ from wbcompliance.serializers import (
38
+ CheckedObjectIncidentRelationshipModelSerializer,
39
+ CheckedObjectIncidentRelationshipRepresentationSerializer,
40
+ RiskIncidentModelSerializer,
41
+ RiskIncidentRepresentationSerializer,
42
+ RiskIncidentTypeRepresentationSerializer,
43
+ )
44
+ from wbcompliance.viewsets.buttons import RiskIncidentButtonConfig
45
+ from wbcompliance.viewsets.display import (
46
+ CheckedObjectIncidentRelationshipRiskRuleDisplayConfig,
47
+ RiskIncidentDisplayConfig,
48
+ )
49
+ from wbcompliance.viewsets.endpoints import (
50
+ CheckedObjectIncidentRelationshipEndpointConfig,
51
+ CheckedObjectIncidentRelationshipRiskRuleEndpointConfig,
52
+ RiskIncidentEndpointConfig,
53
+ )
54
+ from wbcore import viewsets
55
+ from wbcore.contrib.guardian.models import UserObjectPermission
56
+
57
+ User = get_user_model()
58
+
59
+
60
+ class RiskIncidentTypeRepresentationViewSet(viewsets.RepresentationViewSet):
61
+ queryset = RiskIncidentType.objects.all()
62
+ serializer_class = RiskIncidentTypeRepresentationSerializer
63
+ search_fields = ("name",)
64
+
65
+
66
+ class RiskIncidentRepresentationViewSet(viewsets.RepresentationViewSet):
67
+ queryset = RiskIncident.objects.all()
68
+ serializer_class = RiskIncidentRepresentationSerializer
69
+ search_fields = (
70
+ "rule__name",
71
+ "breached_object_repr",
72
+ )
73
+
74
+
75
+ class CheckedObjectIncidentRelationshipRepresentationViewSet(viewsets.RepresentationViewSet):
76
+ queryset = CheckedObjectIncidentRelationship.objects.all()
77
+ serializer_class = CheckedObjectIncidentRelationshipRepresentationSerializer
78
+
79
+
80
+ class RiskIncidentModelViewSet(viewsets.ModelViewSet):
81
+ permission_classes = [RulePermission]
82
+ display_config_class = RiskIncidentDisplayConfig
83
+ endpoint_config_class = RiskIncidentEndpointConfig
84
+ button_config_class = RiskIncidentButtonConfig
85
+
86
+ ordering_fields = [
87
+ "status",
88
+ "date_range",
89
+ "rule__name",
90
+ "object_repr",
91
+ "severity__name",
92
+ "resolved_by__computed_str",
93
+ "comment",
94
+ ]
95
+ ordering = ["-date_range", "-severity__severity_order", "id"]
96
+ serializer_class = RiskIncidentModelSerializer
97
+ queryset = RiskIncident.objects.select_related(
98
+ "resolved_by",
99
+ "breached_content_type",
100
+ "rule",
101
+ "severity",
102
+ ).prefetch_related("checked_object_relationships")
103
+ filterset_class = RiskIncidentFilterSet
104
+
105
+ @action(detail=False, methods=["PATCH"])
106
+ def resolveallincidents(self, request, pk=None):
107
+ resolve_status = request.POST.get("resolve_status", RiskIncident.Status.RESOLVED.name)
108
+ resolve_all_incidents_as_task.delay(
109
+ request.user.id,
110
+ request.POST.get("comment", ""),
111
+ resolve_status == RiskIncident.Status.RESOLVED.name,
112
+ rule_id=request.GET.get("rule_id", None),
113
+ )
114
+ return HttpResponse("All opened incidents are resolved", status=status.HTTP_200_OK)
115
+
116
+ @cached_property
117
+ def rule_content_type(self):
118
+ return ContentType.objects.get_for_model(RiskRule)
119
+
120
+ @cached_property
121
+ def view_rule_permission(self):
122
+ return Permission.objects.get(codename="view_riskrule", content_type__app_label="wbcompliance")
123
+
124
+ def get_queryset(self):
125
+ """
126
+ We protect the queryset to allow only user to see their respective incidents
127
+ """
128
+ queryset = super().get_queryset()
129
+ if not self.request.user.has_perm("administrate_riskrule"):
130
+ queryset = queryset.annotate(
131
+ rule_pk=Cast(
132
+ "rule_id", output_field=CharField()
133
+ ), # We need to cast the rule id into a charfield otherwise postgres complains with the operator
134
+ have_view_permission_on_rule=Exists(
135
+ UserObjectPermission.objects.filter(
136
+ permission=self.view_rule_permission,
137
+ content_type=self.rule_content_type,
138
+ object_pk=OuterRef("rule_pk"),
139
+ user=self.request.user,
140
+ )
141
+ ),
142
+ ).filter(have_view_permission_on_rule=True)
143
+ queryset = queryset.annotate(
144
+ has_subincidents=Exists(CheckedObjectIncidentRelationship.objects.filter(incident=OuterRef("pk"))),
145
+ _group_key=Case(When(has_subincidents=True, then=F("id")), default=None, output_field=IntegerField()),
146
+ checked_date=ExpressionWrapper(F("date_range__endswith") - 1, output_field=DateField()),
147
+ object_repr=Concat(Value("Breached: "), F("breached_object_repr")),
148
+ threshold_repr=Subquery(
149
+ RuleThreshold.objects.filter(severity=OuterRef("severity"), rule=OuterRef("rule")).values(
150
+ "computed_str"
151
+ )[:1]
152
+ ),
153
+ )
154
+ return queryset
155
+
156
+ def add_messages(self, request, instance: RiskIncident | None = None, **kwargs):
157
+ if instance:
158
+ if instance.status == RiskIncident.Status.OPEN and instance.last_ignored_date:
159
+ warning(
160
+ request,
161
+ f"This incident was automatically reopened after being ignored from {instance.last_ignored_date:%Y-%m-%d} to {instance.get_ignore_until_date():%Y-%m-%d}",
162
+ )
163
+
164
+
165
+ class RiskIncidentRiskRuleModelViewSet(RiskIncidentModelViewSet):
166
+ permission_classes = [RulePermission]
167
+
168
+ def get_queryset(self):
169
+ return super().get_queryset().filter(rule=self.kwargs["rule_id"])
170
+
171
+
172
+ class CheckedObjectIncidentRelationshipModelViewSet(viewsets.ModelViewSet):
173
+ permission_classes = [RulePermission]
174
+ ordering = ["-rule_check__evaluation_date"]
175
+ ordering_fields = ["status", "object_repr", "checked_date"]
176
+ serializer_class = CheckedObjectIncidentRelationshipModelSerializer
177
+ queryset = CheckedObjectIncidentRelationship.objects.select_related(
178
+ "resolved_by",
179
+ "incident",
180
+ "rule_check",
181
+ "severity",
182
+ ).annotate(
183
+ object_repr=F("rule_check__rule_checked_object_relationship__checked_object_repr"),
184
+ checked_date=F("rule_check__evaluation_date"),
185
+ date_range=Cast(
186
+ Concat(Value("["), F("checked_date"), Value(","), F("checked_date"), Value("]")),
187
+ output_field=DateRangeField(),
188
+ ),
189
+ threshold_repr=Subquery(
190
+ RuleThreshold.objects.filter(severity=OuterRef("severity"), rule=OuterRef("incident__rule")).values(
191
+ "computed_str"
192
+ )[:1]
193
+ ),
194
+ )
195
+ display_config_class = CheckedObjectIncidentRelationshipRiskRuleDisplayConfig
196
+ endpoint_config_class = CheckedObjectIncidentRelationshipEndpointConfig
197
+ filterset_class = CheckedObjectIncidentRelationshipFilterSet
198
+
199
+
200
+ class CheckedObjectIncidentRelationshipRiskRuleModelViewSet(CheckedObjectIncidentRelationshipModelViewSet):
201
+ endpoint_config_class = CheckedObjectIncidentRelationshipRiskRuleEndpointConfig
202
+
203
+ def get_queryset(self):
204
+ return super().get_queryset().filter(incident=self.kwargs["incident_id"])
@@ -0,0 +1,52 @@
1
+ from contextlib import suppress
2
+
3
+ from django.contrib.contenttypes.models import ContentType
4
+ from rest_framework.reverse import reverse
5
+ from wbcompliance.models.risk_management.checks import RiskCheck
6
+ from wbcore.contrib.icons import WBIcon
7
+ from wbcore.metadata.configs import buttons as bt
8
+ from wbcore.signals import add_additional_resource, add_instance_button
9
+
10
+
11
+ class RiskCheckViewSetMixin:
12
+ @classmethod
13
+ def _get_risk_checks_button_title(cls) -> str:
14
+ return "Checks"
15
+
16
+ @classmethod
17
+ def _add_risk_check_button(cls, sender, many, *args, view=None, **kwargs):
18
+ icon = WBIcon.CONFIRM.icon
19
+ with suppress(AssertionError):
20
+ if view and (obj := view.get_object()):
21
+ icon = RiskCheck.CheckStatus[obj.get_worst_check_status()].icon
22
+ return bt.WidgetButton(key="checks", label=cls._get_risk_checks_button_title(), icon=icon)
23
+
24
+ @classmethod
25
+ def _add_risk_check_add_additional_resource(
26
+ cls, sender, serializer, instance, request, user, view=None, is_list=False, **kwargs
27
+ ):
28
+ if not is_list and instance.checks.exists() and (content_type := ContentType.objects.get_for_model(instance)):
29
+ return {
30
+ "checks": f'{reverse("wbcompliance:riskcheck-list", args=[], request=request)}?triggers=[[{content_type.id},{instance.id}]]'
31
+ }
32
+ return {}
33
+
34
+ def options(self, request, *args, **kwargs):
35
+ """
36
+ Handler method for HTTP 'OPTIONS' request.
37
+ """
38
+ add_instance_button.connect(
39
+ self._add_risk_check_button,
40
+ sender=self.__class__,
41
+ dispatch_uid="wbcompliance_add_instance_button_riskcheck",
42
+ )
43
+ return super().options(request, *args, **kwargs)
44
+
45
+ def get_serializer_class(self):
46
+ serializer_class = super().get_serializer_class()
47
+ add_additional_resource.connect(
48
+ self._add_risk_check_add_additional_resource,
49
+ sender=serializer_class,
50
+ dispatch_uid="wbcompliance_add_additional_resource_riskcheck",
51
+ )
52
+ return serializer_class
@@ -0,0 +1,179 @@
1
+ import pandas as pd
2
+ from django.contrib.auth import get_user_model
3
+ from django.db.models import Case, CharField, Count, OuterRef, Q, Subquery, Value, When
4
+ from django.db.models.functions import Coalesce
5
+ from django.http import HttpResponse
6
+ from django.shortcuts import get_object_or_404
7
+ from rest_framework import status
8
+ from rest_framework.decorators import action
9
+ from wbcompliance.filters import RiskRuleFilterSet
10
+ from wbcompliance.models.risk_management import (
11
+ RiskIncident,
12
+ RiskRule,
13
+ RuleBackend,
14
+ RuleCheckedObjectRelationship,
15
+ RuleThreshold,
16
+ )
17
+ from wbcompliance.models.risk_management.rules import RuleGroup, process_rule_as_task
18
+ from wbcompliance.permissions import RulePermission
19
+ from wbcompliance.serializers import (
20
+ RiskRuleModelSerializer,
21
+ RiskRuleRepresentationSerializer,
22
+ RuleBackendRepresentationSerializer,
23
+ RuleCheckedObjectRelationshipModelSerializer,
24
+ RuleCheckedObjectRelationshipRepresentationSerializer,
25
+ RuleGroupRepresentationSerializer,
26
+ RuleThresholdModelSerializer,
27
+ RuleThresholdRepresentationSerializer,
28
+ )
29
+ from wbcompliance.viewsets.buttons import RiskRuleButtonConfig
30
+ from wbcompliance.viewsets.display import (
31
+ RiskRuleDisplayConfig,
32
+ RuleCheckedObjectRelationshipRiskRuleDisplayConfig,
33
+ RuleThresholdRiskRuleDisplayConfig,
34
+ )
35
+ from wbcompliance.viewsets.endpoints import (
36
+ RiskRuleEndpointConfig,
37
+ RuleCheckedObjectRelationshipRiskRuleEndpointConfig,
38
+ RuleThresholdRiskRuleEndpointConfig,
39
+ )
40
+ from wbcore import serializers as wb_serializers
41
+ from wbcore import viewsets
42
+ from wbcore.contrib.guardian.viewsets.mixins import GuardianFilterMixin
43
+ from wbcore.utils.date import get_date_interval_from_request
44
+
45
+ User = get_user_model()
46
+
47
+
48
+ class RuleGroupRepresentationViewSet(viewsets.RepresentationViewSet):
49
+ queryset = RuleGroup.objects.all()
50
+ serializer_class = RuleGroupRepresentationSerializer
51
+ search_fields = ("name",)
52
+
53
+
54
+ class RuleCheckedObjectRelationshipRepresentationViewSet(viewsets.RepresentationViewSet):
55
+ queryset = RuleCheckedObjectRelationship.objects.all()
56
+ serializer_class = RuleCheckedObjectRelationshipRepresentationSerializer
57
+ search_fields = ("checked_object_repr",)
58
+
59
+
60
+ class RuleBackendRepresentationViewSet(viewsets.RepresentationViewSet):
61
+ queryset = RuleBackend.objects.all()
62
+ serializer_class = RuleBackendRepresentationSerializer
63
+ search_fields = ("name",)
64
+
65
+
66
+ class RuleThresholdRepresentationViewSet(viewsets.RepresentationViewSet):
67
+ queryset = RuleThreshold.objects.all()
68
+ serializer_class = RuleThresholdRepresentationSerializer
69
+
70
+
71
+ class RiskRuleRepresentationViewSet(GuardianFilterMixin, viewsets.RepresentationViewSet):
72
+ queryset = RiskRule.objects.all()
73
+ serializer_class = RiskRuleRepresentationSerializer
74
+ search_fields = (
75
+ "name",
76
+ "description",
77
+ "rule_backend__name",
78
+ )
79
+
80
+
81
+ class RuleThresholdRiskRuleModelViewSet(viewsets.ModelViewSet):
82
+ permission_classes = [RulePermission]
83
+ display_config_class = RuleThresholdRiskRuleDisplayConfig
84
+ endpoint_config_class = RuleThresholdRiskRuleEndpointConfig
85
+ queryset = RuleThreshold.objects.all()
86
+ serializer_class = RuleThresholdModelSerializer
87
+ filterset_fields = {
88
+ "severity": ["exact"],
89
+ }
90
+
91
+ def get_queryset(self):
92
+ return super().get_queryset().filter(rule_id=self.kwargs["rule_id"])
93
+
94
+
95
+ class RuleCheckedObjectRelationshipRiskRuleModelViewSet(viewsets.ModelViewSet):
96
+ permission_classes = [RulePermission]
97
+ display_config_class = RuleCheckedObjectRelationshipRiskRuleDisplayConfig
98
+ endpoint_config_class = RuleCheckedObjectRelationshipRiskRuleEndpointConfig
99
+ queryset = RuleCheckedObjectRelationship.objects.all()
100
+ serializer_class = RuleCheckedObjectRelationshipModelSerializer
101
+ search_fields = ["checked_object_repr"]
102
+ filterset_fields = {
103
+ "checked_object_repr": ["icontains"],
104
+ }
105
+
106
+ def get_queryset(self):
107
+ return super().get_queryset().filter(rule_id=self.kwargs["rule_id"])
108
+
109
+
110
+ class RiskRuleModelViewSet(GuardianFilterMixin, viewsets.ModelViewSet):
111
+ display_config_class = RiskRuleDisplayConfig
112
+ endpoint_config_class = RiskRuleEndpointConfig
113
+ button_config_class = RiskRuleButtonConfig
114
+
115
+ queryset = RiskRule.objects.all()
116
+ serializer_class = RiskRuleModelSerializer
117
+ filterset_class = RiskRuleFilterSet
118
+
119
+ search_fields = ("name", "description", "checked_object_relationships__checked_object_repr")
120
+ ordering_fields = (
121
+ "name",
122
+ "is_enable",
123
+ "only_passive_check_allowed",
124
+ "is_silent",
125
+ "is_mandatory",
126
+ "open_incidents_count",
127
+ )
128
+ ordering = ("name",)
129
+
130
+ def get_serializer_class(self):
131
+ if "pk" in self.kwargs:
132
+ if rule := self.get_object():
133
+ parameter_fields = rule.rule_backend.backend_class.get_serializer_class().get_parameter_fields()
134
+
135
+ class TmpSerializer(RiskRuleModelSerializer):
136
+ class Meta(RiskRuleModelSerializer.Meta):
137
+ flatten_fields = {
138
+ "parameters": wb_serializers.JSONTableField(
139
+ serializer_class=rule.rule_backend.backend_class.get_serializer_class(),
140
+ flatten_field_names=parameter_fields,
141
+ )
142
+ }
143
+
144
+ return TmpSerializer
145
+ return RiskRuleModelSerializer
146
+
147
+ def _subquery_open_risk_incidents_count(self):
148
+ queryset = RiskIncident.objects.filter(status=RiskIncident.Status.OPEN, rule=OuterRef("pk"))
149
+ return Coalesce(Subquery(queryset.values("rule").annotate(c=Count("rule")).values("c")[:1]), 0)
150
+
151
+ def get_queryset(self):
152
+ return (
153
+ super()
154
+ .get_queryset()
155
+ .annotate(
156
+ open_incidents_count=self._subquery_open_risk_incidents_count(),
157
+ in_breach=Case(
158
+ When(Q(open_incidents_count__gt=0) & Q(is_enable=True), then=Value("BREACH")),
159
+ When(Q(open_incidents_count=0) & Q(is_enable=True), then=Value("PASSED")),
160
+ default=Value("INACTIVE"),
161
+ output_field=CharField(),
162
+ ),
163
+ )
164
+ .select_related(
165
+ "rule_backend",
166
+ "creator",
167
+ )
168
+ .prefetch_related("incidents")
169
+ )
170
+
171
+ @action(detail=True, methods=["PATCH"])
172
+ def recheck(self, request, pk=None):
173
+ start, end = get_date_interval_from_request(request, request_type="POST")
174
+ if start and end:
175
+ rule = get_object_or_404(RiskRule, pk=pk)
176
+ for evaluation_date in pd.date_range(start, end, freq="B"):
177
+ process_rule_as_task.delay(rule.id, evaluation_date, override_incident=False)
178
+ return HttpResponse("Rule is checking", status=status.HTTP_200_OK)
179
+ return HttpResponse("Wrong arguments", status=status.HTTP_400_BAD_REQUEST)
@@ -0,0 +1,96 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from django.contrib.contenttypes.models import ContentType
4
+ from django.db.models import Q
5
+ from django.utils.functional import cached_property
6
+ from django.utils.translation import gettext as _
7
+ from wbcompliance.filters import RiskManagementIncidentFilter
8
+ from wbcompliance.models.risk_management import (
9
+ CheckedObjectIncidentRelationship,
10
+ RiskRule,
11
+ )
12
+ from wbcore.pandas import fields as pf
13
+ from wbcore.pandas.views import PandasAPIViewSet
14
+ from wbcore.utils.models import get_object
15
+
16
+ from ..display import RiskManagementIncidentTableDisplayConfig
17
+ from ..endpoints import RiskManagementIncidentTableEndpointConfig
18
+ from ..titles import RiskManagementIncidentTableTitleConfig
19
+
20
+
21
+ class RiskManagementIncidentTableView(PandasAPIViewSet):
22
+ IDENTIFIER = "wbcompliance:riskmanagementincidentpandas"
23
+ filterset_class = RiskManagementIncidentFilter
24
+ queryset = CheckedObjectIncidentRelationship.objects.all()
25
+
26
+ display_config_class = RiskManagementIncidentTableDisplayConfig
27
+ title_config_class = RiskManagementIncidentTableTitleConfig
28
+ endpoint_config_class = RiskManagementIncidentTableEndpointConfig
29
+
30
+ @cached_property
31
+ def checked_object_content_type(self):
32
+ if checked_object_content_type_id := self.request.GET.get("checked_object_content_type", None):
33
+ return ContentType.objects.get(id=checked_object_content_type_id)
34
+
35
+ @cached_property
36
+ def get_rule_map(self):
37
+ rules = RiskRule.objects.filter(
38
+ Q(is_enable=True)
39
+ & (
40
+ Q(rule_backend__allowed_checked_object_content_type__isnull=True)
41
+ | Q(rule_backend__allowed_checked_object_content_type=self.checked_object_content_type)
42
+ )
43
+ )
44
+ return list(map(lambda x: (f'rule_{x["id"]}', x["name"]), rules.order_by("name").values("id", "name")))
45
+
46
+ def get_ordering_fields(self):
47
+ return ["checked_object_repr", *[x[0] for x in self.get_rule_map]]
48
+
49
+ def get_pandas_fields(self, request):
50
+ fields = [
51
+ pf.PKField(key="id", label=_("ID")),
52
+ pf.CharField(key="checked_object_repr", label=_("ID")),
53
+ ]
54
+ for key, label in self.get_rule_map:
55
+ fields.append(pf.IntegerField(key=key, label=label))
56
+ return pf.PandasFields(fields=tuple(fields))
57
+
58
+ def get_queryset(self):
59
+ return CheckedObjectIncidentRelationship.objects.filter(incident__rule__is_enable=True)
60
+
61
+ def get_dataframe(self, request, queryset, **kwargs):
62
+ df = pd.DataFrame()
63
+ if checked_object_content_type := self.checked_object_content_type:
64
+ df = pd.DataFrame(
65
+ queryset.values(
66
+ "rule_check__rule_checked_object_relationship__checked_object_id",
67
+ "incident__rule__id",
68
+ "severity__severity_order",
69
+ ),
70
+ columns=[
71
+ "rule_check__rule_checked_object_relationship__checked_object_id",
72
+ "incident__rule__id",
73
+ "severity__severity_order",
74
+ ],
75
+ )
76
+ df = df.rename(
77
+ columns={
78
+ "rule_check__rule_checked_object_relationship__checked_object_id": "id",
79
+ "incident__rule__id": "rule",
80
+ "severity__severity_order": "severity",
81
+ }
82
+ )
83
+
84
+ if not df.empty:
85
+ df = (
86
+ df.pivot_table(index="id", columns=["rule"], values="severity", aggfunc=np.max, fill_value=-1)
87
+ .rename_axis(None, axis=1)
88
+ .astype("int")
89
+ )
90
+ df = df.rename(columns=lambda x: f"rule_{x}")
91
+ df = df.reset_index()
92
+ df["checked_object_repr"] = df.id.apply(
93
+ lambda x: str(get_object(checked_object_content_type.model_class(), x))
94
+ )
95
+ df = df.where(pd.notnull(df), -1)
96
+ return df
@@ -0,0 +1,17 @@
1
+ from .compliance_form import (
2
+ ComplianceFormSectionRuleTitleConfig,
3
+ ComplianceFormSectionTitleConfig,
4
+ ComplianceFormSignatureSectionRuleTitleConfig,
5
+ ComplianceFormSignatureTitleConfig,
6
+ ComplianceFormTitleConfig,
7
+ )
8
+ from .compliance_task import (
9
+ ComplianceTaskComplianceTaskGroupTitleConfig,
10
+ ComplianceTaskGroupTitleConfig,
11
+ ComplianceTaskInstanceTitleConfig,
12
+ ComplianceTaskMatrixPandasTitleConfig,
13
+ ComplianceTaskTitleConfig,
14
+ ReviewComplianceTaskTitleConfig,
15
+ )
16
+ from .compliance_type import ComplianceTypeTitleConfig
17
+ from .risk_managment import *
@@ -0,0 +1,101 @@
1
+ from django.utils.translation import gettext as _
2
+ from wbcompliance.models import (
3
+ ComplianceForm,
4
+ ComplianceFormRule,
5
+ ComplianceFormSection,
6
+ ComplianceFormSignatureRule,
7
+ ComplianceFormSignatureSection,
8
+ )
9
+ from wbcore.metadata.configs.titles import TitleViewConfig
10
+
11
+
12
+ class ComplianceFormTitleConfig(TitleViewConfig):
13
+ def get_instance_title(self):
14
+ return _("Compliance Form: {{title}} - version {{version}}")
15
+
16
+ def get_list_title(self):
17
+ return _("Compliance Forms")
18
+
19
+ def get_create_title(self):
20
+ return _("New Compliance Form")
21
+
22
+
23
+ class ComplianceFormSignatureTitleConfig(TitleViewConfig):
24
+ def get_instance_title(self):
25
+ return _("Compliance Form Signature: {{_compliance_form.title}} - version {{_compliance_form.version}}")
26
+
27
+ def get_list_title(self):
28
+ return _("Compliance Form Signatures")
29
+
30
+ def get_create_title(self):
31
+ return _("New Compliance Form Signature")
32
+
33
+
34
+ class ComplianceFormSectionTitleConfig(TitleViewConfig):
35
+ def get_instance_title(self):
36
+ if ref_id := self.view.kwargs.get("pk"):
37
+ _section = ComplianceFormSection.objects.get(id=ref_id)
38
+ return _("Compliance Form Section: {} of Compliance Form: - version {}").format(
39
+ _section.name, _section.compliance_form.title, _section.compliance_form.version
40
+ )
41
+
42
+ return _("Compliance Form Section: {{name}}")
43
+
44
+ def get_list_title(self):
45
+ if compliance_form_id := self.view.kwargs.get("compliance_form_id"):
46
+ compliance_form = ComplianceForm.objects.get(id=compliance_form_id)
47
+ return _("Compliance Form: {} - version {}").format(compliance_form.title, compliance_form.version)
48
+ return _("Compliance Form Sections")
49
+
50
+ def get_create_title(self):
51
+ if compliance_form_id := self.view.kwargs.get("compliance_form_id"):
52
+ compliance_form = ComplianceForm.objects.get(id=compliance_form_id)
53
+ return _("New Section of Compliance Form: {} - version {}").format(
54
+ compliance_form.title, compliance_form.version
55
+ )
56
+ return _("New Section")
57
+
58
+
59
+ class ComplianceFormSectionRuleTitleConfig(TitleViewConfig):
60
+ def get_instance_title(self):
61
+ if ref_id := self.view.kwargs.get("pk"):
62
+ _rule = ComplianceFormRule.objects.get(id=ref_id)
63
+ return _("Compliance Form Rule: {} of Section: {}").format(_rule.id, _rule.section.name)
64
+ return _("Compliance Form Rule: {{id}}")
65
+
66
+ def get_list_title(self):
67
+ if section_id := self.view.kwargs.get("section_id"):
68
+ section = ComplianceFormSection.objects.get(id=section_id)
69
+ return _("Compliance Form: {} - version {}").format(
70
+ section.compliance_form.title, section.compliance_form.version
71
+ )
72
+ return _("Compliance Form Rules")
73
+
74
+ def get_create_title(self):
75
+ if section_id := self.view.kwargs.get("section_id"):
76
+ section = ComplianceFormSection.objects.get(id=section_id)
77
+ return _("New Rule of Section: {}").format(section.name)
78
+ return _("New Rule")
79
+
80
+
81
+ class ComplianceFormSignatureSectionRuleTitleConfig(TitleViewConfig):
82
+ def get_instance_title(self):
83
+ if ref_id := self.view.kwargs.get("pk"):
84
+ _rule = ComplianceFormSignatureRule.objects.get(id=ref_id)
85
+ return _("Compliance Form Signature Rule: {} of Section: {}").format(_rule.id, _rule.section.name)
86
+ return _("Compliance Form Signature Rule: {{id}}")
87
+
88
+ def get_list_title(self):
89
+ if section_id := self.view.kwargs.get("section_id"):
90
+ section = ComplianceFormSignatureSection.objects.get(id=section_id)
91
+ return _("Compliance Form Signature: {} - version {}").format(
92
+ section.compliance_form_signature.compliance_form.title,
93
+ section.compliance_form_signature.compliance_form.version,
94
+ )
95
+ return _("Compliance Form Signature Rules")
96
+
97
+ def get_create_title(self):
98
+ if section_id := self.view.kwargs.get("section_id"):
99
+ section = ComplianceFormSignatureSection.objects.get(id=section_id)
100
+ return _("New Rule of Section: {}").format(section.name)
101
+ return _("New Rule")