meta-edc 1.6.4__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.4
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.5
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.3 && \
96
- wget https://raw.githubusercontent.com/meta-trial/meta-edc/1.6.3/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.3 && \
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
@@ -259,10 +259,11 @@ meta_labs/list_data.py,sha256=Cbu8yBpogDno1B10MtC8HCXLRbtxqUGjLxuAjUbyLYg,473
259
259
  meta_labs/processing_profiles.py,sha256=fRX72FOl2rsnOJYhgup5QacfGSXKtWqn2gD3Giz2GDA,500
260
260
  meta_labs/reportables.py,sha256=DB8jyeTHWuRz3ZPCnevLjTuqnsSpFKsrgYJ0XDNVZzU,2201
261
261
  meta_lists/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
262
- meta_lists/admin.py,sha256=g05vpXfVIEBVG03aiPD8Vn-AnR7gL60MbPJAfzsO6R8,2157
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=5wP4B2tITPrfBXFfLRgD47BmNvzErBmDTBjWNzPNFtY,9865
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
@@ -283,8 +284,9 @@ meta_lists/migrations/0017_complications_dmmedications_dmtreatments_and_more.py,
283
284
  meta_lists/migrations/0018_missedreferralreasons.py,sha256=E_-Jrkwz_nAc3SND6EPdkWKeobi6q308J--mDM8JgHA,2639
284
285
  meta_lists/migrations/0019_auto_20250128_0143.py,sha256=NtS06qHmBQnoGZhZTR-OAfmoyllmMAyyTozXGl1JdbM,1577
285
286
  meta_lists/migrations/0020_alter_abnormalfootappearanceobservations_extra_value_and_more.py,sha256=exhYddnIS8101GWd7o_9Z_pqzU99KoYhuIgwu5INGWI,12991
287
+ meta_lists/migrations/0021_diagnosticdevices.py,sha256=S5_GodQ789ouStyZf08d4hEk-9lvYCli_8MCUx77fRE,2918
286
288
  meta_lists/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
287
- meta_lists/models.py,sha256=3amfYtGe4GGtSpLXs36On8k_ZmpDzNs5ga54O-SIABs,3032
289
+ meta_lists/models.py,sha256=Z12cgQl8lKSX9HiyOaov24ywpTh8ZIx1MHHGpinSSfs,3206
288
290
  meta_lists/urls.py,sha256=9NCrRvdkPXzH3L7VMCa0iQnz-j5tujzhvvtRRAGunV8,204
289
291
  meta_pharmacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
290
292
  meta_pharmacy/admin/__init__.py,sha256=oR5RGRlUqHN0kZMuW8j1n5f2e7akQMZR6hj9TuLlaTY,138
@@ -747,8 +749,8 @@ meta_subject/admin/fields.py,sha256=wlKr_x8-PWL4urv6N0L4ti5yG3D7FnLy2U_hH6Baqzc,
747
749
  meta_subject/admin/fieldsets.py,sha256=NjFKDAMvzePa9TijR_-zTLsDbawfBQDuws2BzWDzeZQ,1516
748
750
  meta_subject/admin/followup_examination_admin.py,sha256=nNFQvW_zipgJCd1RDa2A2UOQkcPIq8cirwM2f3Ih0ZY,5278
749
751
  meta_subject/admin/followup_vitals_admin.py,sha256=a2bwBwFsQ1sMmnBgmeCOEv6wNCXuKeIqIwEZB-Lqt_E,2297
750
- meta_subject/admin/glucose_admin.py,sha256=5K9rMFE8jCZRTJBsORJi0aKJF1xizh0303iwdJCrTLM,2902
751
- meta_subject/admin/glucose_fbg_admin.py,sha256=z9m9xGMnIgagqe-BWSRgUJngEN2OYbjKIsQva410BGE,2852
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
752
754
  meta_subject/admin/health_economics/__init__.py,sha256=t0vFauE3auk7FESIvk5tFjc3-Jtshl9eiAJswQBG1gw,212
753
755
  meta_subject/admin/health_economics/health_economics_simple_admin.py,sha256=nL0CvjWQsFsR19ine14iItt9e8Px53Ln_tSppMN8UQ4,1053
754
756
  meta_subject/admin/health_economics/health_economics_update_admin.py,sha256=mtsd1fJ8yvTK1eol5f03fZ8jk1afeYKSxEydPGmma7w,3905
