clinicedc 2.0.7__py3-none-any.whl → 2.0.9__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.

Potentially problematic release.


This version of clinicedc might be problematic. Click here for more details.

Files changed (142) hide show
  1. {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/METADATA +4 -3
  2. {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/RECORD +136 -137
  3. {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/WHEEL +1 -1
  4. edc_action_item/auths.py +37 -32
  5. edc_action_item/models/action_model_mixin.py +1 -2
  6. edc_action_item/models/signals.py +22 -23
  7. edc_action_item/site_action_items.py +5 -9
  8. edc_action_item/utils.py +3 -3
  9. edc_adverse_event/auths.py +55 -51
  10. edc_adverse_event/model_mixins/ae_tmg/ae_tmg_methods_model_mixin.py +2 -4
  11. edc_appointment/auths.py +14 -10
  12. edc_appointment/creators/appointment_creator.py +1 -1
  13. edc_appointment/creators/appointments_creator.py +1 -1
  14. edc_appointment/model_mixins/appointment_methods_model_mixin.py +2 -3
  15. edc_appointment/model_mixins/appointment_model_mixin.py +31 -28
  16. edc_appointment/models/appointment.py +1 -1
  17. edc_appointment/utils.py +19 -24
  18. edc_auth/auth_objects/__init__.py +2 -20
  19. edc_auth/auth_objects/default_groups.py +13 -11
  20. edc_auth/auth_objects/default_roles.py +26 -24
  21. edc_auth/auth_updater/auth_updater.py +13 -2
  22. edc_auth/auth_updater/group_updater.py +12 -10
  23. edc_auth/auth_updater/role_updater.py +2 -2
  24. edc_auth/constants.py +10 -0
  25. edc_auth/import_users.py +3 -3
  26. edc_auth/migrations/0036_alter_userprofile_alternate_email_and_more.py +88 -0
  27. edc_auth/models/user_profile.py +14 -11
  28. edc_auth/site_auths.py +80 -67
  29. edc_consent/auths.py +18 -12
  30. edc_constants/constants.py +1 -0
  31. edc_crf/auths.py +5 -0
  32. edc_dashboard/auths.py +10 -6
  33. edc_dashboard/url_config.py +92 -83
  34. edc_dashboard/url_names.py +4 -4
  35. edc_dashboard/view_mixins/url_request_context_mixin.py +6 -5
  36. edc_data_manager/admin/data_query_admin.py +12 -11
  37. edc_data_manager/auths.py +37 -34
  38. edc_data_manager/rule/query_rule_wrapper.py +7 -7
  39. edc_export/archive_exporter.py +3 -2
  40. edc_export/auths.py +32 -28
  41. edc_export/model_exporter/model_exporter.py +4 -1
  42. edc_facility/auths.py +8 -3
  43. edc_facility/facility.py +8 -9
  44. edc_form_label/custom_label_condition.py +11 -8
  45. edc_form_label/form_label.py +1 -1
  46. edc_form_runners/auths.py +11 -6
  47. edc_form_validators/applicable_field_validator.py +7 -6
  48. edc_form_validators/base_form_validator.py +8 -9
  49. edc_form_validators/other_specify_field_validator.py +2 -8
  50. edc_form_validators/required_field_validator.py +19 -16
  51. edc_identifier/research_identifier.py +11 -10
  52. edc_identifier/simple_identifier.py +8 -2
  53. edc_lab/auths.py +26 -23
  54. edc_lab/lab/aliquot_creator.py +5 -8
  55. edc_lab/lab/primary_aliquot.py +14 -5
  56. edc_lab/migrations/0038_alter_aliquot_slug_alter_box_slug_alter_boxitem_slug_and_more.py +112 -0
  57. edc_lab/model_mixins/requisition/requisition_model_mixin.py +6 -8
  58. edc_lab/models/aliquot.py +2 -2
  59. edc_lab/models/manifest/manifest.py +2 -2
  60. edc_lab/models/manifest/manifest_item.py +1 -1
  61. edc_lab_dashboard/auths.py +16 -11
  62. edc_lab_results/calculate_missing.py +8 -8
  63. edc_lab_results/form_validator_mixins/blood_results_form_validator_mixin.py +2 -2
  64. edc_lab_results/get_summary.py +26 -25
  65. edc_lab_results/model_mixins/blood_result_model_mixin.py +2 -0
  66. edc_label/auths.py +6 -1
  67. edc_label/label_template.py +8 -8
  68. edc_list_data/load_model_data.py +3 -3
  69. edc_list_data/post_migrate_signals.py +1 -1
  70. edc_list_data/preload_data.py +2 -2
  71. edc_list_data/row.py +1 -1
  72. edc_list_data/site_list_data.py +6 -5
  73. edc_locator/auths.py +18 -13
  74. edc_metadata/auths.py +11 -7
  75. edc_metadata/metadata/metadata.py +1 -1
  76. edc_metadata/metadata_rules/crf/crf_rule.py +1 -1
  77. edc_metadata/metadata_rules/metadata_rule_evaluator.py +5 -3
  78. edc_metadata/metadata_rules/rule.py +2 -3
  79. edc_metadata/metadata_rules/rule_evaluator.py +1 -1
  80. edc_metadata/model_mixins/updates/updates_crf_metadata_model_mixin.py +7 -4
  81. edc_metadata/model_mixins/updates/updates_requisition_metadata_model_mixin.py +5 -2
  82. edc_metadata/models/signals.py +10 -11
  83. edc_navbar/auths.py +18 -13
  84. edc_notification/auths.py +9 -4
  85. edc_notification/notification/graded_event_notification.py +2 -2
  86. edc_notification/notification/model_notification.py +3 -30
  87. edc_notification/notification/new_model_notification.py +1 -1
  88. edc_notification/notification/notification.py +1 -1
  89. edc_notification/notification/updated_model_notification.py +2 -2
  90. edc_offstudy/auths.py +12 -7
  91. edc_pdutils/df_exporters/csv_model_exporter.py +5 -2
  92. edc_pharmacy/auths.py +19 -15
  93. edc_pharmacy/models/medication/formulation.py +5 -7
  94. edc_pharmacy/prescribe/create_prescription.py +3 -3
  95. edc_pharmacy/utils/confirm_stock.py +1 -1
  96. edc_pharmacy/utils/confirm_stock_at_site.py +1 -1
  97. edc_pharmacy/views/confirmation_at_site_view.py +6 -9
  98. edc_prn/admin_site.py +5 -0
  99. edc_prn/prn.py +10 -11
  100. edc_prn/urls.py +11 -0
  101. edc_protocol_incident/action_items.py +4 -4
  102. edc_protocol_incident/auths.py +27 -20
  103. edc_pylabels/auths.py +6 -1
  104. edc_qareports/auths.py +11 -7
  105. edc_randomization/admin.py +30 -24
  106. edc_randomization/auths.py +12 -7
  107. edc_randomization/randomizer.py +22 -20
  108. edc_randomization/utils.py +17 -16
  109. edc_refusal/auths.py +7 -2
  110. edc_refusal/model_mixins.py +1 -1
  111. edc_registration/auths.py +28 -23
  112. edc_registration/model_mixins/updates_or_creates_registered_subject_model_mixin.py +13 -4
  113. edc_registration/models/registered_subject.py +1 -1
  114. edc_reportable/utils/get_reference_range_collection.py +2 -3
  115. edc_reportable/utils/load_data.py +1 -1
  116. edc_review_dashboard/auths.py +23 -18
  117. edc_screening/age_evaluator.py +3 -3
  118. edc_screening/auths.py +35 -30
  119. edc_screening/eligibility.py +1 -1
  120. edc_screening/gender_evaluator.py +1 -1
  121. edc_screening/model_mixins/eligibility_model_mixin.py +0 -2
  122. edc_screening/model_mixins/screening_methods_model_mixin.py +1 -1
  123. edc_screening/screening_eligibility.py +2 -4
  124. edc_screening/utils.py +9 -9
  125. edc_search/generate_slug.py +26 -0
  126. edc_search/model_mixins.py +10 -21
  127. edc_sites/auths.py +8 -3
  128. edc_subject_dashboard/auths.py +27 -22
  129. edc_timepoint/apps.py +0 -21
  130. edc_unblinding/auths.py +9 -4
  131. edc_utils/__init__.py +3 -1
  132. edc_utils/show_urls.py +29 -2
  133. edc_visit_schedule/auths.py +6 -1
  134. edc_visit_schedule/site_visit_schedules.py +2 -2
  135. edc_visit_tracking/models/signals.py +2 -2
  136. edc_form_label/models.py +0 -0
  137. edc_search/constants.py +0 -1
  138. edc_search/models.py +0 -0
  139. edc_search/search_slug.py +0 -51
  140. edc_search/updater.py +0 -30
  141. edc_search/wsgi.py +0 -7
  142. {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/licenses/LICENSE +0 -0
@@ -26,7 +26,7 @@ def confirm_stock_at_site(
26
26
  stock_transfer: StockTransfer,
27
27
  stock_codes: list[str],
28
28
  location: UUID,
29
- request: WSGIRequest = None,
29
+ request: WSGIRequest | None = None,
30
30
  ) -> tuple[list[str], list[str], list[str]]:
31
31
  """Confirm stock instances given a list of stock codes
32
32
  and a request/receive pk.
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  import uuid
4
5
  from uuid import uuid4
5
6
 
@@ -97,13 +98,12 @@ class ConfirmationAtSiteView(
97
98
  return min(unconfirmed_count, 12)
98
99
 
99
100
  def get_stock_codes(self, stock_transfer):
100
- stock_codes = [
101
+ return [
101
102
  code
102
103
  for code in stock_transfer.stocktransferitem_set.values_list(
103
104
  "stock__code", flat=True
104
105
  ).all()
105
106
  ]
106
- return stock_codes
107
107
 
108
108
  def get_unconfirmed_count(self, stock_transfer) -> int:
109
109
  return (
@@ -120,10 +120,8 @@ class ConfirmationAtSiteView(
120
120
  def site(self) -> Site | None:
121
121
  obj = None
122
122
  if self.kwargs.get("site_id"):
123
- try:
123
+ with contextlib.suppress(ObjectDoesNotExist):
124
124
  obj = Site.objects.get(id=self.kwargs.get("site_id"))
125
- except ObjectDoesNotExist:
126
- pass
127
125
  return obj
128
126
 
129
127
  @property
@@ -169,14 +167,13 @@ class ConfirmationAtSiteView(
169
167
  def confirmation_at_site_changelist_url(self) -> str:
170
168
  if self.confirmation_at_site:
171
169
  url = reverse("edc_pharmacy_admin:edc_pharmacy_confirmationatsite_changelist")
172
- url = f"{url}?q={self.confirmation_at_site.transfer_confirmation_identifier}"
173
- return url
170
+ return f"{url}?q={self.confirmation_at_site.transfer_confirmation_identifier}"
174
171
  return "/"
175
172
 
176
173
  def get_stock_transfer(
177
174
  self,
178
175
  stock_transfer_identifier: str,
179
- suppress_msg: bool = None,
176
+ suppress_msg: bool | None = None,
180
177
  ) -> StockTransfer | None:
181
178
  stock_transfer = None
182
179
  try:
@@ -198,7 +195,7 @@ class ConfirmationAtSiteView(
198
195
  )
199
196
  return stock_transfer
200
197
 
201
- def post(self, request, *args, **kwargs) -> HttpResponseRedirect:
198
+ def post(self, request, *args, **kwargs) -> HttpResponseRedirect: # noqa: ARG002
202
199
  # cancel
203
200
  if request.POST.get("cancel") and request.POST.get("cancel") == "cancel":
204
201
  url = reverse("edc_pharmacy:home_url")
edc_prn/admin_site.py ADDED
@@ -0,0 +1,5 @@
1
+ from edc_model_admin.admin_site import EdcAdminSite
2
+
3
+ from .apps import AppConfig
4
+
5
+ edc_prn_admin = EdcAdminSite(name="edc_prn_admin", app_label=AppConfig.name)
edc_prn/prn.py CHANGED
@@ -58,19 +58,18 @@ class Prn:
58
58
  try:
59
59
  return django_apps.get_model(self.model)
60
60
  except LookupError as e:
61
- raise PrnError(f"{e}. See {self!r}")
61
+ raise PrnError(f"{e}. See {self!r}") from e
62
62
 
63
- def get_show_on_dashboard(self, subject_identifier=None, **kwargs):
63
+ def get_show_on_dashboard(self, subject_identifier=None, **kwargs): # noqa: ARG002
64
64
  count = 0
65
- if self.show_on_dashboard:
66
- if subject_identifier:
67
- opts = dict(subject_identifier=subject_identifier)
68
- try:
69
- count = self.model_cls.objects.filter(**opts).count()
70
- except FieldError:
71
- opts = self.get_query_opts(subject_identifier)
72
- count = self.model_cls.objects.filter(**opts).count()
73
- return True if count and self.show_on_dashboard else False
65
+ if self.show_on_dashboard and subject_identifier:
66
+ opts = dict(subject_identifier=subject_identifier)
67
+ try:
68
+ count = self.model_cls.objects.filter(**opts).count()
69
+ except FieldError:
70
+ opts = self.get_query_opts(subject_identifier)
71
+ count = self.model_cls.objects.filter(**opts).count()
72
+ return count and self.show_on_dashboard
74
73
 
75
74
  def get_query_opts(self, subject_identifier):
76
75
  """Returns alternative query opts to search on
edc_prn/urls.py ADDED
@@ -0,0 +1,11 @@
1
+ from django.urls import path
2
+ from django.views.generic.base import RedirectView
3
+
4
+ from .admin_site import edc_prn_admin
5
+
6
+ app_name = "edc_prn"
7
+
8
+ urlpatterns = [
9
+ path("admin/", edc_prn_admin.urls),
10
+ path("", RedirectView.as_view(url="admin/"), name="home_url"),
11
+ ]
@@ -9,8 +9,8 @@ from .constants import (
9
9
 
10
10
 
11
11
  class ProtocolDeviationViolationAction(ActionWithNotification):
12
- reference_model = None # "my_app.protocolincident"
13
- admin_site_name = None # "my_app_admin"
12
+ reference_model = "edc_protocol_incident.protocoldeviationviolation"
13
+ admin_site_name = "edc_protocol_incident_admin"
14
14
 
15
15
  name = PROTOCOL_DEVIATION_VIOLATION_ACTION
16
16
  display_name = "Submit Protocol Deviation / Violation Report"
@@ -25,8 +25,8 @@ class ProtocolDeviationViolationAction(ActionWithNotification):
25
25
 
26
26
 
27
27
  class ProtocolIncidentAction(ActionWithNotification):
28
- reference_model = None # "my_app.protocolincident"
29
- admin_site_name = None # "my_app"
28
+ reference_model = "edc_protocol_incident.protocolincident"
29
+ admin_site_name = "edc_protocol_incident_admin"
30
30
 
31
31
  name = PROTOCOL_INCIDENT_ACTION
32
32
  display_name = "Submit Protocol Incident Report"
@@ -14,24 +14,31 @@ from .auth_objects import (
14
14
  )
15
15
  from .constants import PROTOCOL_DEVIATION_VIOLATION, PROTOCOL_INCIDENT
16
16
 
17
- incident_type = getattr(settings, "EDC_PROTOCOL_VIOLATION_TYPE", PROTOCOL_DEVIATION_VIOLATION)
18
-
19
- site_auths.add_group(*protocol_violation_codenames, name=PROTOCOL_VIOLATION)
20
- site_auths.add_group(*protocol_violation_view_codenames, name=PROTOCOL_VIOLATION_VIEW)
21
- site_auths.add_group(*protocol_incident_codenames, name=PROTOCOL_INCIDENT)
22
- site_auths.add_group(*protocol_incident_view_codenames, name=PROTOCOL_INCIDENT_VIEW)
23
-
24
- if incident_type == PROTOCOL_DEVIATION_VIOLATION:
25
- site_auths.update_role(PROTOCOL_VIOLATION, name=CLINICIAN_ROLE)
26
- site_auths.update_role(PROTOCOL_VIOLATION, name=CLINICIAN_SUPER_ROLE)
27
- site_auths.update_role(PROTOCOL_VIOLATION_VIEW, name=AUDITOR_ROLE)
28
- elif incident_type == PROTOCOL_INCIDENT:
29
- site_auths.update_role(PROTOCOL_INCIDENT, name=CLINICIAN_ROLE)
30
- site_auths.update_role(PROTOCOL_INCIDENT, name=CLINICIAN_SUPER_ROLE)
31
- site_auths.update_role(PROTOCOL_INCIDENT_VIEW, name=AUDITOR_ROLE)
32
- else:
33
- raise ValueError(
34
- "Invalid value for settings.EDC_PROTOCOL_VIOLATION_TYPE. "
35
- f"Expected `{PROTOCOL_INCIDENT}` or `{PROTOCOL_DEVIATION_VIOLATION}`. "
36
- f"Got {incident_type}."
17
+
18
+ def update_site_auths() -> None:
19
+ incident_type = getattr(
20
+ settings, "EDC_PROTOCOL_VIOLATION_TYPE", PROTOCOL_DEVIATION_VIOLATION
37
21
  )
22
+
23
+ site_auths.add_group(*protocol_violation_codenames, name=PROTOCOL_VIOLATION)
24
+ site_auths.add_group(*protocol_violation_view_codenames, name=PROTOCOL_VIOLATION_VIEW)
25
+ site_auths.add_group(*protocol_incident_codenames, name=PROTOCOL_INCIDENT)
26
+ site_auths.add_group(*protocol_incident_view_codenames, name=PROTOCOL_INCIDENT_VIEW)
27
+
28
+ if incident_type == PROTOCOL_DEVIATION_VIOLATION:
29
+ site_auths.update_role(PROTOCOL_VIOLATION, name=CLINICIAN_ROLE)
30
+ site_auths.update_role(PROTOCOL_VIOLATION, name=CLINICIAN_SUPER_ROLE)
31
+ site_auths.update_role(PROTOCOL_VIOLATION_VIEW, name=AUDITOR_ROLE)
32
+ elif incident_type == PROTOCOL_INCIDENT:
33
+ site_auths.update_role(PROTOCOL_INCIDENT, name=CLINICIAN_ROLE)
34
+ site_auths.update_role(PROTOCOL_INCIDENT, name=CLINICIAN_SUPER_ROLE)
35
+ site_auths.update_role(PROTOCOL_INCIDENT_VIEW, name=AUDITOR_ROLE)
36
+ else:
37
+ raise ValueError(
38
+ "Invalid value for settings.EDC_PROTOCOL_VIOLATION_TYPE. "
39
+ f"Expected `{PROTOCOL_INCIDENT}` or `{PROTOCOL_DEVIATION_VIOLATION}`. "
40
+ f"Got {incident_type}."
41
+ )
42
+
43
+
44
+ update_site_auths()
edc_pylabels/auths.py CHANGED
@@ -2,4 +2,9 @@ from edc_auth.site_auths import site_auths
2
2
 
3
3
  from .auth_objects import PYLABELS, codenames
4
4
 
5
- site_auths.add_group(*codenames, name=PYLABELS, no_delete=False)
5
+
6
+ def update_site_auths() -> None:
7
+ site_auths.add_group(*codenames, name=PYLABELS, no_delete=False)
8
+
9
+
10
+ update_site_auths()
edc_qareports/auths.py CHANGED
@@ -9,12 +9,16 @@ from .auth_objects import (
9
9
  qa_reports_codenames,
10
10
  )
11
11
 
12
- # groups
13
- site_auths.add_group(*qa_reports_codenames, name=QA_REPORTS)
14
- site_auths.add_group(*qa_reports_codenames, name=QA_REPORTS_AUDIT, view_only=True)
15
12
 
13
+ def update_site_auths() -> None:
14
+ # groups
15
+ site_auths.add_group(*qa_reports_codenames, name=QA_REPORTS)
16
+ site_auths.add_group(*qa_reports_codenames, name=QA_REPORTS_AUDIT, view_only=True)
16
17
 
17
- # roles
18
- site_auths.add_role(QA_REPORTS, name=QA_REPORTS_ROLE)
19
- site_auths.add_role(QA_REPORTS, name=QA_REPORTS_SUPER_ROLE)
20
- site_auths.add_role(QA_REPORTS_AUDIT, name=QA_REPORTS_AUDIT_ROLE)
18
+ # roles
19
+ site_auths.add_role(QA_REPORTS, name=QA_REPORTS_ROLE)
20
+ site_auths.add_role(QA_REPORTS, name=QA_REPORTS_SUPER_ROLE)
21
+ site_auths.add_role(QA_REPORTS_AUDIT, name=QA_REPORTS_AUDIT_ROLE)
22
+
23
+
24
+ update_site_auths()
@@ -1,3 +1,5 @@
1
+ import contextlib
2
+
1
3
  from django.conf import settings
2
4
  from django.contrib import admin
3
5
  from django.contrib.admin.sites import AlreadyRegistered
@@ -65,7 +67,8 @@ class RandomizationListModelAdmin(TemplatesModelAdminMixin, admin.ModelAdmin):
65
67
 
66
68
  def get_readonly_fields(self, request, obj=None):
67
69
  readonly_fields = super().get_readonly_fields(request, obj=obj)
68
- readonly_fields += (
70
+ readonly_fields = (
71
+ *readonly_fields,
69
72
  "subject_identifier",
70
73
  "sid",
71
74
  "site_name",
@@ -75,7 +78,8 @@ class RandomizationListModelAdmin(TemplatesModelAdminMixin, admin.ModelAdmin):
75
78
  "allocated_datetime",
76
79
  "allocated_site",
77
80
  "randomizer_name",
78
- ) + audit_fields
81
+ *audit_fields,
82
+ )
79
83
  return tuple(set(readonly_fields))
80
84
 
81
85
  def get_queryset(self, request):
@@ -90,7 +94,7 @@ class RandomizationListModelAdmin(TemplatesModelAdminMixin, admin.ModelAdmin):
90
94
  return qs
91
95
 
92
96
  def get_list_display(self, request) -> tuple[str, ...]:
93
- list_display = [
97
+ fields = (
94
98
  "sid",
95
99
  "assignment",
96
100
  "site_name",
@@ -98,22 +102,24 @@ class RandomizationListModelAdmin(TemplatesModelAdminMixin, admin.ModelAdmin):
98
102
  "allocated_datetime",
99
103
  "allocated_site",
100
104
  "randomizer_name",
101
- ]
105
+ )
106
+ if flds := site_randomizers.get_by_model(
107
+ self.model._meta.label_lower
108
+ ).get_extra_list_display():
109
+ fields = list(fields)
110
+ for pos, fname in flds:
111
+ fields.insert(pos, fname)
112
+ fields = tuple(fields)
102
113
  if user_is_blinded(request.user.username) or (
103
114
  not user_is_blinded(request.user.username)
104
115
  and RANDO_UNBLINDED not in [g.name for g in request.user.groups.all()]
105
116
  ):
106
- list_display.remove("assignment")
107
- if flds := site_randomizers.get_by_model(
108
- self.model._meta.label_lower
109
- ).get_extra_list_display():
110
- for pos, fldname in flds:
111
- list_display.insert(pos, fldname)
112
- return tuple(list_display)
117
+ fields = tuple([fname for fname in fields if fname != "assignment"])
118
+ return fields
113
119
 
114
120
  @staticmethod
115
121
  def get_fieldnames(request) -> tuple[str, ...]:
116
- fields = [
122
+ fields = (
117
123
  "subject_identifier",
118
124
  "sid",
119
125
  "assignment",
@@ -122,33 +128,35 @@ class RandomizationListModelAdmin(TemplatesModelAdminMixin, admin.ModelAdmin):
122
128
  "allocated_datetime",
123
129
  "allocated_site",
124
130
  "randomizer_name",
125
- ]
131
+ )
126
132
  if user_is_blinded(request.user.username) or (
127
133
  not user_is_blinded(request.user.username)
128
134
  and RANDO_UNBLINDED not in [g.name for g in request.user.groups.all()]
129
135
  ):
130
- fields.remove("assignment")
131
- return tuple(fields)
136
+ fields = tuple([fname for fname in fields if fname != "assignment"])
137
+ return fields
132
138
 
133
139
  def get_list_filter(self, request) -> tuple[str, ...]:
134
- list_filter = [
140
+ fields = (
135
141
  "assignment",
136
142
  "allocated_datetime",
137
143
  "allocated_site",
138
144
  "site_name",
139
145
  "randomizer_name",
140
- ]
146
+ )
141
147
  if flds := site_randomizers.get_by_model(
142
148
  self.model._meta.label_lower
143
149
  ).get_extra_list_filter():
144
- for pos, fldname in flds:
145
- list_filter.insert(pos, fldname)
150
+ fields = list(fields)
151
+ for pos, fname in flds:
152
+ fields.insert(pos, fname)
153
+ fields = tuple(fields)
146
154
  if user_is_blinded(request.user.username) or (
147
155
  not user_is_blinded(request.user.username)
148
156
  and RANDO_UNBLINDED not in [g.name for g in request.user.groups.all()]
149
157
  ):
150
- list_filter.remove("assignment")
151
- return tuple(list_filter)
158
+ fields = tuple([fname for fname in fields if fname != "assignment"])
159
+ return fields
152
160
 
153
161
 
154
162
  def register_admin():
@@ -156,10 +164,8 @@ def register_admin():
156
164
  for randomizer_cls in site_randomizers._registry.values():
157
165
  model = randomizer_cls.model_cls()
158
166
  admin_cls = type(f"{model.__name__}ModelAdmin", (RandomizationListModelAdmin,), {})
159
- try:
167
+ with contextlib.suppress(AlreadyRegistered):
160
168
  edc_randomization_admin.register(model, admin_cls)
161
- except AlreadyRegistered:
162
- pass
163
169
 
164
170
 
165
171
  register_admin()
@@ -9,11 +9,16 @@ from .auth_objects import (
9
9
  update_rando_group_permissions,
10
10
  )
11
11
 
12
- site_auths.add_post_update_func(
13
- "edc_randomization", remove_default_model_permissions_from_edc_permissions
14
- )
15
12
 
16
- site_auths.add_group(get_rando_permissions_codenames, name=RANDO_BLINDED, view_only=True)
17
- site_auths.add_group(get_rando_permissions_codenames, name=RANDO_UNBLINDED, view_only=True)
18
- site_auths.add_post_update_func("edc_randomization", update_rando_group_permissions)
19
- site_auths.add_post_update_func("edc_randomization", make_randomizationlist_view_only)
13
+ def update_site_auths() -> None:
14
+ site_auths.add_post_update_func(
15
+ "edc_randomization", remove_default_model_permissions_from_edc_permissions
16
+ )
17
+
18
+ site_auths.add_group(get_rando_permissions_codenames, name=RANDO_BLINDED, view_only=True)
19
+ site_auths.add_group(get_rando_permissions_codenames, name=RANDO_UNBLINDED, view_only=True)
20
+ site_auths.add_post_update_func("edc_randomization", update_rando_group_permissions)
21
+ site_auths.add_post_update_func("edc_randomization", make_randomizationlist_view_only)
22
+
23
+
24
+ update_site_auths()
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  import warnings
4
5
  from datetime import datetime
5
6
  from pathlib import Path
@@ -8,6 +9,7 @@ from typing import TYPE_CHECKING, Any
8
9
  from django.apps import apps as django_apps
9
10
  from django.conf import settings
10
11
  from django.core.exceptions import ObjectDoesNotExist, ValidationError
12
+ from django.db.models import Q
11
13
 
12
14
  from edc_registration.utils import get_registered_subject_model_cls
13
15
 
@@ -25,15 +27,15 @@ if TYPE_CHECKING:
25
27
  from edc_registration.models import RegisteredSubject
26
28
 
27
29
 
28
- class InvalidAssignmentDescriptionMap(Exception):
30
+ class InvalidAssignmentDescriptionMap(Exception): # noqa: N818
29
31
  pass
30
32
 
31
33
 
32
- class RandomizationListFileNotFound(Exception):
34
+ class RandomizationListFileNotFound(Exception): # noqa: N818
33
35
  pass
34
36
 
35
37
 
36
- class RandomizationListNotLoaded(Exception):
38
+ class RandomizationListNotLoaded(Exception): # noqa: N818
37
39
  pass
38
40
 
39
41
 
@@ -41,7 +43,7 @@ class RandomizationError(Exception):
41
43
  pass
42
44
 
43
45
 
44
- class AlreadyRandomized(ValidationError):
46
+ class AlreadyRandomized(ValidationError): # noqa: N818
45
47
  pass
46
48
 
47
49
 
@@ -113,10 +115,10 @@ class Randomizer:
113
115
  subject_identifier: str | None = None,
114
116
  identifier_attr: str | None = None,
115
117
  identifier_object_name: str | None = None,
116
- report_datetime: datetime = None,
117
- site: Any = None,
118
- user: str = None,
119
- **kwargs,
118
+ report_datetime: datetime | None = None,
119
+ site: Any | None = None,
120
+ user: str | None = None,
121
+ **kwargs, # noqa: ARG002
120
122
  ):
121
123
  self._model_obj = None
122
124
  self._registration_obj = None
@@ -208,8 +210,10 @@ class Randomizer:
208
210
  @property
209
211
  def sid(self):
210
212
  """Returns the SID."""
211
- if self.model_obj.sid is None:
212
- raise RandomizationError(f"SID cannot be None. See {self.model_obj}.")
213
+ if not self.model_obj.sid:
214
+ raise RandomizationError(
215
+ f"SID cannot be None. See {self.model_obj}. Got {self.model_obj.sid}"
216
+ )
213
217
  return self.model_obj.sid
214
218
 
215
219
  @property
@@ -233,7 +237,7 @@ class Randomizer:
233
237
  if not self._model_obj:
234
238
  try:
235
239
  obj = self.model_cls().objects.get(**self.identifier_opts)
236
- except ObjectDoesNotExist:
240
+ except ObjectDoesNotExist as e:
237
241
  opts = dict(site_name=self.site.name, **self.extra_model_obj_options)
238
242
  self._model_obj = (
239
243
  self.model_cls()
@@ -245,7 +249,7 @@ class Randomizer:
245
249
  fld_str = ", ".join([f"{k}=`{v}`" for k, v in opts.items()])
246
250
  raise AllocationError(
247
251
  f"Randomization failed. No additional SIDs available for {fld_str}."
248
- )
252
+ ) from e
249
253
  else:
250
254
  raise AlreadyRandomized(
251
255
  f"{self.identifier_object_name.title()} already randomized. "
@@ -282,7 +286,7 @@ class Randomizer:
282
286
  Called by `registration_obj`.
283
287
  """
284
288
  return self.get_registration_model_cls().objects.get(
285
- sid__isnull=True, **self.identifier_opts
289
+ (Q(sid__isnull=True) | Q(sid="")), **self.identifier_opts
286
290
  )
287
291
 
288
292
  @property
@@ -298,14 +302,14 @@ class Randomizer:
298
302
  if not self._registration_obj:
299
303
  try:
300
304
  self._registration_obj = self.get_unallocated_registration_obj()
301
- except ObjectDoesNotExist:
305
+ except ObjectDoesNotExist as e:
302
306
  try:
303
307
  obj = self.get_registration_model_cls().objects.get(**self.identifier_opts)
304
- except ObjectDoesNotExist:
308
+ except ObjectDoesNotExist as e:
305
309
  raise RandomizationError(
306
310
  f"{self.identifier_object_name.title()} does not exist. "
307
311
  f"Got {getattr(self, self.identifier_attr)}"
308
- )
312
+ ) from e
309
313
  else:
310
314
  raise AlreadyRandomized(
311
315
  f"{self.identifier_object_name.title()} already randomized. "
@@ -313,7 +317,7 @@ class Randomizer:
313
317
  f"Got {getattr(obj, self.identifier_attr)} "
314
318
  f"SID={obj.sid}",
315
319
  code=self.get_registration_model_cls()._meta.label_lower,
316
- )
320
+ ) from e
317
321
  return self._registration_obj
318
322
 
319
323
  @property
@@ -347,7 +351,7 @@ class Randomizer:
347
351
  "Randomization list file not found. "
348
352
  f"Got `{cls.get_randomizationlist_path()}`. See Randomizer {cls.name}."
349
353
  )
350
- try:
354
+ with contextlib.suppress(RandomizationListAlreadyImported):
351
355
  result = cls.importer_cls(
352
356
  assignment_map=cls.assignment_map,
353
357
  randomizationlist_path=cls.get_randomizationlist_path(),
@@ -356,8 +360,6 @@ class Randomizer:
356
360
  extra_csv_fieldnames=cls.extra_csv_fieldnames,
357
361
  **kwargs,
358
362
  ).import_list(**kwargs)
359
- except RandomizationListAlreadyImported:
360
- pass
361
363
  return result
362
364
 
363
365
  @classmethod
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import csv
4
- import os
4
+ import sys
5
+ from pathlib import Path
5
6
  from typing import Any
6
7
 
7
8
  from django.conf import settings
@@ -20,13 +21,13 @@ class RandomizationListExporterError(Exception):
20
21
  pass
21
22
 
22
23
 
23
- class SubjectNotRandomization(Exception):
24
+ class SubjectNotRandomization(Exception): # noqa: N818
24
25
  pass
25
26
 
26
27
 
27
28
  def get_assignment_for_subject(
28
29
  subject_identifier: str,
29
- randomizer_name: str = None,
30
+ randomizer_name: str,
30
31
  identifier_fld: str | None = None,
31
32
  ) -> str:
32
33
  """Returns the assignment for a randomized subject.
@@ -42,7 +43,7 @@ def get_assignment_for_subject(
42
43
 
43
44
  def get_assignment_description_for_subject(
44
45
  subject_identifier: str,
45
- randomizer_name: str = None,
46
+ randomizer_name: str,
46
47
  identifier_fld: str | None = None,
47
48
  ) -> str:
48
49
  """Returns the assignment description for a randomized subject.
@@ -60,7 +61,7 @@ def get_assignment_description_for_subject(
60
61
 
61
62
  def get_object_for_subject(
62
63
  subject_identifier: str,
63
- randomizer_name: str = None,
64
+ randomizer_name: str,
64
65
  identifier_fld: str | None = None,
65
66
  label: str | None = None,
66
67
  ) -> Any:
@@ -81,11 +82,11 @@ def get_object_for_subject(
81
82
  }
82
83
  try:
83
84
  obj = randomizer_cls.model_cls().objects.get(**opts)
84
- except ObjectDoesNotExist:
85
+ except ObjectDoesNotExist as e:
85
86
  raise SubjectNotRandomization(
86
87
  f"{label.title()} not randomized. See Randomizer `{randomizer_name}`. "
87
88
  f"Got {identifier_fld}=`{subject_identifier}`."
88
- )
89
+ ) from e
89
90
  return obj
90
91
 
91
92
 
@@ -118,7 +119,7 @@ def generate_fake_randomization_list(
118
119
 
119
120
  # get site ID and write the file
120
121
  site_id = sites.get_by_attr("name", site_name)
121
- with open(filename, "a+", newline="") as f:
122
+ with Path(filename).open("a+", newline="") as f:
122
123
  writer = csv.DictWriter(f, fieldnames=["sid", "assignment", "site_name", "country"])
123
124
  if write_header:
124
125
  writer.writeheader()
@@ -133,7 +134,7 @@ def generate_fake_randomization_list(
133
134
  )
134
135
  )
135
136
 
136
- print(f"(*) Added {slots} slots for {site_name}.")
137
+ sys.stdout.write(f"(*) Added {slots} slots for {site_name}.\n")
137
138
 
138
139
 
139
140
  def export_randomization_list(
@@ -143,20 +144,20 @@ def export_randomization_list(
143
144
 
144
145
  try:
145
146
  user = get_user_model().objects.get(username=username)
146
- except ObjectDoesNotExist:
147
- raise RandomizationListExporterError(f"User `{username}` does not exist")
147
+ except ObjectDoesNotExist as e:
148
+ raise RandomizationListExporterError(f"User `{username}` does not exist") from e
148
149
  if not user.has_perm(randomizer_cls.model_cls()._meta.label_lower.replace(".", ".view_")):
149
150
  raise RandomizationListExporterError(
150
151
  f"User `{username}` does not have "
151
152
  f"permission to view '{randomizer_cls.model_cls()._meta.label_lower}'"
152
153
  )
153
- path = path or settings.EXPORT_FOLDER
154
+ path = Path(path or settings.EXPORT_FOLDER)
154
155
  timestamp = timezone.now().strftime("%Y%m%d%H%M")
155
- filename = os.path.expanduser(
156
+ filename = Path(
156
157
  f"~/{settings.APP_NAME}_{randomizer_cls.name}_"
157
158
  f"randomizationlist_exported_{timestamp}.csv"
158
- )
159
- filename = os.path.join(path, filename)
159
+ ).expanduser()
160
+ filename = path / filename
160
161
 
161
162
  df = (
162
163
  read_frame(randomizer_cls.model_cls().objects.all(), verbose=False)
@@ -172,5 +173,5 @@ def export_randomization_list(
172
173
  sep="|",
173
174
  )
174
175
  df.to_csv(**opts)
175
- print(filename)
176
+ sys.stdout.write(f"{filename!s}\n")
176
177
  return filename
edc_refusal/auths.py CHANGED
@@ -3,5 +3,10 @@ from edc_screening.auth_objects import SCREENING, SCREENING_SUPER
3
3
 
4
4
  from .auth_objects import codenames
5
5
 
6
- site_auths.update_group(*codenames, name=SCREENING, no_delete=True)
7
- site_auths.update_group(*codenames, name=SCREENING_SUPER)
6
+
7
+ def update_site_auths() -> None:
8
+ site_auths.update_group(*codenames, name=SCREENING, no_delete=True)
9
+ site_auths.update_group(*codenames, name=SCREENING_SUPER)
10
+
11
+
12
+ update_site_auths()
@@ -33,7 +33,7 @@ class SubjectRefusalModelMixin(models.Model):
33
33
 
34
34
  @staticmethod
35
35
  def get_search_slug_fields():
36
- return ["screening_identifier"]
36
+ return ("screening_identifier",)
37
37
 
38
38
  class Meta:
39
39
  abstract = True