meta-edc 1.6.5__py3-none-any.whl → 1.6.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-edc
3
- Version: 1.6.5
3
+ Version: 1.6.6
4
4
  Summary: META Trial EDC (https://www.isrctn.com/ISRCTN76157257)
5
5
  Keywords: django,clinicedc,META EDC,EDC,clinical trials,META Trial,ISRCTN76157257
6
6
  Author: Erik van Widenfelt, Jonathan Willitts
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Requires-Dist: django==5.2.10
20
20
  Requires-Dist: celery>=5.6.2
21
- Requires-Dist: clinicedc==2.3.6
21
+ Requires-Dist: clinicedc==2.3.7
22
22
  Requires-Dist: edc-he>=1.5.2
23
23
  Requires-Dist: edc-microscopy>=1.4.2
24
24
  Requires-Dist: edc-mnsi>=1.4.0
@@ -92,8 +92,8 @@ Assuming you are logged into the account ``myaccount``:
92
92
  mkdir ~/edc && \
93
93
  cd ~/edc && \
94
94
  uv venv && \
95
- uv pip install -U meta-edc==1.6.4 && \
96
- wget https://raw.githubusercontent.com/meta-trial/meta-edc/1.6.4/manage.py && \
95
+ uv pip install -U meta-edc==1.6.5 && \
96
+ wget https://raw.githubusercontent.com/meta-trial/meta-edc/1.6.5/manage.py && \
97
97
  uv pip freeze | grep meta-edc
98
98
 
99
99
  Copy your ``.env`` file to ``~/.etc``.
@@ -143,7 +143,7 @@ From the above example:
143
143
 
144
144
  cd ~/edc && \
145
145
  uv venv --clear && \
146
- uv pip install -U meta-edc==1.6.4 && \
146
+ uv pip install -U meta-edc==1.6.5 && \
147
147
  wget -O manage.py https://raw.githubusercontent.com/meta-trial/meta-edc/1.1.10/manage.py && \
148
148
  uv pip freeze | grep meta-edc && \
149
149
  python manage.py check
@@ -262,7 +262,8 @@ meta_lists/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
262
262
  meta_lists/admin.py,sha256=_Ww9dkT8BiXwbBFAAUuFYjsWmrF22FEVyR_8E5FsH_g,2318
263
263
  meta_lists/admin_site.py,sha256=M4uorELumZDIKD8SHs-AEX1doQFqXhRy-eCcRWYEu9k,165
264
264
  meta_lists/apps.py,sha256=aegHpyrHw188UxhNVFd-v35HP9wO7J7G9BYmliOOE0c,278
265
- meta_lists/list_data.py,sha256=i9JsXMsWImB9ajvS31hyiDu2omtjwm0oOXAq0WZvrPw,10014
265
+ meta_lists/constants.py,sha256=t5IS9rwOKG1nd3Wda2FdUkkJI05Nc9CzXZeSnR4wvEw,42
266
+ meta_lists/list_data.py,sha256=VHZuzUJqvFTbc2jW_N2TKnpbhIY8Lx6cTYCkMcvChhg,10125
266
267
  meta_lists/migrations/0001_initial.py,sha256=87crhJGEUzUxe9TaNTzzKM6AjE3Pjw032PkhYrnRz9k,13881
267
268
  meta_lists/migrations/0002_auto_20191026_2231.py,sha256=lV7keU3h-MC7O_RJLYxSY_Gr2T7ROtCf0qm_WkS2s3g,4608
268
269
  meta_lists/migrations/0003_auto_20191102_0033.py,sha256=LkLUHM5zTj6vg3l1997g57uhncZPLHW-jrJgiUSe6rM,2555
@@ -748,8 +749,8 @@ meta_subject/admin/fields.py,sha256=wlKr_x8-PWL4urv6N0L4ti5yG3D7FnLy2U_hH6Baqzc,
748
749
  meta_subject/admin/fieldsets.py,sha256=NjFKDAMvzePa9TijR_-zTLsDbawfBQDuws2BzWDzeZQ,1516
749
750
  meta_subject/admin/followup_examination_admin.py,sha256=nNFQvW_zipgJCd1RDa2A2UOQkcPIq8cirwM2f3Ih0ZY,5278
750
751
  meta_subject/admin/followup_vitals_admin.py,sha256=a2bwBwFsQ1sMmnBgmeCOEv6wNCXuKeIqIwEZB-Lqt_E,2297
751
- meta_subject/admin/glucose_admin.py,sha256=76j7mGttcJEn4R-futfUt7LDiBFE3ffR_DnIqNzUwmE,3332
752
- meta_subject/admin/glucose_fbg_admin.py,sha256=O5GaNjBR0bZQcbbw1ZrgWHzcRCWRA8viNkOfwk1fVSk,2964
752
+ meta_subject/admin/glucose_admin.py,sha256=8LYeY66Se_2VT3HJuCUnjT10NOTlnvTr-9Ltd3QeRyA,4162
753
+ meta_subject/admin/glucose_fbg_admin.py,sha256=zxn3cwIg0JuIj_kzmE-NzfbLCxl9RncuDozTG-yqXlM,3431
753
754
  meta_subject/admin/health_economics/__init__.py,sha256=t0vFauE3auk7FESIvk5tFjc3-Jtshl9eiAJswQBG1gw,212
754
755
  meta_subject/admin/health_economics/health_economics_simple_admin.py,sha256=nL0CvjWQsFsR19ine14iItt9e8Px53Ln_tSppMN8UQ4,1053
755
756
  meta_subject/admin/health_economics/health_economics_update_admin.py,sha256=mtsd1fJ8yvTK1eol5f03fZ8jk1afeYKSxEydPGmma7w,3905
@@ -784,11 +785,11 @@ meta_subject/form_validators/dm_endpoint_form_validator.py,sha256=KKt2G1UVzeCgpK
784
785
  meta_subject/form_validators/dm_followup_form_validator.py,sha256=78vfIr6BuW5AASdUvjIPpocIgyRhMuxmh73jBVmP8Xk,7977
785
786
  meta_subject/form_validators/egfr_drop_notification_form_validator.py,sha256=RiSwH6ZTt_bWgeTWTtSR9YT58lp7ATKx0AI7CRHSeC0,526
786
787
  meta_subject/form_validators/followup_examination_form_validator.py,sha256=AQItUjh65R-tPGWIzcHuPbCvfkr6Rzhy2dOVyPD_gVA,4237
787
- meta_subject/form_validators/glucose_fbg_form_validator.py,sha256=xh76UCC854s0rB0WPyEEYlzOBlerQCdk-5xe5jiXxrE,3103
788
- meta_subject/form_validators/glucose_form_validator.py,sha256=mx8RnL-8Bq3qa1z3Jdyn352WvdhP6SQWB-SOqmkxxqA,1008
788
+ meta_subject/form_validators/glucose_fbg_form_validator.py,sha256=GERc2U10EQx7R7tMljcM5L5wnCZLfUACn07Z5o4a6dg,3403
789
+ meta_subject/form_validators/glucose_form_validator.py,sha256=3giJ7TRPKmtr1oXYRUJq3qP_ZU4VejBHOjGXQVRAsaw,1316
789
790
  meta_subject/form_validators/health_economics_form_validator.py,sha256=kp-LQU4ydBXlnYFt-0xKuVSb8Y4irlhz9d_X8ppvWgE,250
790
791
  meta_subject/form_validators/hiv_exit_review_form_validator.py,sha256=9w3CHoBUkC4kWzpzGfbmsfULH-70NOegzh3IDJPEqDk,630
791
- meta_subject/form_validators/mixins.py,sha256=6jNZzUyMiFrLmjodmzcQl69w6nNDBNgaF4819V7ZywM,3606
792
+ meta_subject/form_validators/mixins.py,sha256=5mhsCynXu11tKHf2SZ2Cw_IgNe1nSx8JRAfk6CPPPxw,3745
792
793
  meta_subject/forms/__init__.py,sha256=KOuMcL66-FN192az3dDSmzBb7X4r1RE2HM2zpv4PNbQ,2853
793
794
  meta_subject/forms/birth_outcomes_form.py,sha256=qqhZNQKiYk8C8CFebZTQB0hOm4ONrgkTG7hfuK7kW-o,374
794
795
  meta_subject/forms/blood_results/__init__.py,sha256=DYs2J30cgTPxvY9qDuKfr_kF_muoZ1iVmpu_I3wLpdM,528
@@ -1084,6 +1085,8 @@ meta_subject/migrations/0237_historicalhivexitreview_singleton_field_and_more.py
1084
1085
  meta_subject/migrations/0238_historicalhivexitreview_available_and_more.py,sha256=r2kSbcMSbKzQswJSfMP1CqRAZSdDayHjjQzsv6p-D3I,2900
1085
1086
  meta_subject/migrations/0239_glucosefbg_diagnostic_device_and_more.py,sha256=SIU3VMAIOwwvYtKOx6MmHlyAX6QnWi5VC9wtib-OVcI,1220
1086
1087
  meta_subject/migrations/0240_glucose_diagnostic_device_and_more.py,sha256=GzJJSbGU4cHvkRRZQyB94Gfiu8nKJyBJjZQkp2XTcig,2111
1088
+ meta_subject/migrations/0241_remove_glucose_diagnostic_device_and_more.py,sha256=ITanoKLlhkjn9jNuRM951ob5L9vb9av-3K6UpFHH9v0,1024
1089
+ meta_subject/migrations/0242_glucose_ogtt_diagnostic_device_and_more.py,sha256=cL7IfDqNkXdgPz7DIegsTKQtWowHX4U82Y4s1cpS3vE,3176
1087
1090
  meta_subject/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1088
1091
  meta_subject/model_mixins/__init__.py,sha256=2Dae7t4r2f3YubLevk_HH_GVTDrZREOpCDDMYthLV5U,522
1089
1092
  meta_subject/model_mixins/arv_history_model_mixin.py,sha256=Ync4oLFng1OQW3pVxNxbEhKplWVNpUY5BiXy47lv6pk,1573
@@ -1115,8 +1118,8 @@ meta_subject/models/egfr_drop_notification.py,sha256=LSA0s4klzQ69CwPDI_q89eQ49Xb
1115
1118
  meta_subject/models/eq5d3l.py,sha256=itRrbfzL7Jsg9u7ml0SXwsw4nMoMcNyG0-YCD2VeXB0,291
1116
1119
  meta_subject/models/followup_examination.py,sha256=ZNapm_-fofIQiWZtWbtVnmSY4NJ3OdktmY_16rVxvrs,8072
1117
1120
  meta_subject/models/followup_vitals.py,sha256=pBZt4xrw7eVUfHBRtotUh9orJdNjOGxSuNa6JeWQRqA,903
1118
- meta_subject/models/glucose.py,sha256=X4ujhc71RHINwFaoN1JrpwiGNkGOnJFQwr_u-WXaA-4,2526
1119
- meta_subject/models/glucose_fbg.py,sha256=T4jFKB3LeEZ0ujjMEH670-fHrWRSAmfpBgWM6PG17c4,2381
1121
+ meta_subject/models/glucose.py,sha256=gnjmJd8_E_pYq4rlYiXLvN_59mvz4Fd6JvRLJElXGhI,2925
1122
+ meta_subject/models/glucose_fbg.py,sha256=PPu9Nf7APN8LICkTFVs_8V_TR7asSdgylrOCxf33_jo,2448
1120
1123
  meta_subject/models/glucose_review.py,sha256=bCnSBe_C3LeBy1XrZpV9rv-hOk8onZ_Tl37t7dqm-sw,450
1121
1124
  meta_subject/models/health_economics/__init__.py,sha256=J557vTjzK1EvUAM4mx9zDVQ7Zr0W25stnWXz_zE2a-c,245
1122
1125
  meta_subject/models/health_economics/health_economics.py,sha256=lZE6sMmYQTPjz9QbXZ2rznlbgW61G0JYYIQ07IueJvw,9705
@@ -1158,11 +1161,11 @@ meta_visit_schedule/visit_schedules/phase_three/__init__.py,sha256=lnBC3j17W54TQ
1158
1161
  meta_visit_schedule/visit_schedules/phase_three/crfs.py,sha256=M1BTT-eB9B5xpElafhhcgd7N0uX8aisT1igMK0VTP2E,18690
1159
1162
  meta_visit_schedule/visit_schedules/phase_three/crfs_pregnancy.py,sha256=_I6IVLMC3-KlpI86eii790mlx-p2vxWxvNRKD927rXo,169
1160
1163
  meta_visit_schedule/visit_schedules/phase_three/requisitions.py,sha256=Noeki8zX2MCwQVmCCWfuOyh_aN4YC-cyhUmrAZP8d5g,4579
1161
- meta_visit_schedule/visit_schedules/phase_three/schedule.py,sha256=LO9HqV6r_LUEY_7RSAKk6e9FDnUSXA7U1plmj4xG-TE,8289
1164
+ meta_visit_schedule/visit_schedules/phase_three/schedule.py,sha256=pn57uzGRmY8kWZ2LaTm5Upj4UJQ13oEzp8lA4LTvuM4,8363
1162
1165
  meta_visit_schedule/visit_schedules/phase_three/schedule_dm_referral.py,sha256=q12p5wXc2D7lSiJVBnoSQw8Q3vL6uDx1t3PuDJO-Z-U,1735
1163
1166
  meta_visit_schedule/visit_schedules/phase_three/schedule_pregnancy.py,sha256=bEpbpCX3vfZmnsqudr0z8PU5kiaX4ws1sO5Im98Mo28,1106
1164
1167
  meta_visit_schedule/visit_schedules/phase_three/visit_schedule.py,sha256=ak4qazeKlpIlvpqrK9hDDO0fwWuWyvb4Ec-JU31IJxc,654
1165
- meta_edc-1.6.5.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
1166
- meta_edc-1.6.5.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
1167
- meta_edc-1.6.5.dist-info/METADATA,sha256=5hB7Sx67nWaeOpC-tD1v8e1o1WbvyRfZnDEZ1HXwd44,5260
1168
- meta_edc-1.6.5.dist-info/RECORD,,
1168
+ meta_edc-1.6.6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
1169
+ meta_edc-1.6.6.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
1170
+ meta_edc-1.6.6.dist-info/METADATA,sha256=XZhPDJHiDYTlTcCrhq6JEIUONHlJKrQxqiXgn0OFYXI,5260
1171
+ meta_edc-1.6.6.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ HEMACUE = "hemocue"
2
+ ACCUCHEK = "accuchek"
meta_lists/list_data.py CHANGED
@@ -5,6 +5,7 @@ from clinicedc_constants import (
5
5
  DIABETES,
6
6
  LTFU,
7
7
  NONE,
8
+ NOT_APPLICABLE,
8
9
  OTHER,
9
10
  PREGNANCY,
10
11
  TOXICITY,
@@ -13,6 +14,7 @@ from clinicedc_constants import (
13
14
  from edc_offstudy.constants import COMPLETED_FOLLOWUP, WITHDRAWAL
14
15
  from edc_transfer.constants import TRANSFERRED
15
16
 
17
+ from meta_lists.constants import ACCUCHEK, HEMACUE
16
18
  from meta_prn.constants import (
17
19
  CLINICAL_WITHDRAWAL,
18
20
  COMPLETED_FOLLOWUP_48,
@@ -241,8 +243,9 @@ list_data = {
241
243
  (CLINIC, "META Study Clinic"),
242
244
  ],
243
245
  "meta_lists.diagnosticdevices": [
244
- ("hemocue", "Hemocue"),
245
- ("accuchek", "Accu-Chek"),
246
+ (HEMACUE, "Hemocue"),
247
+ (ACCUCHEK, "Accu-chek"),
246
248
  (OTHER, "Other not listed"),
249
+ (NOT_APPLICABLE, "Not applicable"),
247
250
  ],
248
251
  }
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from clinicedc_constants import NOT_APPLICABLE
3
4
  from django.contrib import admin
4
5
  from django.contrib.admin import SimpleListFilter
5
6
  from django.template.loader import render_to_string
@@ -7,6 +8,9 @@ from django_audit_fields.admin import audit_fieldset_tuple
7
8
  from edc_crf.fieldset import crf_status_fieldset
8
9
  from edc_model_admin.history import SimpleHistoryAdmin
9
10
 
11
+ from meta_lists.constants import HEMACUE
12
+ from meta_lists.models import DiagnosticDevices
13
+
10
14
  from ..admin_site import meta_subject_admin
11
15
  from ..forms import GlucoseForm
12
16
  from ..models import Glucose
@@ -36,9 +40,9 @@ class GlucoseAdmin(CrfModelAdminMixin, SimpleHistoryAdmin):
36
40
  "fbg_performed",
37
41
  "fbg_not_performed_reason",
38
42
  "fbg_datetime",
43
+ "fbg_diagnostic_device",
39
44
  "fbg_value",
40
45
  "fbg_units",
41
- "diagnostic_device",
42
46
  )
43
47
  },
44
48
  ),
