clinicedc 2.0.8__py3-none-any.whl → 2.0.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of clinicedc might be problematic. Click here for more details.
- {clinicedc-2.0.8.dist-info → clinicedc-2.0.10.dist-info}/METADATA +5 -3
- {clinicedc-2.0.8.dist-info → clinicedc-2.0.10.dist-info}/RECORD +19 -17
- {clinicedc-2.0.8.dist-info → clinicedc-2.0.10.dist-info}/WHEEL +1 -1
- edc_auth/migrations/0036_alter_userprofile_alternate_email_and_more.py +88 -0
- edc_consent/models/signals.py +16 -13
- edc_consent/site_consents.py +15 -11
- edc_egfr/form_validator_mixins/egfr_form_validator_mixins.py +5 -4
- edc_form_validators/required_field_validator.py +14 -15
- edc_glucose/model_mixins/hba1c_model_mixin.py +1 -1
- edc_glucose/utils.py +2 -2
- edc_lab/lab/requisition_panel.py +10 -10
- edc_lab/migrations/0038_alter_aliquot_slug_alter_box_slug_alter_boxitem_slug_and_more.py +112 -0
- edc_lab_panel/panels.py +13 -13
- edc_lab_results/form_validator_mixins/blood_results_fbg_form_validator_mixin.py +3 -8
- edc_lab_results/form_validator_mixins/blood_results_form_validator_mixin.py +35 -32
- edc_model_admin/mixins/model_admin_form_instructions_mixin.py +4 -4
- edc_visit_schedule/baseline.py +4 -5
- edc_visit_schedule/site_visit_schedules.py +7 -10
- {clinicedc-2.0.8.dist-info → clinicedc-2.0.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clinicedc
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.10
|
|
4
4
|
Summary: A clinical trials data management framework built on Django
|
|
5
5
|
Keywords: django,clinicedc,edc,clinical trials,research,data management,esource
|
|
6
6
|
Author: Erik van Widenfelt, Jonathan Willitts
|
|
@@ -19,13 +19,14 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
20
|
Requires-Dist: argon2-cffi>=25.1.0
|
|
21
21
|
Requires-Dist: arrow>=1.3.0
|
|
22
|
+
Requires-Dist: beautifulsoup4>=4.13.5
|
|
22
23
|
Requires-Dist: boto3>=1.39.16
|
|
23
24
|
Requires-Dist: celery>=5.5.3
|
|
24
25
|
Requires-Dist: cryptography>=45.0.7
|
|
25
26
|
Requires-Dist: django>=5.2.4
|
|
26
27
|
Requires-Dist: django-admin-rangefilter>=0.13.3
|
|
27
|
-
Requires-Dist: django-audit-fields>=2.0.
|
|
28
|
-
Requires-Dist: django-crypto-fields>=1.0
|
|
28
|
+
Requires-Dist: django-audit-fields>=2.0.2
|
|
29
|
+
Requires-Dist: django-crypto-fields>=1.1.0
|
|
29
30
|
Requires-Dist: django-db-views>=0.1.11
|
|
30
31
|
Requires-Dist: django-defender>=0.9.8
|
|
31
32
|
Requires-Dist: django-environ>=0.12.0
|
|
@@ -43,6 +44,7 @@ Requires-Dist: gunicorn>=23.0.0
|
|
|
43
44
|
Requires-Dist: inflect>=7.5.0
|
|
44
45
|
Requires-Dist: mempass>=0.1.2
|
|
45
46
|
Requires-Dist: mysqlclient>=2.2.7
|
|
47
|
+
Requires-Dist: psycopg2>=2.9.10
|
|
46
48
|
Requires-Dist: pycups>=2.0.4
|
|
47
49
|
Requires-Dist: pypdf>=5.9.0
|
|
48
50
|
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
@@ -493,6 +493,7 @@ edc_auth/migrations/0032_userprofile_is_multisite_viewer.py,sha256=aCMDOZvjV4Bs5
|
|
|
493
493
|
edc_auth/migrations/0033_alter_userprofile_is_multisite_viewer.py,sha256=18aB8LKjdVdfDEKvQAbaI9QH5N2JGYID_nxQlLgENK4,602
|
|
494
494
|
edc_auth/migrations/0034_alter_edcpermissions_revision_alter_role_revision.py,sha256=nRIRrx4BM5GXLcQXflcHVIVz9RSqn8-QUx1w05UV6r8,1279
|
|
495
495
|
edc_auth/migrations/0035_alter_edcpermissions_device_created_and_more.py,sha256=kVPv2xUm9A2cqQeA6N__LilfDowUJc2ALt3JhdV9DkE,2685
|
|
496
|
+
edc_auth/migrations/0036_alter_userprofile_alternate_email_and_more.py,sha256=4C82rNciygIcwlop6yMz5g9-nVkx835mYae3Dikgfcg,2869
|
|
496
497
|
edc_auth/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
497
498
|
edc_auth/model_mixins.py,sha256=FIxp2r2UVo3BKWkRA1_HwjSBps7Og3FCVHy0ElWJRXY,475
|
|
498
499
|
edc_auth/models/__init__.py,sha256=yRAmD7GAhf2PQwD_G7PFzJ8G9TxubWOIhkoXQz8HHPE,390
|
|
@@ -563,9 +564,9 @@ edc_consent/modelform_mixins/consent_modelform_mixin/review_fields_modelform_mix
|
|
|
563
564
|
edc_consent/modelform_mixins/requires_consent_modelform_mixin.py,sha256=-rQAmOvl1fx8-lEUB6M4V7tqaTqDRxtid002EWYPIxM,2340
|
|
564
565
|
edc_consent/models/__init__.py,sha256=kTUrf6WlsKInX7jVuD3DyIqRW1OUaeSemflLLzWLV_I,278
|
|
565
566
|
edc_consent/models/edc_permissions.py,sha256=f9PWQ1ZFz4rKT7wFpfr8anQ7mV6sCdNaWNMimPLGae8,338
|
|
566
|
-
edc_consent/models/signals.py,sha256=
|
|
567
|
+
edc_consent/models/signals.py,sha256=3e_VTdCMlE0D1TteK-hk1YxNDbBgAr894SK2SD8a-cA,3148
|
|
567
568
|
edc_consent/navbars.py,sha256=Bqb2UfOCLL6yuT_fSvKYQymbBj7V6q9CwvmI-VdUmls,334
|
|
568
|
-
edc_consent/site_consents.py,sha256=
|
|
569
|
+
edc_consent/site_consents.py,sha256=jQYufDBievxoyOlC0Q1Llxv9xYqCEyk58Q1Fg_cu3mY,16882
|
|
569
570
|
edc_consent/stubs.py,sha256=ZXjNqT6nBHRFHCB5tCOzBC3ZdiiBR3IitygO3FldTZA,513
|
|
570
571
|
edc_consent/system_checks.py,sha256=SsBMKj9xP-9C-bntBfJLDFuYG1iGu1cBjvFDa8IGDJg,4642
|
|
571
572
|
edc_consent/templates/edc_consent/home.html,sha256=MKlGwcdiz224le0XHwXCsDIlTELnfc08-_VABEQl1Ag,1005
|
|
@@ -849,7 +850,7 @@ edc_egfr/calculators/percent_change.py,sha256=YVKho6PrqDdfdwV6Ndrzw_9fRJquCWxTlk
|
|
|
849
850
|
edc_egfr/constants.py,sha256=9L7NcsG-rbd-yg7TnW95XCF5vSL4BO7eaDQD4-mgD80,59
|
|
850
851
|
edc_egfr/egfr.py,sha256=1S8okz_ALH_hFfAA6BF77Oz8_f2RkrT9JK4Q4DqpWA8,9485
|
|
851
852
|
edc_egfr/form_validator_mixins/__init__.py,sha256=rWunR6OZW209YkY3dCjueiu19MSu4GNFVfmfVWT4imY,120
|
|
852
|
-
edc_egfr/form_validator_mixins/egfr_form_validator_mixins.py,sha256=
|
|
853
|
+
edc_egfr/form_validator_mixins/egfr_form_validator_mixins.py,sha256=PEagpNmiTNq70e3BrP7aMsg7cnCvib_MM7tOm72_XzY,1833
|
|
853
854
|
edc_egfr/get_drop_notification_model.py,sha256=E9_vSVtIXrjYau7nnS81HlRzpVWennH4vwcs19Mc42s,287
|
|
854
855
|
edc_egfr/get_egfr_for_subject.py,sha256=3ZAhuB8cfNOw1yW1NdMAsZe0l13aVTPYy335k-1MCUw,951
|
|
855
856
|
edc_egfr/model_mixins/__init__.py,sha256=a426yBWQvZro-ajuTfhRBh2-qJ1Mlb5VELDBw0cq4Cw,124
|
|
@@ -1065,7 +1066,7 @@ edc_form_validators/locale_validator.py,sha256=MztrfUcAKTwL3HfBRYMQQoXy1Acc9374m
|
|
|
1065
1066
|
edc_form_validators/many_to_many_field_validator.py,sha256=fp6B8RrcEvhFuvsuqLjmtVBZSFNkwunLmlEwuxHe7nc,14030
|
|
1066
1067
|
edc_form_validators/other_specify_field_validator.py,sha256=jqoO5ETt4zWNOLfJ2MglqOo4iwdrU0NXpY43O58nuTQ,2300
|
|
1067
1068
|
edc_form_validators/range_field_validator.py,sha256=0FQqzoduPM79YBhaEN975sEl5dF8eMg0vzFGsnBvdIY,1561
|
|
1068
|
-
edc_form_validators/required_field_validator.py,sha256=
|
|
1069
|
+
edc_form_validators/required_field_validator.py,sha256=sc2xYdEM_1W9rDdQZ6wbF1b0Jgz3qbRamHhIzuYkvNM,12001
|
|
1069
1070
|
edc_form_validators/test_case_mixin.py,sha256=C-okM31TYGWz_5lWRX1HYc0xcPX09gTgO6bO-2gkVho,2442
|
|
1070
1071
|
edc_form_validators/urls.py,sha256=_sVCnQeiAFRYKhxga3_DjoKj_4bgs1s2gjcynDiapvA,111
|
|
1071
1072
|
edc_glucose/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -1090,10 +1091,10 @@ edc_glucose/model_mixins/__init__.py,sha256=BBRpyYsEimvbgjVFpQr10THbaeuqz-EGROUf
|
|
|
1090
1091
|
edc_glucose/model_mixins/fasting_model_mixin.py,sha256=ZKE4ugOrIYEwVg5aorWM3FdXOdSYlMjR7TdGJTZwPM8,354
|
|
1091
1092
|
edc_glucose/model_mixins/fbg_model_mixin.py,sha256=NLCMuvG0QHWCMMXcOU5CnGPlLp_7F9SjZ6AyG8uJ-v4,246
|
|
1092
1093
|
edc_glucose/model_mixins/glucose_model_mixin.py,sha256=6KaNtNH7Rt5I-XAxmgFThcJiAtHiGy1OY0Q8eiJRDOY,437
|
|
1093
|
-
edc_glucose/model_mixins/hba1c_model_mixin.py,sha256=
|
|
1094
|
+
edc_glucose/model_mixins/hba1c_model_mixin.py,sha256=JpNd5D-2ePFvELXfV7aq4kOXP1SPgl8E7UiW2Ni3bqQ,974
|
|
1094
1095
|
edc_glucose/model_mixins/ogtt_model_mixin.py,sha256=O4EwgHyVTGnA0POdD95CDWJ6Mi6ylcI-XOnZSglXca8,251
|
|
1095
1096
|
edc_glucose/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1096
|
-
edc_glucose/utils.py,sha256=
|
|
1097
|
+
edc_glucose/utils.py,sha256=dP2CYSWmm_K2JpToAxECTluU2vgKWvFk4Y71YVNSBcs,1420
|
|
1097
1098
|
edc_identifier/__init__.py,sha256=kkhaH8PAtHEohoVF2VENZfZmXeKyAnHWJHS1NEWuLyM,97
|
|
1098
1099
|
edc_identifier/admin.py,sha256=LaKWYyBGNcRJ7yMNajKvVtj3JbcqqLSrizL3sWDf82g,1765
|
|
1099
1100
|
edc_identifier/admin_site.py,sha256=Aev8xku-hX60RlijUe5IJrpxEdiceLbnc4kdLN7uX3g,173
|
|
@@ -1191,7 +1192,7 @@ edc_lab/lab/lab_profile.py,sha256=048sJ-heOe6r8Bvyu945Gm_Fncm8e01Nvw0KWJ2HHfg,18
|
|
|
1191
1192
|
edc_lab/lab/manifest.py,sha256=3ndwq0YM4tjwbhRZU-WgA3Zd-WnuRAAvU5KXsqSxSPc,4024
|
|
1192
1193
|
edc_lab/lab/primary_aliquot.py,sha256=vO6eVsLcmbkmkgGlrNF9N2shPsOIS8tLzN6nzA5Kh6Q,1640
|
|
1193
1194
|
edc_lab/lab/processing_profile.py,sha256=DaJiWx4MAnPkIVOLpm454FvBt_PpzbYwf1qyXiuGh1Y,2390
|
|
1194
|
-
edc_lab/lab/requisition_panel.py,sha256=
|
|
1195
|
+
edc_lab/lab/requisition_panel.py,sha256=8M12H8NpRJb5EJqINjFKmEyRdRQCl2JmEL3qURL2A7g,3763
|
|
1195
1196
|
edc_lab/lab/requisition_panel_group.py,sha256=2nlqt3HXe7t4G5d5ZuQLzbR5-h51HYh3ExqygkfymBg,2098
|
|
1196
1197
|
edc_lab/lab/specimen.py,sha256=rrQKZ1MtjAT1rIcBm2GwbYitdwVabKweubfOVZ-v4_8,2949
|
|
1197
1198
|
edc_lab/lab/specimen_processor.py,sha256=K9S4-USt5UIgToRoiZ27oX3Tr3IOg4JDRherV5xC4oY,1922
|
|
@@ -1240,6 +1241,7 @@ edc_lab/migrations/0034_alter_aliquot_site_alter_box_site_and_more.py,sha256=d7b
|
|
|
1240
1241
|
edc_lab/migrations/0035_alter_aliquot_revision_alter_box_revision_and_more.py,sha256=H2LZjrIDjdNd9tn_8TKv-nYubrVdXxGKHBOjTEnJt_E,10647
|
|
1241
1242
|
edc_lab/migrations/0036_alter_aliquot_comment_alter_aliquot_device_created_and_more.py,sha256=hVWFOxl9FoIHfQEEU9fr3iF5avDlI9Bza_Nsd2iS0C0,38838
|
|
1242
1243
|
edc_lab/migrations/0037_alter_historicalorder_order_datetime_and_more.py,sha256=lXq1crGX0lYkf7Z0BFwUfbk7992enYGjjk91Jp5EFqw,928
|
|
1244
|
+
edc_lab/migrations/0038_alter_aliquot_slug_alter_box_slug_alter_boxitem_slug_and_more.py,sha256=fMJxiIOZlSxnkAM2uFjk1v0OrzgKpnvDh6G5lZBN-PY,3638
|
|
1243
1245
|
edc_lab/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1244
1246
|
edc_lab/model_mixins/__init__.py,sha256=E2gkvV8vy6dny8c-hhsAfIECBJPmiWfcJ0OpbAilQ4s,555
|
|
1245
1247
|
edc_lab/model_mixins/aliquot/__init__.py,sha256=3OmzEYhriUczNup4QTQnpa2430Pc74v5v5otNU7WTAw,291
|
|
@@ -1401,7 +1403,7 @@ edc_lab_dashboard/wsgi.py,sha256=B308vdF9JvIhT5OFCQFvq8pslX8Bz5XWybEcAACYDz8,178
|
|
|
1401
1403
|
edc_lab_panel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1402
1404
|
edc_lab_panel/apps.py,sha256=wBQgzF8013iMzxN-oQyiT9a6bbhJtXN8F_3xvflRgIQ,197
|
|
1403
1405
|
edc_lab_panel/constants.py,sha256=1hcrrKuzDBvtDk5bHZYKUKUB91wYBRHzWJdYIzJDHmM,638
|
|
1404
|
-
edc_lab_panel/panels.py,sha256=
|
|
1406
|
+
edc_lab_panel/panels.py,sha256=qGC-NRfwV6fNobPD5xOUo8AVX_3BCtLGs6AHxggmxOQ,3087
|
|
1405
1407
|
edc_lab_panel/processing_profiles.py,sha256=uBUVicajUXum1QT10agRJviKYylchPZdklQSmVJ_ueM,910
|
|
1406
1408
|
edc_lab_results/__init__.py,sha256=LLcNECzNbJLM2UwhCpWao4IueS3AJVoIIMKBme54pkg,277
|
|
1407
1409
|
edc_lab_results/action_items.py,sha256=T1zQBA5pxDURAwdWqctG0CjuuvfAltq71kItMPgap-0,3864
|
|
@@ -1413,8 +1415,8 @@ edc_lab_results/calculate_missing.py,sha256=mnSCszRLetffXVkCcVZkF5sxsp_1WjHRMzWm
|
|
|
1413
1415
|
edc_lab_results/constants.py,sha256=ZpvT3JboYvywvw2hMn2loEsdUHVWlueP2IzvMZzAdgA,505
|
|
1414
1416
|
edc_lab_results/fieldsets.py,sha256=0wGFo1X7EXXY0zbJ1mozFhed2f_tQbCxVLafojrJhW4,3906
|
|
1415
1417
|
edc_lab_results/form_validator_mixins/__init__.py,sha256=GLCQS8ZO6m-OsuIysw9oqsrDxhGqk8wIcxYcb5VPBBg,251
|
|
1416
|
-
edc_lab_results/form_validator_mixins/blood_results_fbg_form_validator_mixin.py,sha256=
|
|
1417
|
-
edc_lab_results/form_validator_mixins/blood_results_form_validator_mixin.py,sha256
|
|
1418
|
+
edc_lab_results/form_validator_mixins/blood_results_fbg_form_validator_mixin.py,sha256=DsDoc4BvJE89LBcTf9sckJ9LXSdgw54jHJlIxvP_VMM,735
|
|
1419
|
+
edc_lab_results/form_validator_mixins/blood_results_form_validator_mixin.py,sha256=VO0iDEAte_3cRjuwcVaslVuT6gSDotTZxxQUeOUa7M8,4509
|
|
1418
1420
|
edc_lab_results/form_validator_mixins/blood_results_glu_form_validator_mixin.py,sha256=vojao9i1l5DH4t99GM_d76LAeiaAb1l2VA-i2O0gtDI,173
|
|
1419
1421
|
edc_lab_results/get_summary.py,sha256=D8vJARIBacVMaTGBhWk2N4lR0lcdEPuM-GqZQZ2rKXs,2116
|
|
1420
1422
|
edc_lab_results/model_mixin_factories/__init__.py,sha256=zueOK5TVko7qbmQN5xsI7D-WjIp8mx5LLI9M2ZdbC-g,408
|
|
@@ -1803,7 +1805,7 @@ edc_model_admin/mixins/inlines/stacked_inline_modeladmin_mixin.py,sha256=mtSEOgR
|
|
|
1803
1805
|
edc_model_admin/mixins/inlines/tabular_inline_mixin.py,sha256=GY83Zm0NzdAAz7aDNKZLmwHc6pujEbl6G4BaUi26P4U,529
|
|
1804
1806
|
edc_model_admin/mixins/model_admin_bypass_default_form_cls_mixin.py,sha256=VNjKt87aoOrEYEuGDHhIJRAlSiitxp6TH4MQEs-ex4s,5914
|
|
1805
1807
|
edc_model_admin/mixins/model_admin_form_auto_number_mixin.py,sha256=zDMoUAi2FQHygPjbXqnQuTQkBQ3Vgo3lATbrV7Bdgc4,1499
|
|
1806
|
-
edc_model_admin/mixins/model_admin_form_instructions_mixin.py,sha256=
|
|
1808
|
+
edc_model_admin/mixins/model_admin_form_instructions_mixin.py,sha256=KDMbIvmtRQbf7jczmyMTS68A9Gkosjj3IqnKSq9WUhI,2889
|
|
1807
1809
|
edc_model_admin/mixins/model_admin_hide_delete_button_on_condition.py,sha256=KHZjtAsL_gvBvmj46ZFvz-loRnEofOD256g-iercoxo,884
|
|
1808
1810
|
edc_model_admin/mixins/model_admin_institution_mixin.py,sha256=iqWFcHqZVyTICbfL_SLXRTC1NI97lfcLonK_hV_eQdM,150
|
|
1809
1811
|
edc_model_admin/mixins/model_admin_limit_to_selected_foreignkey.py,sha256=I2BnfcJfNa-UosdLObFMcD451-lmwbzwcZ_iAdjvA3s,1245
|
|
@@ -3117,7 +3119,7 @@ edc_visit_schedule/admin/visit_schedule_admin.py,sha256=3E3QF4cNtiS9dgf4b9A911ZA
|
|
|
3117
3119
|
edc_visit_schedule/admin_site.py,sha256=y_1rTzAGI1aG9W8LK_s77axiXRwffdqwVltP09XwAhs,187
|
|
3118
3120
|
edc_visit_schedule/apps.py,sha256=mibLBV26LU352_ELqBNnafobUyRKhq2UFFRV_rabTyA,317
|
|
3119
3121
|
edc_visit_schedule/auths.py,sha256=F498GY111anDlK2g4-5_q7qF5MEcJ8bE5OCnimQZhSU,233
|
|
3120
|
-
edc_visit_schedule/baseline.py,sha256=
|
|
3122
|
+
edc_visit_schedule/baseline.py,sha256=ZXwB8xdT1xFtoyFZ9k_anGUq1mJC6fTOOBu4vu5L8iY,3346
|
|
3121
3123
|
edc_visit_schedule/choices.py,sha256=feq0GogDxFn_MyrnNcynlWeq5QTrXeJOEHA23cRiccQ,304
|
|
3122
3124
|
edc_visit_schedule/constants.py,sha256=4vqZ7MI-4bu9wQ7n7R5hzX2uVwO0ZJZ34uUA3W8yypY,714
|
|
3123
3125
|
edc_visit_schedule/exceptions.py,sha256=ELecFg9kl435cLw354tL90ifQBLAXVclWuhKlqSj5k8,947
|
|
@@ -3177,7 +3179,7 @@ edc_visit_schedule/schedule/schedule.py,sha256=daKuzIx0sa9c4RjedXQtFFzx79bqdEvAJ
|
|
|
3177
3179
|
edc_visit_schedule/schedule/visit_collection.py,sha256=9OJKrLtx-H0eL9oo0dsRqYGPICiAMqEF4xIhhlinx0A,2033
|
|
3178
3180
|
edc_visit_schedule/schedule/window.py,sha256=P_hNhAXBmRxED_0UrlpQoYHF5r_nTEpTK7U2JSAnpCs,5222
|
|
3179
3181
|
edc_visit_schedule/simple_model_validator.py,sha256=pE2QCSI1VhSr9Dd-NGd-72u6e479_vzbS8iL17eg4Yc,771
|
|
3180
|
-
edc_visit_schedule/site_visit_schedules.py,sha256=
|
|
3182
|
+
edc_visit_schedule/site_visit_schedules.py,sha256=nFeazdSDJNSvv0RWl9uTs9ZBiC7kFrGKiZY2w-HRfMY,13207
|
|
3181
3183
|
edc_visit_schedule/subject_schedule.py,sha256=WCLzZquQ0bI3O_eMoHAg94adQ07rYt812gNuiemLZwU,16520
|
|
3182
3184
|
edc_visit_schedule/system_checks.py,sha256=mh38YzVxisDe-aY6liqsu1w9F9yhJkWghKpV04JLH4U,9255
|
|
3183
3185
|
edc_visit_schedule/templates/edc_visit_schedule/home.html,sha256=RqtGsq6-zXi43jU0iSv24-xsIXXHkuiHjJvF-yqDyjA,1112
|
|
@@ -3291,7 +3293,7 @@ edc_vitals/models/fields/waist_circumference.py,sha256=fZcHFDdEwWLjIVLktKrFCD9UU
|
|
|
3291
3293
|
edc_vitals/models/fields/weight.py,sha256=zo9_9e3Cpu0UqoRbWS-iDkcDo6fK80b1dDQy4x4MyxE,921
|
|
3292
3294
|
edc_vitals/utils.py,sha256=vXid44KUXxeaSyund_y5MNXc5DFJs052_PwUAjszE2k,1384
|
|
3293
3295
|
edc_vitals/validators.py,sha256=vNiElWMs0rRnHRNuVoPLRf0rW_C_0xcfUyep1Y_Si5s,156
|
|
3294
|
-
clinicedc-2.0.
|
|
3295
|
-
clinicedc-2.0.
|
|
3296
|
-
clinicedc-2.0.
|
|
3297
|
-
clinicedc-2.0.
|
|
3296
|
+
clinicedc-2.0.10.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
3297
|
+
clinicedc-2.0.10.dist-info/WHEEL,sha256=pFCy50wRV2h7SjJ35YOsQUupaV45rMdgpNIvnXbG5bE,79
|
|
3298
|
+
clinicedc-2.0.10.dist-info/METADATA,sha256=0BK5tKtxTprFm8XoX3_RswrzNMpU2z5Qf8l4feyoftA,15886
|
|
3299
|
+
clinicedc-2.0.10.dist-info/RECORD,,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Generated by Django 5.2.6 on 2025-09-22 12:31
|
|
2
|
+
|
|
3
|
+
import django.core.validators
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("edc_auth", "0035_alter_edcpermissions_device_created_and_more"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AlterField(
|
|
15
|
+
model_name="userprofile",
|
|
16
|
+
name="alternate_email",
|
|
17
|
+
field=models.EmailField(
|
|
18
|
+
blank=True,
|
|
19
|
+
default="",
|
|
20
|
+
max_length=254,
|
|
21
|
+
verbose_name="Alternate email address",
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
migrations.AlterField(
|
|
25
|
+
model_name="userprofile",
|
|
26
|
+
name="clinic_label_printer",
|
|
27
|
+
field=models.CharField(
|
|
28
|
+
blank=True,
|
|
29
|
+
default="",
|
|
30
|
+
help_text='Change in <a href="/edc_label/">Edc Label Administration</a>',
|
|
31
|
+
max_length=100,
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
migrations.AlterField(
|
|
35
|
+
model_name="userprofile",
|
|
36
|
+
name="export_format",
|
|
37
|
+
field=models.CharField(
|
|
38
|
+
blank=True,
|
|
39
|
+
choices=[
|
|
40
|
+
("CSV", "CSV (delimited by pipe `|`)"),
|
|
41
|
+
(114, "Stata v10 or later"),
|
|
42
|
+
(117, "Stata v13 or later"),
|
|
43
|
+
(118, "Stata v14 or later"),
|
|
44
|
+
(119, "Stata v15 or later"),
|
|
45
|
+
],
|
|
46
|
+
default="CSV",
|
|
47
|
+
help_text="Note: requires export permissions",
|
|
48
|
+
max_length=25,
|
|
49
|
+
verbose_name="Export format",
|
|
50
|
+
),
|
|
51
|
+
),
|
|
52
|
+
migrations.AlterField(
|
|
53
|
+
model_name="userprofile",
|
|
54
|
+
name="job_title",
|
|
55
|
+
field=models.CharField(blank=True, default="", max_length=100),
|
|
56
|
+
),
|
|
57
|
+
migrations.AlterField(
|
|
58
|
+
model_name="userprofile",
|
|
59
|
+
name="lab_label_printer",
|
|
60
|
+
field=models.CharField(
|
|
61
|
+
blank=True,
|
|
62
|
+
default="",
|
|
63
|
+
help_text='Change in <a href="/edc_label/">Edc Label Administration</a>',
|
|
64
|
+
max_length=100,
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
migrations.AlterField(
|
|
68
|
+
model_name="userprofile",
|
|
69
|
+
name="mobile",
|
|
70
|
+
field=models.CharField(
|
|
71
|
+
blank=True,
|
|
72
|
+
default="",
|
|
73
|
+
help_text="e.g. +1234567890",
|
|
74
|
+
max_length=25,
|
|
75
|
+
validators=[django.core.validators.RegexValidator(regex="^\\+\\d+")],
|
|
76
|
+
),
|
|
77
|
+
),
|
|
78
|
+
migrations.AlterField(
|
|
79
|
+
model_name="userprofile",
|
|
80
|
+
name="print_server",
|
|
81
|
+
field=models.CharField(
|
|
82
|
+
blank=True,
|
|
83
|
+
default="",
|
|
84
|
+
help_text='Change in <a href="/edc_label/">Edc Label Administration</a>',
|
|
85
|
+
max_length=100,
|
|
86
|
+
),
|
|
87
|
+
),
|
|
88
|
+
]
|
edc_consent/models/signals.py
CHANGED
|
@@ -60,16 +60,19 @@ def requires_consent_on_pre_save(instance, raw, using, update_fields, **kwargs):
|
|
|
60
60
|
def update_appointment_from_consentext_post_save(
|
|
61
61
|
sender, instance, raw, created, using, **kwargs
|
|
62
62
|
):
|
|
63
|
-
if
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
63
|
+
if (
|
|
64
|
+
not raw
|
|
65
|
+
and not kwargs.get("update_fields")
|
|
66
|
+
and isinstance(instance, (ConsentExtensionModelMixin,))
|
|
67
|
+
):
|
|
68
|
+
cdef = site_consents.get_consent_definition(
|
|
69
|
+
model=instance.subject_consent._meta.label_lower,
|
|
70
|
+
version=instance.subject_consent.version,
|
|
71
|
+
)
|
|
72
|
+
visit_schedule, schedule = site_visit_schedules.get_by_consent_definition(cdef)
|
|
73
|
+
subject_schedule = SubjectSchedule(
|
|
74
|
+
instance.subject_consent.subject_identifier,
|
|
75
|
+
visit_schedule=visit_schedule,
|
|
76
|
+
schedule=schedule,
|
|
77
|
+
)
|
|
78
|
+
subject_schedule.refresh_appointments()
|
edc_consent/site_consents.py
CHANGED
|
@@ -182,7 +182,7 @@ class SiteConsents:
|
|
|
182
182
|
|
|
183
183
|
def get_consent_definition(
|
|
184
184
|
self,
|
|
185
|
-
model: str = None,
|
|
185
|
+
model: str | None = None,
|
|
186
186
|
report_datetime: datetime | None = None,
|
|
187
187
|
version: str | None = None,
|
|
188
188
|
site: SingleSite | None = None,
|
|
@@ -222,7 +222,7 @@ class SiteConsents:
|
|
|
222
222
|
|
|
223
223
|
def get_consent_definitions(
|
|
224
224
|
self,
|
|
225
|
-
model: str = None,
|
|
225
|
+
model: str | None = None,
|
|
226
226
|
report_datetime: datetime | None = None,
|
|
227
227
|
version: str | None = None,
|
|
228
228
|
site: SingleSite | None = None,
|
|
@@ -268,10 +268,11 @@ class SiteConsents:
|
|
|
268
268
|
def _filter_cdefs_by_model_or_raise(
|
|
269
269
|
model: str | None,
|
|
270
270
|
consent_definitions: list[ConsentDefinition],
|
|
271
|
-
errror_messages: list[str] = None,
|
|
271
|
+
errror_messages: list[str] | None = None,
|
|
272
272
|
attrname: str | None = None,
|
|
273
273
|
) -> tuple[list[ConsentDefinition], list[str]]:
|
|
274
274
|
attrname = attrname or "model"
|
|
275
|
+
errror_messages = errror_messages or []
|
|
275
276
|
cdefs = consent_definitions
|
|
276
277
|
if model:
|
|
277
278
|
cdefs = [
|
|
@@ -291,17 +292,17 @@ class SiteConsents:
|
|
|
291
292
|
def _filter_cdefs_by_screening_model_or_raise(
|
|
292
293
|
model: str | None,
|
|
293
294
|
consent_definitions: list[ConsentDefinition],
|
|
294
|
-
errror_messages: list[str] = None,
|
|
295
|
+
errror_messages: list[str] | None = None,
|
|
295
296
|
) -> tuple[list[ConsentDefinition], list[str]]:
|
|
297
|
+
errror_messages = errror_messages or []
|
|
296
298
|
cdefs = consent_definitions
|
|
297
299
|
if model:
|
|
298
300
|
cdefs = []
|
|
299
301
|
for cdef in consent_definitions:
|
|
300
302
|
if isinstance(cdef.screening_model, list):
|
|
301
303
|
for screening_model in cdef.screening_model:
|
|
302
|
-
if model == screening_model:
|
|
303
|
-
|
|
304
|
-
cdefs.append(cdef)
|
|
304
|
+
if model == screening_model and cdef not in cdefs:
|
|
305
|
+
cdefs.append(cdef)
|
|
305
306
|
elif model == cdef.screening_model:
|
|
306
307
|
cdefs.append(cdef)
|
|
307
308
|
if not cdefs:
|
|
@@ -311,12 +312,13 @@ class SiteConsents:
|
|
|
311
312
|
errror_messages.append(f"model={model}")
|
|
312
313
|
return cdefs, errror_messages
|
|
313
314
|
|
|
315
|
+
@staticmethod
|
|
314
316
|
def _filter_cdefs_by_report_datetime_or_raise(
|
|
315
|
-
self,
|
|
316
317
|
report_datetime: datetime | None,
|
|
317
318
|
consent_definitions: list[ConsentDefinition],
|
|
318
|
-
errror_messages: list[str] = None,
|
|
319
|
+
errror_messages: list[str] | None = None,
|
|
319
320
|
) -> tuple[list[ConsentDefinition], list[str]]:
|
|
321
|
+
errror_messages = errror_messages or []
|
|
320
322
|
cdefs = deepcopy(consent_definitions)
|
|
321
323
|
if report_datetime:
|
|
322
324
|
cdefs = [
|
|
@@ -340,8 +342,9 @@ class SiteConsents:
|
|
|
340
342
|
self,
|
|
341
343
|
version: str | None,
|
|
342
344
|
consent_definitions: list[ConsentDefinition],
|
|
343
|
-
errror_messages: list[str] = None,
|
|
345
|
+
errror_messages: list[str] | None = None,
|
|
344
346
|
) -> tuple[list[ConsentDefinition], list[str]]:
|
|
347
|
+
errror_messages = errror_messages or []
|
|
345
348
|
cdefs = consent_definitions
|
|
346
349
|
if version:
|
|
347
350
|
cdefs = [cdef for cdef in cdefs if cdef.version == version]
|
|
@@ -359,8 +362,9 @@ class SiteConsents:
|
|
|
359
362
|
self,
|
|
360
363
|
site: SingleSite | None,
|
|
361
364
|
consent_definitions: list[ConsentDefinition],
|
|
362
|
-
errror_messages: list[str] = None,
|
|
365
|
+
errror_messages: list[str] | None = None,
|
|
363
366
|
) -> list[ConsentDefinition]:
|
|
367
|
+
errror_messages = errror_messages or []
|
|
364
368
|
cdefs = consent_definitions
|
|
365
369
|
if site:
|
|
366
370
|
cdefs_copy = [cdef for cdef in consent_definitions]
|
|
@@ -35,10 +35,11 @@ class EgfrCkdEpiFormValidatorMixin:
|
|
|
35
35
|
class EgfrCockcroftGaultFormValidatorMixin:
|
|
36
36
|
def validate_egfr(
|
|
37
37
|
self,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
*,
|
|
39
|
+
gender: str,
|
|
40
|
+
age_in_years: int,
|
|
41
|
+
weight_in_kgs: float,
|
|
42
|
+
ethnicity: str,
|
|
42
43
|
):
|
|
43
44
|
opts = dict(
|
|
44
45
|
gender=gender,
|
|
@@ -3,7 +3,7 @@ from copy import copy
|
|
|
3
3
|
from django.db.models import QuerySet
|
|
4
4
|
from django.utils.translation import gettext_lazy as _
|
|
5
5
|
|
|
6
|
-
from edc_constants.constants import DWTA, NOT_APPLICABLE
|
|
6
|
+
from edc_constants.constants import DWTA, NOT_APPLICABLE, NULL_STRING
|
|
7
7
|
|
|
8
8
|
from .base_form_validator import (
|
|
9
9
|
NOT_REQUIRED_ERROR,
|
|
@@ -128,16 +128,13 @@ class RequiredFieldValidator(BaseFormValidator):
|
|
|
128
128
|
raise InvalidModelFormFieldValidator(errmsg)
|
|
129
129
|
if self.cleaned_data and field_required in self.cleaned_data:
|
|
130
130
|
if condition and (
|
|
131
|
-
self.cleaned_data.get(field_required)
|
|
132
|
-
or self.cleaned_data.get(field_required) == ""
|
|
133
|
-
or self.cleaned_data.get(field_required) == NOT_APPLICABLE
|
|
131
|
+
self.cleaned_data.get(field_required) in [None, NULL_STRING, NOT_APPLICABLE]
|
|
134
132
|
):
|
|
135
133
|
self.raise_required(field=field_required, msg=required_msg)
|
|
136
134
|
elif inverse and (
|
|
137
135
|
not condition
|
|
138
|
-
and self.cleaned_data.get(field_required)
|
|
139
|
-
|
|
140
|
-
and self.cleaned_data.get(field_required) != NOT_APPLICABLE
|
|
136
|
+
and self.cleaned_data.get(field_required)
|
|
137
|
+
not in [None, NULL_STRING, NOT_APPLICABLE]
|
|
141
138
|
):
|
|
142
139
|
self.raise_not_required(field=field_required, msg=not_required_msg)
|
|
143
140
|
return False
|
|
@@ -198,14 +195,17 @@ class RequiredFieldValidator(BaseFormValidator):
|
|
|
198
195
|
field_value = self.cleaned_data.get(field)
|
|
199
196
|
|
|
200
197
|
if field_required_evaluate_as_int:
|
|
201
|
-
field_required_has_value = self.cleaned_data.get(field_required)
|
|
198
|
+
field_required_has_value = self.cleaned_data.get(field_required) not in [
|
|
199
|
+
None,
|
|
200
|
+
NULL_STRING,
|
|
201
|
+
]
|
|
202
202
|
else:
|
|
203
203
|
field_required_has_value = bool(self.cleaned_data.get(field_required))
|
|
204
204
|
|
|
205
|
-
if field_value
|
|
205
|
+
if field_value not in [None, NULL_STRING] and not field_required_has_value:
|
|
206
206
|
self.raise_required(field=field_required, msg=required_msg)
|
|
207
207
|
elif (
|
|
208
|
-
field_value
|
|
208
|
+
field_value in [None, NULL_STRING]
|
|
209
209
|
and field_required_has_value
|
|
210
210
|
and self.cleaned_data.get(field_required) != NOT_APPLICABLE
|
|
211
211
|
and inverse
|
|
@@ -270,13 +270,12 @@ class RequiredFieldValidator(BaseFormValidator):
|
|
|
270
270
|
"""Required `b` if `a`; do not require `b` if not `a`"""
|
|
271
271
|
if is_instance_field:
|
|
272
272
|
self.update_cleaned_data_from_instance(field)
|
|
273
|
-
if (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
):
|
|
273
|
+
if self.cleaned_data.get(field) is not None and self.cleaned_data.get(
|
|
274
|
+
field_required
|
|
275
|
+
) in [None, NULL_STRING]:
|
|
277
276
|
self.raise_required(field=field_required, msg=required_msg)
|
|
278
277
|
elif (
|
|
279
|
-
self.cleaned_data.get(field)
|
|
278
|
+
self.cleaned_data.get(field) in [None, NULL_STRING]
|
|
280
279
|
and self.cleaned_data.get(field_required) is not None
|
|
281
280
|
):
|
|
282
281
|
self.raise_not_required(field=field_required, msg=required_msg)
|
edc_glucose/utils.py
CHANGED
|
@@ -10,7 +10,7 @@ from .constants import GLUCOSE_HIGH_READING
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def validate_glucose_as_millimoles_per_liter(
|
|
13
|
-
prefix: str
|
|
13
|
+
prefix: str, cleaned_data: dict
|
|
14
14
|
) -> None | Decimal:
|
|
15
15
|
converted_value = None
|
|
16
16
|
min_val = Decimal("0.00")
|
|
@@ -27,7 +27,7 @@ def validate_glucose_as_millimoles_per_liter(
|
|
|
27
27
|
units_to=MILLIMOLES_PER_LITER,
|
|
28
28
|
)
|
|
29
29
|
except ConversionNotHandled as e:
|
|
30
|
-
raise forms.ValidationError({f"{prefix}_units": str(e)})
|
|
30
|
+
raise forms.ValidationError({f"{prefix}_units": str(e)}) from e
|
|
31
31
|
if (
|
|
32
32
|
not (min_val <= round_half_away_from_zero(converted_value, 2) <= max_val)
|
|
33
33
|
and round_half_away_from_zero(converted_value, 2) != high_value
|
edc_lab/lab/requisition_panel.py
CHANGED
|
@@ -14,14 +14,14 @@ class RequisitionPanelLookupError(Exception):
|
|
|
14
14
|
pass
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class InvalidProcessingProfile(Exception):
|
|
17
|
+
class InvalidProcessingProfile(Exception): # noqa: N818
|
|
18
18
|
pass
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class PanelAttrs:
|
|
22
22
|
""" "A simple class of panel name attributes."""
|
|
23
23
|
|
|
24
|
-
def __init__(self, name: str
|
|
24
|
+
def __init__(self, name: str, alpha_code: str | None = None) -> None:
|
|
25
25
|
title = " ".join(name.split("_")).title()
|
|
26
26
|
alpha_code = alpha_code or ""
|
|
27
27
|
self.abbreviation = f"{name[0:2]}{name[-1:]}".upper()
|
|
@@ -38,13 +38,13 @@ class RequisitionPanel:
|
|
|
38
38
|
|
|
39
39
|
def __init__(
|
|
40
40
|
self,
|
|
41
|
-
name: str = None,
|
|
42
|
-
processing_profile: ProcessingProfile = None,
|
|
43
|
-
verbose_name: str = None,
|
|
44
|
-
abbreviation: str = None,
|
|
45
|
-
utest_ids:
|
|
46
|
-
is_poc=None,
|
|
47
|
-
reference_range_collection_name=None,
|
|
41
|
+
name: str | None = None,
|
|
42
|
+
processing_profile: ProcessingProfile | None = None,
|
|
43
|
+
verbose_name: str | None = None,
|
|
44
|
+
abbreviation: str | None = None,
|
|
45
|
+
utest_ids: tuple[str | tuple[str, ...], ...] | None = None,
|
|
46
|
+
is_poc: bool | str | None = None,
|
|
47
|
+
reference_range_collection_name: str | None = None,
|
|
48
48
|
) -> None:
|
|
49
49
|
self._panel_model_obj = None
|
|
50
50
|
self.name = name
|
|
@@ -95,7 +95,7 @@ class RequisitionPanel:
|
|
|
95
95
|
f"'{self.requisition_model}'. "
|
|
96
96
|
f"See {self!r} or the lab profile {self.lab_profile_name}."
|
|
97
97
|
f"Got {e}"
|
|
98
|
-
)
|
|
98
|
+
) from e
|
|
99
99
|
return requisition_model_cls
|
|
100
100
|
|
|
101
101
|
@property
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Generated by Django 5.2.6 on 2025-09-22 12:31
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("edc_lab", "0037_alter_historicalorder_order_datetime_and_more"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name="aliquot",
|
|
15
|
+
name="slug",
|
|
16
|
+
field=models.CharField(
|
|
17
|
+
db_index=True,
|
|
18
|
+
default="",
|
|
19
|
+
editable=False,
|
|
20
|
+
help_text="Hold slug field values for quick search. Excludes encrypted fields",
|
|
21
|
+
max_length=250,
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
migrations.AlterField(
|
|
25
|
+
model_name="box",
|
|
26
|
+
name="slug",
|
|
27
|
+
field=models.CharField(
|
|
28
|
+
db_index=True,
|
|
29
|
+
default="",
|
|
30
|
+
editable=False,
|
|
31
|
+
help_text="Hold slug field values for quick search. Excludes encrypted fields",
|
|
32
|
+
max_length=250,
|
|
33
|
+
),
|
|
34
|
+
),
|
|
35
|
+
migrations.AlterField(
|
|
36
|
+
model_name="boxitem",
|
|
37
|
+
name="slug",
|
|
38
|
+
field=models.CharField(
|
|
39
|
+
db_index=True,
|
|
40
|
+
default="",
|
|
41
|
+
editable=False,
|
|
42
|
+
help_text="Hold slug field values for quick search. Excludes encrypted fields",
|
|
43
|
+
max_length=250,
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
migrations.AlterField(
|
|
47
|
+
model_name="historicalaliquot",
|
|
48
|
+
name="slug",
|
|
49
|
+
field=models.CharField(
|
|
50
|
+
db_index=True,
|
|
51
|
+
default="",
|
|
52
|
+
editable=False,
|
|
53
|
+
help_text="Hold slug field values for quick search. Excludes encrypted fields",
|
|
54
|
+
max_length=250,
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
migrations.AlterField(
|
|
58
|
+
model_name="historicalbox",
|
|
59
|
+
name="slug",
|
|
60
|
+
field=models.CharField(
|
|
61
|
+
db_index=True,
|
|
62
|
+
default="",
|
|
63
|
+
editable=False,
|
|
64
|
+
help_text="Hold slug field values for quick search. Excludes encrypted fields",
|
|
65
|
+
max_length=250,
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
migrations.AlterField(
|
|
69
|
+
model_name="historicalboxitem",
|
|
70
|
+
name="slug",
|
|
71
|
+
field=models.CharField(
|
|
72
|
+
db_index=True,
|
|
73
|
+
default="",
|
|
74
|
+
editable=False,
|
|
75
|
+
help_text="Hold slug field values for quick search. Excludes encrypted fields",
|
|
76
|
+
max_length=250,
|
|
77
|
+
),
|
|
78
|
+
),
|
|
79
|
+
migrations.AlterField(
|
|
80
|
+
model_name="historicalmanifest",
|
|
81
|
+
name="slug",
|
|
82
|
+
field=models.CharField(
|
|
83
|
+
db_index=True,
|
|
84
|
+
default="",
|
|
85
|
+
editable=False,
|
|
86
|
+
help_text="Hold slug field values for quick search. Excludes encrypted fields",
|
|
87
|
+
max_length=250,
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
migrations.AlterField(
|
|
91
|
+
model_name="manifest",
|
|
92
|
+
name="slug",
|
|
93
|
+
field=models.CharField(
|
|
94
|
+
db_index=True,
|
|
95
|
+
default="",
|
|
96
|
+
editable=False,
|
|
97
|
+
help_text="Hold slug field values for quick search. Excludes encrypted fields",
|
|
98
|
+
max_length=250,
|
|
99
|
+
),
|
|
100
|
+
),
|
|
101
|
+
migrations.AlterField(
|
|
102
|
+
model_name="manifestitem",
|
|
103
|
+
name="slug",
|
|
104
|
+
field=models.CharField(
|
|
105
|
+
db_index=True,
|
|
106
|
+
default="",
|
|
107
|
+
editable=False,
|
|
108
|
+
help_text="Hold slug field values for quick search. Excludes encrypted fields",
|
|
109
|
+
max_length=250,
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
]
|
edc_lab_panel/panels.py
CHANGED
|
@@ -40,7 +40,7 @@ hba1c_panel = RequisitionPanel(
|
|
|
40
40
|
verbose_name="HbA1c (Venous)",
|
|
41
41
|
processing_profile=hba1c_processing,
|
|
42
42
|
abbreviation="HBA1C",
|
|
43
|
-
utest_ids=
|
|
43
|
+
utest_ids=(("hba1c", "HbA1c"),),
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
hba1c_poc_panel = RequisitionPanel(
|
|
@@ -48,7 +48,7 @@ hba1c_poc_panel = RequisitionPanel(
|
|
|
48
48
|
verbose_name="HbA1c (POC)",
|
|
49
49
|
abbreviation="HBA1C_POC",
|
|
50
50
|
processing_profile=poc_processing,
|
|
51
|
-
utest_ids=
|
|
51
|
+
utest_ids=(("hba1c", "HbA1c"),),
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
|
|
@@ -57,7 +57,7 @@ fbc_panel = RequisitionPanel(
|
|
|
57
57
|
verbose_name="Full Blood Count",
|
|
58
58
|
processing_profile=fbc_processing,
|
|
59
59
|
abbreviation="FBC",
|
|
60
|
-
utest_ids=
|
|
60
|
+
utest_ids=(
|
|
61
61
|
("haemoglobin", "Haemoglobin"),
|
|
62
62
|
"hct",
|
|
63
63
|
"rbc",
|
|
@@ -66,7 +66,7 @@ fbc_panel = RequisitionPanel(
|
|
|
66
66
|
"mcv",
|
|
67
67
|
"mch",
|
|
68
68
|
"mchc",
|
|
69
|
-
|
|
69
|
+
),
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
blood_glucose_panel = RequisitionPanel(
|
|
@@ -74,7 +74,7 @@ blood_glucose_panel = RequisitionPanel(
|
|
|
74
74
|
verbose_name="Blood Glucose (Venous)",
|
|
75
75
|
abbreviation="BGL",
|
|
76
76
|
processing_profile=blood_glucose_processing,
|
|
77
|
-
utest_ids=
|
|
77
|
+
utest_ids=(("glucose", "Glucose"),),
|
|
78
78
|
)
|
|
79
79
|
|
|
80
80
|
blood_glucose_poc_panel = RequisitionPanel(
|
|
@@ -82,7 +82,7 @@ blood_glucose_poc_panel = RequisitionPanel(
|
|
|
82
82
|
verbose_name="Blood Glucose (POC)",
|
|
83
83
|
abbreviation="BGL-POC",
|
|
84
84
|
processing_profile=poc_processing,
|
|
85
|
-
utest_ids=
|
|
85
|
+
utest_ids=(("glucose", "Glucose"),),
|
|
86
86
|
)
|
|
87
87
|
|
|
88
88
|
cd4_panel = RequisitionPanel(
|
|
@@ -90,14 +90,14 @@ cd4_panel = RequisitionPanel(
|
|
|
90
90
|
verbose_name="CD4",
|
|
91
91
|
abbreviation="CD4",
|
|
92
92
|
processing_profile=cd4_processing,
|
|
93
|
-
utest_ids=
|
|
93
|
+
utest_ids=("cd4",),
|
|
94
94
|
)
|
|
95
95
|
vl_panel = RequisitionPanel(
|
|
96
96
|
name=VL,
|
|
97
97
|
verbose_name="Viral Load",
|
|
98
98
|
abbreviation="VL",
|
|
99
99
|
processing_profile=vl_processing,
|
|
100
|
-
utest_ids=
|
|
100
|
+
utest_ids=("vl",),
|
|
101
101
|
)
|
|
102
102
|
|
|
103
103
|
|
|
@@ -106,7 +106,7 @@ rft_panel = RequisitionPanel(
|
|
|
106
106
|
verbose_name="Chemistry: Renal Function Tests",
|
|
107
107
|
abbreviation=RFT,
|
|
108
108
|
processing_profile=rft_processing,
|
|
109
|
-
utest_ids=
|
|
109
|
+
utest_ids=("urea", "creatinine", "uric_acid", "egfr", "egfr_drop"),
|
|
110
110
|
)
|
|
111
111
|
|
|
112
112
|
lipids_panel = RequisitionPanel(
|
|
@@ -114,7 +114,7 @@ lipids_panel = RequisitionPanel(
|
|
|
114
114
|
verbose_name="Chemistry: Lipids",
|
|
115
115
|
abbreviation=LIPIDS,
|
|
116
116
|
processing_profile=lipids_processing,
|
|
117
|
-
utest_ids=
|
|
117
|
+
utest_ids=(LDL, HDL, TRIG, CHOL),
|
|
118
118
|
)
|
|
119
119
|
|
|
120
120
|
lft_panel = RequisitionPanel(
|
|
@@ -122,7 +122,7 @@ lft_panel = RequisitionPanel(
|
|
|
122
122
|
verbose_name="Chemistry: Liver Function Tests",
|
|
123
123
|
abbreviation=LFT,
|
|
124
124
|
processing_profile=lft_processing,
|
|
125
|
-
utest_ids=
|
|
125
|
+
utest_ids=("ast", "alt", "alp", "amylase", "ggt", "albumin"),
|
|
126
126
|
)
|
|
127
127
|
|
|
128
128
|
insulin_panel = RequisitionPanel(
|
|
@@ -130,7 +130,7 @@ insulin_panel = RequisitionPanel(
|
|
|
130
130
|
verbose_name="Insulin",
|
|
131
131
|
abbreviation="INS",
|
|
132
132
|
processing_profile=insulin_processing,
|
|
133
|
-
utest_ids=
|
|
133
|
+
utest_ids=("ins",),
|
|
134
134
|
)
|
|
135
135
|
|
|
136
136
|
sputum_panel = RequisitionPanel(
|
|
@@ -138,5 +138,5 @@ sputum_panel = RequisitionPanel(
|
|
|
138
138
|
verbose_name="Sputum",
|
|
139
139
|
abbreviation="SPM",
|
|
140
140
|
processing_profile=sputum_processing,
|
|
141
|
-
utest_ids=
|
|
141
|
+
utest_ids=(),
|
|
142
142
|
)
|
|
@@ -7,18 +7,13 @@ from edc_glucose.utils import validate_glucose_as_millimoles_per_liter
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class BloodResultsFbgFormValidatorMixin:
|
|
10
|
-
|
|
11
10
|
@property
|
|
12
11
|
def reportables_evaluator_options(self: Any):
|
|
13
12
|
if not self.cleaned_data.get("fasting"):
|
|
14
13
|
raise forms.ValidationError({"fasting": "This field is required."})
|
|
15
|
-
fasting = (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
(self.cleaned_data.get("fasting") == FASTING)
|
|
19
|
-
or (self.cleaned_data.get("fasting") == YES)
|
|
20
|
-
)
|
|
21
|
-
else False
|
|
14
|
+
fasting = bool(
|
|
15
|
+
self.cleaned_data.get("fasting") == FASTING
|
|
16
|
+
or self.cleaned_data.get("fasting") == YES
|
|
22
17
|
)
|
|
23
18
|
return dict(fasting=fasting)
|
|
24
19
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
from collections import namedtuple
|
|
2
3
|
from typing import Any
|
|
3
4
|
|
|
4
|
-
from edc_constants.constants import NO, YES
|
|
5
|
+
from edc_constants.constants import NO, NULL_STRING, YES
|
|
6
|
+
from edc_lab import RequisitionPanel
|
|
5
7
|
from edc_lab.form_validators import CrfRequisitionFormValidatorMixin
|
|
6
8
|
from edc_reportable.forms import ReportablesFormValidatorMixin
|
|
7
9
|
|
|
@@ -15,25 +17,26 @@ class BloodResultsFormValidatorMixin(
|
|
|
15
17
|
CrfRequisitionFormValidatorMixin,
|
|
16
18
|
):
|
|
17
19
|
value_field_suffix = "_value"
|
|
18
|
-
panel = None
|
|
19
|
-
panels =
|
|
20
|
+
panel: RequisitionPanel = None
|
|
21
|
+
panels: tuple[RequisitionPanel, ...] = ()
|
|
20
22
|
is_poc_field: str = "is_poc"
|
|
21
|
-
# egfr_percent_drop_threshold: float = 20.0000
|
|
22
|
-
# egfr_value_threshold: float = 45.0000
|
|
23
|
-
# egfr_formula: str = "ckd-epi"
|
|
24
|
-
# reference_range_collection_name: str = None
|
|
25
23
|
|
|
26
24
|
def evaluate_value(self, **kwargs):
|
|
27
25
|
"""A hook to evaluate a field value"""
|
|
28
26
|
pass
|
|
29
27
|
|
|
30
28
|
def clean(self: Any) -> None:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
if self.cleaned_data.get(self.is_poc_field) not in [None, NULL_STRING]:
|
|
30
|
+
# do not require requisition if poc == YES
|
|
31
|
+
self.required_if(NO, field=self.is_poc_field, field_required="requisition")
|
|
32
|
+
else:
|
|
33
|
+
# requires requisition if any `value` fields have value but inverse not True.
|
|
34
|
+
# It is OK to submit the requisition without any `value` fields data.
|
|
35
|
+
self.required_if_true(
|
|
36
|
+
any(self.fields_names_with_values),
|
|
37
|
+
field_required=self.requisition_field,
|
|
38
|
+
inverse=False,
|
|
39
|
+
)
|
|
37
40
|
|
|
38
41
|
if self.requisition:
|
|
39
42
|
for fields_name in self.fields_names_with_values:
|
|
@@ -43,19 +46,19 @@ class BloodResultsFormValidatorMixin(
|
|
|
43
46
|
utest_id = fields_name
|
|
44
47
|
if f"{utest_id}_units" in self.cleaned_data:
|
|
45
48
|
self.required_if_not_none(
|
|
46
|
-
field=f"{utest_id}{self.value_field_suffix or
|
|
49
|
+
field=f"{utest_id}{self.value_field_suffix or NULL_STRING}",
|
|
47
50
|
field_required=f"{utest_id}_units",
|
|
48
51
|
field_required_evaluate_as_int=True,
|
|
49
52
|
)
|
|
50
53
|
if f"{utest_id}_abnormal" in self.cleaned_data:
|
|
51
54
|
self.required_if_not_none(
|
|
52
|
-
field=f"{utest_id}{self.value_field_suffix or
|
|
55
|
+
field=f"{utest_id}{self.value_field_suffix or NULL_STRING}",
|
|
53
56
|
field_required=f"{utest_id}_abnormal",
|
|
54
57
|
field_required_evaluate_as_int=False,
|
|
55
58
|
)
|
|
56
59
|
if f"{utest_id}_reportable" in self.cleaned_data:
|
|
57
60
|
self.required_if_not_none(
|
|
58
|
-
field=f"{utest_id}{self.value_field_suffix or
|
|
61
|
+
field=f"{utest_id}{self.value_field_suffix or NULL_STRING}",
|
|
59
62
|
field_required=f"{utest_id}_reportable",
|
|
60
63
|
field_required_evaluate_as_int=False,
|
|
61
64
|
)
|
|
@@ -80,34 +83,34 @@ class BloodResultsFormValidatorMixin(
|
|
|
80
83
|
|
|
81
84
|
@property
|
|
82
85
|
def is_poc(self: Any) -> bool:
|
|
83
|
-
if self.cleaned_data.get(self.is_poc_field):
|
|
86
|
+
if self.is_poc_field and self.cleaned_data.get(self.is_poc_field):
|
|
84
87
|
return self.cleaned_data.get(self.is_poc_field) == YES
|
|
85
88
|
return False
|
|
86
89
|
|
|
87
90
|
@property
|
|
88
|
-
def fields_names_with_values(self: Any) ->
|
|
89
|
-
"""Returns a list result `value`
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
def fields_names_with_values(self: Any) -> tuple[str, ...]:
|
|
92
|
+
"""Returns a list result `value` field names that are not None."""
|
|
93
|
+
field_names = (f"{utest_id}{self.value_field_suffix}" for utest_id in self.utest_ids)
|
|
94
|
+
return tuple(
|
|
95
|
+
[
|
|
96
|
+
field_name
|
|
97
|
+
for field_name in field_names
|
|
98
|
+
if self.cleaned_data.get(field_name) not in [None, NULL_STRING]
|
|
99
|
+
]
|
|
100
|
+
)
|
|
96
101
|
|
|
97
102
|
@property
|
|
98
|
-
def utest_ids(self: Any) ->
|
|
103
|
+
def utest_ids(self: Any) -> tuple:
|
|
99
104
|
utest_ids = []
|
|
100
105
|
for panel in self.panel_list:
|
|
101
106
|
for utest_id in panel.utest_ids:
|
|
102
|
-
|
|
107
|
+
with contextlib.suppress(ValueError):
|
|
103
108
|
utest_id, _ = utest_id
|
|
104
|
-
except ValueError:
|
|
105
|
-
pass
|
|
106
109
|
utest_ids.append(utest_id)
|
|
107
|
-
return utest_ids
|
|
110
|
+
return tuple(utest_ids)
|
|
108
111
|
|
|
109
112
|
@property
|
|
110
|
-
def panel_list(self: Any) ->
|
|
113
|
+
def panel_list(self: Any) -> tuple[RequisitionPanel]:
|
|
111
114
|
if self.panel:
|
|
112
|
-
return
|
|
115
|
+
return (self.panel,)
|
|
113
116
|
return self.panels
|
|
@@ -38,12 +38,12 @@ class ModelAdminFormInstructionsMixin:
|
|
|
38
38
|
"additional questions may be "
|
|
39
39
|
"required or some answers may need to be corrected."
|
|
40
40
|
)
|
|
41
|
-
add_instructions =
|
|
42
|
-
change_instructions =
|
|
41
|
+
add_instructions = ()
|
|
42
|
+
change_instructions = ()
|
|
43
43
|
|
|
44
|
-
additional_instructions =
|
|
44
|
+
additional_instructions = ()
|
|
45
45
|
add_additional_instructions = None
|
|
46
|
-
change_additional_instructions =
|
|
46
|
+
change_additional_instructions = ()
|
|
47
47
|
|
|
48
48
|
def get_add_instructions(
|
|
49
49
|
self, extra_context: dict | None, request: WSGIRequest | None = None
|
edc_visit_schedule/baseline.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
from typing import TYPE_CHECKING, Any
|
|
4
5
|
|
|
5
6
|
from .exceptions import SiteVisitScheduleError, VisitScheduleBaselineError
|
|
@@ -28,10 +29,8 @@ class Baseline:
|
|
|
28
29
|
try:
|
|
29
30
|
instance = instance.appointment
|
|
30
31
|
except AttributeError:
|
|
31
|
-
|
|
32
|
+
with contextlib.suppress(AttributeError):
|
|
32
33
|
instance = instance.subject_visit.appointment
|
|
33
|
-
except AttributeError:
|
|
34
|
-
pass
|
|
35
34
|
self.visit_schedule_name = instance.visit_schedule_name
|
|
36
35
|
self.schedule_name = instance.schedule_name
|
|
37
36
|
self.visit_code_sequence = instance.visit_code_sequence
|
|
@@ -59,7 +58,7 @@ class Baseline:
|
|
|
59
58
|
try:
|
|
60
59
|
visit_schedule = site_visit_schedules.get_visit_schedule(self.visit_schedule_name)
|
|
61
60
|
except SiteVisitScheduleError as e:
|
|
62
|
-
raise VisitScheduleBaselineError(str(e))
|
|
61
|
+
raise VisitScheduleBaselineError(str(e)) from e
|
|
63
62
|
return visit_schedule
|
|
64
63
|
|
|
65
64
|
@property
|
|
@@ -67,7 +66,7 @@ class Baseline:
|
|
|
67
66
|
try:
|
|
68
67
|
schedule = self.visit_schedule.schedules.get(self.schedule_name)
|
|
69
68
|
except SiteVisitScheduleError as e:
|
|
70
|
-
raise VisitScheduleBaselineError(str(e))
|
|
69
|
+
raise VisitScheduleBaselineError(str(e)) from e
|
|
71
70
|
return schedule
|
|
72
71
|
|
|
73
72
|
@property
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
import copy
|
|
4
5
|
import sys
|
|
5
6
|
from typing import TYPE_CHECKING
|
|
@@ -68,10 +69,8 @@ class SiteVisitSchedules:
|
|
|
68
69
|
|
|
69
70
|
def get_visit_schedule(self, visit_schedule_name=None) -> VisitSchedule:
|
|
70
71
|
"""Returns a visit schedule instance or raises."""
|
|
71
|
-
|
|
72
|
+
with contextlib.suppress(AttributeError):
|
|
72
73
|
visit_schedule_name = visit_schedule_name.split(".")[0]
|
|
73
|
-
except AttributeError:
|
|
74
|
-
pass
|
|
75
74
|
visit_schedule = self.registry.get(visit_schedule_name)
|
|
76
75
|
if not visit_schedule:
|
|
77
76
|
visit_schedule_names = "', '".join(self.registry.keys())
|
|
@@ -88,10 +87,8 @@ class SiteVisitSchedules:
|
|
|
88
87
|
"""
|
|
89
88
|
visit_schedules = {}
|
|
90
89
|
for visit_schedule_name in visit_schedule_names:
|
|
91
|
-
|
|
92
|
-
visit_schedule_name = visit_schedule_name.split(".")[0]
|
|
93
|
-
except AttributeError:
|
|
94
|
-
pass
|
|
90
|
+
with contextlib.suppress(AttributeError):
|
|
91
|
+
visit_schedule_name = visit_schedule_name.split(".")[0] # noqa: PLW2901
|
|
95
92
|
visit_schedules[visit_schedule_name] = self.get_visit_schedule(visit_schedule_name)
|
|
96
93
|
return visit_schedules or self.registry
|
|
97
94
|
|
|
@@ -105,13 +102,13 @@ class SiteVisitSchedules:
|
|
|
105
102
|
for schedule in visit_schedule.schedules.values():
|
|
106
103
|
try:
|
|
107
104
|
consent_definitions = getattr(schedule, attr)
|
|
108
|
-
except (AttributeError, TypeError):
|
|
105
|
+
except (AttributeError, TypeError) as e:
|
|
109
106
|
raise SiteVisitScheduleError(
|
|
110
107
|
f"Invalid attr for Schedule. See {schedule}. Got `{attr}`."
|
|
111
|
-
)
|
|
108
|
+
) from e
|
|
112
109
|
for _cdef in consent_definitions:
|
|
113
110
|
if _cdef == cdef:
|
|
114
|
-
ret.append([visit_schedule, schedule])
|
|
111
|
+
ret.append([visit_schedule, schedule]) # noqa: PERF401
|
|
115
112
|
if not ret:
|
|
116
113
|
raise SiteVisitScheduleError(
|
|
117
114
|
f"Schedule not found. No schedule exists for {attr}={cdef}."
|
|
File without changes
|