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.

Files changed (64) hide show
  1. {clinicedc-2.0.14.dist-info → clinicedc-2.0.16.dist-info}/METADATA +1 -1
  2. {clinicedc-2.0.14.dist-info → clinicedc-2.0.16.dist-info}/RECORD +64 -64
  3. edc_analytics/custom_tables/age.py +1 -0
  4. edc_analytics/custom_tables/art.py +1 -0
  5. edc_analytics/custom_tables/bmi.py +1 -0
  6. edc_analytics/custom_tables/bp.py +1 -0
  7. edc_analytics/custom_tables/fasting.py +1 -0
  8. edc_analytics/custom_tables/fbg.py +1 -0
  9. edc_analytics/custom_tables/fbg_ogtt.py +1 -0
  10. edc_analytics/custom_tables/hba1c.py +1 -0
  11. edc_analytics/custom_tables/ogtt.py +1 -0
  12. edc_analytics/custom_tables/waist.py +1 -0
  13. edc_analytics/row/row_definitions.py +1 -1
  14. edc_analytics/row/row_statistics_with_gender.py +1 -0
  15. edc_analytics/stata/get_stata_labels_from_model.py +5 -6
  16. edc_analytics/table.py +1 -0
  17. edc_appointment/creators/appointments_creator.py +5 -9
  18. edc_dx/diagnoses.py +2 -2
  19. edc_dx/form_validators/diagnosis_form_validator_mixin.py +1 -0
  20. edc_dx/form_validators/result_form_validator_mixin.py +14 -16
  21. edc_dx_review/form_mixins/clinical_review_baseline_required_form_mixin.py +1 -0
  22. edc_dx_review/medical_date.py +2 -1
  23. edc_dx_review/model_mixins/clinical_review_baseline_model_mixin.py +1 -0
  24. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_chol_model_mixin.py +1 -0
  25. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_dm_model_mixin.py +1 -0
  26. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_hiv_model_mixin.py +1 -0
  27. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_htn_model_mixin.py +1 -0
  28. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_model_mixin.py +1 -0
  29. edc_dx_review/model_mixins/dx_location_model_mixin.py +1 -0
  30. edc_dx_review/model_mixins/factory/baseline_review_model_mixin_factory.py +1 -0
  31. edc_dx_review/model_mixins/factory/calculate_date.py +1 -0
  32. edc_dx_review/model_mixins/factory/dx_initial_review_model_mixin_factory.py +2 -1
  33. edc_dx_review/model_mixins/factory/followup_review_model_mixin_factory.py +1 -0
  34. edc_dx_review/model_mixins/factory/rx_initial_review_model_mixin_factory.py +1 -0
  35. edc_dx_review/model_mixins/followup_review/followup_review_model_mixin.py +1 -0
  36. edc_dx_review/model_mixins/followup_review/hiv_followup_review_model_mixin.py +1 -0
  37. edc_dx_review/model_mixins/initial_review/chol_initial_review_model_mixin.py +1 -0
  38. edc_dx_review/model_mixins/initial_review/hiv_initial_model_mixins.py +1 -0
  39. edc_dx_review/model_mixins/initial_review/ncd_initial_review_model_mixin.py +1 -0
  40. edc_dx_review/radio_fields.py +3 -3
  41. edc_dx_review/utils.py +2 -1
  42. edc_form_describer/form_describer.py +13 -8
  43. edc_form_describer/forms_reference.py +24 -25
  44. edc_form_describer/make_forms_reference.py +10 -12
  45. edc_form_describer/management/commands/make_forms_reference.py +8 -8
  46. edc_form_describer/markdown_writer.py +11 -10
  47. edc_randomization/randomizer.py +2 -5
  48. edc_randomization/utils.py +10 -0
  49. edc_visit_schedule/schedule/schedule.py +13 -13
  50. edc_visit_tracking/action_items.py +1 -2
  51. edc_visit_tracking/apps.py +1 -1
  52. edc_visit_tracking/context_processors.py +1 -2
  53. edc_visit_tracking/crf_date_validator.py +4 -4
  54. edc_visit_tracking/form_validators/visit_form_validator.py +51 -45
  55. edc_visit_tracking/form_validators/visit_missed_form_validator.py +6 -1
  56. edc_visit_tracking/model_mixins/utils.py +1 -1
  57. edc_visit_tracking/modelform_mixins/crf/visit_tracking_crf_modelform_mixin.py +4 -4
  58. edc_visit_tracking/modelform_mixins/utils.py +2 -2
  59. edc_visit_tracking/models/signals.py +1 -1
  60. edc_visit_tracking/typing_stubs.py +3 -3
  61. edc_visit_tracking/utils.py +1 -1
  62. edc_visit_tracking/visit_sequence.py +3 -4
  63. {clinicedc-2.0.14.dist-info → clinicedc-2.0.16.dist-info}/WHEEL +0 -0
  64. {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
- if report_datetime_local.date() < self.appt_datetime_local.date():
173
- appt_datetime_str = formatted_datetime(
174
- self.appt_datetime_local, format_as_date=True
175
- )
176
- self.raise_validation_error(
177
- {
178
- "report_datetime": (
179
- "Invalid. Cannot be before appointment date. "
180
- f"Got appointment date {appt_datetime_str}"
181
- )
182
- },
183
- INVALID_ERROR,
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 is_baseline(instance=self.appointment):
191
- if report_datetime_local := self.report_datetime:
192
- if report_datetime_local.date() != self.appt_datetime_local.date():
193
- appt_datetime_str = formatted_datetime(
194
- self.appt_datetime_local, format_as_date=True
195
- )
196
- self.raise_validation_error(
197
- {
198
- "report_datetime": (
199
- "Invalid. Must match appointment date at baseline. "
200
- "If necessary, change the appointment date and "
201
- f"try again. Got appointment date {appt_datetime_str}"
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": ("Invalid. This is an unscheduled visit. See appointment.")},
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 self.validate_unscheduled_visit_reason:
271
- if "reason_unscheduled" in self.cleaned_data:
272
- self.applicable_if(
273
- UNSCHEDULED,
274
- field="reason",
275
- field_applicable="reason_unscheduled",
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
- self.required_if(
279
- OTHER,
280
- field="reason_unscheduled",
281
- field_required="reason_unscheduled_other",
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 or KEYED)
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 = self.cleaned_data.get("contact_made") == NO and contact_attempts_count < 3
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
 
@@ -18,7 +18,7 @@ def visit_tracking_check_in_progress_on_post_save(
18
18
  """Calls method on the visit tracking instance"""
19
19
  if not raw and not update_fields:
20
20
  try:
21
- instance.appointment
21
+ instance.appointment # noqa: B018
22
22
  except AttributeError:
23
23
  pass
24
24
  else:
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from datetime import datetime
4
- from typing import Any, Generic, Protocol, Tuple, TypeVar
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(Generic[_M]):
36
+ class Options([_M]):
37
37
  def label_lower(self) -> str: ...
38
38
 
39
- def fields(self) -> Tuple[Field]: ... # noqa
39
+ def fields(self) -> tuple[Field]: ...
40
40
 
41
41
  def get_fields(
42
42
  self, include_parents: bool = ..., include_hidden: bool = ...
@@ -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
- try:
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