@@ -50,6 +54,7 @@ class GlucoseAdmin(CrfModelAdminMixin, SimpleHistoryAdmin):
50
54
  "ogtt_not_performed_reason",
51
55
  "ogtt_base_datetime",
52
56
  "ogtt_datetime",
57
+ "ogtt_diagnostic_device",
53
58
  "ogtt_value",
54
59
  "ogtt_units",
55
60
  )
@@ -78,7 +83,8 @@ class GlucoseAdmin(CrfModelAdminMixin, SimpleHistoryAdmin):
78
83
  "ogtt_performed": admin.VERTICAL,
79
84
  "ogtt_units": admin.VERTICAL,
80
85
  "endpoint_today": admin.VERTICAL,
81
- "diagnostic_device": admin.VERTICAL,
86
+ "fbg_diagnostic_device": admin.VERTICAL,
87
+ "ogtt_diagnostic_device": admin.VERTICAL,
82
88
  }
83
89
 
84
90
  def get_list_display(self, request) -> tuple[str, ...]:
@@ -104,3 +110,19 @@ class GlucoseAdmin(CrfModelAdminMixin, SimpleHistoryAdmin):
104
110
  @admin.display(description="OGTT", ordering="ogtt_value")
105
111
  def ogtt(self, obj):
106
112
  return obj.ogtt_value
