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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clinicedc
3
- Version: 2.0.8
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.0
28
- Requires-Dist: django-crypto-fields>=1.0.1
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=WwiYuzNsnK3cjRHgw-ouh6CX6-yuj0WMeTfRIehlbTI,3168
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=fZ6WOXeBWfKfNWVh5M0DZbB75fEHJon1NFxo9VZG12M,16621
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=v1mX09NUELl9b7oNPP5XGS2dsMz9mwgKUM8aI_UZiWY,1850
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=9EvWP17O1hhkAW1eBetxZcLsWdUVY2xGXRMOy5aqZWo,12080
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=9SLbx8tJxeV6sKVQnf_T3sOukaGISbm_Y5OVBlBL5gI,978
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=UrT9JXGJXJFF71l0k_x804Mj4KUSS74o7ZtUrNFkBq8,1427
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=Bt9JjnI-Nad05dw-9UmAsk7lIOW-kfjfBAKF_6HjbC8,3656
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=ns4NbZVfj8uRgIe4-amezujZT0mHztee-vS5lwPn18g,3080
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=zegUwaMnZ4EfBmPe7Z154cdGOISLcpbTSjz48AFuU6Y,815
1417
- edc_lab_results/form_validator_mixins/blood_results_form_validator_mixin.py,sha256=-e45yzjACr1Tg743tgf4V0AXBecC8xjTXB7VPHChJH0,4202
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=UB0io8crTx7tvK2vbRDsXpqbiLNec94boxGqi5mDwd0,2897
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=kuX94p-anNm5w_d2PQQsWt20QwCb24xOrCAeKgq2aSU,3341
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=DhSsUyc6lwlSQBfCwwrv16sx3Y0kHfxEkeIL_EqxMOQ,13173
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.8.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
3295
- clinicedc-2.0.8.dist-info/WHEEL,sha256=5h_Q-_6zWQhhADpsAD_Xpw7gFbCRK5WjOOEq0nB806Q,79
3296
- clinicedc-2.0.8.dist-info/METADATA,sha256=uY9lgrGB_ugQvi_1ogUj-TYliQxMZDRhOryLpW3Jals,15815
3297
- clinicedc-2.0.8.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.8.18
2
+ Generator: uv 0.8.19
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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
+ ]
@@ -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 not raw and not kwargs.get("update_fields"):
64
- if isinstance(instance, (ConsentExtensionModelMixin,)):
65
- cdef = site_consents.get_consent_definition(
66
- model=instance.subject_consent._meta.label_lower,
67
- version=instance.subject_consent.version,
68
- )
69
- visit_schedule, schedule = site_visit_schedules.get_by_consent_definition(cdef)
70
- subject_schedule = SubjectSchedule(
71
- instance.subject_consent.subject_identifier,
72
- visit_schedule=visit_schedule,
73
- schedule=schedule,
74
- )
75
- subject_schedule.refresh_appointments()
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()
@@ -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
- if cdef not in cdefs:
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
- gender: str = None,
39
- age_in_years: int = None,
40
- weight_in_kgs: float = None,
41
- ethnicity: str = None,
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) is None
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) is not None
139
- and self.cleaned_data.get(field_required) != ""
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) is not None
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 is not None and not field_required_has_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 is None
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
- self.cleaned_data.get(field) is not None
275
- and self.cleaned_data.get(field_required) is None
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) is None
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)
@@ -29,7 +29,7 @@ class Hba1cModelMixin(models.Model):
29
29
  verbose_name="HbA1c units",
30
30
  max_length=15,
31
31
  default=PERCENT,
32
- editable=False,
32
+ blank=True,
33
33
  )
34
34
 
35
35
  hba1c_datetime = models.DateTimeField(
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 = None, cleaned_data: dict = None
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
@@ -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 = None, alpha_code: str = None) -> None:
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: list[str | tuple] = None,
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=[("hba1c", "HbA1c")],
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=[("hba1c", "HbA1c")],
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=[("glucose", "Glucose")],
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=[("glucose", "Glucose")],
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=["cd4"],
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=["vl"],
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=["urea", "creatinine", "uric_acid", "egfr", "egfr_drop"],
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=[LDL, HDL, TRIG, CHOL],
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=["ast", "alt", "alp", "amylase", "ggt", "albumin"],
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=["ins"],
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
- True
17
- if (
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 = None
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
- # do not require requisition if poc
32
- self.required_if(NO, field=self.is_poc_field, field_required="requisition")
33
- self.required_if_true(
34
- not self.is_poc and any(self.fields_names_with_values),
35
- field_required=self.requisition_field,
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) -> list:
89
- """Returns a list result `value` fields that are not None"""
90
- fields_names_with_values = []
91
- field_names = [f"{utest_id}{self.value_field_suffix}" for utest_id in self.utest_ids]
92
- for field_name in field_names:
93
- if self.cleaned_data.get(field_name):
94
- fields_names_with_values.append(field_name)
95
- return field_names
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) -> list:
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
- try:
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) -> list:
113
+ def panel_list(self: Any) -> tuple[RequisitionPanel]:
111
114
  if self.panel:
112
- return [self.panel]
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 = None
42
- change_instructions = None
41
+ add_instructions = ()
42
+ change_instructions = ()
43
43
 
44
- additional_instructions = None
44
+ additional_instructions = ()
45
45
  add_additional_instructions = None
46
- change_additional_instructions = None
46
+ change_additional_instructions = ()
47
47
 
48
48
  def get_add_instructions(
49
49
  self, extra_context: dict | None, request: WSGIRequest | None = None
@@ -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
- try:
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
- try:
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
- try:
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}."