clinicedc 2.0.34__py3-none-any.whl → 2.0.35__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.34.dist-info → clinicedc-2.0.35.dist-info}/METADATA +1 -1
- {clinicedc-2.0.34.dist-info → clinicedc-2.0.35.dist-info}/RECORD +155 -165
- {clinicedc-2.0.34.dist-info → clinicedc-2.0.35.dist-info}/WHEEL +1 -1
- edc_action_item/templatetags/action_item_extras.py +1 -1
- edc_adverse_event/form_validator_mixins/death_report_form_validator.py +1 -1
- edc_adverse_event/form_validator_mixins/requires_death_report_form_validator_mixin.py +5 -3
- edc_adverse_event/form_validators/death_report_tmg.py +2 -2
- edc_adverse_event/modeladmin_mixins/ae_tmg_admin_mixin.py +1 -1
- edc_adverse_event/modeladmin_mixins/utils.py +1 -1
- edc_adverse_event/utils.py +1 -1
- edc_appointment/admin/appointment_admin.py +24 -24
- edc_appointment/admin/list_filters.py +5 -5
- edc_appointment/appointment_reason_updater.py +6 -5
- edc_appointment/appointment_status_updater.py +2 -2
- edc_appointment/context_processors.py +1 -2
- edc_appointment/creators/appointment_creator.py +9 -8
- edc_appointment/creators/unscheduled_appointment_creator.py +31 -24
- edc_appointment/creators/utils.py +35 -37
- edc_appointment/exceptions.py +3 -3
- edc_appointment/form_runners.py +2 -2
- edc_appointment/form_validator_mixins/next_appointment_crf_form_validator_mixin.py +5 -5
- edc_appointment/form_validator_mixins/window_period_form_validator_mixin.py +7 -7
- edc_appointment/form_validators/appointment_form_validator.py +16 -23
- edc_appointment/management/commands/close_appointments.py +3 -3
- edc_appointment/management/commands/reset_visit_code_sequences.py +1 -1
- edc_appointment/management/commands/update_appointment_status.py +2 -2
- edc_appointment/management/commands/update_skipped_appointments.py +7 -7
- edc_appointment/managers.py +6 -7
- edc_appointment/model_mixins/appointment_model_mixin.py +3 -4
- edc_appointment/modeladmin_mixins/next_appointment_crf_modeladmin_mixin.py +2 -2
- edc_appointment/modelform_mixins/next_appointment_crf_modelform_mixins.py +25 -24
- edc_appointment/models/signals.py +1 -1
- edc_appointment/skip_appointments.py +10 -29
- edc_appointment/utils.py +43 -47
- edc_appointment/view_mixins/appointment_view_mixin.py +11 -13
- edc_appointment/views/unscheduled_appointment_view.py +5 -6
- edc_consent/consent_definition.py +5 -13
- edc_consent/consent_definition_extension.py +0 -2
- edc_consent/form_validators/consent_definition_form_validator_mixin.py +26 -30
- edc_consent/form_validators/subject_consent_form_validator.py +3 -3
- edc_consent/modelform_mixins/consent_modelform_mixin/consent_modelform_validation_mixin.py +1 -1
- edc_consent/modelform_mixins/requires_consent_modelform_mixin.py +4 -5
- edc_consent/site_consents.py +13 -14
- edc_crf/crf_form_validator.py +13 -19
- edc_crf/crf_form_validator_mixins.py +2 -17
- edc_data_manager/admin/actions.py +1 -1
- edc_data_manager/admin/data_query_admin.py +1 -1
- edc_dx/form_validators/result_form_validator_mixin.py +1 -1
- edc_dx_review/medical_date.py +1 -1
- edc_egfr/egfr.py +4 -8
- edc_egfr/form_validator_mixins/egfr_form_validator_mixins.py +2 -2
- edc_facility/facility.py +7 -11
- edc_facility/holidays.py +3 -3
- edc_facility/models/holiday.py +1 -1
- edc_form_runners/form_runner.py +15 -16
- edc_form_validators/base_form_validator.py +5 -1
- edc_form_validators/date_range_validator.py +49 -61
- edc_form_validators/date_validator.py +1 -1
- edc_form_validators/extra_mixins/study_day_form_validator.py +1 -1
- edc_lab/form_validators/crf_requisition_form_validator_mixin.py +21 -15
- edc_lab/form_validators/requisition_form_validator_mixin.py +3 -3
- edc_lab_results/form_validator_mixins/blood_results_form_validator_mixin.py +1 -1
- edc_ltfu/modelform_mixins.py +1 -1
- edc_metadata/metadata/metadata.py +20 -7
- edc_metadata/metadata_rules/logic.py +5 -4
- edc_metadata/metadata_rules/predicate.py +22 -24
- edc_model_form/mixins/report_datetime_modelform_mixin.py +1 -5
- edc_offstudy/model_mixins/offstudy_model_mixin.py +1 -1
- edc_offstudy/modelform_mixins/crf/offstudy_crf_modelform_mixin.py +2 -2
- edc_offstudy/utils.py +4 -4
- edc_pdf_reports/crf_pdf_report.py +2 -1
- edc_pdutils/helper.py +3 -3
- edc_pdutils/utils/convert_dates_from_model.py +4 -3
- edc_pharmacy/admin/actions/confirm_stock.py +3 -3
- edc_pharmacy/admin/actions/delete_items_for_stock_request.py +4 -3
- edc_pharmacy/admin/actions/delete_order_items.py +7 -3
- edc_pharmacy/admin/actions/delete_receive_items.py +5 -3
- edc_pharmacy/admin/actions/process_repack_request.py +7 -11
- edc_pharmacy/admin/autocomplete_admin.py +1 -1
- edc_pharmacy/admin/list_filters.py +48 -46
- edc_pharmacy/admin/medication/assignment_admin.py +4 -4
- edc_pharmacy/admin/medication/dosage_guideline_admin.py +3 -3
- edc_pharmacy/admin/medication/formulation_admin.py +5 -5
- edc_pharmacy/admin/medication/medication_admin.py +3 -3
- edc_pharmacy/admin/prescription/rx_admin.py +2 -2
- edc_pharmacy/admin/prescription/rx_refill_admin.py +11 -17
- edc_pharmacy/admin/remove_fields_for_blinded_users.py +1 -1
- edc_pharmacy/admin/reports/stock_availability_admin.py +12 -8
- edc_pharmacy/admin/stock/allocation_admin.py +13 -17
- edc_pharmacy/admin/stock/allocation_proxy_admin.py +1 -2
- edc_pharmacy/admin/stock/confirmation_admin.py +4 -8
- edc_pharmacy/admin/stock/confirmation_at_site_item_admin.py +1 -1
- edc_pharmacy/admin/stock/container_admin.py +3 -3
- edc_pharmacy/admin/stock/location_admin.py +3 -6
- edc_pharmacy/admin/stock/lot_admin.py +9 -12
- edc_pharmacy/admin/stock/order_admin.py +2 -5
- edc_pharmacy/admin/stock/order_item_admin.py +14 -22
- edc_pharmacy/admin/stock/product_admin.py +6 -9
- edc_pharmacy/admin/stock/receive_admin.py +2 -2
- edc_pharmacy/admin/stock/receive_item_admin.py +3 -3
- edc_pharmacy/admin/stock/repack_request_admin.py +7 -10
- edc_pharmacy/admin/stock/stock_adjustment_admin.py +1 -1
- edc_pharmacy/admin/stock/stock_admin.py +17 -28
- edc_pharmacy/admin/stock/stock_proxy_admin.py +3 -4
- edc_pharmacy/admin/stock/stock_request_admin.py +6 -10
- edc_pharmacy/admin/stock/stock_request_item_admin.py +9 -18
- edc_pharmacy/admin/stock/stock_transfer_admin.py +5 -5
- edc_pharmacy/admin/stock/stock_transfer_item_admin.py +3 -3
- edc_pharmacy/admin/stock/storage_bin_admin.py +1 -1
- edc_pharmacy/admin/stock/storage_bin_item_admin.py +2 -2
- edc_pharmacy/form_validators/crf/study_medication_form_validator.py +27 -27
- edc_pharmacy/forms/stock/confirmation_form.py +2 -2
- edc_pharmacy/forms/stock/dispense_form.py +2 -2
- edc_pharmacy/forms/stock/lot_form.py +6 -6
- edc_pharmacy/forms/stock/order_form.py +4 -6
- edc_pharmacy/forms/stock/product_form.py +2 -3
- edc_pharmacy/forms/stock/receive_form.py +4 -4
- edc_pharmacy/forms/stock/receive_item_form.py +7 -5
- edc_pharmacy/forms/stock/repack_request_form.py +10 -8
- edc_pharmacy/forms/stock/stock_form.py +2 -3
- edc_pharmacy/forms/stock/stock_request_form.py +10 -8
- edc_pharmacy/forms/stock/stock_request_item_form.py +6 -5
- edc_pharmacy/forms/stock/stock_transfer_form.py +16 -14
- edc_pharmacy/forms/stock/supplier_form.py +2 -2
- edc_pharmacy/labels/label_data.py +2 -3
- edc_pharmacy/model_mixins/study_medication_crf_model_mixin.py +1 -1
- edc_pharmacy/models/medication/dosage_guideline.py +3 -3
- edc_pharmacy/models/model_mixins.py +12 -10
- edc_pharmacy/models/prescription/rx.py +1 -1
- edc_pharmacy/models/prescription/rx_refill.py +1 -1
- edc_pharmacy/models/signals.py +2 -3
- edc_pharmacy/models/storage/utils.py +4 -4
- edc_pharmacy/pdf_reports/manifest_pdf_report.py +3 -6
- edc_pharmacy/pdf_reports/stock_pdf_report.py +1 -3
- edc_pharmacy/refill/refill_creator.py +3 -4
- edc_protocol/validators.py +10 -16
- edc_reportable/forms/reportables_form_validator_mixin.py +1 -1
- edc_screening/form_validator_mixins.py +2 -1
- edc_transfer/form_validators.py +1 -1
- edc_transfer/model_mixins.py +1 -1
- edc_utils/age.py +17 -15
- edc_utils/date.py +7 -7
- edc_utils/text.py +7 -6
- edc_visit_schedule/exceptions.py +8 -0
- edc_visit_schedule/model_mixins/off_schedule_model_mixin.py +1 -1
- edc_visit_schedule/model_mixins/on_schedule_model_mixin.py +1 -1
- edc_visit_schedule/modelform_mixins/off_schedule_modelform_mixin.py +1 -3
- edc_visit_schedule/schedule/visit_collection.py +1 -1
- edc_visit_schedule/schedule/window.py +0 -2
- edc_visit_schedule/subject_schedule.py +1 -1
- edc_visit_schedule/utils.py +4 -4
- edc_visit_schedule/visit/visit.py +9 -3
- edc_visit_schedule/visit/window_period.py +1 -1
- edc_visit_tracking/form_validators/visit_form_validator.py +1 -1
- edc_metadata/metadata_wrappers/__init__.py +0 -5
- edc_metadata/metadata_wrappers/crf_metadata_wrapper.py +0 -5
- edc_metadata/metadata_wrappers/crf_metadata_wrappers.py +0 -8
- edc_metadata/metadata_wrappers/metadata_wrapper.py +0 -74
- edc_metadata/metadata_wrappers/metadata_wrappers.py +0 -33
- edc_metadata/metadata_wrappers/requisition_metadata_wrapper.py +0 -26
- edc_metadata/metadata_wrappers/requisition_metadata_wrappers.py +0 -10
- edc_pharmacy/management/__init__.py +0 -0
- edc_pharmacy/management/commands/__init__.py +0 -0
- edc_pharmacy/management/commands/update_initial_pharmacy_data.py +0 -10
- {clinicedc-2.0.34.dist-info → clinicedc-2.0.35.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
from typing import TYPE_CHECKING, Any
|
|
4
5
|
|
|
5
6
|
from django.apps import apps as django_apps
|
|
@@ -50,7 +51,7 @@ class AppointmentViewMixin:
|
|
|
50
51
|
if self.appointment.related_visit:
|
|
51
52
|
report_datetime = self.appointment.related_visit.report_datetime
|
|
52
53
|
kwargs.update(report_datetime=report_datetime)
|
|
53
|
-
has_call_manager =
|
|
54
|
+
has_call_manager = bool(django_apps.app_configs.get("edc_call_manager"))
|
|
54
55
|
kwargs.update(
|
|
55
56
|
appointment=self.appointment,
|
|
56
57
|
appointments=self.appointments,
|
|
@@ -92,18 +93,15 @@ class AppointmentViewMixin:
|
|
|
92
93
|
|
|
93
94
|
@property
|
|
94
95
|
def appointment(self) -> Appointment:
|
|
95
|
-
if not self._appointment:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
self._appointment = self.appointment_model_cls.objects.get(**opts)
|
|
105
|
-
except ObjectDoesNotExist:
|
|
106
|
-
pass
|
|
96
|
+
if not self._appointment and self.appointment_id:
|
|
97
|
+
try:
|
|
98
|
+
self._appointment = self.appointment_model_cls.objects.get(
|
|
99
|
+
id=self.appointment_id
|
|
100
|
+
)
|
|
101
|
+
except ObjectDoesNotExist:
|
|
102
|
+
if opts := self.appointment_options:
|
|
103
|
+
with contextlib.suppress(ObjectDoesNotExist):
|
|
104
|
+
self._appointment = self.appointment_model_cls.objects.get(**opts)
|
|
107
105
|
return self._appointment
|
|
108
106
|
|
|
109
107
|
@property
|
|
@@ -3,7 +3,6 @@ from django.core.exceptions import ObjectDoesNotExist
|
|
|
3
3
|
from django.http.response import HttpResponseRedirect
|
|
4
4
|
from django.urls.base import reverse
|
|
5
5
|
from django.utils.html import format_html
|
|
6
|
-
from django.utils.safestring import mark_safe
|
|
7
6
|
from django.views.generic.base import View
|
|
8
7
|
|
|
9
8
|
from ..creators import UnscheduledAppointmentCreator
|
|
@@ -30,7 +29,7 @@ class UnscheduledAppointmentView(View):
|
|
|
30
29
|
unscheduled_appointment_cls = UnscheduledAppointmentCreator
|
|
31
30
|
dashboard_template = "subject_dashboard_template"
|
|
32
31
|
|
|
33
|
-
def get(self, request, *args, **kwargs):
|
|
32
|
+
def get(self, request, *args, **kwargs): # noqa: ARG002
|
|
34
33
|
kwargs["suggested_visit_code_sequence"] = int(kwargs["visit_code_sequence"])
|
|
35
34
|
kw = dict(
|
|
36
35
|
subject_identifier=kwargs.get("subject_identifier"),
|
|
@@ -61,15 +60,15 @@ class UnscheduledAppointmentView(View):
|
|
|
61
60
|
messages.success(
|
|
62
61
|
self.request,
|
|
63
62
|
format_html(
|
|
64
|
-
"Appointment {} was created successfully.",
|
|
65
|
-
|
|
63
|
+
"Appointment {appt} was created successfully.",
|
|
64
|
+
appt=creator.appointment, # nosec B308, B703
|
|
66
65
|
),
|
|
67
66
|
)
|
|
68
67
|
messages.warning(
|
|
69
68
|
self.request,
|
|
70
69
|
format_html(
|
|
71
|
-
"Remember to adjust the appointment date and time on appointment {}.",
|
|
72
|
-
|
|
70
|
+
"Remember to adjust the appointment date and time on appointment {appt}.",
|
|
71
|
+
appt=creator.appointment, # nosec B308, B703
|
|
73
72
|
),
|
|
74
73
|
)
|
|
75
74
|
return HttpResponseRedirect(
|
|
@@ -82,16 +82,8 @@ class ConsentDefinition:
|
|
|
82
82
|
raise ConsentDefinitionError(f"Invalid gender. Got {self.gender}.")
|
|
83
83
|
if not self.start.tzinfo:
|
|
84
84
|
raise ConsentDefinitionError(f"Naive datetime not allowed. Got {self.start}.")
|
|
85
|
-
if str(self.start.tzinfo).upper() != "UTC":
|
|
86
|
-
raise ConsentDefinitionError(
|
|
87
|
-
f"Start date must be UTC. Got {self.start} / {self.start.tzinfo}."
|
|
88
|
-
)
|
|
89
85
|
if not self.end.tzinfo:
|
|
90
|
-
raise ConsentDefinitionError(f"Naive datetime not allowed Got {self.end}.")
|
|
91
|
-
if str(self.end.tzinfo).upper() != "UTC":
|
|
92
|
-
raise ConsentDefinitionError(
|
|
93
|
-
f"End date must be UTC. Got {self.end} / {self.start.tzinfo}."
|
|
94
|
-
)
|
|
86
|
+
raise ConsentDefinitionError(f"Naive datetime not allowed. Got {self.end}.")
|
|
95
87
|
self.check_date_within_study_period()
|
|
96
88
|
|
|
97
89
|
def model_create(self, **kwargs) -> ConsentLikeModel:
|
|
@@ -147,7 +139,7 @@ class ConsentDefinition:
|
|
|
147
139
|
|
|
148
140
|
def get_consent_for(
|
|
149
141
|
self,
|
|
150
|
-
subject_identifier: str
|
|
142
|
+
subject_identifier: str,
|
|
151
143
|
site_id: int | None = None,
|
|
152
144
|
raise_if_not_consented: bool | None = None,
|
|
153
145
|
) -> ConsentLikeModel | None:
|
|
@@ -209,9 +201,9 @@ class ConsentDefinition:
|
|
|
209
201
|
<= getattr(self, attr)
|
|
210
202
|
<= ceil_secs(study_close_datetime)
|
|
211
203
|
):
|
|
212
|
-
open_date_string = formatted_datetime(study_open_datetime)
|
|
213
|
-
close_date_string = formatted_datetime(study_close_datetime)
|
|
214
|
-
attr_date_string = formatted_datetime(getattr(self, attr))
|
|
204
|
+
open_date_string = formatted_datetime(to_local(study_open_datetime))
|
|
205
|
+
close_date_string = formatted_datetime(to_local(study_close_datetime))
|
|
206
|
+
attr_date_string = formatted_datetime(to_local(getattr(self, attr)))
|
|
215
207
|
raise ConsentDefinitionError(
|
|
216
208
|
f"Invalid {attr} date. "
|
|
217
209
|
f"Must be within the opening and closing dates of the protocol. "
|
|
@@ -72,8 +72,6 @@ class ConsentDefinitionExtension:
|
|
|
72
72
|
self.sort_index = self.name
|
|
73
73
|
if not self.start.tzinfo:
|
|
74
74
|
raise ConsentDefinitionError(f"Naive datetime not allowed. Got {self.start}.")
|
|
75
|
-
if str(self.start.tzinfo) != "UTC":
|
|
76
|
-
raise ConsentDefinitionError(f"Start date must be UTC. Got {self.start}.")
|
|
77
75
|
self.extends.check_date_within_study_period()
|
|
78
76
|
|
|
79
77
|
def update_visit_collection(
|
|
@@ -13,15 +13,15 @@ from edc_consent.exceptions import (
|
|
|
13
13
|
SiteConsentError,
|
|
14
14
|
)
|
|
15
15
|
from edc_consent.site_consents import site_consents
|
|
16
|
+
from edc_consent.stubs import ConsentLikeModel
|
|
16
17
|
from edc_form_validators import INVALID_ERROR
|
|
17
18
|
from edc_sites.site import sites as site_sites
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
|
-
|
|
21
|
+
pass
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class ConsentDefinitionFormValidatorMixin:
|
|
24
|
-
|
|
25
25
|
@property
|
|
26
26
|
def subject_consent(self):
|
|
27
27
|
cdef = self.get_consent_definition()
|
|
@@ -29,48 +29,48 @@ class ConsentDefinitionFormValidatorMixin:
|
|
|
29
29
|
|
|
30
30
|
def get_consent_datetime_or_raise(
|
|
31
31
|
self,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
error_code: str = None,
|
|
32
|
+
reference_datetime: datetime | None = None,
|
|
33
|
+
reference_datetime_field: str | None = None,
|
|
34
|
+
error_code: str | None = None,
|
|
36
35
|
) -> datetime:
|
|
37
36
|
"""Returns the consent_datetime of this subject"""
|
|
38
37
|
consent_obj = self.get_consent_or_raise(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
fldname=fldname,
|
|
38
|
+
reference_datetime=reference_datetime,
|
|
39
|
+
reference_datetime_field=reference_datetime_field,
|
|
42
40
|
error_code=error_code,
|
|
43
41
|
)
|
|
44
42
|
return consent_obj.consent_datetime
|
|
45
43
|
|
|
46
44
|
def get_consent_or_raise(
|
|
47
45
|
self,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
fldname: str | None = None,
|
|
46
|
+
reference_datetime: datetime | None = None,
|
|
47
|
+
reference_datetime_field: str | None = None,
|
|
51
48
|
error_code: str | None = None,
|
|
52
|
-
) ->
|
|
49
|
+
) -> ConsentLikeModel:
|
|
53
50
|
"""Returns the consent_datetime of this subject.
|
|
54
51
|
|
|
55
52
|
Wraps func `consent_datetime_or_raise` to re-raise exceptions
|
|
56
53
|
as ValidationError.
|
|
57
54
|
"""
|
|
58
55
|
|
|
59
|
-
fldname = fldname or "report_datetime"
|
|
60
56
|
error_code = error_code or INVALID_ERROR
|
|
61
57
|
try:
|
|
62
58
|
consent_obj = site_consents.get_consent_or_raise(
|
|
63
59
|
subject_identifier=self.subject_identifier,
|
|
64
|
-
report_datetime=report_datetime,
|
|
65
|
-
site_id=getattr(site, "id", None),
|
|
60
|
+
report_datetime=reference_datetime or self.report_datetime,
|
|
61
|
+
site_id=getattr(self.site, "id", None),
|
|
66
62
|
)
|
|
67
63
|
except (NotConsentedError, ConsentDefinitionNotConfiguredForUpdate) as e:
|
|
68
|
-
self.raise_validation_error(
|
|
64
|
+
self.raise_validation_error(
|
|
65
|
+
{reference_datetime_field or self.report_datetime_field_attr: str(e)},
|
|
66
|
+
error_code,
|
|
67
|
+
exc=e,
|
|
68
|
+
)
|
|
69
69
|
return consent_obj
|
|
70
70
|
|
|
71
71
|
def get_consent_definition(
|
|
72
72
|
self,
|
|
73
|
-
report_datetime: datetime
|
|
73
|
+
report_datetime: datetime,
|
|
74
74
|
fldname: str = None,
|
|
75
75
|
error_code: str = None,
|
|
76
76
|
) -> ConsentDefinition:
|
|
@@ -84,19 +84,15 @@ class ConsentDefinitionFormValidatorMixin:
|
|
|
84
84
|
report_datetime=report_datetime,
|
|
85
85
|
)
|
|
86
86
|
except ConsentDefinitionDoesNotExist as e:
|
|
87
|
-
self.raise_validation_error({fldname: str(e)}, error_code)
|
|
88
|
-
except SiteConsentError:
|
|
87
|
+
self.raise_validation_error({fldname: str(e)}, error_code, exc=e)
|
|
88
|
+
except SiteConsentError as e:
|
|
89
89
|
possible_consents = "', '".join(
|
|
90
90
|
[cdef.display_name for cdef in site_consents.consent_definitions]
|
|
91
91
|
)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
)
|
|
99
|
-
},
|
|
100
|
-
error_code,
|
|
101
|
-
)
|
|
92
|
+
msg = _(
|
|
93
|
+
"Date does not fall within a valid consent period. "
|
|
94
|
+
"Possible consents are '%(possible_consents)s'. "
|
|
95
|
+
) % {"possible_consents": possible_consents}
|
|
96
|
+
|
|
97
|
+
self.raise_validation_error({fldname: msg}, error_code, exc=e)
|
|
102
98
|
return consent_definition
|
|
@@ -9,7 +9,7 @@ from edc_form_validators import INVALID_ERROR
|
|
|
9
9
|
from edc_screening.form_validator_mixins import SubjectScreeningFormValidatorMixin
|
|
10
10
|
from edc_sites.site import sites
|
|
11
11
|
from edc_utils import AgeValueError, age
|
|
12
|
-
from edc_utils.date import to_local
|
|
12
|
+
from edc_utils.date import to_local
|
|
13
13
|
from edc_utils.text import convert_php_dateformat
|
|
14
14
|
|
|
15
15
|
from ..site_consents import site_consents
|
|
@@ -63,7 +63,7 @@ class SubjectConsentFormValidatorMixin(SubjectScreeningFormValidatorMixin):
|
|
|
63
63
|
self.raise_validation_error(
|
|
64
64
|
{"consent_datetime": "This field is required."}, INVALID_ERROR
|
|
65
65
|
)
|
|
66
|
-
self._consent_datetime =
|
|
66
|
+
self._consent_datetime = self.cleaned_data.get("consent_datetime")
|
|
67
67
|
else:
|
|
68
68
|
self._consent_datetime = self.instance.consent_datetime
|
|
69
69
|
return self._consent_datetime
|
|
@@ -75,7 +75,7 @@ class SubjectConsentFormValidatorMixin(SubjectScreeningFormValidatorMixin):
|
|
|
75
75
|
try:
|
|
76
76
|
rdelta = age(self.dob, self.subject_screening.report_datetime.date())
|
|
77
77
|
except AgeValueError as e:
|
|
78
|
-
self.raise_validation_error(str(e), INVALID_ERROR)
|
|
78
|
+
self.raise_validation_error(str(e), INVALID_ERROR, exc=e)
|
|
79
79
|
return rdelta.years
|
|
80
80
|
|
|
81
81
|
def validate_age(self) -> None:
|
|
@@ -8,7 +8,7 @@ from django import forms
|
|
|
8
8
|
|
|
9
9
|
from edc_constants.constants import NO, YES
|
|
10
10
|
from edc_model_form.utils import get_field_or_raise
|
|
11
|
-
from edc_utils import AgeValueError, age, formatted_age
|
|
11
|
+
from edc_utils.age import AgeValueError, age, formatted_age
|
|
12
12
|
|
|
13
13
|
from ...exceptions import ConsentDefinitionValidityPeriodError
|
|
14
14
|
from ...site_consents import site_consents
|
|
@@ -5,8 +5,7 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
from django import forms
|
|
6
6
|
|
|
7
7
|
from edc_sites import site_sites
|
|
8
|
-
from edc_utils import formatted_date
|
|
9
|
-
from edc_utils.date import to_utc
|
|
8
|
+
from edc_utils import formatted_date, to_local
|
|
10
9
|
|
|
11
10
|
from ..consent_definition import ConsentDefinition
|
|
12
11
|
from ..exceptions import (
|
|
@@ -36,7 +35,7 @@ class RequiresConsentModelFormMixin:
|
|
|
36
35
|
return cleaned_data
|
|
37
36
|
|
|
38
37
|
def validate_against_dob(self, consent_obj):
|
|
39
|
-
if consent_obj and
|
|
38
|
+
if consent_obj and to_local(self.report_datetime).date() < consent_obj.dob:
|
|
40
39
|
dte_str = formatted_date(consent_obj.dob)
|
|
41
40
|
raise forms.ValidationError(f"Report datetime cannot be before DOB. Got {dte_str}")
|
|
42
41
|
|
|
@@ -53,7 +52,7 @@ class RequiresConsentModelFormMixin:
|
|
|
53
52
|
site_id=self.site.id,
|
|
54
53
|
)
|
|
55
54
|
except (NotConsentedError, ConsentDefinitionNotConfiguredForUpdate) as e:
|
|
56
|
-
raise forms.ValidationError({"__all__": str(e)})
|
|
55
|
+
raise forms.ValidationError({"__all__": str(e)}) from e
|
|
57
56
|
return consent_obj
|
|
58
57
|
|
|
59
58
|
@property
|
|
@@ -66,5 +65,5 @@ class RequiresConsentModelFormMixin:
|
|
|
66
65
|
report_datetime=self.report_datetime,
|
|
67
66
|
)
|
|
68
67
|
except ConsentDefinitionDoesNotExist as e:
|
|
69
|
-
raise forms.ValidationError(e)
|
|
68
|
+
raise forms.ValidationError(e) from e
|
|
70
69
|
return cdef
|
edc_consent/site_consents.py
CHANGED
|
@@ -10,7 +10,7 @@ from django.core.management.color import color_style
|
|
|
10
10
|
from django.utils.module_loading import import_module, module_has_submodule
|
|
11
11
|
|
|
12
12
|
from edc_sites.site import sites as site_sites
|
|
13
|
-
from edc_utils import ceil_secs, floor_secs, formatted_date,
|
|
13
|
+
from edc_utils import ceil_secs, floor_secs, formatted_date, to_local
|
|
14
14
|
|
|
15
15
|
from .exceptions import (
|
|
16
16
|
AlreadyRegistered,
|
|
@@ -154,9 +154,9 @@ class SiteConsents:
|
|
|
154
154
|
raise_if_not_consented=raise_if_not_consented,
|
|
155
155
|
)
|
|
156
156
|
|
|
157
|
-
if consent_obj and
|
|
157
|
+
if consent_obj and report_datetime < consent_obj.consent_datetime:
|
|
158
158
|
if not consent_definition.updates:
|
|
159
|
-
dte = formatted_date(report_datetime)
|
|
159
|
+
dte = formatted_date(to_local(report_datetime))
|
|
160
160
|
raise ConsentDefinitionNotConfiguredForUpdate(
|
|
161
161
|
f"Consent not configured to update any previous versions. "
|
|
162
162
|
f"Got '{consent_definition.version}'. "
|
|
@@ -164,12 +164,12 @@ class SiteConsents:
|
|
|
164
164
|
f"'{consent_definition.version}' "
|
|
165
165
|
f"of consent on or after report_datetime='{dte}'?"
|
|
166
166
|
)
|
|
167
|
-
if consent_definition.start <=
|
|
167
|
+
if consent_definition.start <= report_datetime <= consent_definition.end:
|
|
168
168
|
# ensures the higher version is returned if there is overlap
|
|
169
169
|
pass
|
|
170
170
|
elif (
|
|
171
171
|
consent_definition.updates.start
|
|
172
|
-
<=
|
|
172
|
+
<= report_datetime
|
|
173
173
|
<= consent_definition.updates.end
|
|
174
174
|
):
|
|
175
175
|
# return the previous version consent (updated_by)
|
|
@@ -245,19 +245,19 @@ class SiteConsents:
|
|
|
245
245
|
|
|
246
246
|
# filter cdefs to try to get just one.
|
|
247
247
|
# by model, report_datetime, version, site
|
|
248
|
-
cdefs,
|
|
249
|
-
|
|
248
|
+
cdefs, error_messages = self._filter_cdefs_by_model_or_raise(
|
|
249
|
+
model, cdefs, error_messages
|
|
250
|
+
)
|
|
251
|
+
cdefs, error_messages = self._filter_cdefs_by_report_datetime_or_raise(
|
|
250
252
|
report_datetime, cdefs, error_messages
|
|
251
253
|
)
|
|
252
|
-
cdefs,
|
|
254
|
+
cdefs, error_messages = self._filter_cdefs_by_version_or_raise(
|
|
253
255
|
version, cdefs, error_messages
|
|
254
256
|
)
|
|
255
257
|
cdefs = self.filter_cdefs_by_site_or_raise(site, cdefs, error_messages)
|
|
256
|
-
|
|
257
|
-
cdefs, error_msg = self._filter_cdefs_by_screening_model_or_raise(
|
|
258
|
+
cdefs, _ = self._filter_cdefs_by_screening_model_or_raise(
|
|
258
259
|
screening_model, cdefs, error_messages
|
|
259
260
|
)
|
|
260
|
-
|
|
261
261
|
# apply additional criteria
|
|
262
262
|
for k, v in kwargs.items():
|
|
263
263
|
if v is not None:
|
|
@@ -324,17 +324,16 @@ class SiteConsents:
|
|
|
324
324
|
cdefs = [
|
|
325
325
|
cdef
|
|
326
326
|
for cdef in cdefs
|
|
327
|
-
if floor_secs(cdef.start) <=
|
|
327
|
+
if floor_secs(cdef.start) <= report_datetime <= ceil_secs(cdef.end)
|
|
328
328
|
]
|
|
329
|
+
date_string = formatted_date(to_local(report_datetime))
|
|
329
330
|
if not cdefs:
|
|
330
|
-
date_string = formatted_date(report_datetime)
|
|
331
331
|
using_msg = "Using " + " and ".join(errror_messages)
|
|
332
332
|
raise ConsentDefinitionDoesNotExist(
|
|
333
333
|
"Date does not fall within the validity period of any "
|
|
334
334
|
f"consent definition. Got {date_string}. {using_msg}. "
|
|
335
335
|
f"Possible consent definitions are: {consent_definitions}. "
|
|
336
336
|
)
|
|
337
|
-
date_string = formatted_date(report_datetime)
|
|
338
337
|
errror_messages.append(f"report_datetime={date_string}")
|
|
339
338
|
return cdefs, errror_messages
|
|
340
339
|
|
edc_crf/crf_form_validator.py
CHANGED
|
@@ -10,7 +10,7 @@ from edc_appointment.form_validator_mixins import WindowPeriodFormValidatorMixin
|
|
|
10
10
|
from edc_form_validators import INVALID_ERROR, FormValidator
|
|
11
11
|
from edc_registration import get_registered_subject_model_cls
|
|
12
12
|
from edc_sites.form_validator_mixin import SiteFormValidatorMixin
|
|
13
|
-
from edc_utils import floor_secs, formatted_datetime
|
|
13
|
+
from edc_utils import floor_secs, formatted_datetime
|
|
14
14
|
from edc_utils.date import to_local
|
|
15
15
|
from edc_visit_tracking.modelform_mixins import get_related_visit
|
|
16
16
|
|
|
@@ -65,13 +65,8 @@ class CrfFormValidator(
|
|
|
65
65
|
# falls within appointment's window period
|
|
66
66
|
self.validate_crf_datetime_in_window_period()
|
|
67
67
|
# not before consent date
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
report_datetime=report_datetime,
|
|
71
|
-
site=self.site,
|
|
72
|
-
fldname="report_datetime",
|
|
73
|
-
)
|
|
74
|
-
if floor_secs(report_datetime) < floor_secs(consent_datetime):
|
|
68
|
+
consent_datetime = self.get_consent_datetime_or_raise()
|
|
69
|
+
if floor_secs(self.report_datetime) < floor_secs(consent_datetime):
|
|
75
70
|
msg = _("Invalid. Cannot be before date of consent. Participant consented on")
|
|
76
71
|
formatted_date = formatted_datetime(to_local(consent_datetime))
|
|
77
72
|
err_message = format_lazy(
|
|
@@ -83,24 +78,25 @@ class CrfFormValidator(
|
|
|
83
78
|
)
|
|
84
79
|
|
|
85
80
|
def validate_crf_datetime_in_window_period(self) -> None:
|
|
86
|
-
|
|
81
|
+
self.datetime_in_window_or_raise(
|
|
87
82
|
self.appointment,
|
|
88
83
|
self.report_datetime,
|
|
89
84
|
self.report_datetime_field_attr,
|
|
90
|
-
|
|
91
|
-
self.datetime_in_window_or_raise(*opts)
|
|
85
|
+
)
|
|
92
86
|
|
|
93
87
|
@property
|
|
94
88
|
def appointment(self) -> Appointment:
|
|
95
89
|
try:
|
|
96
|
-
|
|
97
|
-
except AttributeError:
|
|
90
|
+
appointment = self.related_visit.appointment
|
|
91
|
+
except AttributeError as e:
|
|
98
92
|
msg = _("is required")
|
|
99
93
|
verbose_name = self.related_visit._meta.verbose_name
|
|
100
94
|
self.raise_validation_error(
|
|
101
95
|
format_lazy("{verbose_name} {msg}.", msg=msg, verbose_name=verbose_name),
|
|
102
96
|
INVALID_ERROR,
|
|
97
|
+
exc=e,
|
|
103
98
|
)
|
|
99
|
+
return appointment
|
|
104
100
|
|
|
105
101
|
@property
|
|
106
102
|
def related_visit_model_attr(self) -> str:
|
|
@@ -127,19 +123,17 @@ class CrfFormValidator(
|
|
|
127
123
|
self, related_visit_model_attr=self.related_visit_model_attr
|
|
128
124
|
)
|
|
129
125
|
except AttributeError as e:
|
|
130
|
-
raise CrfFormValidatorError(f"{e}. See {self.__class__}")
|
|
126
|
+
raise CrfFormValidatorError(f"{e}. See {self.__class__}") from e
|
|
131
127
|
|
|
132
128
|
def validate_datetime_against_report_datetime(self, field: str) -> None:
|
|
133
129
|
"""Datetime cannot be after report_datetime"""
|
|
134
130
|
if (
|
|
135
131
|
self.cleaned_data.get(field)
|
|
136
132
|
and floor_secs(self.report_datetime)
|
|
137
|
-
and floor_secs(
|
|
138
|
-
> floor_secs(self.report_datetime)
|
|
133
|
+
and floor_secs(self.cleaned_data.get(field)) > floor_secs(self.report_datetime)
|
|
139
134
|
):
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
)
|
|
135
|
+
msg = _("Cannot be after report datetime")
|
|
136
|
+
self.raise_validation_error({field: msg}, INVALID_ERROR)
|
|
143
137
|
|
|
144
138
|
def validate_date_against_report_datetime(self, field: str) -> None:
|
|
145
139
|
"""Date cannot be after report_datetime"""
|
|
@@ -7,7 +7,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
|
|
7
7
|
|
|
8
8
|
from edc_consent.form_validators import ConsentDefinitionFormValidatorMixin
|
|
9
9
|
from edc_form_validators import ReportDatetimeFormValidatorMixin
|
|
10
|
-
from edc_utils import age,
|
|
10
|
+
from edc_utils import age, to_local
|
|
11
11
|
from edc_visit_schedule.schedule import Schedule
|
|
12
12
|
from edc_visit_schedule.visit_schedule import VisitSchedule
|
|
13
13
|
from edc_visit_tracking.modelform_mixins import get_related_visit
|
|
@@ -43,7 +43,7 @@ class BaseFormValidatorMixin(
|
|
|
43
43
|
@property
|
|
44
44
|
def age_in_years(self) -> int | None:
|
|
45
45
|
if self.report_datetime and self.subject_consent.dob:
|
|
46
|
-
return age(self.subject_consent.dob,
|
|
46
|
+
return age(self.subject_consent.dob, to_local(self.report_datetime)).years
|
|
47
47
|
return None
|
|
48
48
|
|
|
49
49
|
|
|
@@ -58,21 +58,6 @@ class CrfFormValidatorMixin(BaseFormValidatorMixin):
|
|
|
58
58
|
"""Always returns the subject_identifier from related_visit"""
|
|
59
59
|
return self.related_visit.subject_identifier
|
|
60
60
|
|
|
61
|
-
@property
|
|
62
|
-
def report_datetime(self) -> datetime:
|
|
63
|
-
"""Returns report_datetime or raises.
|
|
64
|
-
|
|
65
|
-
Report datetime is always a required field on a CRF model,
|
|
66
|
-
Django will raise a field ValidationError before getting
|
|
67
|
-
here if report_datetime is None.
|
|
68
|
-
"""
|
|
69
|
-
report_datetime = None
|
|
70
|
-
if self.report_datetime_field_attr in self.cleaned_data:
|
|
71
|
-
report_datetime = self.cleaned_data.get(self.report_datetime_field_attr)
|
|
72
|
-
elif self.instance:
|
|
73
|
-
report_datetime = self.instance.report_datetime
|
|
74
|
-
return report_datetime
|
|
75
|
-
|
|
76
61
|
@property
|
|
77
62
|
def related_visit_model_attr(self) -> str:
|
|
78
63
|
return self.model.related_visit_model_attr()
|
|
@@ -9,7 +9,7 @@ from django.utils.html import format_html
|
|
|
9
9
|
from django.utils.safestring import mark_safe
|
|
10
10
|
|
|
11
11
|
from edc_constants.constants import CLOSED, OPEN
|
|
12
|
-
from edc_utils import formatted_datetime
|
|
12
|
+
from edc_utils.text import formatted_datetime
|
|
13
13
|
|
|
14
14
|
from ..models import QueryRule
|
|
15
15
|
from ..rule import update_query_rules
|
|
@@ -24,7 +24,7 @@ from edc_constants.constants import (
|
|
|
24
24
|
from edc_model_admin.dashboard import ModelAdminSubjectDashboardMixin
|
|
25
25
|
from edc_model_admin.history import SimpleHistoryAdmin
|
|
26
26
|
from edc_sites.admin import SiteModelAdminMixin
|
|
27
|
-
from edc_utils import formatted_datetime
|
|
27
|
+
from edc_utils.text import formatted_datetime
|
|
28
28
|
|
|
29
29
|
from ..admin_site import edc_data_manager_admin
|
|
30
30
|
from ..auth_objects import DATA_MANAGER
|
|
@@ -5,7 +5,7 @@ from django.conf import settings
|
|
|
5
5
|
|
|
6
6
|
from edc_dx_review.utils import raise_if_clinical_review_does_not_exist
|
|
7
7
|
from edc_form_validators import INVALID_ERROR, FormValidator
|
|
8
|
-
from edc_utils import convert_php_dateformat
|
|
8
|
+
from edc_utils.text import convert_php_dateformat
|
|
9
9
|
from edc_visit_schedule.utils import raise_if_baseline
|
|
10
10
|
|
|
11
11
|
from ..diagnoses import ClinicalReviewBaselineRequired, Diagnoses, InitialReviewRequired
|
edc_dx_review/medical_date.py
CHANGED
|
@@ -7,7 +7,7 @@ from django.core.exceptions import ValidationError
|
|
|
7
7
|
|
|
8
8
|
from edc_constants.constants import GT, GTE, LT, LTE
|
|
9
9
|
from edc_model import estimated_date_from_ago
|
|
10
|
-
from edc_utils import convert_php_dateformat
|
|
10
|
+
from edc_utils.text import convert_php_dateformat
|
|
11
11
|
|
|
12
12
|
FAILED_COMPARISON = "FAILED_COMPARISON"
|
|
13
13
|
BEFORE_AFTER_BOTH_TRUE = "BEFORE_AFTER_BOTH_TRUE"
|
edc_egfr/egfr.py
CHANGED
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from datetime import date, datetime
|
|
4
4
|
from decimal import Decimal
|
|
5
5
|
from typing import TYPE_CHECKING, Any
|
|
6
|
-
from zoneinfo import ZoneInfo
|
|
7
6
|
|
|
8
7
|
from dateutil.relativedelta import relativedelta
|
|
9
8
|
from django.core.exceptions import ObjectDoesNotExist
|
|
@@ -89,13 +88,10 @@ class Egfr:
|
|
|
89
88
|
if self.dob:
|
|
90
89
|
self.age_in_years = age(
|
|
91
90
|
born=self.dob,
|
|
92
|
-
reference_dt=self.report_datetime
|
|
91
|
+
reference_dt=self.report_datetime,
|
|
93
92
|
).years
|
|
94
93
|
elif not self.dob and self.age_in_years:
|
|
95
|
-
self.dob = (
|
|
96
|
-
self.report_datetime.astimezone(ZoneInfo("UTC"))
|
|
97
|
-
- relativedelta(years=self.age_in_years)
|
|
98
|
-
).date()
|
|
94
|
+
self.dob = (self.report_datetime - relativedelta(years=self.age_in_years)).date()
|
|
99
95
|
else:
|
|
100
96
|
raise EgfrError("Expected `age_in_years` or `dob`. Got None for both.")
|
|
101
97
|
|
|
@@ -105,7 +101,7 @@ class Egfr:
|
|
|
105
101
|
self.percent_drop_threshold = calling_crf.percent_drop_threshold
|
|
106
102
|
self.related_visit = calling_crf.related_visit
|
|
107
103
|
self.report_datetime = calling_crf.report_datetime
|
|
108
|
-
self.assay_date = calling_crf.assay_datetime.
|
|
104
|
+
self.assay_date = calling_crf.assay_datetime.date()
|
|
109
105
|
else:
|
|
110
106
|
self.creatinine_units = creatinine_units
|
|
111
107
|
self.creatinine_value = creatinine_value
|
|
@@ -113,7 +109,7 @@ class Egfr:
|
|
|
113
109
|
self.related_visit = related_visit
|
|
114
110
|
self.report_datetime = report_datetime
|
|
115
111
|
if assay_datetime:
|
|
116
|
-
self.assay_date = assay_datetime.
|
|
112
|
+
self.assay_date = assay_datetime.date()
|
|
117
113
|
|
|
118
114
|
if self.percent_drop_threshold is not None and self.percent_drop_threshold < 1.0:
|
|
119
115
|
raise EgfrError(
|
|
@@ -28,7 +28,7 @@ class EgfrCkdEpiFormValidatorMixin:
|
|
|
28
28
|
try:
|
|
29
29
|
value = EgfrCkdEpi(**opts).value
|
|
30
30
|
except (EgfrCalculatorError, CalculatorError, ConversionNotHandled) as e:
|
|
31
|
-
raise forms.ValidationError(e)
|
|
31
|
+
raise forms.ValidationError(e) from e
|
|
32
32
|
return value
|
|
33
33
|
|
|
34
34
|
|
|
@@ -52,5 +52,5 @@ class EgfrCockcroftGaultFormValidatorMixin:
|
|
|
52
52
|
try:
|
|
53
53
|
value = EgfrCockcroftGault(**opts).value
|
|
54
54
|
except (EgfrCalculatorError, CalculatorError, ConversionNotHandled) as e:
|
|
55
|
-
self.raise_validation_error({"__all__": str(e)}, INVALID_ERROR)
|
|
55
|
+
self.raise_validation_error({"__all__": str(e)}, INVALID_ERROR, exc=e)
|
|
56
56
|
return value
|