113
+
114
+ def get_changeform_initial_data(self, request) -> dict:
115
+ initial_data = super().get_changeform_initial_data(request)
116
+ try:
117
+ obj = DiagnosticDevices.objects.get(name=HEMACUE)
118
+ except DiagnosticDevices.DoesNotExist:
119
+ pass
120
+ else:
121
+ initial_data["fbg_diagnostic_device"] = obj.id
122
+ try:
123
+ obj = DiagnosticDevices.objects.get(name=NOT_APPLICABLE)
124
+ except DiagnosticDevices.DoesNotExist:
125
+ pass
126
+ else:
127
+ initial_data["ogtt_diagnostic_device"] = obj.id
128
+ return initial_data
@@ -7,6 +7,9 @@ from django_audit_fields import audit_fieldset_tuple
7
7
  from edc_crf.fieldset import crf_status_fieldset
8
8
  from edc_model_admin.history import SimpleHistoryAdmin
9
9
 
10
+ from meta_lists.constants import HEMACUE
11
+ from meta_lists.models import DiagnosticDevices
12
+
10
13
  from ..admin_site import meta_subject_admin
11
14
  from ..forms import GlucoseFbgForm
12
15
  from ..models import GlucoseFbg
@@ -36,9 +39,9 @@ class GlucoseFbgAdmin(CrfModelAdminMixin, SimpleHistoryAdmin):
36
39
  "fbg_performed",