@@ -783,11 +785,11 @@ meta_subject/form_validators/dm_endpoint_form_validator.py,sha256=KKt2G1UVzeCgpK
783
785
  meta_subject/form_validators/dm_followup_form_validator.py,sha256=78vfIr6BuW5AASdUvjIPpocIgyRhMuxmh73jBVmP8Xk,7977
784
786
  meta_subject/form_validators/egfr_drop_notification_form_validator.py,sha256=RiSwH6ZTt_bWgeTWTtSR9YT58lp7ATKx0AI7CRHSeC0,526
785
787
  meta_subject/form_validators/followup_examination_form_validator.py,sha256=AQItUjh65R-tPGWIzcHuPbCvfkr6Rzhy2dOVyPD_gVA,4237
786
- meta_subject/form_validators/glucose_fbg_form_validator.py,sha256=xh76UCC854s0rB0WPyEEYlzOBlerQCdk-5xe5jiXxrE,3103
787
- 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
788
790
  meta_subject/form_validators/health_economics_form_validator.py,sha256=kp-LQU4ydBXlnYFt-0xKuVSb8Y4irlhz9d_X8ppvWgE,250
789
791
  meta_subject/form_validators/hiv_exit_review_form_validator.py,sha256=9w3CHoBUkC4kWzpzGfbmsfULH-70NOegzh3IDJPEqDk,630
790
- meta_subject/form_validators/mixins.py,sha256=6jNZzUyMiFrLmjodmzcQl69w6nNDBNgaF4819V7ZywM,3606
792
+ meta_subject/form_validators/mixins.py,sha256=5mhsCynXu11tKHf2SZ2Cw_IgNe1nSx8JRAfk6CPPPxw,3745
791
793
  meta_subject/forms/__init__.py,sha256=KOuMcL66-FN192az3dDSmzBb7X4r1RE2HM2zpv4PNbQ,2853
792
794
  meta_subject/forms/birth_outcomes_form.py,sha256=qqhZNQKiYk8C8CFebZTQB0hOm4ONrgkTG7hfuK7kW-o,374
793
795
  meta_subject/forms/blood_results/__init__.py,sha256=DYs2J30cgTPxvY9qDuKfr_kF_muoZ1iVmpu_I3wLpdM,528
@@ -1081,6 +1083,10 @@ meta_subject/migrations/0235_glucosefbg_endpoint_today_and_more.py,sha256=ctq-Np
1081
1083
  meta_subject/migrations/0236_alter_historicalhivexitreview_other_current_arv_regimen_and_more.py,sha256=YU8IC-nONmzwV6jJ3P-f5jpp9HUEMyKKhRpWgl8HjHo,1883
1082
1084
  meta_subject/migrations/0237_historicalhivexitreview_singleton_field_and_more.py,sha256=V_6u5YMGCRYgwrwqJpQam53fZcYWHHWDRWBhPtTkTuw,2115
1083
1085
  meta_subject/migrations/0238_historicalhivexitreview_available_and_more.py,sha256=r2kSbcMSbKzQswJSfMP1CqRAZSdDayHjjQzsv6p-D3I,2900
1086
+ meta_subject/migrations/0239_glucosefbg_diagnostic_device_and_more.py,sha256=SIU3VMAIOwwvYtKOx6MmHlyAX6QnWi5VC9wtib-OVcI,1220
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
1084
1090
  meta_subject/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1085
1091
  meta_subject/model_mixins/__init__.py,sha256=2Dae7t4r2f3YubLevk_HH_GVTDrZREOpCDDMYthLV5U,522
1086
1092
  meta_subject/model_mixins/arv_history_model_mixin.py,sha256=Ync4oLFng1OQW3pVxNxbEhKplWVNpUY5BiXy47lv6pk,1573
@@ -1106,13 +1112,14 @@ meta_subject/models/delivery.py,sha256=IMMrTtWrMtBL2Kn8jJ4ACkGE0RYq4sAxym46zL0Bn
1106
1112
  meta_subject/models/diabetes/__init__.py,sha256=jJi8M56-D4jVx3Q6fkDqMWg9XfeJOw_l6Ge864OyNSA,112
1107
1113
  meta_subject/models/diabetes/dm_endpoint.py,sha256=DPfLru5u6wBbu_JiEyrRJRbmcF5nKox0U4A07dRNayY,1846
1108
1114
  meta_subject/models/diabetes/dm_followup.py,sha256=n1KqEkt38vxJDXVEsdffjD2A1YOxGM31m_HdgZWpuLc,6414
