clinicedc 2.0.21__py3-none-any.whl → 2.0.22__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.21.dist-info → clinicedc-2.0.22.dist-info}/METADATA +1 -1
- {clinicedc-2.0.21.dist-info → clinicedc-2.0.22.dist-info}/RECORD +63 -63
- edc_appointment/utils.py +4 -5
- edc_pdf_reports/report.py +2 -2
- edc_subject_dashboard/middleware.py +2 -3
- edc_subject_dashboard/requisition_labels.py +2 -2
- edc_subject_dashboard/requisition_report.py +10 -6
- edc_subject_dashboard/templatetags/edc_subject_dashboard_extras.py +33 -33
- edc_subject_dashboard/view_mixins/subject_visit_view_mixin.py +1 -1
- edc_subject_dashboard/view_utils/crf_button.py +5 -4
- edc_subject_dashboard/view_utils/go_to_forms_button.py +1 -2
- edc_subject_dashboard/view_utils/subject_screening_button.py +2 -6
- edc_subject_dashboard/views/base_requisition_view.py +2 -3
- edc_subject_dashboard/views/refresh_appointments_view.py +6 -6
- edc_subject_dashboard/views/requisition_print_actions_view.py +5 -5
- edc_subject_dashboard/views/requisition_verify_actions_view.py +1 -1
- edc_timepoint/form_mixin.py +3 -3
- edc_timepoint/timepoint.py +1 -1
- edc_timepoint/timepoint_collection.py +3 -3
- edc_transfer/action_items.py +1 -2
- edc_transfer/form_validators.py +2 -2
- edc_unblinding/action_items.py +2 -4
- edc_unblinding/admin/autocomplete_admin.py +2 -2
- edc_unblinding/admin/unblinding_request_admin.py +2 -2
- edc_unblinding/admin/unblinding_review_admin.py +2 -2
- edc_unblinding/auths.py +11 -5
- edc_unblinding/models/unblinding_review.py +2 -2
- edc_utils/age.py +7 -3
- edc_utils/celery.py +3 -3
- edc_utils/context_processors_check.py +1 -1
- edc_utils/get_datetime_from_env.py +1 -1
- edc_utils/get_static_file.py +24 -14
- edc_utils/logging_filters/ignore_specific_ip_disallowed_host.py +1 -1
- edc_utils/message_in_queue.py +1 -4
- edc_utils/show_urls.py +1 -1
- edc_utils/text.py +1 -1
- edc_view_utils/model_button.py +2 -3
- edc_visit_schedule/exceptions.py +3 -3
- edc_visit_schedule/management/commands/find_invalid_onschedules.py +6 -4
- edc_visit_schedule/modelform_mixins/crf/visit_schedule_crf_modelform_mixin.py +1 -1
- edc_visit_schedule/modelform_mixins/off_schedule_modelform_mixin.py +3 -3
- edc_visit_schedule/modelform_mixins/visit_schedule_non_crf_modelform_mixin.py +1 -1
- edc_visit_schedule/models/signals.py +3 -8
- edc_visit_schedule/ordered_collection.py +4 -8
- edc_visit_schedule/post_migrate_signals.py +2 -2
- edc_visit_schedule/schedule/schedule.py +3 -7
- edc_visit_schedule/schedule/window.py +1 -1
- edc_visit_schedule/simple_model_validator.py +4 -4
- edc_visit_schedule/site_visit_schedules.py +12 -16
- edc_visit_schedule/subject_schedule.py +8 -8
- edc_visit_schedule/system_checks.py +3 -3
- edc_visit_schedule/templatetags/edc_visit_schedule_extras.py +2 -2
- edc_visit_schedule/utils.py +13 -14
- edc_visit_schedule/view_mixins.py +1 -1
- edc_visit_schedule/visit/crf.py +7 -6
- edc_visit_schedule/visit/forms_collection.py +2 -2
- edc_visit_schedule/visit/requisition.py +3 -3
- edc_visit_schedule/visit/visit.py +30 -30
- edc_visit_schedule/visit/window_period.py +3 -2
- edc_visit_schedule/visit_schedule/schedules_collection.py +5 -3
- edc_visit_schedule/visit_schedule/visit_schedule.py +5 -5
- {clinicedc-2.0.21.dist-info → clinicedc-2.0.22.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.21.dist-info → clinicedc-2.0.22.dist-info}/licenses/LICENSE +0 -0
|
@@ -204,7 +204,7 @@ class Schedule:
|
|
|
204
204
|
f"See {visit}. Got visit.timepoint={visit.timepoint}."
|
|
205
205
|
)
|
|
206
206
|
visit.base_timepoint = self.base_timepoint
|
|
207
|
-
self.visits.update({visit.code: visit})
|
|
207
|
+
self.visits.update(**{visit.code: visit})
|
|
208
208
|
return visit
|
|
209
209
|
|
|
210
210
|
@property
|
|
@@ -235,9 +235,7 @@ class Schedule:
|
|
|
235
235
|
visit_codes.append(visit_code)
|
|
236
236
|
return visit_codes
|
|
237
237
|
|
|
238
|
-
def subject(
|
|
239
|
-
self, subject_identifier: str, consent_definition: ConsentDefinition = None
|
|
240
|
-
) -> SubjectSchedule:
|
|
238
|
+
def subject(self, subject_identifier: str) -> SubjectSchedule:
|
|
241
239
|
"""Returns a SubjectSchedule instance for this subject.
|
|
242
240
|
|
|
243
241
|
Note: SubjectSchedule puts a subject on/off schedule by
|
|
@@ -292,9 +290,7 @@ class Schedule:
|
|
|
292
290
|
f"'{consent_definition.proxy_model}', "
|
|
293
291
|
f"version='{consent_definition.version}'). "
|
|
294
292
|
)
|
|
295
|
-
self.subject(
|
|
296
|
-
subject_identifier, consent_definition=consent_definition
|
|
297
|
-
).put_on_schedule(
|
|
293
|
+
self.subject(subject_identifier).put_on_schedule(
|
|
298
294
|
onschedule_datetime,
|
|
299
295
|
skip_baseline=skip_baseline,
|
|
300
296
|
skip_get_current_site=skip_get_current_site,
|
|
@@ -49,7 +49,7 @@ class Window:
|
|
|
49
49
|
try:
|
|
50
50
|
self.visits.get(self.visit_code)
|
|
51
51
|
except VisitCollectionError as e:
|
|
52
|
-
raise ScheduleError(e)
|
|
52
|
+
raise ScheduleError(e) from e
|
|
53
53
|
if self.is_scheduled_visit or not self.visits.next(self.visit_code):
|
|
54
54
|
self.raise_for_scheduled_not_in_window()
|
|
55
55
|
else:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from django.apps import apps as django_apps
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class InvalidModel(Exception):
|
|
4
|
+
class InvalidModel(Exception): # noqa: N818
|
|
5
5
|
pass
|
|
6
6
|
|
|
7
7
|
|
|
@@ -14,11 +14,11 @@ class SimpleModelValidator:
|
|
|
14
14
|
def __init__(self, model=None, attr=None):
|
|
15
15
|
try:
|
|
16
16
|
app_label, _ = model.split(".")
|
|
17
|
-
except AttributeError:
|
|
18
|
-
raise InvalidModel(f"Invalid label lower format for '{attr}'.
|
|
17
|
+
except AttributeError as e:
|
|
18
|
+
raise InvalidModel(f"Invalid label lower format for '{attr}'. Got {model}") from e
|
|
19
19
|
else:
|
|
20
20
|
app_labels = [app_config.name for app_config in django_apps.get_app_configs()]
|
|
21
21
|
if app_label not in app_labels:
|
|
22
22
|
raise InvalidModel(
|
|
23
|
-
f"Invalid model. app_label does not exist for
|
|
23
|
+
f"Invalid model. app_label does not exist for '{attr}'. Got {model}"
|
|
24
24
|
)
|
|
@@ -121,9 +121,7 @@ class SiteVisitSchedules:
|
|
|
121
121
|
visit_schedule, schedule = ret[0]
|
|
122
122
|
return visit_schedule, schedule
|
|
123
123
|
|
|
124
|
-
def get_by_onschedule_model(
|
|
125
|
-
self, onschedule_model: str = None
|
|
126
|
-
) -> tuple[VisitSchedule, Schedule]:
|
|
124
|
+
def get_by_onschedule_model(self, onschedule_model: str) -> tuple[VisitSchedule, Schedule]:
|
|
127
125
|
"""Returns a tuple of (visit_schedule, schedule)
|
|
128
126
|
for the given onschedule model.
|
|
129
127
|
|
|
@@ -132,7 +130,7 @@ class SiteVisitSchedules:
|
|
|
132
130
|
return self.get_by_model(attr="onschedule_model", model=onschedule_model)
|
|
133
131
|
|
|
134
132
|
def get_by_offschedule_model(
|
|
135
|
-
self, offschedule_model: str
|
|
133
|
+
self, offschedule_model: str
|
|
136
134
|
) -> tuple[VisitSchedule, Schedule]:
|
|
137
135
|
"""Returns a tuple of visit_schedule, schedule
|
|
138
136
|
for the given offschedule model.
|
|
@@ -142,7 +140,7 @@ class SiteVisitSchedules:
|
|
|
142
140
|
return self.get_by_model(attr="offschedule_model", model=offschedule_model)
|
|
143
141
|
|
|
144
142
|
def get_by_loss_to_followup_model(
|
|
145
|
-
self, loss_to_followup_model: str
|
|
143
|
+
self, loss_to_followup_model: str
|
|
146
144
|
) -> tuple[VisitSchedule, Schedule]:
|
|
147
145
|
"""Returns a tuple of visit_schedule, schedule
|
|
148
146
|
for the given loss_to_followup model.
|
|
@@ -151,9 +149,7 @@ class SiteVisitSchedules:
|
|
|
151
149
|
"""
|
|
152
150
|
return self.get_by_model(attr="loss_to_followup_model", model=loss_to_followup_model)
|
|
153
151
|
|
|
154
|
-
def get_by_model(
|
|
155
|
-
self, attr: str = None, model: str = None
|
|
156
|
-
) -> tuple[VisitSchedule, Schedule]:
|
|
152
|
+
def get_by_model(self, attr: str, model: str) -> tuple[VisitSchedule, Schedule]:
|
|
157
153
|
ret = []
|
|
158
154
|
model = model.lower()
|
|
159
155
|
for visit_schedule in self.visit_schedules.values():
|
|
@@ -178,14 +174,15 @@ class SiteVisitSchedules:
|
|
|
178
174
|
visit_schedule, schedule = ret[0]
|
|
179
175
|
return visit_schedule, schedule
|
|
180
176
|
|
|
181
|
-
def get_by_offstudy_model(self, offstudy_model: str
|
|
177
|
+
def get_by_offstudy_model(self, offstudy_model: str) -> list[VisitSchedule]:
|
|
182
178
|
"""Returns a list of visit_schedules for the given
|
|
183
179
|
offstudy model.
|
|
184
180
|
"""
|
|
185
|
-
visit_schedules = [
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
181
|
+
visit_schedules = [
|
|
182
|
+
visit_schedule
|
|
183
|
+
for visit_schedule in self.visit_schedules.values()
|
|
184
|
+
if visit_schedule.offstudy_model == offstudy_model
|
|
185
|
+
]
|
|
189
186
|
if not visit_schedules:
|
|
190
187
|
raise SiteVisitScheduleError(
|
|
191
188
|
f"No visit schedules have been defined using the "
|
|
@@ -220,7 +217,7 @@ class SiteVisitSchedules:
|
|
|
220
217
|
@staticmethod
|
|
221
218
|
def get_offstudy_model() -> str:
|
|
222
219
|
offstudy_models = []
|
|
223
|
-
for
|
|
220
|
+
for visit_schedule in site_visit_schedules.get_visit_schedules().values():
|
|
224
221
|
if visit_schedule.offstudy_model not in offstudy_models:
|
|
225
222
|
offstudy_models.append(visit_schedule.offstudy_model)
|
|
226
223
|
if len(offstudy_models) > 1:
|
|
@@ -236,8 +233,7 @@ class SiteVisitSchedules:
|
|
|
236
233
|
"No off study model defined in visit_schedule. "
|
|
237
234
|
f"Got registered visit_schedules: {visit_schedule_names}."
|
|
238
235
|
)
|
|
239
|
-
|
|
240
|
-
return offstudy_model
|
|
236
|
+
return offstudy_models[0]
|
|
241
237
|
|
|
242
238
|
@property
|
|
243
239
|
def all_post_consent_models(self) -> dict[str, str]:
|
|
@@ -237,13 +237,13 @@ class SubjectSchedule:
|
|
|
237
237
|
schedule_name=self.schedule_name,
|
|
238
238
|
visit_schedule_name=self.visit_schedule_name,
|
|
239
239
|
)
|
|
240
|
-
except ObjectDoesNotExist:
|
|
240
|
+
except ObjectDoesNotExist as e:
|
|
241
241
|
raise NotOnScheduleError(
|
|
242
242
|
"Failed to take subject off schedule. "
|
|
243
243
|
f"Subject has not been put on schedule "
|
|
244
244
|
f"'{self.visit_schedule_name}.{self.schedule_name}'. "
|
|
245
245
|
f"Got '{self.subject_identifier}'."
|
|
246
|
-
)
|
|
246
|
+
) from e
|
|
247
247
|
|
|
248
248
|
if history_obj:
|
|
249
249
|
self.update_history_or_raise(
|
|
@@ -293,7 +293,7 @@ class SubjectSchedule:
|
|
|
293
293
|
subject_identifier=self.subject_identifier,
|
|
294
294
|
schedule_name=self.schedule_name,
|
|
295
295
|
visit_schedule_name=self.visit_schedule_name,
|
|
296
|
-
**{f"{related_visit_model_attr}__report_datetime__gt":
|
|
296
|
+
**{f"{related_visit_model_attr}__report_datetime__gt": offschedule_datetime},
|
|
297
297
|
)
|
|
298
298
|
except ObjectDoesNotExist:
|
|
299
299
|
appointments = None
|
|
@@ -302,7 +302,7 @@ class SubjectSchedule:
|
|
|
302
302
|
subject_identifier=self.subject_identifier,
|
|
303
303
|
schedule_name=self.schedule_name,
|
|
304
304
|
visit_schedule_name=self.visit_schedule_name,
|
|
305
|
-
**{f"{related_visit_model_attr}__report_datetime__gt":
|
|
305
|
+
**{f"{related_visit_model_attr}__report_datetime__gt": offschedule_datetime},
|
|
306
306
|
)
|
|
307
307
|
if appointments:
|
|
308
308
|
raise InvalidOffscheduleDate(
|
|
@@ -336,12 +336,12 @@ class SubjectSchedule:
|
|
|
336
336
|
model_cls = django_apps.get_model(self.registered_subject_model)
|
|
337
337
|
try:
|
|
338
338
|
obj = model_cls.objects.get(subject_identifier=self.subject_identifier)
|
|
339
|
-
except ObjectDoesNotExist:
|
|
339
|
+
except ObjectDoesNotExist as e:
|
|
340
340
|
raise UnknownSubjectError(
|
|
341
341
|
f"Failed to put subject on schedule. Unknown subject. "
|
|
342
342
|
f"Searched `{self.registered_subject_model}`. "
|
|
343
343
|
f"Got subject_identifier=`{self.subject_identifier}`."
|
|
344
|
-
)
|
|
344
|
+
) from e
|
|
345
345
|
return obj
|
|
346
346
|
|
|
347
347
|
@property
|
|
@@ -350,11 +350,11 @@ class SubjectSchedule:
|
|
|
350
350
|
onschedule_obj = self.onschedule_model_cls.objects.get(
|
|
351
351
|
subject_identifier=self.subject_identifier
|
|
352
352
|
)
|
|
353
|
-
except ObjectDoesNotExist:
|
|
353
|
+
except ObjectDoesNotExist as e:
|
|
354
354
|
raise NotOnScheduleError(
|
|
355
355
|
f"Subject has not been put on a schedule `{self.schedule_name}`. "
|
|
356
356
|
f"Got subject_identifier=`{self.subject_identifier}`."
|
|
357
|
-
)
|
|
357
|
+
) from e
|
|
358
358
|
return onschedule_obj
|
|
359
359
|
|
|
360
360
|
def onschedule_or_raise(self, report_datetime=None, compare_as_datetimes=None):
|
|
@@ -5,7 +5,7 @@ from collections import Counter, defaultdict
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from django.apps import apps as django_apps
|
|
8
|
-
from django.core.checks import Error, Warning
|
|
8
|
+
from django.core.checks import Error, Warning # noqa: A004
|
|
9
9
|
from django.core.exceptions import ObjectDoesNotExist
|
|
10
10
|
|
|
11
11
|
from .site_visit_schedules import site_visit_schedules
|
|
@@ -55,7 +55,7 @@ def check_subject_schedule_history(app_configs, **kwargs) -> list:
|
|
|
55
55
|
)
|
|
56
56
|
for obj in subject_schedule_history_cls.objects.all():
|
|
57
57
|
try:
|
|
58
|
-
obj.onschedule_obj
|
|
58
|
+
obj.onschedule_obj # noqa: B018
|
|
59
59
|
except LookupError as e:
|
|
60
60
|
errors.append(
|
|
61
61
|
Error(
|
|
@@ -201,7 +201,7 @@ def check_multiple_proxies_same_proxy_root(
|
|
|
201
201
|
if proxies_counter & proxies_sharing_roots_counter == proxies_counter:
|
|
202
202
|
# OK if proxies counter reflects ALL defined proxy shared roots
|
|
203
203
|
del proxy_root_to_child_proxies[proxy_root]
|
|
204
|
-
elif len(proxies_counter) == 1 and next(iter(proxies_counter.values())) <= 2:
|
|
204
|
+
elif len(proxies_counter) == 1 and next(iter(proxies_counter.values())) <= 2: # noqa: PLR2004
|
|
205
205
|
# OK for a single proxy to be defined in two places (CRFs collection + PRNs)
|
|
206
206
|
del proxy_root_to_child_proxies[proxy_root]
|
|
207
207
|
else:
|
|
@@ -51,7 +51,7 @@ def subject_schedule_footer_row(
|
|
|
51
51
|
context.update(
|
|
52
52
|
offschedule_datetime=None,
|
|
53
53
|
onschedule_datetime=onschedule_model_obj.onschedule_datetime,
|
|
54
|
-
href=mark_safe(href), # nosec B703, B308
|
|
54
|
+
href=mark_safe(href), # nosec B703, B308 # noqa: S308
|
|
55
55
|
)
|
|
56
56
|
elif history_obj.offschedule_datetime:
|
|
57
57
|
# subject is OFF this schedule (offschedule_model_obj)
|
|
@@ -71,6 +71,6 @@ def subject_schedule_footer_row(
|
|
|
71
71
|
context.update(
|
|
72
72
|
offschedule_datetime=history_obj.offschedule_datetime,
|
|
73
73
|
onschedule_datetime=onschedule_model_obj.onschedule_datetime,
|
|
74
|
-
href=mark_safe(href), # nosec B703, B308
|
|
74
|
+
href=mark_safe(href), # nosec B703, B308 # noqa: S308
|
|
75
75
|
)
|
|
76
76
|
return context
|
edc_visit_schedule/utils.py
CHANGED
|
@@ -76,9 +76,7 @@ def raise_if_not_baseline(subject_visit) -> None:
|
|
|
76
76
|
raise forms.ValidationError("This form is only available for completion at baseline.")
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
def get_onschedule_models(
|
|
80
|
-
subject_identifier: str = None, report_datetime: datetime = None
|
|
81
|
-
) -> list[str]:
|
|
79
|
+
def get_onschedule_models(subject_identifier: str, report_datetime: datetime) -> list[str]:
|
|
82
80
|
"""Returns a list of onschedule models, in label_lower format,
|
|
83
81
|
for this subject and date.
|
|
84
82
|
"""
|
|
@@ -96,7 +94,7 @@ def get_onschedule_models(
|
|
|
96
94
|
return onschedule_models
|
|
97
95
|
|
|
98
96
|
|
|
99
|
-
def get_offschedule_models(subject_identifier
|
|
97
|
+
def get_offschedule_models(subject_identifier: str, report_datetime: datetime) -> list[str]:
|
|
100
98
|
"""Returns a list of offschedule models, in label_lower format,
|
|
101
99
|
for this subject and date.
|
|
102
100
|
|
|
@@ -141,7 +139,7 @@ def off_schedule_or_raise(
|
|
|
141
139
|
)
|
|
142
140
|
|
|
143
141
|
|
|
144
|
-
def off_all_schedules_or_raise(subject_identifier: str
|
|
142
|
+
def off_all_schedules_or_raise(subject_identifier: str):
|
|
145
143
|
"""Raises an exception if subject is still on any schedule."""
|
|
146
144
|
for visit_schedule in site_visit_schedules.get_visit_schedules().values():
|
|
147
145
|
for schedule in visit_schedule.schedules.values():
|
|
@@ -171,8 +169,8 @@ def off_all_schedules_or_raise(subject_identifier: str = None):
|
|
|
171
169
|
|
|
172
170
|
|
|
173
171
|
def offstudy_datetime_after_all_offschedule_datetimes(
|
|
174
|
-
subject_identifier: str
|
|
175
|
-
offstudy_datetime: datetime
|
|
172
|
+
subject_identifier: str,
|
|
173
|
+
offstudy_datetime: datetime,
|
|
176
174
|
exception_cls=None,
|
|
177
175
|
) -> None:
|
|
178
176
|
exception_cls = exception_cls or forms.ValidationError
|
|
@@ -206,10 +204,10 @@ def offstudy_datetime_after_all_offschedule_datetimes(
|
|
|
206
204
|
|
|
207
205
|
|
|
208
206
|
def report_datetime_within_onschedule_offschedule_datetimes(
|
|
209
|
-
subject_identifier: str
|
|
210
|
-
report_datetime: datetime
|
|
211
|
-
visit_schedule_name: str
|
|
212
|
-
schedule_name: str
|
|
207
|
+
subject_identifier: str,
|
|
208
|
+
report_datetime: datetime,
|
|
209
|
+
visit_schedule_name: str,
|
|
210
|
+
schedule_name: str,
|
|
213
211
|
exception_cls=None,
|
|
214
212
|
):
|
|
215
213
|
exception_cls = exception_cls or forms.ValidationError
|
|
@@ -219,11 +217,11 @@ def report_datetime_within_onschedule_offschedule_datetimes(
|
|
|
219
217
|
onschedule_obj = schedule.onschedule_model_cls.objects.get(
|
|
220
218
|
subject_identifier=subject_identifier
|
|
221
219
|
)
|
|
222
|
-
except ObjectDoesNotExist:
|
|
220
|
+
except ObjectDoesNotExist as e:
|
|
223
221
|
raise OnScheduleError(
|
|
224
222
|
f"Subject is not on schedule. {visit_schedule_name}.{schedule_name}. "
|
|
225
223
|
f"Got {subject_identifier}"
|
|
226
|
-
)
|
|
224
|
+
) from e
|
|
227
225
|
try:
|
|
228
226
|
offschedule_obj = schedule.offschedule_model_cls.objects.get(
|
|
229
227
|
subject_identifier=subject_identifier,
|
|
@@ -280,7 +278,7 @@ def get_onschedule_model_instance(
|
|
|
280
278
|
raise OffScheduleError(
|
|
281
279
|
"Subject is not on a schedule. Using subject_identifier="
|
|
282
280
|
f"`{subject_identifier}` and appt_datetime=`{dte_as_str}`. Got {e}"
|
|
283
|
-
)
|
|
281
|
+
) from e
|
|
284
282
|
return onschedule_obj
|
|
285
283
|
|
|
286
284
|
|
|
@@ -302,6 +300,7 @@ def get_proxy_root_model(proxy_model: models.Model) -> models.Model | None:
|
|
|
302
300
|
"""
|
|
303
301
|
if proxy_model._meta.proxy:
|
|
304
302
|
return proxy_model._meta.concrete_model
|
|
303
|
+
return None
|
|
305
304
|
|
|
306
305
|
|
|
307
306
|
def check_models_in_visit_schedule() -> dict[str, list]:
|
|
@@ -37,7 +37,7 @@ class VisitScheduleViewMixin:
|
|
|
37
37
|
pass
|
|
38
38
|
else:
|
|
39
39
|
self.onschedule_models.append(onschedule_model_obj)
|
|
40
|
-
self.visit_schedules.update({visit_schedule.name: visit_schedule})
|
|
40
|
+
self.visit_schedules.update(**{visit_schedule.name: visit_schedule})
|
|
41
41
|
if schedule.is_onschedule(self.subject_identifier, timezone.now()):
|
|
42
42
|
self.current_schedule = schedule
|
|
43
43
|
self.current_visit_schedule = visit_schedule
|
edc_visit_schedule/visit/crf.py
CHANGED
|
@@ -15,12 +15,13 @@ class CrfModelNotProxyModelError(Exception):
|
|
|
15
15
|
class Crf:
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
*,
|
|
19
|
+
show_order: int,
|
|
20
|
+
model: str,
|
|
21
|
+
required: bool | None = None,
|
|
22
|
+
additional: bool | None = None,
|
|
23
|
+
site_ids: list[int] | None = None,
|
|
24
|
+
shares_proxy_root: bool | None = None,
|
|
24
25
|
) -> None:
|
|
25
26
|
self.additional = additional
|
|
26
27
|
self.model = model.lower()
|
|
@@ -19,7 +19,7 @@ class FormsCollection:
|
|
|
19
19
|
*forms: Crf | Requisition,
|
|
20
20
|
name: str | None = None,
|
|
21
21
|
check_sequence: bool | None = None,
|
|
22
|
-
**kwargs,
|
|
22
|
+
**kwargs, # noqa: ARG002
|
|
23
23
|
):
|
|
24
24
|
check_sequence = True if check_sequence is None else check_sequence
|
|
25
25
|
self.collection_is_unique_or_raise(forms)
|
|
@@ -85,7 +85,7 @@ class FormsCollection:
|
|
|
85
85
|
def insert(self, index, value):
|
|
86
86
|
if value:
|
|
87
87
|
forms = list(self._forms)
|
|
88
|
-
for
|
|
88
|
+
for _, item in forms:
|
|
89
89
|
if item.name == value.name:
|
|
90
90
|
raise FormsCollectionError(
|
|
91
91
|
f"Insert failed. Item is not unique. Got {value.name}"
|
|
@@ -14,7 +14,7 @@ class ScheduledRequisitionError(Exception):
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class Requisition(Crf):
|
|
17
|
-
def __init__(self, panel=None, required: bool = None, **kwargs):
|
|
17
|
+
def __init__(self, panel=None, required: bool | None = None, **kwargs):
|
|
18
18
|
required = False if required is None else required
|
|
19
19
|
self.panel = panel
|
|
20
20
|
if not self.panel.requisition_model:
|
|
@@ -52,10 +52,10 @@ class Requisition(Crf):
|
|
|
52
52
|
|
|
53
53
|
See also: edc_lab.
|
|
54
54
|
"""
|
|
55
|
-
from edc_lab.site_labs import site_labs
|
|
55
|
+
from edc_lab.site_labs import site_labs # noqa: PLC0415
|
|
56
56
|
|
|
57
57
|
try:
|
|
58
|
-
self.panel.requisition_model_cls
|
|
58
|
+
self.panel.requisition_model_cls # noqa: B018
|
|
59
59
|
except LookupError as e:
|
|
60
60
|
raise RequisitionLookupError(e) from e
|
|
61
61
|
|
|
@@ -20,7 +20,7 @@ if TYPE_CHECKING:
|
|
|
20
20
|
|
|
21
21
|
from dateutil.relativedelta import relativedelta
|
|
22
22
|
|
|
23
|
-
from edc_facility import Facility
|
|
23
|
+
from edc_facility.facility import Facility
|
|
24
24
|
|
|
25
25
|
from .crf import Crf
|
|
26
26
|
from .requisition import Requisition
|
|
@@ -38,7 +38,7 @@ class VisitError(Exception):
|
|
|
38
38
|
pass
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
class BaseDatetimeNotSet(Exception):
|
|
41
|
+
class BaseDatetimeNotSet(Exception): # noqa: N818
|
|
42
42
|
pass
|
|
43
43
|
|
|
44
44
|
|
|
@@ -53,10 +53,11 @@ class VisitDate:
|
|
|
53
53
|
|
|
54
54
|
def __init__(
|
|
55
55
|
self,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
*,
|
|
57
|
+
rlower: relativedelta,
|
|
58
|
+
rupper: relativedelta,
|
|
59
|
+
timepoint: Decimal | None = None,
|
|
60
|
+
base_timepoint: Decimal | None = None,
|
|
60
61
|
):
|
|
61
62
|
self._base: datetime | None = None
|
|
62
63
|
self._lower: datetime | None = None
|
|
@@ -73,7 +74,7 @@ class VisitDate:
|
|
|
73
74
|
return self._base
|
|
74
75
|
|
|
75
76
|
@base.setter
|
|
76
|
-
def base(self, dt: datetime
|
|
77
|
+
def base(self, dt: datetime):
|
|
77
78
|
self._base = to_local(dt)
|
|
78
79
|
self._lower, self._upper = self._window_period.get_window(dt=self._base)
|
|
79
80
|
|
|
@@ -100,25 +101,25 @@ class Visit:
|
|
|
100
101
|
|
|
101
102
|
def __init__(
|
|
102
103
|
self,
|
|
103
|
-
code: str
|
|
104
|
-
timepoint: int | float | Decimal
|
|
105
|
-
rbase: relativedelta
|
|
106
|
-
rlower: relativedelta
|
|
107
|
-
rupper: relativedelta
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
add_window_gap_to_lower: bool | None = None,
|
|
111
|
-
max_window_gap_to_lower: int | None = None,
|
|
104
|
+
code: str,
|
|
105
|
+
timepoint: int | float | Decimal,
|
|
106
|
+
rbase: relativedelta,
|
|
107
|
+
rlower: relativedelta,
|
|
108
|
+
rupper: relativedelta,
|
|
109
|
+
title: str | None = None,
|
|
110
|
+
facility_name: str | None = None,
|
|
112
111
|
crfs: CrfCollection | None = None,
|
|
113
|
-
|
|
112
|
+
crfs_prn: CrfCollection | None = None,
|
|
114
113
|
crfs_unscheduled: CrfCollection | None = None,
|
|
115
114
|
crfs_missed: CrfCollection | None = None,
|
|
116
|
-
|
|
117
|
-
crfs_prn: CrfCollection | None = None,
|
|
115
|
+
requisitions: RequisitionCollection | None = None,
|
|
118
116
|
requisitions_prn: RequisitionCollection | None = None,
|
|
119
|
-
|
|
117
|
+
requisitions_unscheduled: RequisitionCollection | None = None,
|
|
118
|
+
rlower_late: relativedelta = None,
|
|
119
|
+
rupper_late: relativedelta = None,
|
|
120
|
+
add_window_gap_to_lower: bool | None = None,
|
|
121
|
+
max_window_gap_to_lower: int | None = None,
|
|
120
122
|
allow_unscheduled: bool | None = None,
|
|
121
|
-
facility_name: str | None = None,
|
|
122
123
|
instructions: str | None = None,
|
|
123
124
|
base_timepoint: int | float | Decimal | None = None,
|
|
124
125
|
grouping=None,
|
|
@@ -162,14 +163,14 @@ class Visit:
|
|
|
162
163
|
raise VisitCodeError(f"Invalid visit code. Got '{code}'")
|
|
163
164
|
self.code = code # unique
|
|
164
165
|
self.dates = self.visit_date_cls(
|
|
165
|
-
rlower=rlower,
|
|
166
|
-
rupper=rupper,
|
|
166
|
+
rlower=self.rlower,
|
|
167
|
+
rupper=self.rupper,
|
|
167
168
|
timepoint=self.timepoint,
|
|
168
169
|
base_timepoint=self.base_timepoint,
|
|
169
170
|
)
|
|
170
171
|
self.late_dates = self.visit_date_cls(
|
|
171
|
-
rlower=rlower_late
|
|
172
|
-
rupper=rupper_late
|
|
172
|
+
rlower=self.rlower_late,
|
|
173
|
+
rupper=self.rupper_late,
|
|
173
174
|
timepoint=self.timepoint,
|
|
174
175
|
base_timepoint=self.base_timepoint,
|
|
175
176
|
)
|
|
@@ -252,11 +253,10 @@ class Visit:
|
|
|
252
253
|
return get_requisition
|
|
253
254
|
|
|
254
255
|
def get_models(self) -> list:
|
|
255
|
-
models = []
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
models.append(django_apps.get_model(crf.model))
|
|
256
|
+
models = [django_apps.get_model(crf.model) for crf in self.crfs]
|
|
257
|
+
models.extend(
|
|
258
|
+
[django_apps.get_model(requisition.model) for requisition in self.requisitions]
|
|
259
|
+
)
|
|
260
260
|
return models
|
|
261
261
|
|
|
262
262
|
@property
|
|
@@ -14,8 +14,9 @@ from edc_utils import to_local
|
|
|
14
14
|
class WindowPeriod:
|
|
15
15
|
def __init__(
|
|
16
16
|
self,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
*,
|
|
18
|
+
rlower: relativedelta,
|
|
19
|
+
rupper: relativedelta,
|
|
19
20
|
timepoint: Decimal | None = None,
|
|
20
21
|
base_timepoint: Decimal | None = None,
|
|
21
22
|
no_floor: bool | None = None,
|
|
@@ -10,11 +10,13 @@ class SchedulesCollection(OrderedCollection):
|
|
|
10
10
|
key = "name"
|
|
11
11
|
ordering_attr = "sequence"
|
|
12
12
|
|
|
13
|
-
def __init__(self, visit_schedule_name: str
|
|
13
|
+
def __init__(self, visit_schedule_name: str, *args, **kwargs) -> None:
|
|
14
14
|
self.visit_schedule_name = visit_schedule_name
|
|
15
15
|
super().__init__(*args, **kwargs)
|
|
16
16
|
|
|
17
|
-
def get_schedule(
|
|
17
|
+
def get_schedule(
|
|
18
|
+
self, model: str | None = None, schedule_name: str | None = None
|
|
19
|
+
) -> Schedule:
|
|
18
20
|
"""Returns a schedule or raises; by name, by onschedule/offschedule model
|
|
19
21
|
or by model label_lower.
|
|
20
22
|
"""
|
|
@@ -22,7 +24,7 @@ class SchedulesCollection(OrderedCollection):
|
|
|
22
24
|
if model:
|
|
23
25
|
model = model.lower()
|
|
24
26
|
for item in self.values():
|
|
25
|
-
if item.onschedule_model
|
|
27
|
+
if model in (item.onschedule_model, item.offschedule_model):
|
|
26
28
|
schedule = item
|
|
27
29
|
if schedule:
|
|
28
30
|
break
|
|
@@ -27,7 +27,7 @@ class VisitScheduleAppointmentModelError(Exception):
|
|
|
27
27
|
pass
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class AlreadyRegisteredSchedule(Exception):
|
|
30
|
+
class AlreadyRegisteredSchedule(Exception): # noqa: N818
|
|
31
31
|
pass
|
|
32
32
|
|
|
33
33
|
|
|
@@ -35,8 +35,8 @@ class VisitSchedule:
|
|
|
35
35
|
name_regex = r"[a-z0-9\_\-]+$"
|
|
36
36
|
name_regex_msg = "numbers, lower case letters and '_'"
|
|
37
37
|
schedules_collection = SchedulesCollection
|
|
38
|
-
create_metadata_on_reasons =
|
|
39
|
-
delete_metadata_on_reasons =
|
|
38
|
+
create_metadata_on_reasons: tuple[str, ...] = (SCHEDULED, UNSCHEDULED, MISSED_VISIT)
|
|
39
|
+
delete_metadata_on_reasons: tuple[str, ...] = ()
|
|
40
40
|
|
|
41
41
|
def __init__(
|
|
42
42
|
self,
|
|
@@ -77,7 +77,7 @@ class VisitSchedule:
|
|
|
77
77
|
|
|
78
78
|
if not re.match(self.name_regex, name):
|
|
79
79
|
raise VisitScheduleNameError(
|
|
80
|
-
f"Visit schedule name may only contain {self.name_regex_msg}.
|
|
80
|
+
f"Visit schedule name may only contain {self.name_regex_msg}. Got {name}"
|
|
81
81
|
)
|
|
82
82
|
self.title = self.verbose_name = verbose_name or " ".join(
|
|
83
83
|
[s.capitalize() for s in name.split("_")]
|
|
@@ -107,7 +107,7 @@ class VisitSchedule:
|
|
|
107
107
|
raise AlreadyRegisteredSchedule(
|
|
108
108
|
f"Schedule '{schedule.name}' is already registered. See '{self}'"
|
|
109
109
|
)
|
|
110
|
-
self.schedules.update({schedule.name: schedule})
|
|
110
|
+
self.schedules.update(**{schedule.name: schedule})
|
|
111
111
|
self._all_post_consent_models = None
|
|
112
112
|
return schedule
|
|
113
113
|
|
|
File without changes
|
|
File without changes
|