37
40
  "fbg_not_performed_reason",
38
41
  "fbg_datetime",
42
+ "fbg_diagnostic_device",
39
43
  "fbg_value",
40
44
  "fbg_units",
41
- "diagnostic_device",
42
45
  )
43
46
  },
44
47
  ),
@@ -71,7 +74,7 @@ class GlucoseFbgAdmin(CrfModelAdminMixin, SimpleHistoryAdmin):
71
74
  "fbg_units": admin.VERTICAL,
72
75
  "fbg_performed": admin.VERTICAL,
73
76
  "endpoint_today": admin.VERTICAL,
74
- "diagnostic_device": admin.VERTICAL,
77
+ "fbg_diagnostic_device": admin.VERTICAL,
75
78
  }
76
79
 
77
80
  @admin.display(description="FBG", ordering="fbg_value")
@@ -90,3 +93,13 @@ class GlucoseFbgAdmin(CrfModelAdminMixin, SimpleHistoryAdmin):
90
93
  list_filter = list(list_filter)
91
94
  list_filter.insert(2, GlucoseListFilter)
92
95
  return tuple(list_filter)
96
+
97
+ def get_changeform_initial_data(self, request) -> dict:
98
+ initial_data = super().get_changeform_initial_data(request)
99
+ try:
100
+ obj = DiagnosticDevices.objects.get(name=HEMACUE)
101
+ except DiagnosticDevices.DoesNotExist:
102
+ pass
103
+ else:
104
+ initial_data["fbg_diagnostic_device"] = obj.id
105
+ return initial_data
@@ -25,13 +25,21 @@ class GlucoseFbgFormValidator(
25
25
  {"fbg_datetime": "Invalid. Must be on or after report date above"},
26
26
  INVALID_ERROR,
27
27
  )
