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
@@ -43,8 +43,8 @@ class RequiredFieldValidator(BaseFormValidator):
43
43
  def required_if(
44
44
  self,
45
45
  *responses: str | int | bool,
46
- field: str = None,
47
- field_required: str = None,
46
+ field: str,
47
+ field_required: str,
48
48
  required_msg: str | None = None,
49
49
  not_required_msg: str | None = None,
50
50
  optional_if_dwta: bool | None = None,
@@ -82,7 +82,7 @@ class RequiredFieldValidator(BaseFormValidator):
82
82
  self.get(field_required, inline_set=inline_set) is not None
83
83
  )
84
84
  else:
85
- field_required_has_value = self.get(field_required, inline_set=inline_set)
85
+ field_required_has_value = bool(self.get(field_required, inline_set=inline_set))
86
86
 
87
87
  if field in self.cleaned_data:
88
88
  if (DWTA in responses and optional_if_dwta and field_value == DWTA) or (
@@ -117,7 +117,7 @@ class RequiredFieldValidator(BaseFormValidator):
117
117
  def required_if_true(
118
118
  self,
119
119
  condition: bool,
120
- field_required: str = None,
120
+ field_required: str,
121
121
  required_msg: str | None = None,
122
122
  not_required_msg: str | None = None,
123
123
  inverse: bool | None = None,
@@ -145,7 +145,7 @@ class RequiredFieldValidator(BaseFormValidator):
145
145
  def not_required_if_true(
146
146
  self,
147
147
  condition: bool,
148
- field: str = None,
148
+ field: str,
149
149
  msg: str | None = None,
150
150
  is_instance_field: bool | None = None,
151
151
  ) -> bool:
@@ -170,8 +170,8 @@ class RequiredFieldValidator(BaseFormValidator):
170
170
 
171
171
  def required_if_not_none(
172
172
  self,
173
- field: str = None,
174
- field_required: str = None,
173
+ field: str,
174
+ field_required: str,
175
175
  required_msg: str | None = None,
176
176
  not_required_msg: str | None = None,
177
177
  optional_if_dwta: bool | None = None,
@@ -182,6 +182,9 @@ class RequiredFieldValidator(BaseFormValidator):
182
182
  """Raises an exception or returns False.
183
183
 
184
184
  If field is not none, field_required is "required".
185
+
186
+ Note: CharFields usually default to an empty string and not NULL.
187
+ For IntegerFields, zero is a value.
185
188
  """
186
189
  inverse = True if inverse is None else inverse
187
190
  if is_instance_field:
@@ -197,7 +200,7 @@ class RequiredFieldValidator(BaseFormValidator):
197
200
  if field_required_evaluate_as_int:
198
201
  field_required_has_value = self.cleaned_data.get(field_required) is not None
199
202
  else:
200
- field_required_has_value = self.cleaned_data.get(field_required)
203
+ field_required_has_value = bool(self.cleaned_data.get(field_required))
201
204
 
202
205
  if field_value is not None and not field_required_has_value:
203
206
  self.raise_required(field=field_required, msg=required_msg)
@@ -221,8 +224,8 @@ class RequiredFieldValidator(BaseFormValidator):
221
224
  def not_required_if(
222
225
  self,
223
226
  *responses: str | int | bool,
224
- field: str = None,
225
- field_required: str = None,
227
+ field: str,
228
+ field_required: str | None = None,
226
229
  field_not_required: str | None = None,
227
230
  required_msg: str | None = None,
228
231
  not_required_msg: str | None = None,
@@ -259,8 +262,8 @@ class RequiredFieldValidator(BaseFormValidator):
259
262
 
260
263
  def require_together(
261
264
  self,
262
- field: str = None,
263
- field_required: str = None,
265
+ field: str,
266
+ field_required: str,
264
267
  required_msg: str | None = None,
265
268
  is_instance_field: bool | None = None,
266
269
  ) -> bool:
@@ -280,15 +283,15 @@ class RequiredFieldValidator(BaseFormValidator):
280
283
  return False
281
284
 
282
285
  @staticmethod
283
- def _inspect_params(
284
- *responses: str | int | bool, field: str = None, field_required: str = None
285
- ) -> bool:
286
+ def _inspect_params(*responses: str | int | bool, field: str, field_required: str) -> bool:
286
287
  """Inspects params and raises if any are None"""
287
288
  if not field:
288
289
  errmsg = _("`field` cannot be `None`")
289
290
  raise InvalidModelFormFieldValidator(f"{errmsg}.")
290
291
  if not responses:
291
- errmsg = _(f"At least one valid response for field '{field}' must be provided.")
292
+ errmsg = _(
293
+ "At least one valid response for field '{field}' must be provided."
294
+ ).format(field=field)
292
295
  raise InvalidModelFormFieldValidator(errmsg)
293
296
  if not field_required:
294
297
  raise InvalidModelFormFieldValidator('"field_required" cannot be None.')
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
16
16
  from .models import IdentifierModel
17
17
 
18
18
 
19
- class IdentifierMissingTemplateValue(Exception):
19
+ class IdentifierMissingTemplateValue(Exception): # noqa: N818
20
20
  pass
21
21
 
22
22
 
@@ -38,6 +38,7 @@ class ResearchIdentifier:
38
38
  identifier: str | None = None,
39
39
  ) -> None:
40
40
  self._identifier = None
41
+ self._sequence_number = None
41
42
  self.requesting_model = requesting_model
42
43
  if not self.requesting_model:
43
44
  raise IdentifierError("Invalid requesting_model. Got None")
@@ -121,10 +122,10 @@ class ResearchIdentifier:
121
122
  for key in keys:
122
123
  try:
123
124
  value = getattr(self, key)
124
- except AttributeError:
125
+ except AttributeError as e:
125
126
  raise IdentifierMissingTemplateValue(
126
127
  f"Required option not provided. Got '{key}'."
127
- )
128
+ ) from e
128
129
  else:
129
130
  if value:
130
131
  template_opts.update({key: value})
@@ -141,15 +142,15 @@ class ResearchIdentifier:
141
142
  @property
142
143
  def sequence_number(self) -> int:
143
144
  """Returns the next sequence number to use."""
144
- try:
145
- identifier_model = (
145
+ if self._sequence_number is None:
146
+ if identifier_model := (
146
147
  self.identifier_model_cls.objects.filter(
147
148
  name=self.label, device_id=self.device_id, site=self.site
148
149
  )
149
150
  .order_by("-sequence_number")
150
151
  .first()
151
- )
152
- sequence_number = identifier_model.sequence_number + 1
153
- except AttributeError:
154
- sequence_number = 1
155
- return sequence_number
152
+ ):
153
+ self._sequence_number = identifier_model.sequence_number + 1
154
+ else:
155
+ self._sequence_number = 1
156
+ return self._sequence_number
@@ -1,5 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from secrets import choice
2
- from typing import Any
4
+ from typing import TYPE_CHECKING, Any
3
5
 
4
6
  from django.apps import apps as django_apps
5
7
  from django.core.exceptions import ObjectDoesNotExist
@@ -8,6 +10,9 @@ from django.utils import timezone
8
10
 
9
11
  from .utils import convert_to_human_readable
10
12
 
13
+ if TYPE_CHECKING:
14
+ from edc_identifier.models import IdentifierModel
15
+
11
16
 
12
17
  class DuplicateIdentifierError(Exception):
13
18
  pass
@@ -195,7 +200,7 @@ class SimpleUniqueIdentifier:
195
200
  def model_cls(self) -> type[models.Model]:
196
201
  return django_apps.get_model(self.model)
197
202
 
198
- def update_identifier_model(self, **kwargs) -> bool:
203
+ def update_identifier_model(self, **kwargs) -> bool | IdentifierModel:
199
204
  """Attempts to update identifier_model and returns True (or instance)
200
205
  if successful else False if identifier already exists.
201
206
  """
@@ -210,5 +215,6 @@ class SimpleUniqueIdentifier:
210
215
  try:
211
216
  self.model_cls.objects.get(identifier=self.identifier)
212
217
  except ObjectDoesNotExist:
218
+ opts = {k: v for k, v in opts.items() if v is not None}
213
219
  return self.model_cls.objects.create(**opts)
214
220
  return False
edc_lab/auths.py CHANGED
@@ -9,6 +9,7 @@ from edc_auth.constants import (
9
9
  PII_VIEW,
10
10
  )
11
11
  from edc_auth.site_auths import site_auths
12
+ from edc_export.constants import EXPORT
12
13
 
13
14
  from .auth_objects import (
14
15
  LAB,
@@ -18,26 +19,28 @@ from .auth_objects import (
18
19
  lab_view_codenames,
19
20
  )
20
21
 
21
- site_auths.add_group(*lab_codenames, name=LAB)
22
- site_auths.add_group(*lab_view_codenames, name=LAB_VIEW)
23
-
24
- if django_apps.is_installed("edc_export"):
25
- from edc_export.constants import EXPORT
26
-
27
- site_auths.update_group(
28
- "edc_lab.export_aliquot",
29
- "edc_lab.export_box",
30
- "edc_lab.export_boxitem",
31
- "edc_lab.export_boxtype",
32
- "edc_lab.export_order",
33
- "edc_lab.export_panel",
34
- "edc_lab.export_result",
35
- "edc_lab.export_resultitem",
36
- name=EXPORT,
37
- )
38
-
39
-
40
- site_auths.add_role(ADMINISTRATION, EVERYONE, LAB, PII_VIEW, name=LAB_TECHNICIAN_ROLE)
41
- site_auths.update_role(LAB, name=CLINICIAN_ROLE)
42
- site_auths.update_role(LAB, name=NURSE_ROLE)
43
- site_auths.update_role(LAB_VIEW, name=AUDITOR_ROLE)
22
+
23
+ def update_site_auths():
24
+ site_auths.add_group(*lab_codenames, name=LAB)
25
+ site_auths.add_group(*lab_view_codenames, name=LAB_VIEW)
26
+
27
+ if django_apps.is_installed("edc_export"):
28
+ site_auths.update_group(
29
+ "edc_lab.export_aliquot",
30
+ "edc_lab.export_box",
31
+ "edc_lab.export_boxitem",
32
+ "edc_lab.export_boxtype",
33
+ "edc_lab.export_order",
34
+ "edc_lab.export_panel",
35
+ "edc_lab.export_result",
36
+ "edc_lab.export_resultitem",
37
+ name=EXPORT,
38
+ )
39
+
40
+ site_auths.add_role(ADMINISTRATION, EVERYONE, LAB, PII_VIEW, name=LAB_TECHNICIAN_ROLE)
41
+ site_auths.update_role(LAB, name=CLINICIAN_ROLE)
42
+ site_auths.update_role(LAB, name=NURSE_ROLE)
43
+ site_auths.update_role(LAB_VIEW, name=AUDITOR_ROLE)
44
+
45
+
46
+ update_site_auths()
@@ -90,18 +90,15 @@ class AliquotCreator:
90
90
  parent_segment=self.parent_segment,
91
91
  )
92
92
 
93
- parent_identifier = parent_identifier or aliquot_identifier_obj.identifier
94
-
95
- aliquot = self.aliquot_model_cls.objects.create(
93
+ return self.aliquot_model_cls.objects.create(
96
94
  aliquot_identifier=aliquot_identifier_obj.identifier,
97
95
  aliquot_type=aliquot_type.name,
98
96
  alpha_code=aliquot_type.alpha_code,
99
97
  count=count,
100
98
  numeric_code=aliquot_type.numeric_code,
101
- parent_identifier=parent_identifier,
99
+ parent_identifier=parent_identifier or aliquot_identifier_obj.identifier,
102
100
  identifier_prefix=self.identifier_prefix,
103
- is_primary=True if self.is_primary else False,
104
- requisition_identifier=self.requisition_identifier,
105
- subject_identifier=self.subject_identifier,
101
+ is_primary=bool(self.is_primary),
102
+ requisition_identifier=self.requisition_identifier or "",
103
+ subject_identifier=self.subject_identifier or "",
106
104
  )
107
- return aliquot
@@ -1,3 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from edc_lab import AliquotCreator, AliquotType
7
+
8
+
1
9
  class PrimaryAliquotError(Exception):
2
10
  pass
3
11
 
@@ -11,11 +19,12 @@ class PrimaryAliquot:
11
19
 
12
20
  def __init__(
13
21
  self,
14
- aliquot_type=None,
15
- subject_identifier=None,
16
- requisition_identifier=None,
17
- identifier_prefix=None,
18
- aliquot_creator_cls=None,
22
+ *,
23
+ aliquot_creator_cls: type[AliquotCreator],
24
+ aliquot_type: AliquotType | None = None,
25
+ subject_identifier: str | None = None,
26
+ requisition_identifier: str | None = None,
27
+ identifier_prefix: str | None = None,
19
28
  ):
20
29
  self._object = None
21
30
  self.aliquot_creator_cls = aliquot_creator_cls
@@ -0,0 +1,112 @@
1
+ # Generated by Django 5.2.6 on 2025-09-22 12:31
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("edc_lab", "0037_alter_historicalorder_order_datetime_and_more"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name="aliquot",
15
+ name="slug",
16
+ field=models.CharField(
17
+ db_index=True,
18
+ default="",
19
+ editable=False,
20
+ help_text="Hold slug field values for quick search. Excludes encrypted fields",
21
+ max_length=250,
22
+ ),
23
+ ),
24
+ migrations.AlterField(
25
+ model_name="box",
26
+ name="slug",
27
+ field=models.CharField(
28
+ db_index=True,
29
+ default="",
30
+ editable=False,
31
+ help_text="Hold slug field values for quick search. Excludes encrypted fields",
32
+ max_length=250,
33
+ ),
34
+ ),
35
+ migrations.AlterField(
36
+ model_name="boxitem",
37
+ name="slug",
38
+ field=models.CharField(
39
+ db_index=True,
40
+ default="",
41
+ editable=False,
42
+ help_text="Hold slug field values for quick search. Excludes encrypted fields",
43
+ max_length=250,
44
+ ),
45
+ ),
46
+ migrations.AlterField(
47
+ model_name="historicalaliquot",
48
+ name="slug",
49
+ field=models.CharField(
50
+ db_index=True,
51
+ default="",
52
+ editable=False,
53
+ help_text="Hold slug field values for quick search. Excludes encrypted fields",
54
+ max_length=250,
55
+ ),
56
+ ),
57
+ migrations.AlterField(
58
+ model_name="historicalbox",
59
+ name="slug",
60
+ field=models.CharField(
61
+ db_index=True,
62
+ default="",
63
+ editable=False,
64
+ help_text="Hold slug field values for quick search. Excludes encrypted fields",
65
+ max_length=250,
66
+ ),
67
+ ),
68
+ migrations.AlterField(
69
+ model_name="historicalboxitem",
70
+ name="slug",
71
+ field=models.CharField(
72
+ db_index=True,
73
+ default="",
74
+ editable=False,
75
+ help_text="Hold slug field values for quick search. Excludes encrypted fields",
76
+ max_length=250,
77
+ ),
78
+ ),
79
+ migrations.AlterField(
80
+ model_name="historicalmanifest",
81
+ name="slug",
82
+ field=models.CharField(
83
+ db_index=True,
84
+ default="",
85
+ editable=False,
86
+ help_text="Hold slug field values for quick search. Excludes encrypted fields",
87
+ max_length=250,
88
+ ),
89
+ ),
90
+ migrations.AlterField(
91
+ model_name="manifest",
92
+ name="slug",
93
+ field=models.CharField(
94
+ db_index=True,
95
+ default="",
96
+ editable=False,
97
+ help_text="Hold slug field values for quick search. Excludes encrypted fields",
98
+ max_length=250,
99
+ ),
100
+ ),
101
+ migrations.AlterField(
102
+ model_name="manifestitem",
103
+ name="slug",
104
+ field=models.CharField(
105
+ db_index=True,
106
+ default="",
107
+ editable=False,
108
+ help_text="Hold slug field values for quick search. Excludes encrypted fields",
109
+ max_length=250,
110
+ ),
111
+ ),
112
+ ]
@@ -136,15 +136,13 @@ class RequisitionModelMixin(
136
136
 
137
137
  def get_search_slug_fields(self):
138
138
  fields = super().get_search_slug_fields()
139
- fields.extend(
140
- [
141
- "subject_identifier",
142
- "requisition_identifier",
143
- "human_readable_identifier",
144
- "identifier_prefix",
145
- ]
139
+ return (
140
+ *fields,
141
+ "subject_identifier",
142
+ "requisition_identifier",
143
+ "human_readable_identifier",
144
+ "identifier_prefix",
146
145
  )
147
- return fields
148
146
 
149
147
  class Meta(NonUniqueSubjectIdentifierFieldMixin.Meta):
150
148
  abstract = True
edc_lab/models/aliquot.py CHANGED
@@ -24,13 +24,13 @@ class Aliquot(
24
24
  BaseUuidModel,
25
25
  ):
26
26
  def get_search_slug_fields(self):
27
- return [
27
+ return (
28
28
  "aliquot_identifier",
29
29
  "human_readable_identifier",
30
30
  "subject_identifier",
31
31
  "parent_identifier",
32
32
  "requisition_identifier",
33
- ]
33
+ )
34
34
 
35
35
  objects = Manager()
36
36
 
@@ -21,12 +21,12 @@ class Manifest(ManifestModelMixin, PdfReportModelMixin, SearchSlugModelMixin, Ba
21
21
  pdf_report_cls = ManifestPdfReport
22
22
 
23
23
  def get_search_slug_fields(self):
24
- return [
24
+ return (
25
25
  "manifest_identifier",
26
26
  "human_readable_identifier",
27
27
  "shipper.name",
28
28
  "consignee.name",
29
- ]
29
+ )
30
30
 
31
31
  consignee = models.ForeignKey(Consignee, verbose_name="Consignee", on_delete=PROTECT)
32
32
 
@@ -17,7 +17,7 @@ class ManifestItemManager(SearchSlugManager, models.Manager):
17
17
 
18
18
  class ManifestItem(SiteModelMixin, SearchSlugModelMixin, VerifyModelMixin, BaseUuidModel):
19
19
  def get_search_slug_fields(self):
20
- return ["identifier", "human_readable_identifier"]
20
+ return "identifier", "human_readable_identifier"
21
21
 
22
22
  manifest = models.ForeignKey(Manifest, on_delete=PROTECT)
23
23
 
@@ -8,16 +8,21 @@ from edc_lab_dashboard.auth_objects import (
8
8
  lab_navbar_tuples,
9
9
  )
10
10
 
11
- site_auths.add_post_update_func(
12
- "edc_lab_dashboard", remove_default_model_permissions_from_edc_permissions
13
- )
14
11
 
15
- site_auths.add_custom_permissions_tuples(
16
- model="edc_lab_dashboard.edcpermissions", codename_tuples=lab_navbar_tuples
17
- )
18
- site_auths.add_custom_permissions_tuples(
19
- model="edc_lab_dashboard.edcpermissions", codename_tuples=lab_dashboard_tuples
20
- )
12
+ def update_site_auths():
13
+ site_auths.add_post_update_func(
14
+ "edc_lab_dashboard", remove_default_model_permissions_from_edc_permissions
15
+ )
16
+
17
+ site_auths.add_custom_permissions_tuples(
18
+ model="edc_lab_dashboard.edcpermissions", codename_tuples=lab_navbar_tuples
19
+ )
20
+ site_auths.add_custom_permissions_tuples(
21
+ model="edc_lab_dashboard.edcpermissions", codename_tuples=lab_dashboard_tuples
22
+ )
23
+
24
+ site_auths.update_group(*lab_dashboard_codenames, *lab_navbar_codenames, name=LAB)
25
+ site_auths.update_group(*lab_dashboard_codenames, *lab_navbar_codenames, name=LAB_VIEW)
26
+
21
27
 
22
- site_auths.update_group(*lab_dashboard_codenames, *lab_navbar_codenames, name=LAB)
23
- site_auths.update_group(*lab_dashboard_codenames, *lab_navbar_codenames, name=LAB_VIEW)
28
+ update_site_auths()
@@ -1,3 +1,4 @@
1
+ import contextlib
1
2
  from typing import Any
2
3
 
3
4
 
@@ -20,11 +21,10 @@ def calculate_missing(obj: Any, panel: Any) -> tuple[int, str | None]:
20
21
  missing = []
21
22
  for utest_id in panel.utest_ids:
22
23
  for field in fields:
23
- try:
24
- utest_id, _ = utest_id
25
- except ValueError:
26
- pass
27
- if field == utest_id or field == f"{utest_id}_value":
28
- if getattr(obj, field) is None:
29
- missing.append(field)
30
- return len(missing), ",".join(missing) if missing else None
24
+ with contextlib.suppress(ValueError):
25
+ utest_id, _ = utest_id # noqa: PLW2901
26
+ if field == utest_id or (
27
+ field == f"{utest_id}_value" and getattr(obj, field) is None
28
+ ):
29
+ missing.append(field)
30
+ return len(missing), ",".join(missing) if missing else ""
@@ -51,13 +51,13 @@ class BloodResultsFormValidatorMixin(
51
51
  self.required_if_not_none(
52
52
  field=f"{utest_id}{self.value_field_suffix or ''}",
53
53
  field_required=f"{utest_id}_abnormal",
54
- field_required_evaluate_as_int=True,
54
+ field_required_evaluate_as_int=False,
55
55
  )
56
56
  if f"{utest_id}_reportable" in self.cleaned_data:
57
57
  self.required_if_not_none(
58
58
  field=f"{utest_id}{self.value_field_suffix or ''}",
59
59
  field_required=f"{utest_id}_reportable",
60
- field_required_evaluate_as_int=True,
60
+ field_required_evaluate_as_int=False,
61
61
  )
62
62
  self.evaluate_value(prefix=utest_id)
63
63
  self.validate_reportable_fields(
@@ -18,36 +18,37 @@ def get_summary(obj) -> tuple[list[str], list[str], list[str]]:
18
18
  except ValueError:
19
19
  utest_id = field_name
20
20
  reference_range_collection = get_reference_range_collection(obj)
21
- if units := getattr(obj, f"{utest_id}_units", None):
22
- if value := getattr(obj, field_name):
23
- opts.update(units=units, label=utest_id)
21
+ if (units := getattr(obj, f"{utest_id}_units", None)) and (
22
+ value := getattr(obj, field_name)
23
+ ):
24
+ opts.update(units=units, label=utest_id)
25
+ try:
26
+ grading_data, grading_eval_phrase = reference_range_collection.get_grade(
27
+ value, **opts
28
+ )
29
+ except NotEvaluated as e:
30
+ errors.append(f"{e}.")
31
+ grading_data = None
32
+ grading_eval_phrase = None
33
+ if (
34
+ grading_data
35
+ and grading_data.grade
36
+ in reference_range_collection.reportable_grades(utest_id)
37
+ ):
38
+ setattr(obj, f"{utest_id}_grade", grading_data.grade)
39
+ setattr(obj, f"{utest_id}_grade_description", grading_data.description)
40
+ reportable.append(f"{grading_eval_phrase}")
41
+ else:
24
42
  try:
25
- grading_data, grading_eval_phrase = reference_range_collection.get_grade(
43
+ is_normal, normal_data = reference_range_collection.is_normal(
26
44
  value, **opts
27
45
  )
28
46
  except NotEvaluated as e:
29
47
  errors.append(f"{e}.")
30
- grading_data = None
31
- grading_eval_phrase = None
32
- if (
33
- grading_data
34
- and grading_data.grade
35
- in reference_range_collection.reportable_grades(utest_id)
36
- ):
37
- setattr(obj, f"{utest_id}_grade", grading_data.grade)
38
- setattr(obj, f"{utest_id}_grade_description", grading_data.description)
39
- reportable.append(f"{grading_eval_phrase}")
40
48
  else:
41
- try:
42
- is_normal, normal_data = reference_range_collection.is_normal(
43
- value, **opts
49
+ if is_normal is False:
50
+ abnormal.append(
51
+ f"{normal_data.label}: {value} {units} "
52
+ f"{normal_data.phrase} Abnormal"
44
53
  )
45
- except NotEvaluated as e:
46
- errors.append(f"{e}.")
47
- else:
48
- if is_normal is False:
49
- abnormal.append(
50
- f"{normal_data.label}: {value} {units} "
51
- f"{normal_data.phrase} Abnormal"
52
- )
53
54
  return reportable, abnormal, errors
@@ -70,6 +70,8 @@ class BloodResultsMethodsModelMixin(models.Model):
70
70
  return get_summary(self)
71
71
 
72
72
  def get_summary_options(self: Any) -> dict:
73
+ # note: gender and dob are queried from RegisteredSubject
74
+ # in reportables
73
75
  return dict(
74
76
  subject_identifier=self.subject_visit.subject_identifier,
75
77
  report_datetime=self.report_datetime,
edc_label/auths.py CHANGED
@@ -2,4 +2,9 @@ from edc_auth.site_auths import site_auths
2
2
 
3
3
  from .auth_objects import LABELING, codenames
4
4
 
5
- site_auths.add_group(*codenames, name=LABELING, no_delete=False)
5
+
6
+ def update_site_auths():
7
+ site_auths.add_group(*codenames, name=LABELING, no_delete=False)
8
+
9
+
10
+ update_site_auths()