meta-edc 0.3.7__py3-none-any.whl → 0.3.15__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. meta_auth/auth_objects.py +10 -3
  2. meta_consent/baker_recipes.py +4 -4
  3. meta_consent/consents.py +1 -1
  4. meta_consent/migrations/0025_alter_historicalsubjectconsent_first_name_and_more.py +151 -0
  5. meta_consent/models/signals.py +16 -13
  6. meta_consent/models/subject_consent_v1.py +1 -3
  7. meta_consent/tests/tests/test_form_validators.py +1 -1
  8. meta_dashboard/templates/meta_dashboard/bootstrap3/screening/listboard.html +4 -4
  9. meta_dashboard/templates/meta_dashboard/bootstrap3/subject/listboard.html +1 -2
  10. meta_edc/settings/debug.py +9 -9
  11. meta_edc/settings/defaults.py +25 -18
  12. meta_edc/settings/live.py +1 -9
  13. meta_edc/settings/uat.py +1 -14
  14. meta_edc/templates/meta_edc/bootstrap3/home.html +8 -5
  15. meta_edc/tests/test_settings.py +176 -0
  16. meta_edc/tests/tests/test_endpoints.py +20 -19
  17. meta_edc/urls.py +1 -1
  18. {meta_edc-0.3.7.dist-info → meta_edc-0.3.15.dist-info}/METADATA +5 -4
  19. {meta_edc-0.3.7.dist-info → meta_edc-0.3.15.dist-info}/RECORD +128 -74
  20. meta_prn/action_items.py +44 -2
  21. meta_prn/admin/__init__.py +3 -0
  22. meta_prn/admin/dm_referral_admin.py +49 -0
  23. meta_prn/admin/offschedule_dm_referral_admin.py +47 -0
  24. meta_prn/admin/onschedule_dm_referral_admin.py +39 -0
  25. meta_prn/baker_recipes.py +8 -1
  26. meta_prn/choices.py +2 -1
  27. meta_prn/constants.py +4 -1
  28. meta_prn/forms/__init__.py +2 -0
  29. meta_prn/forms/dm_referral_form.py +40 -0
  30. meta_prn/forms/offschedule_dm_referral_form.py +35 -0
  31. meta_prn/forms/offschedule_form.py +6 -0
  32. meta_prn/migrations/0057_historicalonscheduledmreferral_and_more.py +1156 -0
  33. meta_prn/migrations/0058_dmreferral_referral_note_and_more.py +29 -0
  34. meta_prn/migrations/0059_alter_historicaloffstudymedication_reason_and_more.py +53 -0
  35. meta_prn/models/__init__.py +13 -2
  36. meta_prn/models/dm_referral.py +39 -0
  37. meta_prn/models/offschedule.py +15 -1
  38. meta_prn/models/onschedule.py +6 -0
  39. meta_prn/models/signals.py +41 -1
  40. meta_prn/tests/tests/test_dm_referral.py +206 -0
  41. meta_screening/form_validators/screening_part_two.py +1 -1
  42. meta_screening/migrations/0062_remove_icpreferral_site_and_more.py +27 -0
  43. meta_screening/migrations/0063_alter_historicalscreeningpartone_fasting_duration_str_and_more.py +184 -0
  44. meta_screening/migrations/0064_remove_historicalscreeningpartone_fasting_duration_minutes_and_more.py +126 -0
  45. meta_screening/migrations/0065_auto_20240516_0352.py +31 -0
  46. meta_screening/migrations/0066_alter_historicalscreeningpartone_fasting_duration_delta_and_more.py +103 -0
  47. meta_screening/models/__init__.py +1 -1
  48. meta_screening/tests/meta_test_case_mixin.py +2 -2
  49. meta_screening/tests/options.py +3 -3
  50. meta_sites/__init__.py +0 -1
  51. meta_sites/sites.py +8 -7
  52. meta_subject/action_items.py +23 -0
  53. meta_subject/admin/__init__.py +1 -1
  54. meta_subject/admin/birth_outcome_admin.py +2 -3
  55. meta_subject/admin/delivery_admin.py +0 -1
  56. meta_subject/admin/diabetes/__init__.py +2 -0
  57. meta_subject/admin/diabetes/dm_diagnosis_admin.py +89 -0
  58. meta_subject/admin/{dm_referral_followup_admin.py → diabetes/dm_followup_admin.py} +15 -8
  59. meta_subject/admin/glucose_admin.py +1 -1
  60. meta_subject/admin/glucose_fbg_admin.py +34 -8
  61. meta_subject/admin/subject_visit_admin.py +4 -1
  62. meta_subject/baker_recipes.py +6 -0
  63. meta_subject/choices.py +8 -0
  64. meta_subject/constants.py +2 -1
  65. meta_subject/form_validators/__init__.py +2 -1
  66. meta_subject/form_validators/dm_diagnosis_form_validator.py +38 -0
  67. meta_subject/form_validators/dm_dx_result_form_validator.py +7 -0
  68. meta_subject/form_validators/{dm_referral_followup_form_validator.py → dm_followup_form_validator.py} +41 -2
  69. meta_subject/forms/__init__.py +1 -0
  70. meta_subject/forms/diabetes/__init__.py +3 -0
  71. meta_subject/forms/diabetes/dm_diagnosis_form.py +13 -0
  72. meta_subject/forms/diabetes/dm_dx_result_form.py +11 -0
  73. meta_subject/forms/diabetes/dm_followup_form.py +25 -0
  74. meta_subject/forms/glucose_fbg_form.py +38 -16
  75. meta_subject/forms/subject_visit_form.py +16 -0
  76. meta_subject/metadata_rules/metadata_rules.py +14 -0
  77. meta_subject/metadata_rules/predicates.py +22 -0
  78. meta_subject/migrations/0181_dmreferralfollowup_action_identifier_and_more.py +143 -0
  79. meta_subject/migrations/0182_rename_dmreferralfollowup_dmfollowup_and_more.py +54 -0
  80. meta_subject/migrations/0183_alter_dmfollowup_on_dm_medications_and_more.py +31 -0
  81. meta_subject/migrations/0184_alter_glucose_options_and_more.py +31 -0
  82. meta_subject/migrations/0185_alter_bloodresultsins_fasting_duration_str_and_more.py +82 -0
  83. meta_subject/migrations/0186_healtheconomicsupdate_singleton_field_and_more.py +55 -0
  84. meta_subject/migrations/0187_dmdiagnosis_historicaldmdiagnosis_dmdxresult_and_more.py +451 -0
  85. meta_subject/migrations/0188_historicaldmdxresult_dmdxresult.py +403 -0
  86. meta_subject/migrations/0189_alter_dmdxresult_options_and_more.py +116 -0
  87. meta_subject/migrations/0190_dmdiagnosis_dx_no_tmg_reason_and_more.py +65 -0
  88. meta_subject/migrations/0191_alter_dmdiagnosis_dx_no_tmg_reason_and_more.py +70 -0
  89. meta_subject/migrations/0192_rename_glucose_quantifier_glucosefbg_fbg_quantifier_and_more.py +44 -0
  90. meta_subject/migrations/0193_alter_glucosefbg_fbg_value_and_more.py +44 -0
  91. meta_subject/migrations/0194_remove_glucosefbg_assay_datetime_and_more.py +166 -0
  92. meta_subject/migrations/0195_alter_glucosefbg_fbg_datetime_and_more.py +27 -0
  93. meta_subject/migrations/0196_glucosefbg_fbg_not_performed_reason_and_more.py +49 -0
  94. meta_subject/migrations/0197_glucosefbg_fasting_duration_estimated_and_more.py +33 -0
  95. meta_subject/migrations/0198_alter_glucosefbg_fasting_duration_estimated_and_more.py +33 -0
  96. meta_subject/migrations/0199_auto_20240516_0247.py +18 -0
  97. meta_subject/migrations/0200_rename_fasting_duration_minutes_bloodresultsins_fasting_duration_delta_and_more.py +43 -0
  98. meta_subject/migrations/0201_alter_bloodresultsins_fasting_duration_delta_and_more.py +58 -0
  99. meta_subject/migrations/0202_auto_20240516_0315.py +32 -0
  100. meta_subject/migrations/0203_alter_bloodresultsins_fasting_duration_delta_and_more.py +67 -0
  101. meta_subject/migrations/0204_glucosefbg_repeat_fbg_date_and_more.py +27 -0
  102. meta_subject/migrations/0205_historicalsubjectrequisition_crf_status_and_more.py +80 -0
  103. meta_subject/migrations/0206_bloodresultsfbc_crf_status_and_more.py +62 -0
  104. meta_subject/models/__init__.py +1 -1
  105. meta_subject/models/blood_results/blood_results_fbc.py +3 -2
  106. meta_subject/models/blood_results/blood_results_hba1c.py +2 -0
  107. meta_subject/models/blood_results/blood_results_ins.py +2 -0
  108. meta_subject/models/blood_results/blood_results_lft.py +2 -0
  109. meta_subject/models/blood_results/blood_results_lipid.py +2 -0
  110. meta_subject/models/blood_results/blood_results_rft.py +2 -0
  111. meta_subject/models/diabetes/__init__.py +3 -0
  112. meta_subject/models/diabetes/dm_diagnosis.py +50 -0
  113. meta_subject/models/diabetes/dm_dx_result.py +70 -0
  114. meta_subject/models/{dm_referral_followup.py → diabetes/dm_followup.py} +18 -6
  115. meta_subject/models/glucose.py +5 -15
  116. meta_subject/models/glucose_fbg.py +40 -51
  117. meta_subject/models/health_economics/health_economics_update.py +2 -0
  118. meta_subject/models/subject_requisition.py +3 -4
  119. meta_subject/tests/tests/test_egfr.py +6 -5
  120. meta_subject/tests/tests/test_metadata_rules.py +32 -2
  121. meta_visit_schedule/constants.py +3 -1
  122. meta_visit_schedule/visit_schedules/phase_three/crfs.py +12 -1
  123. meta_visit_schedule/visit_schedules/phase_three/schedule_dm_referral.py +60 -0
  124. meta_visit_schedule/visit_schedules/phase_three/visit_schedule.py +2 -0
  125. meta_subject/forms/dm_referral_followup.py +0 -18
  126. {meta_edc-0.3.7.dist-info → meta_edc-0.3.15.dist-info}/AUTHORS +0 -0
  127. {meta_edc-0.3.7.dist-info → meta_edc-0.3.15.dist-info}/LICENSE +0 -0
  128. {meta_edc-0.3.7.dist-info → meta_edc-0.3.15.dist-info}/WHEEL +0 -0
  129. {meta_edc-0.3.7.dist-info → meta_edc-0.3.15.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  from django.contrib import admin
2
2
  from django_audit_fields.admin import audit_fieldset_tuple
3
3
  from edc_model_admin.history import SimpleHistoryAdmin
4
+ from edc_sites.admin import SiteModelAdminMixin
4
5
  from edc_visit_schedule.fieldsets import visit_schedule_fieldset_tuple
5
6
  from edc_visit_tracking.modeladmin_mixins import VisitModelAdminMixin
6
7
 
@@ -11,7 +12,9 @@ from .modeladmin import ModelAdminMixin
11
12
 
12
13
 
13
14
  @admin.register(SubjectVisit, site=meta_subject_admin)
14
- class SubjectVisitAdmin(VisitModelAdminMixin, ModelAdminMixin, SimpleHistoryAdmin):
15
+ class SubjectVisitAdmin(
16
+ VisitModelAdminMixin, SiteModelAdminMixin, ModelAdminMixin, SimpleHistoryAdmin
17
+ ):
15
18
  show_dashboard_in_list_display_pos = 2
16
19
 
17
20
  form = SubjectVisitForm
@@ -11,6 +11,7 @@ from .models import (
11
11
  BirthOutcomes,
12
12
  Delivery,
13
13
  FollowupExamination,
14
+ HealthEconomicsUpdate,
14
15
  MedicationAdherence,
15
16
  SubjectRequisition,
16
17
  SubjectVisit,
@@ -103,3 +104,8 @@ birthoutcomes = Recipe(
103
104
  birth_outcome=LIVE_AT_TERM,
104
105
  birth_weight=320,
105
106
  )
107
+
108
+ healtheconomicsupdate = Recipe(
109
+ HealthEconomicsUpdate,
110
+ report_datetime=get_utcnow(),
111
+ )
meta_subject/choices.py CHANGED
@@ -10,6 +10,7 @@ from edc_constants.constants import (
10
10
  OPEN,
11
11
  OTHER,
12
12
  PATIENT,
13
+ PENDING,
13
14
  PRESENT,
14
15
  PRESENT_WITH_REINFORCEMENT,
15
16
  YES,
@@ -80,6 +81,13 @@ DYSLIPIDAEMIA_RX_CHOICES = (
80
81
  (NOT_APPLICABLE, _("Not applicable")),
81
82
  )
82
83
 
84
+ ENDPOINT_CHOICES = (
85
+ (YES, _(YES)),
86
+ (PENDING, _("No. A repeat FBG will be scheduled")),
87
+ (NO, _(NO)),
88
+ (NOT_APPLICABLE, _("Not applicable")),
89
+ )
90
+
83
91
  FEELING_DURATION_CHOICES = (
84
92
  (ALL_OF_THE_TIME, _("All of the time")),
85
93
  (MOST_OF_THE_TIME, _("Most of the time")),
meta_subject/constants.py CHANGED
@@ -1,9 +1,11 @@
1
1
  from datetime import date
2
2
 
3
3
  ALL_OF_THE_TIME = "all_of_the_time"
4
+ AMENDMENT_DATE = date(2023, 11, 24)
4
5
  APPT = "appt"
5
6
  APPT_OTHER = "other_routine_appt"
6
7
  DELIVERY_ACTION = "delivery_action"
8
+ DM_FOLLOWUP_ACTION = "dm_followup_action"
7
9
  FOLLOWUP_EXAMINATION_ACTION = "followup-examination-ae"
8
10
  GOOD_BIT_OF_THE_TIME = "good_bit_of_the_time"
9
11
  LITTLE_OF_THE_TIME = "little_of_the_time"
@@ -16,4 +18,3 @@ NONE_OF_THE_TIME = "none_of_the_time"
16
18
  NO_COMPLICATIONS = "no_complications"
17
19
  SOME_OF_THE_TIME = "some_of_the_time"
18
20
  URINE_PREGNANCY_ACTION = "urine_pregnancy_action"
19
- AMENDMENT_DATE = date(2023, 11, 24)
@@ -1,6 +1,7 @@
1
1
  from .birth_outcomes_form_validator import BirthOutcomesFormValidator
2
2
  from .delivery_form_validator import DeliveryFormValidator
3
- from .dm_referral_followup_form_validator import DmReferralFollowupFormValidator
3
+ from .dm_diagnosis_form_validator import DmDiagnosisFormValidator
4
+ from .dm_followup_form_validator import DmFollowupFormValidator
4
5
  from .egfr_drop_notification_form_validator import EgfrDropNotificationFormValidator
5
6
  from .followup_examination_form_validator import FollowupExaminationFormValidator
6
7
  from .glucose_form_validator import GlucoseFormValidator
@@ -0,0 +1,38 @@
1
+ from edc_constants.constants import NO, YES
2
+ from edc_crf.crf_form_validator import CrfFormValidator
3
+ from edc_form_validators import INVALID_ERROR
4
+
5
+
6
+ class DmDiagnosisFormValidator(CrfFormValidator):
7
+ def clean(self):
8
+ # dx_date must be on or before report datetime
9
+ if (
10
+ self.cleaned_data.get("report_datetime")
11
+ and self.cleaned_data.get("dx_date")
12
+ and self.cleaned_data.get("dx_date")
13
+ > self.cleaned_data.get("report_datetime").date()
14
+ ):
15
+ self.raise_validation_error(
16
+ {"dx_date": "Invalid. Expected a date on or before the report date above. "},
17
+ INVALID_ERROR,
18
+ )
19
+
20
+ self.required_if(YES, field="dx_tmg", field_required="dx_tmg_date")
21
+
22
+ if (
23
+ self.cleaned_data.get("dx_tmg_date")
24
+ and self.cleaned_data.get("dx_date")
25
+ and self.cleaned_data.get("dx_tmg_date") > self.cleaned_data.get("dx_date")
26
+ ):
27
+ self.raise_validation_error(
28
+ {
29
+ "dx_tmg_date": (
30
+ "Invalid. Expected a date on or before the diagnosis date above. "
31
+ )
32
+ },
33
+ INVALID_ERROR,
34
+ )
35
+
36
+ self.required_if(NO, field="dx_tmg", field_required="dx_no_tmg_reason")
37
+
38
+ # TODO: do we check for lab results listed in the inline?
@@ -0,0 +1,7 @@
1
+ from edc_form_validators import FormValidator
2
+
3
+
4
+ class DmDiagnosisFormValidator(FormValidator):
5
+ def clean(self):
6
+ self.required_if_true(self.cleaned_data.get("report_date"), field_required="utestid")
7
+ self.required_if_true(self.cleaned_data.get("utestid"), field_required="value")
@@ -1,11 +1,15 @@
1
1
  from django import forms
2
+ from django.core.exceptions import ObjectDoesNotExist
2
3
  from edc_constants.constants import NO, OTHER, YES
3
4
  from edc_crf.crf_form_validator import CrfFormValidator
4
5
  from edc_form_validators import INVALID_ERROR
6
+ from edc_utils import formatted_date
5
7
  from edc_utils.date import to_local
6
8
 
9
+ from meta_prn.models import DmReferral
7
10
 
8
- class DmReferralFollowupFormValidator(CrfFormValidator):
11
+
12
+ class DmFollowupFormValidator(CrfFormValidator):
9
13
  def clean(self):
10
14
 
11
15
  # referral_date must be before report datetime
@@ -15,10 +19,31 @@ class DmReferralFollowupFormValidator(CrfFormValidator):
15
19
  and self.cleaned_data.get("referral_date") >= to_local(self.report_datetime).date()
16
20
  ):
17
21
  self.raise_validation_error(
18
- {"referral_date": "Invalid. Cannot be on or after report date"},
22
+ {"referral_date": "Invalid. Expected a date prior to the report date above."},
19
23
  INVALID_ERROR,
20
24
  )
21
25
 
26
+ # try to match referral date to the referal form
27
+ try:
28
+ dm_referral = DmReferral.objects.get(subject_identifier=self.subject_identifier)
29
+ except ObjectDoesNotExist:
30
+ self.raise_validation_error(
31
+ {"__all__": "Original Referral form not found."}, INVALID_ERROR
32
+ )
33
+ else:
34
+ if dm_referral.referral_date != self.cleaned_data.get("referral_date"):
35
+ referral_dt = formatted_date(dm_referral.referral_date)
36
+ report_dt = formatted_date(to_local(dm_referral.report_datetime).date())
37
+ self.raise_validation_error(
38
+ {
39
+ "referral_date": (
40
+ f"Invalid. Expected `{referral_dt}` "
41
+ f"based on the referral form submitted on {report_dt}."
42
+ )
43
+ },
44
+ INVALID_ERROR,
45
+ )
46
+
22
47
  # Diabetes clinic attendance
23
48
  self.m2m_required_if(
24
49
  NO,
@@ -147,6 +172,20 @@ class DmReferralFollowupFormValidator(CrfFormValidator):
147
172
  INVALID_ERROR,
148
173
  )
149
174
 
175
+ # dm_medications_init_date must be on or after attended_date
176
+ if self.cleaned_data.get("dm_medications_init_date") and self.cleaned_data.get(
177
+ "dm_medications_init_date"
178
+ ) < self.cleaned_data.get("attended_date"):
179
+ self.raise_validation_error(
180
+ {
181
+ "dm_medications_init_date": (
182
+ "Invalid. Expected a date on or after date patient "
183
+ "attended the health facility."
184
+ )
185
+ },
186
+ INVALID_ERROR,
187
+ )
188
+
150
189
  self.m2m_required_if(
151
190
  YES,
152
191
  field="on_dm_medications",
@@ -10,6 +10,7 @@ from .blood_results import (
10
10
  from .complications_glycemia_form import ComplicationsGlycemiaForm
11
11
  from .concomitant_medication_form import ConcomitantMedicationForm
12
12
  from .delivery_form import DeliveryForm
13
+ from .diabetes import DmDiagnosisForm, DmDxResultForm, DmFollowupForm
13
14
  from .egfr_drop_notification_form import EgfrDropNotificationForm
14
15
  from .eq53d3l_form import Eq5d3lForm
15
16
  from .followup_examination_form import FollowupExaminationForm
@@ -0,0 +1,3 @@
1
+ from .dm_diagnosis_form import DmDiagnosisForm
2
+ from .dm_dx_result_form import DmDxResultForm
3
+ from .dm_followup_form import DmFollowupForm
@@ -0,0 +1,13 @@
1
+ from django import forms
2
+ from edc_crf.modelform_mixins import CrfModelFormMixin
3
+
4
+ from ...form_validators import DmDiagnosisFormValidator
5
+ from ...models import DmDiagnosis
6
+
7
+
8
+ class DmDiagnosisForm(CrfModelFormMixin, forms.ModelForm):
9
+ form_validator_cls = DmDiagnosisFormValidator
10
+
11
+ class Meta:
12
+ model = DmDiagnosis
13
+ fields = "__all__"
@@ -0,0 +1,11 @@
1
+ from django import forms
2
+
3
+ from ...models import DmDxResult
4
+
5
+
6
+ class DmDxResultForm(forms.ModelForm):
7
+ # form_validator_cls = DmDxResultFormValidator
8
+
9
+ class Meta:
10
+ model = DmDxResult
11
+ fields = "__all__"
@@ -0,0 +1,25 @@
1
+ from django import forms
2
+ from edc_action_item.forms import ActionItemCrfFormMixin
3
+ from edc_crf.modelform_mixins import CrfModelFormMixin
4
+ from edc_model_fields.widgets import SliderWidget
5
+
6
+ from ...form_validators import DmFollowupFormValidator
7
+ from ...models import DmFollowup
8
+
9
+
10
+ class DmFollowupForm(CrfModelFormMixin, ActionItemCrfFormMixin, forms.ModelForm):
11
+ form_validator_cls = DmFollowupFormValidator
12
+
13
+ visual_score_slider = forms.CharField(
14
+ label="Visual Score", widget=SliderWidget(attrs={"min": 0, "max": 100})
15
+ )
16
+
17
+ class Meta:
18
+ model = DmFollowup
19
+ fields = "__all__"
20
+ help_text = {"action_identifier": "(read-only)"}
21
+ widgets = {
22
+ "action_identifier": forms.TextInput(
23
+ attrs={"required": False, "readonly": "readonly"}
24
+ ),
25
+ }
@@ -1,31 +1,53 @@
1
+ from decimal import Decimal
2
+
1
3
  from django import forms
4
+ from edc_constants.constants import NO, YES
2
5
  from edc_crf.crf_form_validator import CrfFormValidator
3
6
  from edc_crf.modelform_mixins import CrfModelFormMixin
4
7
  from edc_form_validators import INVALID_ERROR
5
8
  from edc_glucose.utils import validate_glucose_as_millimoles_per_liter
6
- from edc_lab_results.form_validator_mixins import (
7
- BloodResultsFormValidatorMixin,
8
- BloodResultsGluFormValidatorMixin,
9
- )
10
9
 
11
10
  from ..models import GlucoseFbg
12
11
 
13
12
 
14
- class GlucoseFbgFormValidator(
15
- BloodResultsGluFormValidatorMixin, BloodResultsFormValidatorMixin, CrfFormValidator
16
- ):
13
+ class GlucoseFbgFormValidator(CrfFormValidator):
17
14
  def clean(self):
18
- if (
19
- self.cleaned_data.get("report_datetime")
20
- and self.cleaned_data.get("assay_datetime")
21
- and self.cleaned_data.get("assay_datetime")
22
- < self.cleaned_data.get("report_datetime")
23
- ):
15
+ converted_value = None
16
+ self.required_if(YES, field="fasting", field_required="fasting_duration_str")
17
+ self.required_if(NO, field="fbg_performed", field_required="fbg_not_performed_reason")
18
+ self.required_if(YES, field="fbg_performed", field_required="fbg_datetime")
19
+ if self.cleaned_data.get("fbg_datetime") and self.cleaned_data.get(
20
+ "fbg_datetime"
21
+ ) < self.cleaned_data.get("report_datetime"):
24
22
  self.raise_validation_error(
25
- {"assay_datetime": "Cannot be before report date"}, INVALID_ERROR
23
+ {"fbg_datetime": "Invalid. Must be on or after report date above"},
24
+ INVALID_ERROR,
25
+ )
26
+ self.required_if(YES, field="fbg_performed", field_required="fbg_value")
27
+ if self.cleaned_data.get("fbg_value") is not None:
28
+ converted_value = validate_glucose_as_millimoles_per_liter(
29
+ "fbg", self.cleaned_data
26
30
  )
27
- if self.cleaned_data.get("glucose_value") is not None:
28
- validate_glucose_as_millimoles_per_liter("glucose", self.cleaned_data)
31
+
32
+ self.applicable_if(YES, field="fbg_performed", field_applicable="fbg_units")
33
+
34
+ # repeat_fbg_date
35
+ condition = converted_value and converted_value >= Decimal("7.0")
36
+ self.required_if_true(condition, field_required="repeat_fbg_date")
37
+ if self.cleaned_data.get("repeat_fbg_date") and self.cleaned_data.get("fbg_datetime"):
38
+ diffdays = (
39
+ self.cleaned_data.get("repeat_fbg_date")
40
+ - self.cleaned_data.get("fbg_datetime").date()
41
+ ).days
42
+ if not (7 <= diffdays <= 10):
43
+ self.raise_validation_error(
44
+ {
45
+ "repeat_fbg_date": (
46
+ f"Must be 7 to 10 days from date measured above. Got {diffdays}."
47
+ )
48
+ },
49
+ INVALID_ERROR,
50
+ )
29
51
 
30
52
 
31
53
  class GlucoseFbgForm(CrfModelFormMixin, forms.ModelForm):
@@ -5,6 +5,9 @@ from edc_offstudy.modelform_mixins import OffstudyNonCrfModelFormMixin
5
5
  from edc_visit_tracking.form_validators import VisitFormValidator
6
6
  from edc_visit_tracking.modelform_mixins import VisitTrackingModelFormMixin
7
7
 
8
+ from meta_prn.models import OffStudyMedication
9
+ from meta_visit_schedule.constants import SCHEDULE_DM_REFERRAL
10
+
8
11
  from ..models import SubjectVisit
9
12
 
10
13
 
@@ -21,6 +24,19 @@ class SubjectVisitForm(
21
24
  ):
22
25
  form_validator_cls = SubjectVisitFormValidator
23
26
 
27
+ def clean(self):
28
+ cleaned_data = super().clean()
29
+ if (
30
+ self.cleaned_data.get("appointment").schedule_name == SCHEDULE_DM_REFERRAL
31
+ and not OffStudyMedication.objects.filter(
32
+ subject_identifier=self.subject_identifier
33
+ ).exists()
34
+ ):
35
+ raise forms.ValidationError(
36
+ f"Submit form `{OffStudyMedication._meta.verbose_name}` first."
37
+ )
38
+ return cleaned_data
39
+
24
40
  class Meta:
25
41
  model = SubjectVisit
26
42
  fields = "__all__"
@@ -41,6 +41,20 @@ class HealthEconomicsRuleGroup(CrfRuleGroup):
41
41
  source_model = "meta_subject.subjectvisit"
42
42
 
43
43
 
44
+ @register()
45
+ class HealthEconomicsUpdateRuleGroup(CrfRuleGroup):
46
+ hecon = CrfRule(
47
+ predicate=pc.health_economics_update_required,
48
+ consequence=REQUIRED,
49
+ alternative=NOT_REQUIRED,
50
+ target_models=["healtheconomicsupdate"],
51
+ )
52
+
53
+ class Meta:
54
+ app_label = "meta_subject"
55
+ source_model = "meta_subject.subjectvisit"
56
+
57
+
44
58
  @register()
45
59
  class HbA1cCrfRuleGroup(CrfRuleGroup):
46
60
  hba1c = CrfRule(
@@ -173,6 +173,28 @@ class Predicates(PersistantSingletonMixin):
173
173
  required = True
174
174
  return required
175
175
 
176
+ def health_economics_update_required(self, visit, **kwargs) -> bool:
177
+ """Returns true if `healtheconomicsupdate` was not completed at
178
+ month 3 or ever.
179
+
180
+ `healtheconomicsupdate` is a singleton CRF.
181
+ """
182
+ required = False
183
+ model_cls = django_apps.get_model(f"{self.app_label}.healtheconomicsupdate")
184
+ try:
185
+ obj = model_cls.objects.get(
186
+ subject_visit__subject_identifier=visit.subject_identifier,
187
+ )
188
+ except ObjectDoesNotExist:
189
+ obj = None
190
+ if (
191
+ not obj
192
+ and visit.appointment.visit_code_sequence == 0
193
+ and visit.appointment.visit_code not in [DAY1, WEEK2, MONTH1]
194
+ ):
195
+ required = True
196
+ return required
197
+
176
198
  def mnsi_required(self, visit, **kwargs) -> bool:
177
199
  """Returns True if MNSI assessment was not performed at the
178
200
  1M, 3M or 6M visits.
@@ -0,0 +1,143 @@
1
+ # Generated by Django 4.2.11 on 2024-04-03 19:45
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("edc_action_item", "0037_remove_actionitem_reference_model_and_more"),
11
+ ("meta_subject", "0180_dmreferralfollowup_missed_referral_reasons_and_more"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name="dmreferralfollowup",
17
+ name="action_identifier",
18
+ field=models.CharField(blank=True, max_length=50, null=True, unique=True),
19
+ ),
20
+ migrations.AddField(
21
+ model_name="dmreferralfollowup",
22
+ name="action_item",
23
+ field=models.ForeignKey(
24
+ blank=True,
25
+ null=True,
26
+ on_delete=django.db.models.deletion.PROTECT,
27
+ to="edc_action_item.actionitem",
28
+ ),
29
+ ),
30
+ migrations.AddField(
31
+ model_name="dmreferralfollowup",
32
+ name="action_item_reason",
33
+ field=models.TextField(editable=False, null=True),
34
+ ),
35
+ migrations.AddField(
36
+ model_name="dmreferralfollowup",
37
+ name="parent_action_identifier",
38
+ field=models.CharField(
39
+ blank=True,
40
+ help_text="action identifier that links to parent reference model instance.",
41
+ max_length=30,
42
+ null=True,
43
+ ),
44
+ ),
45
+ migrations.AddField(
46
+ model_name="dmreferralfollowup",
47
+ name="parent_action_item",
48
+ field=models.ForeignKey(
49
+ blank=True,
50
+ null=True,
51
+ on_delete=django.db.models.deletion.PROTECT,
52
+ related_name="+",
53
+ to="edc_action_item.actionitem",
54
+ ),
55
+ ),
56
+ migrations.AddField(
57
+ model_name="dmreferralfollowup",
58
+ name="related_action_identifier",
59
+ field=models.CharField(
60
+ blank=True,
61
+ help_text="action identifier that links to related reference model instance.",
62
+ max_length=30,
63
+ null=True,
64
+ ),
65
+ ),
66
+ migrations.AddField(
67
+ model_name="dmreferralfollowup",
68
+ name="related_action_item",
69
+ field=models.ForeignKey(
70
+ blank=True,
71
+ null=True,
72
+ on_delete=django.db.models.deletion.PROTECT,
73
+ related_name="+",
74
+ to="edc_action_item.actionitem",
75
+ ),
76
+ ),
77
+ migrations.AddField(
78
+ model_name="historicaldmreferralfollowup",
79
+ name="action_identifier",
80
+ field=models.CharField(blank=True, db_index=True, max_length=50, null=True),
81
+ ),
82
+ migrations.AddField(
83
+ model_name="historicaldmreferralfollowup",
84
+ name="action_item",
85
+ field=models.ForeignKey(
86
+ blank=True,
87
+ db_constraint=False,
88
+ null=True,
89
+ on_delete=django.db.models.deletion.DO_NOTHING,
90
+ related_name="+",
91
+ to="edc_action_item.actionitem",
92
+ ),
93
+ ),
94
+ migrations.AddField(
95
+ model_name="historicaldmreferralfollowup",
96
+ name="action_item_reason",
97
+ field=models.TextField(editable=False, null=True),
98
+ ),
99
+ migrations.AddField(
100
+ model_name="historicaldmreferralfollowup",
101
+ name="parent_action_identifier",
102
+ field=models.CharField(
103
+ blank=True,
104
+ help_text="action identifier that links to parent reference model instance.",
105
+ max_length=30,
106
+ null=True,
107
+ ),
108
+ ),
109
+ migrations.AddField(
110
+ model_name="historicaldmreferralfollowup",
111
+ name="parent_action_item",
112
+ field=models.ForeignKey(
113
+ blank=True,
114
+ db_constraint=False,
115
+ null=True,
116
+ on_delete=django.db.models.deletion.DO_NOTHING,
117
+ related_name="+",
118
+ to="edc_action_item.actionitem",
119
+ ),
120
+ ),
121
+ migrations.AddField(
122
+ model_name="historicaldmreferralfollowup",
123
+ name="related_action_identifier",
124
+ field=models.CharField(
125
+ blank=True,
126
+ help_text="action identifier that links to related reference model instance.",
127
+ max_length=30,
128
+ null=True,
129
+ ),
130
+ ),
131
+ migrations.AddField(
132
+ model_name="historicaldmreferralfollowup",
133
+ name="related_action_item",
134
+ field=models.ForeignKey(
135
+ blank=True,
136
+ db_constraint=False,
137
+ null=True,
138
+ on_delete=django.db.models.deletion.DO_NOTHING,
139
+ related_name="+",
140
+ to="edc_action_item.actionitem",
141
+ ),
142
+ ),
143
+ ]
@@ -0,0 +1,54 @@
1
+ # Generated by Django 4.2.11 on 2024-04-04 14:18
2
+
3
+ from django.conf import settings
4
+ from django.db import migrations
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("sites", "0002_alter_domain_unique"),
11
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12
+ ("edc_action_item", "0037_remove_actionitem_reference_model_and_more"),
13
+ ("meta_lists", "0018_missedreferralreasons"),
14
+ ("meta_subject", "0181_dmreferralfollowup_action_identifier_and_more"),
15
+ ]
16
+
17
+ operations = [
18
+ migrations.RenameModel(
19
+ old_name="DmReferralFollowup",
20
+ new_name="DmFollowup",
21
+ ),
22
+ migrations.RenameModel(
23
+ old_name="HistoricalDmReferralFollowup",
24
+ new_name="HistoricalDmFollowup",
25
+ ),
26
+ migrations.AlterModelOptions(
27
+ name="dmfollowup",
28
+ options={
29
+ "default_manager_name": "objects",
30
+ "default_permissions": ("add", "change", "delete", "view", "export", "import"),
31
+ "verbose_name": "Diabetes follow-up after referral",
32
+ "verbose_name_plural": "Diabetes follow-up after referral",
33
+ },
34
+ ),
35
+ migrations.AlterModelOptions(
36
+ name="historicaldmfollowup",
37
+ options={
38
+ "get_latest_by": ("history_date", "history_id"),
39
+ "ordering": ("-history_date", "-history_id"),
40
+ "verbose_name": "historical Diabetes follow-up after referral",
41
+ "verbose_name_plural": "historical Diabetes follow-up after referral",
42
+ },
43
+ ),
44
+ migrations.RenameIndex(
45
+ model_name="dmfollowup",
46
+ new_name="meta_subjec_subject_dd09d5_idx",
47
+ old_name="meta_subjec_subject_cfb7da_idx",
48
+ ),
49
+ migrations.RenameIndex(
50
+ model_name="dmfollowup",
51
+ new_name="meta_subjec_subject_1313a7_idx",
52
+ old_name="meta_subjec_subject_257846_idx",
53
+ ),
54
+ ]
@@ -0,0 +1,31 @@
1
+ # Generated by Django 4.2.11 on 2024-04-05 20:19
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("meta_subject", "0182_rename_dmreferralfollowup_dmfollowup_and_more"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name="dmfollowup",
15
+ name="on_dm_medications",
16
+ field=models.CharField(
17
+ choices=[("Yes", "Yes"), ("No", "No"), ("N/A", "Not applicable")],
18
+ max_length=25,
19
+ verbose_name="Are you currently taking any drug therapy for diabetes?",
20
+ ),
21
+ ),
22
+ migrations.AlterField(
23
+ model_name="historicaldmfollowup",
24
+ name="on_dm_medications",
25
+ field=models.CharField(
26
+ choices=[("Yes", "Yes"), ("No", "No"), ("N/A", "Not applicable")],
27
+ max_length=25,
28
+ verbose_name="Are you currently taking any drug therapy for diabetes?",
29
+ ),
30
+ ),
31
+ ]