28
+ self.applicable_if(
29
+ YES, field="fbg_performed", field_applicable="fbg_diagnostic_device"
30
+ )
28
31
  self.required_if(YES, field="fbg_performed", field_required="fbg_value")
29
32
 
30
33
  self.applicable_if(YES, field="fbg_performed", field_applicable="fbg_units")
31
34
 
32
35
  # endpoint
33
- self.applicable_if(YES, field="fbg_performed", field_applicable="endpoint_today")
34
- self.validate_endpoint_fields()
36
+ self.applicable_if(
37
+ YES,
38
+ field="fbg_performed",
39
+ field_applicable="endpoint_today",
40
+ not_applicable_msg="This field is not applicable. Not performed",
41
+ )
42
+ self.validate_endpoint_fields(performed=self.cleaned_data.get("fbg_value") is not None)
35
43
 
36
44
  # repeat_fbg_date
37
45
  self.required_if(PENDING, field="endpoint_today", field_required="repeat_fbg_date")
@@ -1,4 +1,4 @@
1
- from clinicedc_constants import PENDING
1
+ from clinicedc_constants import PENDING, YES
2
2
  from edc_crf.crf_form_validator import CrfFormValidator
3
3
  from edc_glucose.form_validators import FbgOgttFormValidatorMixin