1115
+ meta_subject/models/diagnostic.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1109
1116
  meta_subject/models/diet_and_lifestyle.py,sha256=4QVNVyF813AC9Rp2xm3Gr3nRtHSZmCsuGx0PWOcMpuc,1061
1110
1117
  meta_subject/models/egfr_drop_notification.py,sha256=LSA0s4klzQ69CwPDI_q89eQ49XbcBOQ-Q21IEyXdgOk,557
1111
1118
  meta_subject/models/eq5d3l.py,sha256=itRrbfzL7Jsg9u7ml0SXwsw4nMoMcNyG0-YCD2VeXB0,291
1112
1119
  meta_subject/models/followup_examination.py,sha256=ZNapm_-fofIQiWZtWbtVnmSY4NJ3OdktmY_16rVxvrs,8072
1113
1120
  meta_subject/models/followup_vitals.py,sha256=pBZt4xrw7eVUfHBRtotUh9orJdNjOGxSuNa6JeWQRqA,903
1114
- meta_subject/models/glucose.py,sha256=3EJAIetRpUqJpx9VVIpZNNbOkwqkxGqnF8exZPZYgzU,2248
1115
- meta_subject/models/glucose_fbg.py,sha256=3itY4k8xITRz276UDTNL014KsgZi7lnl1_WBlxCJWEg,2103
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
1116
1123
  meta_subject/models/glucose_review.py,sha256=bCnSBe_C3LeBy1XrZpV9rv-hOk8onZ_Tl37t7dqm-sw,450
1117
1124
  meta_subject/models/health_economics/__init__.py,sha256=J557vTjzK1EvUAM4mx9zDVQ7Zr0W25stnWXz_zE2a-c,245
1118
1125
  meta_subject/models/health_economics/health_economics.py,sha256=lZE6sMmYQTPjz9QbXZ2rznlbgW61G0JYYIQ07IueJvw,9705
@@ -1154,11 +1161,11 @@ meta_visit_schedule/visit_schedules/phase_three/__init__.py,sha256=lnBC3j17W54TQ
1154
1161
  meta_visit_schedule/visit_schedules/phase_three/crfs.py,sha256=M1BTT-eB9B5xpElafhhcgd7N0uX8aisT1igMK0VTP2E,18690
1155
1162
  meta_visit_schedule/visit_schedules/phase_three/crfs_pregnancy.py,sha256=_I6IVLMC3-KlpI86eii790mlx-p2vxWxvNRKD927rXo,169
1156
1163
  meta_visit_schedule/visit_schedules/phase_three/requisitions.py,sha256=Noeki8zX2MCwQVmCCWfuOyh_aN4YC-cyhUmrAZP8d5g,4579
1157
- 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
1158
1165
  meta_visit_schedule/visit_schedules/phase_three/schedule_dm_referral.py,sha256=q12p5wXc2D7lSiJVBnoSQw8Q3vL6uDx1t3PuDJO-Z-U,1735
1159
1166
  meta_visit_schedule/visit_schedules/phase_three/schedule_pregnancy.py,sha256=bEpbpCX3vfZmnsqudr0z8PU5kiaX4ws1sO5Im98Mo28,1106
1160
1167
  meta_visit_schedule/visit_schedules/phase_three/visit_schedule.py,sha256=ak4qazeKlpIlvpqrK9hDDO0fwWuWyvb4Ec-JU31IJxc,654
