clinicedc 2.0.7__py3-none-any.whl → 2.0.8__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.8.dist-info}/METADATA +1 -1
- {clinicedc-2.0.7.dist-info → clinicedc-2.0.8.dist-info}/RECORD +134 -137
- {clinicedc-2.0.7.dist-info → clinicedc-2.0.8.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/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/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.8.dist-info}/licenses/LICENSE +0 -0
edc_randomization/randomizer.py
CHANGED
|
@@ -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
|
|
212
|
-
raise RandomizationError(
|
|
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
|
-
|
|
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
|
edc_randomization/utils.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import csv
|
|
4
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
156
|
+
filename = Path(
|
|
156
157
|
f"~/{settings.APP_NAME}_{randomizer_cls.name}_"
|
|
157
158
|
f"randomizationlist_exported_{timestamp}.csv"
|
|
158
|
-
)
|
|
159
|
-
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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()
|
edc_refusal/model_mixins.py
CHANGED
edc_registration/auths.py
CHANGED
|
@@ -4,27 +4,32 @@ from edc_auth.constants import PII, PII_VIEW
|
|
|
4
4
|
from edc_auth.site_auths import site_auths
|
|
5
5
|
from edc_export.constants import EXPORT
|
|
6
6
|
|
|
7
|
-
if django_apps.is_installed("edc_export"):
|
|
8
|
-
site_auths.update_group("edc_registration.export_registeredsubject", name=EXPORT)
|
|
9
|
-
site_auths.update_group(
|
|
10
|
-
"edc_registration.display_dob",
|
|
11
|
-
"edc_registration.display_firstname",
|
|
12
|
-
"edc_registration.display_identity",
|
|
13
|
-
"edc_registration.display_initials",
|
|
14
|
-
"edc_registration.display_lastname",
|
|
15
|
-
"edc_registration.view_historicalregisteredsubject",
|
|
16
|
-
"edc_registration.view_registeredsubject",
|
|
17
|
-
name=PII,
|
|
18
|
-
)
|
|
19
7
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
8
|
+
def update_site_auths() -> None:
|
|
9
|
+
if django_apps.is_installed("edc_export"):
|
|
10
|
+
site_auths.update_group("edc_registration.export_registeredsubject", name=EXPORT)
|
|
11
|
+
site_auths.update_group(
|
|
12
|
+
"edc_registration.display_dob",
|
|
13
|
+
"edc_registration.display_firstname",
|
|
14
|
+
"edc_registration.display_identity",
|
|
15
|
+
"edc_registration.display_initials",
|
|
16
|
+
"edc_registration.display_lastname",
|
|
17
|
+
"edc_registration.view_historicalregisteredsubject",
|
|
18
|
+
"edc_registration.view_registeredsubject",
|
|
19
|
+
name=PII,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
site_auths.update_group(
|
|
23
|
+
"edc_registration.display_dob",
|
|
24
|
+
"edc_registration.display_firstname",
|
|
25
|
+
"edc_registration.display_identity",
|
|
26
|
+
"edc_registration.display_initials",
|
|
27
|
+
"edc_registration.display_lastname",
|
|
28
|
+
"edc_registration.view_historicalregisteredsubject",
|
|
29
|
+
"edc_registration.view_registeredsubject",
|
|
30
|
+
name=PII_VIEW,
|
|
31
|
+
)
|
|
32
|
+
site_auths.add_pii_model("edc_registration.registeredsubject")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
update_site_auths()
|
|
@@ -5,7 +5,9 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
|
|
6
6
|
from django.core.exceptions import ObjectDoesNotExist
|
|
7
7
|
from django.db import models
|
|
8
|
+
from django.db.models import CharField, TextField
|
|
8
9
|
|
|
10
|
+
from edc_constants.constants import NULL_STRING
|
|
9
11
|
from edc_model import DEFAULT_BASE_FIELDS
|
|
10
12
|
|
|
11
13
|
from ..utils import get_registered_subject_model_cls
|
|
@@ -99,13 +101,20 @@ class UpdatesOrCreatesRegistrationModelMixin(models.Model):
|
|
|
99
101
|
"""
|
|
100
102
|
registration_options = {}
|
|
101
103
|
rs = self.registration_model()
|
|
102
|
-
for
|
|
103
|
-
if
|
|
104
|
+
for fname, value in self.__dict__.items():
|
|
105
|
+
if fname not in (*DEFAULT_BASE_FIELDS, "_state"):
|
|
104
106
|
try:
|
|
105
|
-
getattr(rs,
|
|
106
|
-
registration_options.update({k: v})
|
|
107
|
+
getattr(rs, fname)
|
|
107
108
|
except AttributeError:
|
|
108
109
|
pass
|
|
110
|
+
else:
|
|
111
|
+
value = ( # noqa: PLW2901
|
|
112
|
+
NULL_STRING
|
|
113
|
+
if value is None
|
|
114
|
+
and isinstance(rs._meta.get_field(fname), (CharField, TextField))
|
|
115
|
+
else value
|
|
116
|
+
)
|
|
117
|
+
registration_options.update({fname: value})
|
|
109
118
|
registration_identifier = registration_options.get("registration_identifier")
|
|
110
119
|
if registration_identifier:
|
|
111
120
|
registration_options["registration_identifier"] = self.to_string(
|
|
@@ -143,7 +143,7 @@ class RegisteredSubject(UniqueSubjectIdentifierModelMixin, SiteModelMixin, BaseU
|
|
|
143
143
|
|
|
144
144
|
def save(self, *args, **kwargs):
|
|
145
145
|
if self.identity:
|
|
146
|
-
self.additional_key =
|
|
146
|
+
self.additional_key = ""
|
|
147
147
|
self.set_uuid_as_subject_identifier_if_none()
|
|
148
148
|
self.raise_on_duplicate("subject_identifier")
|
|
149
149
|
self.raise_on_duplicate("identity")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
from django.core.exceptions import ObjectDoesNotExist
|
|
@@ -21,10 +22,8 @@ def get_reference_range_collection(obj) -> ReferenceRangeCollection:
|
|
|
21
22
|
name = obj.requisition.panel_object.reference_range_collection_name
|
|
22
23
|
except AttributeError:
|
|
23
24
|
name = obj.panel_object.reference_range_collection_name
|
|
24
|
-
|
|
25
|
+
with contextlib.suppress(ObjectDoesNotExist):
|
|
25
26
|
reference_range_collection = reference_range_colllection_model_cls().objects.get(
|
|
26
27
|
name=name
|
|
27
28
|
)
|
|
28
|
-
except ObjectDoesNotExist:
|
|
29
|
-
pass
|
|
30
29
|
return reference_range_collection
|
|
@@ -60,7 +60,7 @@ def load_reference_ranges(
|
|
|
60
60
|
collection_name: str,
|
|
61
61
|
normal_data: dict[str, list[Formula]],
|
|
62
62
|
grading_data: dict[str, list[Formula]],
|
|
63
|
-
reportable_grades: list[int],
|
|
63
|
+
reportable_grades: list[int] | None = None,
|
|
64
64
|
reportable_grades_exceptions: dict[str, list[int]] | None = None,
|
|
65
65
|
keep_existing: bool | None = None,
|
|
66
66
|
create_missing_normal: bool | None = None,
|
edc_review_dashboard/auths.py
CHANGED
|
@@ -10,23 +10,28 @@ from edc_auth.utils import remove_default_model_permissions_from_edc_permissions
|
|
|
10
10
|
|
|
11
11
|
REVIEW = "REVIEW"
|
|
12
12
|
|
|
13
|
-
site_auths.add_post_update_func(
|
|
14
|
-
"edc_review_dashboard", remove_default_model_permissions_from_edc_permissions
|
|
15
|
-
)
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
def update_site_auths():
|
|
15
|
+
site_auths.add_post_update_func(
|
|
16
|
+
"edc_review_dashboard", remove_default_model_permissions_from_edc_permissions
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
site_auths.add_custom_permissions_tuples(
|
|
20
|
+
model="edc_review_dashboard.edcpermissions",
|
|
21
|
+
codename_tuples=[
|
|
22
|
+
(
|
|
23
|
+
"edc_review_dashboard.view_subject_review_listboard",
|
|
24
|
+
"Can access subject review listboard",
|
|
25
|
+
)
|
|
26
|
+
],
|
|
27
|
+
)
|
|
28
|
+
site_auths.add_group("edc_review_dashboard.view_subject_review_listboard", name=REVIEW)
|
|
29
|
+
|
|
30
|
+
site_auths.update_role(REVIEW, name=AUDITOR_ROLE)
|
|
31
|
+
site_auths.update_role(REVIEW, name=TMG_ROLE)
|
|
32
|
+
site_auths.update_role(REVIEW, name=CLINICIAN_ROLE)
|
|
33
|
+
site_auths.update_role(REVIEW, name=CLINICIAN_SUPER_ROLE)
|
|
34
|
+
site_auths.update_role(REVIEW, name=NURSE_ROLE)
|
|
35
|
+
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
site_auths.update_role(REVIEW, name=TMG_ROLE)
|
|
30
|
-
site_auths.update_role(REVIEW, name=CLINICIAN_ROLE)
|
|
31
|
-
site_auths.update_role(REVIEW, name=CLINICIAN_SUPER_ROLE)
|
|
32
|
-
site_auths.update_role(REVIEW, name=NURSE_ROLE)
|
|
37
|
+
update_site_auths()
|
edc_screening/age_evaluator.py
CHANGED
|
@@ -8,11 +8,11 @@ from edc_reportable.exceptions import ValueBoundryError
|
|
|
8
8
|
|
|
9
9
|
class AgeEvaluator(ReportableAgeEvaluator):
|
|
10
10
|
def __init__(self, **kwargs) -> None:
|
|
11
|
-
self.reasons_ineligible: str
|
|
11
|
+
self.reasons_ineligible: str = ""
|
|
12
12
|
super().__init__(**kwargs)
|
|
13
13
|
|
|
14
14
|
def eligible(self, age: int | None = None) -> bool:
|
|
15
|
-
self.reasons_ineligible =
|
|
15
|
+
self.reasons_ineligible = ""
|
|
16
16
|
eligible = False
|
|
17
17
|
if age:
|
|
18
18
|
try:
|
|
@@ -26,7 +26,7 @@ class AgeEvaluator(ReportableAgeEvaluator):
|
|
|
26
26
|
return eligible
|
|
27
27
|
|
|
28
28
|
def in_bounds_or_raise(self, age: int = None, **kwargs):
|
|
29
|
-
self.reasons_ineligible =
|
|
29
|
+
self.reasons_ineligible = ""
|
|
30
30
|
dob = localtime(timezone.now() - relativedelta(years=age)).date()
|
|
31
31
|
age_units = "years"
|
|
32
32
|
report_datetime = localtime(timezone.now())
|
edc_screening/auths.py
CHANGED
|
@@ -9,38 +9,43 @@ from edc_auth.utils import remove_default_model_permissions_from_edc_permissions
|
|
|
9
9
|
|
|
10
10
|
from .auth_objects import SCREENING, SCREENING_ROLE, SCREENING_SUPER, SCREENING_VIEW
|
|
11
11
|
|
|
12
|
-
site_auths.add_post_update_func(
|
|
13
|
-
"edc_screening", remove_default_model_permissions_from_edc_permissions
|
|
14
|
-
)
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
("edc_screening.nav_screening_section", "Can access screening section"),
|
|
21
|
-
),
|
|
22
|
-
)
|
|
13
|
+
def update_site_auths() -> None:
|
|
14
|
+
site_auths.add_post_update_func(
|
|
15
|
+
"edc_screening", remove_default_model_permissions_from_edc_permissions
|
|
16
|
+
)
|
|
23
17
|
|
|
24
|
-
site_auths.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
18
|
+
site_auths.add_custom_permissions_tuples(
|
|
19
|
+
model="edc_screening.edcpermissions",
|
|
20
|
+
codename_tuples=(
|
|
21
|
+
("edc_screening.view_screening_listboard", "Can access Screening listboard"),
|
|
22
|
+
("edc_screening.nav_screening_section", "Can access screening section"),
|
|
23
|
+
),
|
|
24
|
+
)
|
|
29
25
|
|
|
30
|
-
site_auths.add_group(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
26
|
+
site_auths.add_group(
|
|
27
|
+
"edc_screening.view_screening_listboard",
|
|
28
|
+
"edc_screening.nav_screening_section",
|
|
29
|
+
name=SCREENING,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
site_auths.add_group(
|
|
33
|
+
"edc_screening.view_screening_listboard",
|
|
34
|
+
"edc_screening.nav_screening_section",
|
|
35
|
+
name=SCREENING_SUPER,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
site_auths.add_group(
|
|
39
|
+
"edc_screening.view_screening_listboard",
|
|
40
|
+
"edc_screening.nav_screening_section",
|
|
41
|
+
name=SCREENING_VIEW,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
site_auths.add_role(SCREENING, name=SCREENING_ROLE)
|
|
45
|
+
site_auths.update_role(SCREENING, name=CLINICIAN_ROLE)
|
|
46
|
+
site_auths.update_role(SCREENING, name=NURSE_ROLE)
|
|
47
|
+
site_auths.update_role(SCREENING_SUPER, name=CLINICIAN_SUPER_ROLE)
|
|
48
|
+
site_auths.update_role(SCREENING_VIEW, name=AUDITOR_ROLE)
|
|
35
49
|
|
|
36
|
-
site_auths.add_group(
|
|
37
|
-
"edc_screening.view_screening_listboard",
|
|
38
|
-
"edc_screening.nav_screening_section",
|
|
39
|
-
name=SCREENING_VIEW,
|
|
40
|
-
)
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
site_auths.update_role(SCREENING, name=CLINICIAN_ROLE)
|
|
44
|
-
site_auths.update_role(SCREENING, name=NURSE_ROLE)
|
|
45
|
-
site_auths.update_role(SCREENING_SUPER, name=CLINICIAN_SUPER_ROLE)
|
|
46
|
-
site_auths.update_role(SCREENING_VIEW, name=AUDITOR_ROLE)
|
|
51
|
+
update_site_auths()
|
edc_screening/eligibility.py
CHANGED
|
@@ -45,7 +45,7 @@ class Eligibility:
|
|
|
45
45
|
self.eligible = all([v for v in self.criteria.values()])
|
|
46
46
|
|
|
47
47
|
if self.eligible:
|
|
48
|
-
self.reasons_ineligible =
|
|
48
|
+
self.reasons_ineligible = ""
|
|
49
49
|
else:
|
|
50
50
|
self.reasons_ineligible = {k: v for k, v in self.criteria.items() if not v}
|
|
51
51
|
for k, v in self.criteria.items():
|
|
@@ -45,8 +45,6 @@ class EligibilityModelMixin(EligibilityFieldsModelMixin, models.Model):
|
|
|
45
45
|
"""
|
|
46
46
|
# if self.eligibility_cls:
|
|
47
47
|
self.eligibility_cls(model_obj=self)
|
|
48
|
-
# self.eligible = eligibility_obj.is_eligible
|
|
49
|
-
# self.reasons_ineligible = eligibility_obj.reasons_ineligible
|
|
50
48
|
if not self.id:
|
|
51
49
|
self.screening_identifier = self.identifier_cls().identifier
|
|
52
50
|
if self.eligible:
|
|
@@ -16,7 +16,7 @@ class ScreeningMethodsModeMixin(models.Model):
|
|
|
16
16
|
|
|
17
17
|
@staticmethod
|
|
18
18
|
def get_search_slug_fields():
|
|
19
|
-
return
|
|
19
|
+
return "screening_identifier", "subject_identifier", "reference"
|
|
20
20
|
|
|
21
21
|
@property
|
|
22
22
|
def estimated_dob(self: SubjectScreeningModelStub) -> date:
|
|
@@ -174,7 +174,7 @@ class ScreeningEligibility:
|
|
|
174
174
|
setattr(
|
|
175
175
|
self.model_obj,
|
|
176
176
|
self.reasons_ineligible_fld_name,
|
|
177
|
-
"|".join(self.reasons_ineligible.values()) or
|
|
177
|
+
"|".join(self.reasons_ineligible.values()) or "",
|
|
178
178
|
)
|
|
179
179
|
self.set_eligible_model_field()
|
|
180
180
|
self.set_fld_attrs_on_model()
|
|
@@ -229,9 +229,7 @@ class ScreeningEligibility:
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
def formatted_reasons_ineligible(self) -> str:
|
|
232
|
-
str_values = "<BR>".join(
|
|
233
|
-
[x for x in self.reasons_ineligible.values() if x is not None]
|
|
234
|
-
)
|
|
232
|
+
str_values = "<BR>".join([x for x in self.reasons_ineligible.values() if x])
|
|
235
233
|
return format_html(
|
|
236
234
|
"{}",
|
|
237
235
|
mark_safe(str_values), # nosec B703 B308
|
edc_screening/utils.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
import re
|
|
4
5
|
from typing import TYPE_CHECKING, Any
|
|
5
6
|
|
|
@@ -33,14 +34,15 @@ def get_subject_screening_model_cls() -> Any:
|
|
|
33
34
|
return django_apps.get_model(get_subject_screening_model())
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
def format_reasons_ineligible(*str_values: str, delimiter=None) -> str:
|
|
37
|
+
def format_reasons_ineligible(*str_values: str | None, delimiter: str | None = None) -> str:
|
|
37
38
|
reasons = None
|
|
38
39
|
delimiter = delimiter or "|"
|
|
39
|
-
str_values =
|
|
40
|
+
str_values = str_values or []
|
|
41
|
+
str_values = tuple(x for x in str_values if x)
|
|
40
42
|
if str_values:
|
|
41
43
|
reasons = format_html(
|
|
42
44
|
"{}",
|
|
43
|
-
mark_safe(delimiter.join(str_values)), #
|
|
45
|
+
mark_safe(delimiter.join(str_values)), # noqa: S308
|
|
44
46
|
)
|
|
45
47
|
return reasons
|
|
46
48
|
|
|
@@ -73,10 +75,10 @@ def get_subject_screening_or_raise(
|
|
|
73
75
|
)
|
|
74
76
|
except ObjectDoesNotExist as e:
|
|
75
77
|
if is_modelform:
|
|
76
|
-
raise forms.ValidationError("Not allowed. Screening form not found.")
|
|
78
|
+
raise forms.ValidationError("Not allowed. Screening form not found.") from e
|
|
77
79
|
raise ObjectDoesNotExist(
|
|
78
80
|
f"{e} screening_identifier={screening_identifier}. Perhaps catch this in the form."
|
|
79
|
-
)
|
|
81
|
+
) from e
|
|
80
82
|
return subject_screening
|
|
81
83
|
|
|
82
84
|
|
|
@@ -101,10 +103,8 @@ def is_eligible_or_raise(
|
|
|
101
103
|
)
|
|
102
104
|
|
|
103
105
|
url_name = url_name or "screening_listboard_url"
|
|
104
|
-
|
|
106
|
+
with contextlib.suppress(InvalidDashboardUrlName):
|
|
105
107
|
url_name = url_names.get(url_name)
|
|
106
|
-
except InvalidDashboardUrlName:
|
|
107
|
-
pass
|
|
108
108
|
|
|
109
109
|
if not subject_screening.eligible:
|
|
110
110
|
try:
|
|
@@ -128,7 +128,7 @@ def is_eligible_or_raise(
|
|
|
128
128
|
else:
|
|
129
129
|
msg = format_html(
|
|
130
130
|
'Not allowed. Subject is not eligible. See subject <A href="{}">{}</A>',
|
|
131
|
-
mark_safe(url), #
|
|
131
|
+
mark_safe(url), # noqa: S308
|
|
132
132
|
subject_screening.screening_identifier,
|
|
133
133
|
)
|
|
134
134
|
raise forms.ValidationError(msg)
|