clinicedc 2.0.33__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.

Files changed (165) hide show
  1. {clinicedc-2.0.33.dist-info → clinicedc-2.0.35.dist-info}/METADATA +1 -1
  2. {clinicedc-2.0.33.dist-info → clinicedc-2.0.35.dist-info}/RECORD +155 -165
  3. {clinicedc-2.0.33.dist-info → clinicedc-2.0.35.dist-info}/WHEEL +1 -1
  4. edc_action_item/templatetags/action_item_extras.py +1 -1
  5. edc_adverse_event/form_validator_mixins/death_report_form_validator.py +1 -1
  6. edc_adverse_event/form_validator_mixins/requires_death_report_form_validator_mixin.py +5 -3
  7. edc_adverse_event/form_validators/death_report_tmg.py +2 -2
  8. edc_adverse_event/modeladmin_mixins/ae_tmg_admin_mixin.py +1 -1
  9. edc_adverse_event/modeladmin_mixins/utils.py +1 -1
  10. edc_adverse_event/utils.py +1 -1
  11. edc_appointment/admin/appointment_admin.py +24 -24
  12. edc_appointment/admin/list_filters.py +5 -5
  13. edc_appointment/appointment_reason_updater.py +6 -5
  14. edc_appointment/appointment_status_updater.py +2 -2
  15. edc_appointment/context_processors.py +1 -2
  16. edc_appointment/creators/appointment_creator.py +9 -8
  17. edc_appointment/creators/unscheduled_appointment_creator.py +31 -24
  18. edc_appointment/creators/utils.py +35 -37
  19. edc_appointment/exceptions.py +3 -3
  20. edc_appointment/form_runners.py +2 -2
  21. edc_appointment/form_validator_mixins/next_appointment_crf_form_validator_mixin.py +5 -5
  22. edc_appointment/form_validator_mixins/window_period_form_validator_mixin.py +7 -7
  23. edc_appointment/form_validators/appointment_form_validator.py +16 -23
  24. edc_appointment/management/commands/close_appointments.py +3 -3
  25. edc_appointment/management/commands/reset_visit_code_sequences.py +1 -1
  26. edc_appointment/management/commands/update_appointment_status.py +2 -2
  27. edc_appointment/management/commands/update_skipped_appointments.py +7 -7
  28. edc_appointment/managers.py +6 -7
  29. edc_appointment/model_mixins/appointment_model_mixin.py +3 -4
  30. edc_appointment/modeladmin_mixins/next_appointment_crf_modeladmin_mixin.py +2 -2
  31. edc_appointment/modelform_mixins/next_appointment_crf_modelform_mixins.py +25 -24
  32. edc_appointment/models/signals.py +1 -1
  33. edc_appointment/skip_appointments.py +10 -29
  34. edc_appointment/utils.py +43 -47
  35. edc_appointment/view_mixins/appointment_view_mixin.py +11 -13
  36. edc_appointment/views/unscheduled_appointment_view.py +5 -6
  37. edc_consent/consent_definition.py +5 -13
  38. edc_consent/consent_definition_extension.py +0 -2
  39. edc_consent/form_validators/consent_definition_form_validator_mixin.py +26 -30
  40. edc_consent/form_validators/subject_consent_form_validator.py +3 -3
  41. edc_consent/modelform_mixins/consent_modelform_mixin/consent_modelform_validation_mixin.py +1 -1
  42. edc_consent/modelform_mixins/requires_consent_modelform_mixin.py +4 -5
  43. edc_consent/site_consents.py +13 -14
  44. edc_crf/crf_form_validator.py +13 -19
  45. edc_crf/crf_form_validator_mixins.py +2 -17
  46. edc_data_manager/admin/actions.py +1 -1
  47. edc_data_manager/admin/data_query_admin.py +1 -1
  48. edc_dx/form_validators/result_form_validator_mixin.py +1 -1
  49. edc_dx_review/medical_date.py +1 -1
  50. edc_egfr/egfr.py +4 -8
  51. edc_egfr/form_validator_mixins/egfr_form_validator_mixins.py +2 -2
  52. edc_facility/facility.py +7 -11
  53. edc_facility/holidays.py +3 -3
  54. edc_facility/models/holiday.py +1 -1
  55. edc_form_runners/form_runner.py +15 -16
  56. edc_form_validators/base_form_validator.py +5 -1
  57. edc_form_validators/date_range_validator.py +49 -61
  58. edc_form_validators/date_validator.py +1 -1
  59. edc_form_validators/extra_mixins/study_day_form_validator.py +1 -1
  60. edc_lab/form_validators/crf_requisition_form_validator_mixin.py +21 -15
  61. edc_lab/form_validators/requisition_form_validator_mixin.py +3 -3
  62. edc_lab_results/form_validator_mixins/blood_results_form_validator_mixin.py +1 -1
  63. edc_ltfu/modelform_mixins.py +1 -1
  64. edc_metadata/metadata/metadata.py +20 -7
  65. edc_metadata/metadata_rules/logic.py +5 -4
  66. edc_metadata/metadata_rules/predicate.py +22 -24
  67. edc_model_form/mixins/report_datetime_modelform_mixin.py +1 -5
  68. edc_offstudy/model_mixins/offstudy_model_mixin.py +1 -1
  69. edc_offstudy/modelform_mixins/crf/offstudy_crf_modelform_mixin.py +2 -2
  70. edc_offstudy/utils.py +4 -4
  71. edc_pdf_reports/crf_pdf_report.py +2 -1
  72. edc_pdutils/helper.py +3 -3
  73. edc_pdutils/utils/convert_dates_from_model.py +4 -3
  74. edc_pharmacy/admin/actions/confirm_stock.py +3 -3
  75. edc_pharmacy/admin/actions/delete_items_for_stock_request.py +4 -3
  76. edc_pharmacy/admin/actions/delete_order_items.py +7 -3
  77. edc_pharmacy/admin/actions/delete_receive_items.py +5 -3
  78. edc_pharmacy/admin/actions/process_repack_request.py +7 -11
  79. edc_pharmacy/admin/autocomplete_admin.py +1 -1
  80. edc_pharmacy/admin/list_filters.py +48 -46
  81. edc_pharmacy/admin/medication/assignment_admin.py +4 -4
  82. edc_pharmacy/admin/medication/dosage_guideline_admin.py +3 -3
  83. edc_pharmacy/admin/medication/formulation_admin.py +5 -5
  84. edc_pharmacy/admin/medication/medication_admin.py +3 -3
  85. edc_pharmacy/admin/prescription/rx_admin.py +2 -2
  86. edc_pharmacy/admin/prescription/rx_refill_admin.py +11 -17
  87. edc_pharmacy/admin/remove_fields_for_blinded_users.py +1 -1
  88. edc_pharmacy/admin/reports/stock_availability_admin.py +12 -8
  89. edc_pharmacy/admin/stock/allocation_admin.py +13 -17
  90. edc_pharmacy/admin/stock/allocation_proxy_admin.py +1 -2
  91. edc_pharmacy/admin/stock/confirmation_admin.py +4 -8
  92. edc_pharmacy/admin/stock/confirmation_at_site_item_admin.py +1 -1
  93. edc_pharmacy/admin/stock/container_admin.py +3 -3
  94. edc_pharmacy/admin/stock/location_admin.py +3 -6
  95. edc_pharmacy/admin/stock/lot_admin.py +9 -12
  96. edc_pharmacy/admin/stock/order_admin.py +2 -5
  97. edc_pharmacy/admin/stock/order_item_admin.py +14 -22
  98. edc_pharmacy/admin/stock/product_admin.py +6 -9
  99. edc_pharmacy/admin/stock/receive_admin.py +2 -2
  100. edc_pharmacy/admin/stock/receive_item_admin.py +3 -3
  101. edc_pharmacy/admin/stock/repack_request_admin.py +7 -10
  102. edc_pharmacy/admin/stock/stock_adjustment_admin.py +1 -1
  103. edc_pharmacy/admin/stock/stock_admin.py +17 -28
  104. edc_pharmacy/admin/stock/stock_proxy_admin.py +3 -4
  105. edc_pharmacy/admin/stock/stock_request_admin.py +6 -10
  106. edc_pharmacy/admin/stock/stock_request_item_admin.py +9 -18
  107. edc_pharmacy/admin/stock/stock_transfer_admin.py +5 -5
  108. edc_pharmacy/admin/stock/stock_transfer_item_admin.py +3 -3
  109. edc_pharmacy/admin/stock/storage_bin_admin.py +1 -1
  110. edc_pharmacy/admin/stock/storage_bin_item_admin.py +2 -2
  111. edc_pharmacy/form_validators/crf/study_medication_form_validator.py +27 -27
  112. edc_pharmacy/forms/stock/confirmation_form.py +2 -2
  113. edc_pharmacy/forms/stock/dispense_form.py +2 -2
  114. edc_pharmacy/forms/stock/lot_form.py +6 -6
  115. edc_pharmacy/forms/stock/order_form.py +4 -6
  116. edc_pharmacy/forms/stock/product_form.py +2 -3
  117. edc_pharmacy/forms/stock/receive_form.py +4 -4
  118. edc_pharmacy/forms/stock/receive_item_form.py +7 -5
  119. edc_pharmacy/forms/stock/repack_request_form.py +10 -8
  120. edc_pharmacy/forms/stock/stock_form.py +2 -3
  121. edc_pharmacy/forms/stock/stock_request_form.py +10 -8
  122. edc_pharmacy/forms/stock/stock_request_item_form.py +6 -5
  123. edc_pharmacy/forms/stock/stock_transfer_form.py +16 -14
  124. edc_pharmacy/forms/stock/supplier_form.py +2 -2
  125. edc_pharmacy/labels/label_data.py +2 -3
  126. edc_pharmacy/model_mixins/study_medication_crf_model_mixin.py +1 -1
  127. edc_pharmacy/models/medication/dosage_guideline.py +3 -3
  128. edc_pharmacy/models/model_mixins.py +12 -10
  129. edc_pharmacy/models/prescription/rx.py +1 -1
  130. edc_pharmacy/models/prescription/rx_refill.py +1 -1
  131. edc_pharmacy/models/signals.py +2 -3
  132. edc_pharmacy/models/storage/utils.py +4 -4
  133. edc_pharmacy/pdf_reports/manifest_pdf_report.py +3 -6
  134. edc_pharmacy/pdf_reports/stock_pdf_report.py +1 -3
  135. edc_pharmacy/refill/refill_creator.py +3 -4
  136. edc_protocol/validators.py +10 -16
  137. edc_reportable/forms/reportables_form_validator_mixin.py +1 -1
  138. edc_screening/form_validator_mixins.py +2 -1
  139. edc_transfer/form_validators.py +1 -1
  140. edc_transfer/model_mixins.py +1 -1
  141. edc_utils/age.py +17 -15
  142. edc_utils/date.py +7 -7
  143. edc_utils/text.py +7 -6
  144. edc_visit_schedule/exceptions.py +8 -0
  145. edc_visit_schedule/model_mixins/off_schedule_model_mixin.py +1 -1
  146. edc_visit_schedule/model_mixins/on_schedule_model_mixin.py +1 -1
  147. edc_visit_schedule/modelform_mixins/off_schedule_modelform_mixin.py +1 -3
  148. edc_visit_schedule/schedule/visit_collection.py +1 -1
  149. edc_visit_schedule/schedule/window.py +0 -2
  150. edc_visit_schedule/subject_schedule.py +1 -1
  151. edc_visit_schedule/utils.py +4 -4
  152. edc_visit_schedule/visit/visit.py +9 -3
  153. edc_visit_schedule/visit/window_period.py +1 -1
  154. edc_visit_tracking/form_validators/visit_form_validator.py +1 -1
  155. edc_metadata/metadata_wrappers/__init__.py +0 -5
  156. edc_metadata/metadata_wrappers/crf_metadata_wrapper.py +0 -5
  157. edc_metadata/metadata_wrappers/crf_metadata_wrappers.py +0 -8
  158. edc_metadata/metadata_wrappers/metadata_wrapper.py +0 -74
  159. edc_metadata/metadata_wrappers/metadata_wrappers.py +0 -33
  160. edc_metadata/metadata_wrappers/requisition_metadata_wrapper.py +0 -26
  161. edc_metadata/metadata_wrappers/requisition_metadata_wrappers.py +0 -10
  162. edc_pharmacy/management/__init__.py +0 -0
  163. edc_pharmacy/management/commands/__init__.py +0 -0
  164. edc_pharmacy/management/commands/update_initial_pharmacy_data.py +0 -10
  165. {clinicedc-2.0.33.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 = True if django_apps.app_configs.get("edc_call_manager") else False
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
- if 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
- try:
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
- mark_safe(creator.appointment), # nosec B308, B703
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
- mark_safe(creator.appointment), # nosec B308, B703
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 = None,
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
- from django.contrib.sites.models import Site
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
- report_datetime: datetime = None,
33
- site: Site = None,
34
- fldname: str = None,
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
- report_datetime=report_datetime,
40
- site=site,
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
- report_datetime: datetime = None,
49
- site: Site = None,
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
- ) -> datetime:
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({fldname: str(e)}, error_code)
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 = None,
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
- self.raise_validation_error(
93
- {
94
- fldname: _(
95
- "Date does not fall within a valid consent period. "
96
- "Possible consents are '%(possible_consents)s'. "
97
- % {"possible_consents": possible_consents}
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, to_utc
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 = to_utc(self.cleaned_data.get("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 to_utc(self.report_datetime).date() < consent_obj.dob:
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
@@ -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, to_utc
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 to_utc(report_datetime) < consent_obj.consent_datetime:
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 <= to_utc(report_datetime) <= consent_definition.end:
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
- <= to_utc(report_datetime)
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, error_msg = self._filter_cdefs_by_model_or_raise(model, cdefs, error_messages)
249
- cdefs, error_msg = self._filter_cdefs_by_report_datetime_or_raise(
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, error_msg = self._filter_cdefs_by_version_or_raise(
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) <= to_utc(report_datetime) <= ceil_secs(cdef.end)
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
 
@@ -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, to_utc
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
- report_datetime = to_utc(self.report_datetime)
69
- consent_datetime = self.get_consent_datetime_or_raise(
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
- opts = [
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
- return self.related_visit.appointment
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(to_utc(self.cleaned_data.get(field)))
138
- > floor_secs(self.report_datetime)
133
+ and floor_secs(self.cleaned_data.get(field)) > floor_secs(self.report_datetime)
139
134
  ):
140
- self.raise_validation_error(
141
- {field: _("Cannot be after report datetime")}, INVALID_ERROR
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, to_utc
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, to_utc(self.report_datetime)).years
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
@@ -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.astimezone(ZoneInfo("UTC")),
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.astimezone(ZoneInfo("UTC")).date()
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.astimezone(ZoneInfo("UTC")).date()
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