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.
- {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/METADATA +4 -3
- {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/RECORD +136 -137
- {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/WHEEL +1 -1
- edc_action_item/auths.py +37 -32
- edc_action_item/models/action_model_mixin.py +1 -2
- edc_action_item/models/signals.py +22 -23
- edc_action_item/site_action_items.py +5 -9
- edc_action_item/utils.py +3 -3
- edc_adverse_event/auths.py +55 -51
- edc_adverse_event/model_mixins/ae_tmg/ae_tmg_methods_model_mixin.py +2 -4
- edc_appointment/auths.py +14 -10
- edc_appointment/creators/appointment_creator.py +1 -1
- edc_appointment/creators/appointments_creator.py +1 -1
- edc_appointment/model_mixins/appointment_methods_model_mixin.py +2 -3
- edc_appointment/model_mixins/appointment_model_mixin.py +31 -28
- edc_appointment/models/appointment.py +1 -1
- edc_appointment/utils.py +19 -24
- edc_auth/auth_objects/__init__.py +2 -20
- edc_auth/auth_objects/default_groups.py +13 -11
- edc_auth/auth_objects/default_roles.py +26 -24
- edc_auth/auth_updater/auth_updater.py +13 -2
- edc_auth/auth_updater/group_updater.py +12 -10
- edc_auth/auth_updater/role_updater.py +2 -2
- edc_auth/constants.py +10 -0
- edc_auth/import_users.py +3 -3
- edc_auth/migrations/0036_alter_userprofile_alternate_email_and_more.py +88 -0
- edc_auth/models/user_profile.py +14 -11
- edc_auth/site_auths.py +80 -67
- edc_consent/auths.py +18 -12
- edc_constants/constants.py +1 -0
- edc_crf/auths.py +5 -0
- edc_dashboard/auths.py +10 -6
- edc_dashboard/url_config.py +92 -83
- edc_dashboard/url_names.py +4 -4
- edc_dashboard/view_mixins/url_request_context_mixin.py +6 -5
- edc_data_manager/admin/data_query_admin.py +12 -11
- edc_data_manager/auths.py +37 -34
- edc_data_manager/rule/query_rule_wrapper.py +7 -7
- edc_export/archive_exporter.py +3 -2
- edc_export/auths.py +32 -28
- edc_export/model_exporter/model_exporter.py +4 -1
- edc_facility/auths.py +8 -3
- edc_facility/facility.py +8 -9
- edc_form_label/custom_label_condition.py +11 -8
- edc_form_label/form_label.py +1 -1
- edc_form_runners/auths.py +11 -6
- edc_form_validators/applicable_field_validator.py +7 -6
- edc_form_validators/base_form_validator.py +8 -9
- edc_form_validators/other_specify_field_validator.py +2 -8
- edc_form_validators/required_field_validator.py +19 -16
- edc_identifier/research_identifier.py +11 -10
- edc_identifier/simple_identifier.py +8 -2
- edc_lab/auths.py +26 -23
- edc_lab/lab/aliquot_creator.py +5 -8
- edc_lab/lab/primary_aliquot.py +14 -5
- edc_lab/migrations/0038_alter_aliquot_slug_alter_box_slug_alter_boxitem_slug_and_more.py +112 -0
- edc_lab/model_mixins/requisition/requisition_model_mixin.py +6 -8
- edc_lab/models/aliquot.py +2 -2
- edc_lab/models/manifest/manifest.py +2 -2
- edc_lab/models/manifest/manifest_item.py +1 -1
- edc_lab_dashboard/auths.py +16 -11
- edc_lab_results/calculate_missing.py +8 -8
- edc_lab_results/form_validator_mixins/blood_results_form_validator_mixin.py +2 -2
- edc_lab_results/get_summary.py +26 -25
- edc_lab_results/model_mixins/blood_result_model_mixin.py +2 -0
- edc_label/auths.py +6 -1
- edc_label/label_template.py +8 -8
- edc_list_data/load_model_data.py +3 -3
- edc_list_data/post_migrate_signals.py +1 -1
- edc_list_data/preload_data.py +2 -2
- edc_list_data/row.py +1 -1
- edc_list_data/site_list_data.py +6 -5
- edc_locator/auths.py +18 -13
- edc_metadata/auths.py +11 -7
- edc_metadata/metadata/metadata.py +1 -1
- edc_metadata/metadata_rules/crf/crf_rule.py +1 -1
- edc_metadata/metadata_rules/metadata_rule_evaluator.py +5 -3
- edc_metadata/metadata_rules/rule.py +2 -3
- edc_metadata/metadata_rules/rule_evaluator.py +1 -1
- edc_metadata/model_mixins/updates/updates_crf_metadata_model_mixin.py +7 -4
- edc_metadata/model_mixins/updates/updates_requisition_metadata_model_mixin.py +5 -2
- edc_metadata/models/signals.py +10 -11
- edc_navbar/auths.py +18 -13
- edc_notification/auths.py +9 -4
- edc_notification/notification/graded_event_notification.py +2 -2
- edc_notification/notification/model_notification.py +3 -30
- edc_notification/notification/new_model_notification.py +1 -1
- edc_notification/notification/notification.py +1 -1
- edc_notification/notification/updated_model_notification.py +2 -2
- edc_offstudy/auths.py +12 -7
- edc_pdutils/df_exporters/csv_model_exporter.py +5 -2
- edc_pharmacy/auths.py +19 -15
- edc_pharmacy/models/medication/formulation.py +5 -7
- edc_pharmacy/prescribe/create_prescription.py +3 -3
- edc_pharmacy/utils/confirm_stock.py +1 -1
- edc_pharmacy/utils/confirm_stock_at_site.py +1 -1
- edc_pharmacy/views/confirmation_at_site_view.py +6 -9
- edc_prn/admin_site.py +5 -0
- edc_prn/prn.py +10 -11
- edc_prn/urls.py +11 -0
- edc_protocol_incident/action_items.py +4 -4
- edc_protocol_incident/auths.py +27 -20
- edc_pylabels/auths.py +6 -1
- edc_qareports/auths.py +11 -7
- edc_randomization/admin.py +30 -24
- edc_randomization/auths.py +12 -7
- edc_randomization/randomizer.py +22 -20
- edc_randomization/utils.py +17 -16
- edc_refusal/auths.py +7 -2
- edc_refusal/model_mixins.py +1 -1
- edc_registration/auths.py +28 -23
- edc_registration/model_mixins/updates_or_creates_registered_subject_model_mixin.py +13 -4
- edc_registration/models/registered_subject.py +1 -1
- edc_reportable/utils/get_reference_range_collection.py +2 -3
- edc_reportable/utils/load_data.py +1 -1
- edc_review_dashboard/auths.py +23 -18
- edc_screening/age_evaluator.py +3 -3
- edc_screening/auths.py +35 -30
- edc_screening/eligibility.py +1 -1
- edc_screening/gender_evaluator.py +1 -1
- edc_screening/model_mixins/eligibility_model_mixin.py +0 -2
- edc_screening/model_mixins/screening_methods_model_mixin.py +1 -1
- edc_screening/screening_eligibility.py +2 -4
- edc_screening/utils.py +9 -9
- edc_search/generate_slug.py +26 -0
- edc_search/model_mixins.py +10 -21
- edc_sites/auths.py +8 -3
- edc_subject_dashboard/auths.py +27 -22
- edc_timepoint/apps.py +0 -21
- edc_unblinding/auths.py +9 -4
- edc_utils/__init__.py +3 -1
- edc_utils/show_urls.py +29 -2
- edc_visit_schedule/auths.py +6 -1
- edc_visit_schedule/site_visit_schedules.py +2 -2
- edc_visit_tracking/models/signals.py +2 -2
- edc_form_label/models.py +0 -0
- edc_search/constants.py +0 -1
- edc_search/models.py +0 -0
- edc_search/search_slug.py +0 -51
- edc_search/updater.py +0 -30
- edc_search/wsgi.py +0 -7
- {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
|
|
47
|
-
field_required: str
|
|
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
|
|
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
|
|
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
|
|
174
|
-
field_required: str
|
|
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
|
|
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
|
|
263
|
-
field_required: str
|
|
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 = _(
|
|
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
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
site_auths.
|
|
41
|
-
site_auths.update_role(LAB, name=
|
|
42
|
-
site_auths.update_role(
|
|
43
|
-
|
|
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()
|
edc_lab/lab/aliquot_creator.py
CHANGED
|
@@ -90,18 +90,15 @@ class AliquotCreator:
|
|
|
90
90
|
parent_segment=self.parent_segment,
|
|
91
91
|
)
|
|
92
92
|
|
|
93
|
-
|
|
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=
|
|
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
|
edc_lab/lab/primary_aliquot.py
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
20
|
+
return "identifier", "human_readable_identifier"
|
|
21
21
|
|
|
22
22
|
manifest = models.ForeignKey(Manifest, on_delete=PROTECT)
|
|
23
23
|
|
edc_lab_dashboard/auths.py
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
utest_id, _ = utest_id
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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=
|
|
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=
|
|
60
|
+
field_required_evaluate_as_int=False,
|
|
61
61
|
)
|
|
62
62
|
self.evaluate_value(prefix=utest_id)
|
|
63
63
|
self.validate_reportable_fields(
|
edc_lab_results/get_summary.py
CHANGED
|
@@ -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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
value
|
|
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
|
-
|
|
5
|
+
|
|
6
|
+
def update_site_auths():
|
|
7
|
+
site_auths.add_group(*codenames, name=LABELING, no_delete=False)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
update_site_auths()
|