clinicedc 2.0.14__py3-none-any.whl → 2.0.16__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.14.dist-info → clinicedc-2.0.16.dist-info}/METADATA +1 -1
- {clinicedc-2.0.14.dist-info → clinicedc-2.0.16.dist-info}/RECORD +64 -64
- edc_analytics/custom_tables/age.py +1 -0
- edc_analytics/custom_tables/art.py +1 -0
- edc_analytics/custom_tables/bmi.py +1 -0
- edc_analytics/custom_tables/bp.py +1 -0
- edc_analytics/custom_tables/fasting.py +1 -0
- edc_analytics/custom_tables/fbg.py +1 -0
- edc_analytics/custom_tables/fbg_ogtt.py +1 -0
- edc_analytics/custom_tables/hba1c.py +1 -0
- edc_analytics/custom_tables/ogtt.py +1 -0
- edc_analytics/custom_tables/waist.py +1 -0
- edc_analytics/row/row_definitions.py +1 -1
- edc_analytics/row/row_statistics_with_gender.py +1 -0
- edc_analytics/stata/get_stata_labels_from_model.py +5 -6
- edc_analytics/table.py +1 -0
- edc_appointment/creators/appointments_creator.py +5 -9
- edc_dx/diagnoses.py +2 -2
- edc_dx/form_validators/diagnosis_form_validator_mixin.py +1 -0
- edc_dx/form_validators/result_form_validator_mixin.py +14 -16
- edc_dx_review/form_mixins/clinical_review_baseline_required_form_mixin.py +1 -0
- edc_dx_review/medical_date.py +2 -1
- edc_dx_review/model_mixins/clinical_review_baseline_model_mixin.py +1 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_chol_model_mixin.py +1 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_dm_model_mixin.py +1 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_hiv_model_mixin.py +1 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_htn_model_mixin.py +1 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_model_mixin.py +1 -0
- edc_dx_review/model_mixins/dx_location_model_mixin.py +1 -0
- edc_dx_review/model_mixins/factory/baseline_review_model_mixin_factory.py +1 -0
- edc_dx_review/model_mixins/factory/calculate_date.py +1 -0
- edc_dx_review/model_mixins/factory/dx_initial_review_model_mixin_factory.py +2 -1
- edc_dx_review/model_mixins/factory/followup_review_model_mixin_factory.py +1 -0
- edc_dx_review/model_mixins/factory/rx_initial_review_model_mixin_factory.py +1 -0
- edc_dx_review/model_mixins/followup_review/followup_review_model_mixin.py +1 -0
- edc_dx_review/model_mixins/followup_review/hiv_followup_review_model_mixin.py +1 -0
- edc_dx_review/model_mixins/initial_review/chol_initial_review_model_mixin.py +1 -0
- edc_dx_review/model_mixins/initial_review/hiv_initial_model_mixins.py +1 -0
- edc_dx_review/model_mixins/initial_review/ncd_initial_review_model_mixin.py +1 -0
- edc_dx_review/radio_fields.py +3 -3
- edc_dx_review/utils.py +2 -1
- edc_form_describer/form_describer.py +13 -8
- edc_form_describer/forms_reference.py +24 -25
- edc_form_describer/make_forms_reference.py +10 -12
- edc_form_describer/management/commands/make_forms_reference.py +8 -8
- edc_form_describer/markdown_writer.py +11 -10
- edc_randomization/randomizer.py +2 -5
- edc_randomization/utils.py +10 -0
- edc_visit_schedule/schedule/schedule.py +13 -13
- edc_visit_tracking/action_items.py +1 -2
- edc_visit_tracking/apps.py +1 -1
- edc_visit_tracking/context_processors.py +1 -2
- edc_visit_tracking/crf_date_validator.py +4 -4
- edc_visit_tracking/form_validators/visit_form_validator.py +51 -45
- edc_visit_tracking/form_validators/visit_missed_form_validator.py +6 -1
- edc_visit_tracking/model_mixins/utils.py +1 -1
- edc_visit_tracking/modelform_mixins/crf/visit_tracking_crf_modelform_mixin.py +4 -4
- edc_visit_tracking/modelform_mixins/utils.py +2 -2
- edc_visit_tracking/models/signals.py +1 -1
- edc_visit_tracking/typing_stubs.py +3 -3
- edc_visit_tracking/utils.py +1 -1
- edc_visit_tracking/visit_sequence.py +3 -4
- {clinicedc-2.0.14.dist-info → clinicedc-2.0.16.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.14.dist-info → clinicedc-2.0.16.dist-info}/licenses/LICENSE +0 -0
|
@@ -168,41 +168,44 @@ class VisitFormValidator(WindowPeriodFormValidatorMixin, FormValidator):
|
|
|
168
168
|
"""Asserts the report_datetime is not before the
|
|
169
169
|
appt_datetime.
|
|
170
170
|
"""
|
|
171
|
-
if report_datetime_local := self.report_datetime
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
171
|
+
if (report_datetime_local := self.report_datetime) and (
|
|
172
|
+
report_datetime_local.date() < self.appt_datetime_local.date()
|
|
173
|
+
):
|
|
174
|
+
appt_datetime_str = formatted_datetime(
|
|
175
|
+
self.appt_datetime_local, format_as_date=True
|
|
176
|
+
)
|
|
177
|
+
self.raise_validation_error(
|
|
178
|
+
{
|
|
179
|
+
"report_datetime": (
|
|
180
|
+
"Invalid. Cannot be before appointment date. "
|
|
181
|
+
f"Got appointment date {appt_datetime_str}"
|
|
182
|
+
)
|
|
183
|
+
},
|
|
184
|
+
INVALID_ERROR,
|
|
185
|
+
)
|
|
185
186
|
|
|
186
187
|
def validate_visit_datetime_matches_appt_datetime_at_baseline(self) -> None:
|
|
187
188
|
"""Asserts the report_datetime matches the appt_datetime
|
|
188
189
|
as baseline.
|
|
189
190
|
"""
|
|
190
|
-
if
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
INVALID_ERROR,
|
|
191
|
+
if (
|
|
192
|
+
(is_baseline(instance=self.appointment))
|
|
193
|
+
and (report_datetime_local := self.report_datetime)
|
|
194
|
+
and (report_datetime_local.date() != self.appt_datetime_local.date())
|
|
195
|
+
):
|
|
196
|
+
appt_datetime_str = formatted_datetime(
|
|
197
|
+
self.appt_datetime_local, format_as_date=True
|
|
198
|
+
)
|
|
199
|
+
self.raise_validation_error(
|
|
200
|
+
{
|
|
201
|
+
"report_datetime": (
|
|
202
|
+
"Invalid. Must match appointment date at baseline. "
|
|
203
|
+
"If necessary, change the appointment date and "
|
|
204
|
+
f"try again. Got appointment date {appt_datetime_str}"
|
|
205
205
|
)
|
|
206
|
+
},
|
|
207
|
+
INVALID_ERROR,
|
|
208
|
+
)
|
|
206
209
|
|
|
207
210
|
def validate_visits_completed_in_order(self) -> None:
|
|
208
211
|
"""Asserts visits are completed in order."""
|
|
@@ -210,7 +213,7 @@ class VisitFormValidator(WindowPeriodFormValidatorMixin, FormValidator):
|
|
|
210
213
|
try:
|
|
211
214
|
visit_sequence.enforce_sequence()
|
|
212
215
|
except VisitSequenceError as e:
|
|
213
|
-
raise forms.ValidationError(e, code=INVALID_ERROR)
|
|
216
|
+
raise forms.ValidationError(e, code=INVALID_ERROR) from e
|
|
214
217
|
|
|
215
218
|
def validate_visit_code_sequence_and_reason(self) -> None:
|
|
216
219
|
"""Asserts the `reason` makes sense relative to the
|
|
@@ -234,7 +237,7 @@ class VisitFormValidator(WindowPeriodFormValidatorMixin, FormValidator):
|
|
|
234
237
|
and EDC_VISIT_TRACKING_ALLOW_MISSED_UNSCHEDULED is False
|
|
235
238
|
):
|
|
236
239
|
raise forms.ValidationError(
|
|
237
|
-
{"reason":
|
|
240
|
+
{"reason": "Invalid. This is an unscheduled visit. See appointment."},
|
|
238
241
|
code=INVALID_ERROR,
|
|
239
242
|
)
|
|
240
243
|
# raise if CRF metadata exist
|
|
@@ -267,23 +270,25 @@ class VisitFormValidator(WindowPeriodFormValidatorMixin, FormValidator):
|
|
|
267
270
|
field_required="reason_missed_other",
|
|
268
271
|
)
|
|
269
272
|
|
|
270
|
-
if
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
273
|
+
if (
|
|
274
|
+
self.validate_unscheduled_visit_reason
|
|
275
|
+
and "reason_unscheduled" in self.cleaned_data
|
|
276
|
+
):
|
|
277
|
+
self.applicable_if(
|
|
278
|
+
UNSCHEDULED,
|
|
279
|
+
field="reason",
|
|
280
|
+
field_applicable="reason_unscheduled",
|
|
281
|
+
)
|
|
277
282
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
+
self.required_if(
|
|
284
|
+
OTHER,
|
|
285
|
+
field="reason_unscheduled",
|
|
286
|
+
field_required="reason_unscheduled_other",
|
|
287
|
+
)
|
|
283
288
|
|
|
284
289
|
def metadata_exists_for(
|
|
285
290
|
self,
|
|
286
|
-
entry_status: str = None,
|
|
291
|
+
entry_status: str | None = None,
|
|
287
292
|
filter_models: list[str] | None = None,
|
|
288
293
|
exclude_models: list[str] | None = None,
|
|
289
294
|
) -> int:
|
|
@@ -291,8 +296,9 @@ class VisitFormValidator(WindowPeriodFormValidatorMixin, FormValidator):
|
|
|
291
296
|
the given entry_status.
|
|
292
297
|
"""
|
|
293
298
|
exclude_opts: dict = {}
|
|
299
|
+
entry_status = entry_status or KEYED
|
|
294
300
|
filter_opts = deepcopy(self.crf_filter_options)
|
|
295
|
-
filter_opts.update(entry_status=entry_status
|
|
301
|
+
filter_opts.update(entry_status=entry_status)
|
|
296
302
|
if filter_models:
|
|
297
303
|
filter_opts.update(model__in=filter_models)
|
|
298
304
|
if exclude_models:
|
|
@@ -3,6 +3,8 @@ from edc_form_validators import FormValidator
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class VisitMissedFormValidator(FormValidator):
|
|
6
|
+
min_contact_attempts_count = 3
|
|
7
|
+
|
|
6
8
|
def clean(self) -> None:
|
|
7
9
|
self.applicable_if(YES, field="contact_attempted", field_applicable="contact_made")
|
|
8
10
|
self.required_if(
|
|
@@ -16,7 +18,10 @@ class VisitMissedFormValidator(FormValidator):
|
|
|
16
18
|
if self.cleaned_data.get("contact_attempts_count") is None
|
|
17
19
|
else self.cleaned_data.get("contact_attempts_count")
|
|
18
20
|
)
|
|
19
|
-
cond =
|
|
21
|
+
cond = (
|
|
22
|
+
self.cleaned_data.get("contact_made") == NO
|
|
23
|
+
and contact_attempts_count < self.min_contact_attempts_count
|
|
24
|
+
)
|
|
20
25
|
self.required_if_true(
|
|
21
26
|
cond,
|
|
22
27
|
field_required="contact_attempts_explained",
|
|
@@ -25,7 +25,7 @@ def get_related_visit_model_attr(model_cls) -> str:
|
|
|
25
25
|
fld_cls.related_model is not None
|
|
26
26
|
and fld_cls.related_model == related_visit_model_cls
|
|
27
27
|
):
|
|
28
|
-
attrs.append(fld_cls.name)
|
|
28
|
+
attrs.append(fld_cls.name) # noqa: PERF401
|
|
29
29
|
if len(attrs) > 1:
|
|
30
30
|
raise RelatedVisitFieldError(
|
|
31
31
|
f"More than one field is related to the visit model. See {model_cls}. "
|
|
@@ -76,11 +76,11 @@ class VisitTrackingCrfModelFormMixin:
|
|
|
76
76
|
def related_visit_model_attr(self) -> str:
|
|
77
77
|
try:
|
|
78
78
|
return self._meta.model.related_visit_model_attr()
|
|
79
|
-
except AttributeError:
|
|
79
|
+
except AttributeError as e:
|
|
80
80
|
raise VisitTrackingCrfModelFormMixinError(
|
|
81
81
|
"Expected method `related_visit_model_attr`. Is this a CRF? "
|
|
82
82
|
f"See model {self._meta.model}"
|
|
83
|
-
)
|
|
83
|
+
) from e
|
|
84
84
|
|
|
85
85
|
def validate_visit_tracking(self: Any) -> None:
|
|
86
86
|
# trigger a validation error if visit field is None
|
|
@@ -104,7 +104,7 @@ class VisitTrackingCrfModelFormMixin:
|
|
|
104
104
|
CrfReportDateBeforeStudyStart,
|
|
105
105
|
CrfReportDateIsFuture,
|
|
106
106
|
) as e:
|
|
107
|
-
raise forms.ValidationError({self.report_datetime_field_attr: str(e)})
|
|
107
|
+
raise forms.ValidationError({self.report_datetime_field_attr: str(e)}) from e
|
|
108
108
|
|
|
109
109
|
def validate_visits_completed_in_order(self) -> None:
|
|
110
110
|
"""Asserts visits are completed in order."""
|
|
@@ -115,4 +115,4 @@ class VisitTrackingCrfModelFormMixin:
|
|
|
115
115
|
try:
|
|
116
116
|
visit_sequence.enforce_sequence(document_type="CRF")
|
|
117
117
|
except VisitSequenceError as e:
|
|
118
|
-
raise forms.ValidationError(str(e), code=INVALID_ERROR)
|
|
118
|
+
raise forms.ValidationError(str(e), code=INVALID_ERROR) from e
|
|
@@ -29,9 +29,9 @@ def get_related_visit(
|
|
|
29
29
|
VisitTrackingCrfModelFormMixin
|
|
30
30
|
| InlineCrfModelFormMixin
|
|
31
31
|
| CrfFormValidator
|
|
32
|
-
| CrfFormValidatorMixin
|
|
32
|
+
| CrfFormValidatorMixin
|
|
33
33
|
),
|
|
34
|
-
related_visit_model_attr: str = None,
|
|
34
|
+
related_visit_model_attr: str | None = None,
|
|
35
35
|
) -> RelatedVisitModel | None:
|
|
36
36
|
"""Returns the related visit model instance or None.
|
|
37
37
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Protocol, TypeVar
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
7
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
@@ -33,10 +33,10 @@ class ModelBase(type):
|
|
|
33
33
|
def _base_manager(cls: type[_Self]) -> BaseManager[_Self]: ...
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
class Options(
|
|
36
|
+
class Options([_M]):
|
|
37
37
|
def label_lower(self) -> str: ...
|
|
38
38
|
|
|
39
|
-
def fields(self) ->
|
|
39
|
+
def fields(self) -> tuple[Field]: ...
|
|
40
40
|
|
|
41
41
|
def get_fields(
|
|
42
42
|
self, include_parents: bool = ..., include_hidden: bool = ...
|
edc_visit_tracking/utils.py
CHANGED
|
@@ -63,7 +63,7 @@ def get_subject_visit_missed_model() -> str:
|
|
|
63
63
|
try:
|
|
64
64
|
model = settings.SUBJECT_VISIT_MISSED_MODEL
|
|
65
65
|
except AttributeError as e:
|
|
66
|
-
raise ImproperlyConfigured(f"{error_msg} Got {e}.")
|
|
66
|
+
raise ImproperlyConfigured(f"{error_msg} Got {e}.") from e
|
|
67
67
|
else:
|
|
68
68
|
if not model:
|
|
69
69
|
raise ImproperlyConfigured(f"{error_msg} Got None.")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
from django.utils.translation import gettext_lazy as _
|
|
@@ -39,7 +40,7 @@ class VisitSequence:
|
|
|
39
40
|
self.visit_code = self.appointment.visit_code
|
|
40
41
|
self.visit_code_sequence = self.appointment.visit_code_sequence
|
|
41
42
|
|
|
42
|
-
def enforce_sequence(self, document_type: str = None) -> None:
|
|
43
|
+
def enforce_sequence(self, document_type: str | None = None) -> None:
|
|
43
44
|
"""Raises an exception if sequence is not adhered to; that is,
|
|
44
45
|
the visit reports are not completed in order.
|
|
45
46
|
|
|
@@ -80,10 +81,8 @@ class VisitSequence:
|
|
|
80
81
|
previous_visit_code = self.visit_code
|
|
81
82
|
else:
|
|
82
83
|
previous = self.appointment.schedule.visits.previous(self.visit_code)
|
|
83
|
-
|
|
84
|
+
with contextlib.suppress(AttributeError):
|
|
84
85
|
previous_visit_code = previous.code
|
|
85
|
-
except AttributeError:
|
|
86
|
-
pass
|
|
87
86
|
return previous_visit_code
|
|
88
87
|
|
|
89
88
|
@property
|
|
File without changes
|
|
File without changes
|