4
4
 
@@ -13,7 +13,8 @@ class GlucoseFormValidator(
13
13
  CrfFormValidator,
14
14
  ):
15
15
  def clean(self):
16
- self.validate_glucose_testing_matrix()
16
+ self.required_if(YES, field="fasting", field_required="fasting_duration_str")
17
+ self.validate_glucose_testing_matrix(include_fbg=True)
17
18
  has_results = (
18
19
  self.cleaned_data.get("fbg_value") is not None
19
20
  and self.cleaned_data.get("ogtt_value") is not None
@@ -21,6 +22,11 @@ class GlucoseFormValidator(
21
22
  )
22
23
  self.applicable_if_true(has_results, field_applicable="endpoint_today")
23
24
  if has_results:
24
- self.validate_endpoint_fields()
25
+ self.validate_endpoint_fields(
26
+ performed=bool(
27
+ self.cleaned_data.get("fbg_value") is not None
28
+ or self.cleaned_data.get("ogtt_value") is not None
29
+ )
30
+ )
25
31
  self.required_if(PENDING, field="endpoint_today", field_required="repeat_fbg_date")
26
32
  self.validate_repeat_fbg_date()
@@ -7,33 +7,34 @@ from meta_reports.models import GlucoseSummary
7
7
 
8
8
 
9
9
  class EndpointValidatorMixin:
10
- def validate_endpoint_fields(self):
11
- is_endpoint = self.is_endpoint()
12
- if is_endpoint == YES and self.cleaned_data.get("endpoint_today") != YES:
13
- self.raise_validation_error(
14
- {
15
- "endpoint_today": (
16
- "Participant has reached a study endpoint today. Expected YES"
17
- )
18
- },
19
- INVALID_ERROR,
20
- )
21
- elif is_endpoint == PENDING and self.cleaned_data.get("endpoint_today") != PENDING:
22
- self.raise_validation_error(
23
- {
24
- "endpoint_today": (
25
- "Participant has not reached a study endpoint today. "
26
- "Expected to repeat FBG"
27
- )
28
- },
29
- INVALID_ERROR,
30
- )
10
+ def validate_endpoint_fields(self, performed: bool):
11
+ if performed:
12
+ is_endpoint = self.is_endpoint()
13
+ if is_endpoint == YES and self.cleaned_data.get("endpoint_today") != YES:
14
+ self.raise_validation_error(
15
+ {
16
+ "endpoint_today": (
17
+ "Participant has reached a study endpoint today. Expected YES"
18
+ )
19
+ },
20
+ INVALID_ERROR,
21
+ )
22
+ elif is_endpoint == PENDING and self.cleaned_data.get("endpoint_today") != PENDING:
23
+ self.raise_validation_error(
24
+ {
25
+ "endpoint_today": (
26
+ "Participant has not reached a study endpoint today. "
27
+ "Expected to repeat FBG"
28
+ )
29
+ },
30
+ INVALID_ERROR,
31
+ )
31
32
 
32
- elif is_endpoint == NO and self.cleaned_data.get("endpoint_today") != NO:
33
- self.raise_validation_error(
34
- {"endpoint_today": "Participant has not reached a study endpoint today"},
35
- INVALID_ERROR,
36
- )
33
+ elif is_endpoint == NO and self.cleaned_data.get("endpoint_today") != NO:
34
+ self.raise_validation_error(
35
+ {"endpoint_today": "Participant has not reached a study endpoint today"},
36
+ INVALID_ERROR,
37
+ )
37
38
 
38
39
  def is_endpoint(self):
39
40
  value = NO
@@ -0,0 +1,35 @@
1
+ # Generated by Django 5.2.10 on 2026-01-23 13:40
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("meta_lists", "0021_diagnosticdevices"),
11
+ ("meta_subject", "0240_glucose_diagnostic_device_and_more"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.RenameField(
16
+ model_name="glucose",
17
+ old_name="diagnostic_device",
18
+ new_name="fbg_diagnostic_device",
19
+ ),
20
+ migrations.RenameField(
21
+ model_name="glucosefbg",
22
+ old_name="diagnostic_device",
23
+ new_name="fbg_diagnostic_device",
24
+ ),
25
+ migrations.RenameField(
26
+ model_name="historicalglucose",
27
+ old_name="diagnostic_device",
28
+ new_name="fbg_diagnostic_device",
29
+ ),
30
+ migrations.RenameField(
31
+ model_name="historicalglucosefbg",
32
+ old_name="diagnostic_device",
33
+ new_name="fbg_diagnostic_device",
34
+ ),
35
+ ]
@@ -0,0 +1,86 @@
1
+ # Generated by Django 5.2.10 on 2026-01-23 14:00
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("meta_lists", "0021_diagnosticdevices"),
11
+ ("meta_subject", "0241_remove_glucose_diagnostic_device_and_more"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name="glucose",
17
+ name="ogtt_diagnostic_device",
18
+ field=models.ForeignKey(
19
+ limit_choices_to={"name__in": ["accuchek", "hemocue", "OTHER", "N/A"]},
20
+ null=True,
21
+ on_delete=django.db.models.deletion.PROTECT,
22
+ related_name="ogtt_diagnostic_device",
23
+ to="meta_lists.diagnosticdevices",
24
+ ),
25
+ ),
26
+ migrations.AddField(
27
+ model_name="historicalglucose",
28
+ name="ogtt_diagnostic_device",
29
+ field=models.ForeignKey(
30
+ blank=True,
31
+ db_constraint=False,
32
+ limit_choices_to={"name__in": ["accuchek", "hemocue", "OTHER", "N/A"]},
33
+ null=True,
34
+ on_delete=django.db.models.deletion.DO_NOTHING,
35
+ related_name="+",
36
+ to="meta_lists.diagnosticdevices",
37
+ ),
38
+ ),
39
+ migrations.AlterField(
40
+ model_name="glucose",
41
+ name="fbg_diagnostic_device",
42
+ field=models.ForeignKey(
43
+ limit_choices_to={"name__in": ["accuchek", "hemocue", "OTHER", "N/A"]},
44
+ null=True,
45
+ on_delete=django.db.models.deletion.PROTECT,
46
+ related_name="fbg_diagnostic_device",
47
+ to="meta_lists.diagnosticdevices",
48
+ ),
49
+ ),
50
+ migrations.AlterField(
51
+ model_name="glucosefbg",
52
+ name="fbg_diagnostic_device",
53
+ field=models.ForeignKey(
54
+ limit_choices_to={"name__in": ["accuchek", "hemocue", "OTHER", "N/A"]},
55
+ null=True,
56
+ on_delete=django.db.models.deletion.PROTECT,
57
+ to="meta_lists.diagnosticdevices",
58
+ ),
59
+ ),
60
+ migrations.AlterField(
61
+ model_name="historicalglucose",
62
+ name="fbg_diagnostic_device",
63
+ field=models.ForeignKey(
64
+ blank=True,
65
+ db_constraint=False,
66
+ limit_choices_to={"name__in": ["accuchek", "hemocue", "OTHER", "N/A"]},
67
+ null=True,
68
+ on_delete=django.db.models.deletion.DO_NOTHING,
69
+ related_name="+",
70
+ to="meta_lists.diagnosticdevices",
71
+ ),
72
+ ),
73
+ migrations.AlterField(
74
+ model_name="historicalglucosefbg",
75
+ name="fbg_diagnostic_device",
76
+ field=models.ForeignKey(
77
+ blank=True,
78
+ db_constraint=False,
79
+ limit_choices_to={"name__in": ["accuchek", "hemocue", "OTHER", "N/A"]},
80
+ null=True,
81
+ on_delete=django.db.models.deletion.DO_NOTHING,
82
+ related_name="+",
83
+ to="meta_lists.diagnosticdevices",
84
+ ),
85
+ ),
86
+ ]
@@ -9,6 +9,7 @@ from edc_glucose.model_mixin_factories import (
9
9
  from edc_model.models import BaseUuidModel
10
10
  from edc_utils import formatted_date
11
11
 
12
+ from meta_lists.constants import ACCUCHEK, HEMACUE
12
13
  from meta_lists.models import DiagnosticDevices
13
14
 
14
15
  from ..choices import ENDPOINT_CHOICES
@@ -48,12 +49,13 @@ class Glucose(
48
49
  verbose_name="If NO, provide reason", max_length=150, default="", blank=True
49
50
  )
50
51
 
51
- diagnostic_device = models.ForeignKey(
52
+ fbg_diagnostic_device = models.ForeignKey(
52
53
  DiagnosticDevices,
53
54
  on_delete=models.PROTECT,
54
55
  null=True,
55
56
  blank=False,
56
- limit_choices_to={"name__in": ["accuchek", "hemocue", OTHER]},
57
+ limit_choices_to={"name__in": [ACCUCHEK, HEMACUE, OTHER, NOT_APPLICABLE]},
58
+ related_name="fbg_diagnostic_device",
57
59
  )
58
60
 
59
61
  ogtt_performed = models.CharField(
@@ -66,6 +68,15 @@ class Glucose(
66
68
  verbose_name="If NO, provide reason", max_length=150, default="", blank=True
67
69
  )
68
70
 
71
+ ogtt_diagnostic_device = models.ForeignKey(
72
+ DiagnosticDevices,
73
+ on_delete=models.PROTECT,
74
+ null=True,
75
+ blank=False,
76
+ limit_choices_to={"name__in": [ACCUCHEK, HEMACUE, OTHER, NOT_APPLICABLE]},
77
+ related_name="ogtt_diagnostic_device",
78
+ )
79
+
69
80
  endpoint_today = models.CharField(
70
81
  verbose_name="Has the participant reached a study endpoint today?",
71
82
  max_length=25,
@@ -8,6 +8,7 @@ from edc_glucose.model_mixin_factories import (
8
8
  from edc_model.models import BaseUuidModel
9
9
  from edc_utils import formatted_date
10
10
 
11
+ from meta_lists.constants import ACCUCHEK, HEMACUE
11
12
  from meta_lists.models import DiagnosticDevices
12
13
 
13
14
  from ..choices import ENDPOINT_CHOICES
@@ -71,12 +72,12 @@ class GlucoseFbg(
71
72
  help_text="Date should be within 1 week of report date",
72
73
  )
73
74
 
74
- diagnostic_device = models.ForeignKey(
75
+ fbg_diagnostic_device = models.ForeignKey(
75
76
  DiagnosticDevices,
76
77
  on_delete=models.PROTECT,
77
78
  null=True,
78
79
  blank=False,
79
- limit_choices_to={"name__in": ["accuchek", "hemocue", OTHER]},
80
+ limit_choices_to={"name__in": [ACCUCHEK, HEMACUE, OTHER, NOT_APPLICABLE]},
80
81
  )
81
82
 
82
83
  class Meta(CrfModelMixin.Meta, BaseUuidModel.Meta):
@@ -291,6 +291,7 @@ visit36 = Visit(
291
291
  requisitions=requisitions_36m,
292
292
  crfs=crfs_36m,
293
293
  facility_name=FIVE_DAY_CLINIC,
294
+ allow_unscheduled_extended=True,
294
295
  )
295
296
 
296
297
  visit39 = Visit(
@@ -340,6 +341,7 @@ visit48 = Visit(
340
341
  requisitions=requisitions_48m,
341
342
  crfs=crfs_48m,
342
343
  facility_name=FIVE_DAY_CLINIC,
344
+ allow_unscheduled_extended=True,
343
345
  )
344
346
 
345
347
  visits = [