1161
- meta_edc-1.6.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
1162
- meta_edc-1.6.4.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
1163
- meta_edc-1.6.4.dist-info/METADATA,sha256=xPE4Kg9X6_5hUYU41p1ooEJY9KbKnLZ128c6truDNpQ,5260
1164
- meta_edc-1.6.4.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,,
meta_lists/admin.py CHANGED
@@ -7,6 +7,7 @@ from .models import (
7
7
  BaselineSymptoms,
8
8
  Complications,
9
9
  DiabetesSymptoms,
10
+ DiagnosticDevices,
10
11
  DmMedications,
11
12
  DmTreatments,
12
13
  HealthcareWorkers,
@@ -82,3 +83,8 @@ class InvestigationsAdmin(ListModelAdminMixin, admin.ModelAdmin):
82
83
  @admin.register(HealthcareWorkers, site=meta_lists_admin)
83
84
  class HealthcareWorkersAdmin(ListModelAdminMixin, admin.ModelAdmin):
84
85
  pass
86
+
87
+
88
+ @admin.register(DiagnosticDevices, site=meta_lists_admin)
89
+ class DiagnosticDevicesAdmin(ListModelAdminMixin, admin.ModelAdmin):
90
+ pass
@@ -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,
@@ -240,4 +242,10 @@ list_data = {
240
242
  "edc_facility.healthfacilitytypes": [
241
243
  (CLINIC, "META Study Clinic"),
242
244
  ],
245
+ "meta_lists.diagnosticdevices": [
246
+ (HEMACUE, "Hemocue"),
247
+ (ACCUCHEK, "Accu-chek"),
248
+ (OTHER, "Other not listed"),
249
+ (NOT_APPLICABLE, "Not applicable"),
250
+ ],
243
251
  }
@@ -0,0 +1,88 @@
1
+ # Generated by Django 5.2.10 on 2026-01-22 19:43
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ (
10
+ "meta_lists",
11
+ "0020_alter_abnormalfootappearanceobservations_extra_value_and_more",
12
+ ),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.CreateModel(
17
+ name="DiagnosticDevices",
18
+ fields=[
19
+ (
20
+ "name",
21
+ models.CharField(
22
+ help_text="This is the stored value, required",
23
+ max_length=250,
24
+ unique=True,
25
+ verbose_name="Stored value",
26
+ ),
27
+ ),
28
+ (
29
+ "plural_name",
30
+ models.CharField(
31
+ default="", max_length=250, verbose_name="Plural name"
32
+ ),
33
+ ),
34
+ (
35
+ "display_name",
36
+ models.CharField(
37
+ help_text="(suggest 40 characters max.)",
38
+ max_length=250,
39
+ unique=True,
40
+ verbose_name="Name",
41
+ ),
42
+ ),
43
+ (
44
+ "display_index",
45
+ models.IntegerField(
46
+ default=0,
47
+ help_text="Index to control display order if not alphabetical, not required",
48
+ verbose_name="display index",
49
+ ),
50
+ ),
51
+ (
52
+ "field_name",
53
+ models.CharField(
54
+ blank=True,
55
+ default="",
56
+ editable=False,
57
+ help_text="Not required",
58
+ max_length=25,
59
+ ),
60
+ ),
61
+ ("extra_value", models.CharField(default="", max_length=250)),
62
+ (
63
+ "version",
64
+ models.CharField(default="1.0", editable=False, max_length=35),
65
+ ),
66
+ ("id", models.AutoField(primary_key=True, serialize=False)),
67
+ ],
68
+ options={
69
+ "verbose_name": "Diagnostic device",
70
+ "verbose_name_plural": "Diagnostic devices",
71
+ "abstract": False,
72
+ "default_permissions": (
73
+ "add",
74
+ "change",
75
+ "delete",
76
+ "view",
77
+ "export",
78
+ "import",
79
+ ),
80
+ "indexes": [
81
+ models.Index(
82
+ fields=["display_index", "display_name"],
83
+ name="meta_lists__display_e9a915_idx",
84
+ )
85
+ ],
86
+ },
87
+ ),
88
+ ]
meta_lists/models.py CHANGED
@@ -101,3 +101,9 @@ class MissedReferralReasons(ListModelMixin):
101
101
  class Meta(ListModelMixin.Meta):
102
102
  verbose_name = "Missed Referral Reasons"
103
103
  verbose_name_plural = "Missed Referral Reasons"
104
+
105
+
106
+ class DiagnosticDevices(ListModelMixin):
107
+ class Meta(ListModelMixin.Meta):
108
+ verbose_name = "Diagnostic device"
109
+ verbose_name_plural = "Diagnostic devices"
@@ -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,6 +40,7 @@ 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
46
  )
@@ -49,6 +54,7 @@ class GlucoseAdmin(CrfModelAdminMixin, SimpleHistoryAdmin):
49
54
  "ogtt_not_performed_reason",
50
55
  "ogtt_base_datetime",
51
56
  "ogtt_datetime",
57
+ "ogtt_diagnostic_device",
52
58
  "ogtt_value",
53
59
  "ogtt_units",
54
60
  )
@@ -77,18 +83,46 @@ class GlucoseAdmin(CrfModelAdminMixin, SimpleHistoryAdmin):
77
83
  "ogtt_performed": admin.VERTICAL,
78
84
  "ogtt_units": admin.VERTICAL,
79
85
  "endpoint_today": admin.VERTICAL,
86
+ "fbg_diagnostic_device": admin.VERTICAL,
87
+ "ogtt_diagnostic_device": admin.VERTICAL,
80
88
  }
81
89
 
82
90
  def get_list_display(self, request) -> tuple[str, ...]:
83
91
  list_display = super().get_list_display(request)
84
92
  list_display = list(list_display)
85
- list_display.insert(3, "ogtt_value")
93
+ list_display.insert(3, "ogtt")
86
94
  list_display.insert(3, "fbg_value")
95
+ list_display = [f for f in list_display if f != "__str__"]
87
96
  return tuple(list_display)
88
97
 
89
98
  def get_list_filter(self, request) -> tuple[str | type[SimpleListFilter], ...]:
90
99
  list_filter = super().get_list_filter(request)
91
100
  list_filter = list(list_filter)
101
+ list_filter.insert(2, "diagnostic_device")
92
102
  list_filter.insert(2, OgttListFilter)
93
103
  list_filter.insert(2, FbgListFilter)
94
104
  return tuple(list_filter)
105
+
106
+ @admin.display(description="FBG", ordering="fbg_value")
107
+ def fbg(self, obj):
108
+ return obj.fbg_value
109
+
110
+ @admin.display(description="OGTT", ordering="ogtt_value")
111
+ def ogtt(self, obj):
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,6 +39,7 @@ 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
45
  )
@@ -70,6 +74,7 @@ class GlucoseFbgAdmin(CrfModelAdminMixin, SimpleHistoryAdmin):
70
74
  "fbg_units": admin.VERTICAL,
71
75
  "fbg_performed": admin.VERTICAL,
72
76
  "endpoint_today": admin.VERTICAL,
77
+ "fbg_diagnostic_device": admin.VERTICAL,
73
78
  }
74
79
 
75
80
  @admin.display(description="FBG", ordering="fbg_value")
@@ -79,8 +84,8 @@ class GlucoseFbgAdmin(CrfModelAdminMixin, SimpleHistoryAdmin):
79
84
  def get_list_display(self, request) -> tuple[str, ...]:
80
85
  list_display = super().get_list_display(request)
81
86
  list_display = list(list_display)
82
- # list_display.insert(3, "ogtt")
83
87
  list_display.insert(3, "fbg")
88
+ list_display = [f for f in list_display if f != "__str__"]
84
89
  return tuple(list_display)
85
90
 
86
91
  def get_list_filter(self, request) -> tuple[str | type[SimpleListFilter], ...]:
@@ -88,3 +93,13 @@ class GlucoseFbgAdmin(CrfModelAdminMixin, SimpleHistoryAdmin):
88
93
  list_filter = list(list_filter)
89
94
  list_filter.insert(2, GlucoseListFilter)
90
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,39 @@
1
+ # Generated by Django 5.2.10 on 2026-01-22 19:43
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", "0238_historicalhivexitreview_available_and_more"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name="glucosefbg",
17
+ name="diagnostic_device",
18
+ field=models.ForeignKey(
19
+ blank=True,
20
+ limit_choices_to={"name__icontains": "FBG"},
21
+ null=True,
22
+ on_delete=django.db.models.deletion.PROTECT,
23
+ to="meta_lists.diagnosticdevices",
24
+ ),
25
+ ),
26
+ migrations.AddField(
27
+ model_name="historicalglucosefbg",
28
+ name="diagnostic_device",
29
+ field=models.ForeignKey(
30
+ blank=True,
31
+ db_constraint=False,
32
+ limit_choices_to={"name__icontains": "FBG"},
33
+ null=True,
34
+ on_delete=django.db.models.deletion.DO_NOTHING,
35
+ related_name="+",
36
+ to="meta_lists.diagnosticdevices",
37
+ ),
38
+ ),
39
+ ]
@@ -0,0 +1,61 @@
1
+ # Generated by Django 5.2.10 on 2026-01-23 01:46
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", "0239_glucosefbg_diagnostic_device_and_more"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name="glucose",
17
+ name="diagnostic_device",
18
+ field=models.ForeignKey(
19
+ limit_choices_to={"name__in": ["accuchek", "hemocue", "OTHER"]},
20
+ null=True,
21
+ on_delete=django.db.models.deletion.PROTECT,
22
+ to="meta_lists.diagnosticdevices",
23
+ ),
24
+ ),
25
+ migrations.AddField(
26
+ model_name="historicalglucose",
27
+ name="diagnostic_device",
28
+ field=models.ForeignKey(
29
+ blank=True,
30
+ db_constraint=False,
31
+ limit_choices_to={"name__in": ["accuchek", "hemocue", "OTHER"]},
32
+ null=True,
33
+ on_delete=django.db.models.deletion.DO_NOTHING,
34
+ related_name="+",
35
+ to="meta_lists.diagnosticdevices",
36
+ ),
37
+ ),
38
+ migrations.AlterField(
39
+ model_name="glucosefbg",
40
+ name="diagnostic_device",
41
+ field=models.ForeignKey(
42
+ limit_choices_to={"name__in": ["accuchek", "hemocue", "OTHER"]},
43
+ null=True,
44
+ on_delete=django.db.models.deletion.PROTECT,
45
+ to="meta_lists.diagnosticdevices",
46
+ ),
47
+ ),
48
+ migrations.AlterField(
49
+ model_name="historicalglucosefbg",
50
+ name="diagnostic_device",
51
+ field=models.ForeignKey(
52
+ blank=True,
53
+ db_constraint=False,
54
+ limit_choices_to={"name__in": ["accuchek", "hemocue", "OTHER"]},
55
+ null=True,
56
+ on_delete=django.db.models.deletion.DO_NOTHING,
57
+ related_name="+",
58
+ to="meta_lists.diagnosticdevices",
59
+ ),
60
+ ),
61
+ ]
@@ -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
+ ]
File without changes
@@ -1,4 +1,4 @@
1
- from clinicedc_constants import NOT_APPLICABLE
1
+ from clinicedc_constants import NOT_APPLICABLE, OTHER
2
2
  from django.db import models
3
3
  from edc_constants.choices import YES_NO
4
4
  from edc_glucose.model_mixin_factories import (
@@ -9,6 +9,9 @@ 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
13
+ from meta_lists.models import DiagnosticDevices
14
+
12
15
  from ..choices import ENDPOINT_CHOICES
13
16
  from ..constants import AMENDMENT_DATE
14
17
  from ..model_mixins import CrfModelMixin
@@ -46,6 +49,15 @@ class Glucose(
46
49
  verbose_name="If NO, provide reason", max_length=150, default="", blank=True
47
50
  )
48
51
 
52
+ fbg_diagnostic_device = models.ForeignKey(
53
+ DiagnosticDevices,
54
+ on_delete=models.PROTECT,
55
+ null=True,
56
+ blank=False,
57
+ limit_choices_to={"name__in": [ACCUCHEK, HEMACUE, OTHER, NOT_APPLICABLE]},
58
+ related_name="fbg_diagnostic_device",
59
+ )
60
+
49
61
  ogtt_performed = models.CharField(
50
62
  verbose_name="Was the OGTT test performed?",
51
63
  max_length=15,
@@ -56,6 +68,15 @@ class Glucose(
56
68
  verbose_name="If NO, provide reason", max_length=150, default="", blank=True
57
69
  )
58
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
+
59
80
  endpoint_today = models.CharField(
60
81
  verbose_name="Has the participant reached a study endpoint today?",
61
82
  max_length=25,
@@ -1,4 +1,4 @@
1
- from clinicedc_constants import NO, NOT_APPLICABLE
1
+ from clinicedc_constants import NO, NOT_APPLICABLE, OTHER
2
2
  from django.db import models
3
3
  from edc_constants.choices import YES_NO
4
4
  from edc_glucose.model_mixin_factories import (
@@ -8,6 +8,9 @@ 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
12
+ from meta_lists.models import DiagnosticDevices
13
+
11
14
  from ..choices import ENDPOINT_CHOICES
12
15
  from ..constants import AMENDMENT_DATE
13
16
  from ..model_mixins import CrfModelMixin
@@ -69,6 +72,14 @@ class GlucoseFbg(
69
72
  help_text="Date should be within 1 week of report date",
70
73
  )
71
74
 
75
+ fbg_diagnostic_device = models.ForeignKey(
76
+ DiagnosticDevices,
77
+ on_delete=models.PROTECT,
78
+ null=True,
79
+ blank=False,
80
+ limit_choices_to={"name__in": [ACCUCHEK, HEMACUE, OTHER, NOT_APPLICABLE]},
81
+ )
82
+
72
83
  class Meta(CrfModelMixin.Meta, BaseUuidModel.Meta):
73
84
  verbose_name = "Glucose (FBG)"
74
85
  verbose_name_plural = "Glucose (FBG)"
@@ -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 = [