clinicedc 2.0.13__py3-none-any.whl → 2.0.15__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.

Files changed (58) hide show
  1. {clinicedc-2.0.13.dist-info → clinicedc-2.0.15.dist-info}/METADATA +2 -3
  2. {clinicedc-2.0.13.dist-info → clinicedc-2.0.15.dist-info}/RECORD +58 -58
  3. edc_analytics/custom_tables/age.py +1 -0
  4. edc_analytics/custom_tables/art.py +1 -0
  5. edc_analytics/custom_tables/bmi.py +1 -0
  6. edc_analytics/custom_tables/bp.py +1 -0
  7. edc_analytics/custom_tables/fasting.py +1 -0
  8. edc_analytics/custom_tables/fbg.py +1 -0
  9. edc_analytics/custom_tables/fbg_ogtt.py +1 -0
  10. edc_analytics/custom_tables/hba1c.py +1 -0
  11. edc_analytics/custom_tables/ogtt.py +1 -0
  12. edc_analytics/custom_tables/waist.py +1 -0
  13. edc_analytics/row/row_definitions.py +1 -1
  14. edc_analytics/row/row_statistics_with_gender.py +1 -0
  15. edc_analytics/stata/get_stata_labels_from_model.py +5 -6
  16. edc_analytics/table.py +1 -0
  17. edc_appointment/creators/appointments_creator.py +5 -9
  18. edc_dx/diagnoses.py +2 -2
  19. edc_dx/form_validators/diagnosis_form_validator_mixin.py +1 -0
  20. edc_dx/form_validators/result_form_validator_mixin.py +14 -16
  21. edc_dx_review/form_mixins/clinical_review_baseline_required_form_mixin.py +1 -0
  22. edc_dx_review/medical_date.py +2 -1
  23. edc_dx_review/model_mixins/clinical_review_baseline_model_mixin.py +1 -0
  24. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_chol_model_mixin.py +1 -0
  25. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_dm_model_mixin.py +1 -0
  26. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_hiv_model_mixin.py +1 -0
  27. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_htn_model_mixin.py +1 -0
  28. edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_model_mixin.py +1 -0
  29. edc_dx_review/model_mixins/dx_location_model_mixin.py +1 -0
  30. edc_dx_review/model_mixins/factory/baseline_review_model_mixin_factory.py +1 -0
  31. edc_dx_review/model_mixins/factory/calculate_date.py +1 -0
  32. edc_dx_review/model_mixins/factory/dx_initial_review_model_mixin_factory.py +2 -1
  33. edc_dx_review/model_mixins/factory/followup_review_model_mixin_factory.py +1 -0
  34. edc_dx_review/model_mixins/factory/rx_initial_review_model_mixin_factory.py +1 -0
  35. edc_dx_review/model_mixins/followup_review/followup_review_model_mixin.py +1 -0
  36. edc_dx_review/model_mixins/followup_review/hiv_followup_review_model_mixin.py +1 -0
  37. edc_dx_review/model_mixins/initial_review/chol_initial_review_model_mixin.py +1 -0
  38. edc_dx_review/model_mixins/initial_review/hiv_initial_model_mixins.py +1 -0
  39. edc_dx_review/model_mixins/initial_review/ncd_initial_review_model_mixin.py +1 -0
  40. edc_dx_review/radio_fields.py +3 -3
  41. edc_dx_review/utils.py +2 -1
  42. edc_randomization/randomizer.py +2 -5
  43. edc_randomization/utils.py +10 -0
  44. edc_visit_tracking/action_items.py +1 -2
  45. edc_visit_tracking/apps.py +1 -1
  46. edc_visit_tracking/context_processors.py +1 -2
  47. edc_visit_tracking/crf_date_validator.py +4 -4
  48. edc_visit_tracking/form_validators/visit_form_validator.py +51 -45
  49. edc_visit_tracking/form_validators/visit_missed_form_validator.py +6 -1
  50. edc_visit_tracking/model_mixins/utils.py +1 -1
  51. edc_visit_tracking/modelform_mixins/crf/visit_tracking_crf_modelform_mixin.py +4 -4
  52. edc_visit_tracking/modelform_mixins/utils.py +2 -2
  53. edc_visit_tracking/models/signals.py +1 -1
  54. edc_visit_tracking/typing_stubs.py +3 -3
  55. edc_visit_tracking/utils.py +1 -1
  56. edc_visit_tracking/visit_sequence.py +3 -4
  57. {clinicedc-2.0.13.dist-info → clinicedc-2.0.15.dist-info}/WHEEL +0 -0
  58. {clinicedc-2.0.13.dist-info → clinicedc-2.0.15.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clinicedc
3
- Version: 2.0.13
3
+ Version: 2.0.15
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
@@ -45,7 +45,6 @@ Requires-Dist: gunicorn>=23.0.0
45
45
  Requires-Dist: inflect>=7.5.0
46
46
  Requires-Dist: mempass>=0.1.2
47
47
  Requires-Dist: mysqlclient>=2.2.7
48
- Requires-Dist: psycopg2>=2.9.10
49
48
  Requires-Dist: pycups>=2.0.4
50
49
  Requires-Dist: pypdf>=5.9.0
51
50
  Requires-Dist: python-dateutil>=2.9.0.post0
@@ -57,7 +56,7 @@ Requires-Dist: tqdm>=4.67.1
57
56
  Requires-Dist: twilio>=9.7.0
58
57
  Maintainer: Erik van Widenfelt
59
58
  Maintainer-email: Erik van Widenfelt <ew2789@gmail.com>
60
- Requires-Python: >=3.12
59
+ Requires-Python: >=3.12, <3.14
61
60
  Project-URL: Changelog, https://github.com/clinicedc/clinicedc/blob/main/CHANGES
62
61
  Project-URL: Documentation, https://github.com/clinicedc/clinicedc/docs
63
62
  Project-URL: Homepage, https://github.com/clinicedc/clinicedc
@@ -296,27 +296,27 @@ edc_analytics/__init__.py,sha256=Tne7Oyy6wpFVNcFoEpurXJ4_I3Tz93jvqOMmTTXW_X0,123
296
296
  edc_analytics/apps.py,sha256=bZ9Gtr1YdBX2Q1nkrzOi7wG9_Ri3vUj8ZOATve3hDzo,229
297
297
  edc_analytics/constants.py,sha256=a8aUKrhSWKFfLBm9e7CFQk0mEdkFNbGbAxpUrqaTXjw,481
298
298
  edc_analytics/custom_tables/__init__.py,sha256=TVI12TnJ5aMqTW2liyS4AjfLAIgg5rY8W_4WBsWfNw8,330
299
- edc_analytics/custom_tables/age.py,sha256=8XH1uYf8fLyH0kKLmtO5bpRcXBWH_Ytovj9bC33Spos,2614
300
- edc_analytics/custom_tables/art.py,sha256=6o9ILFdO9uT0oioqZNtEr4fwOpwcXvO4qDR7JmDXD90,2716
301
- edc_analytics/custom_tables/bmi.py,sha256=ugLtbQ-UOaSTlhfgkhIlW5i3T48z6WeHb8-LMlf8EWc,3843
302
- edc_analytics/custom_tables/bp.py,sha256=Tu6tMY2PuBVce_eziR4x8j3Iz4CecJyWSqN3DuBfgFc,3188
303
- edc_analytics/custom_tables/fasting.py,sha256=kCOOFF9B6mM8z4mcd1ykNVdwbstTAmaqACKdtN_sZ34,3992
304
- edc_analytics/custom_tables/fbg.py,sha256=96a8cIo7Cny-pOGy9ZzhLcnHKhfbY8vgheyBRfl8XJ4,2966
305
- edc_analytics/custom_tables/fbg_ogtt.py,sha256=4xEh5c08uaUhe7vWJ8ll_1xhyzJ6tD4M0-hAi1ItIE8,12127
299
+ edc_analytics/custom_tables/age.py,sha256=Jx78ka_AZ3qskHEAPeR1nQwENM6-MY12gEsM6rcZwY4,2615
300
+ edc_analytics/custom_tables/art.py,sha256=pgT7w2t6vzE5WFos4pe9E2XIEynFBBKFzryL1NV2abQ,2717
301
+ edc_analytics/custom_tables/bmi.py,sha256=5Ww5438pCAV_ApJ9k-_TIoJ805PibG-zAkvbv-1XUew,3844
302
+ edc_analytics/custom_tables/bp.py,sha256=THkAu7CKd9eZVlYDPtUrRFZg8zbO49U66kgEwtwRYQA,3189
303
+ edc_analytics/custom_tables/fasting.py,sha256=_r9_yyfleq99iNscgXYPNuP-cvyel1pLsEAU_I8JSJM,3993
304
+ edc_analytics/custom_tables/fbg.py,sha256=EX1PMVmjJ6r1RBeHzaFdpyEdYy2aJIhz1yB4I3JB-Pg,2967
305
+ edc_analytics/custom_tables/fbg_ogtt.py,sha256=Z22yMyKEu6PxDaD4BWAw5VAWj-dcSqNXHjPtC_IdXgY,12128
306
306
  edc_analytics/custom_tables/gender.py,sha256=lLPggxFYv-G0hnY_Bkl3rAdAitCNJ6KEud2FOGfaubQ,252
307
- edc_analytics/custom_tables/hba1c.py,sha256=cSQ_jgBvh8UVUaRr-yGJpprKuH9R_JESlXAn0MQcKcs,2620
308
- edc_analytics/custom_tables/ogtt.py,sha256=b1i5QLIz7B5erAOelMocGPgQ4kbia7lxVa4VAkvQwog,2887
309
- edc_analytics/custom_tables/waist.py,sha256=-XV3ZVmmdQg_ff7ckSdtSDxYHxgsnfQVtHrF5FVenyw,3144
307
+ edc_analytics/custom_tables/hba1c.py,sha256=tyK6EaxKfVeHbrZXfPV8s1QaiqPEC4_S1sSb3hFPsgw,2621
308
+ edc_analytics/custom_tables/ogtt.py,sha256=_WuSBG0_bj4SH0LELOkLPdAnOzWHLhLB0x43uPc-U-s,2888
309
+ edc_analytics/custom_tables/waist.py,sha256=tWQtmwn0AwV3JFmlbTSvcUq9TkTM19ewwHSTLHeun3s,3145
310
310
  edc_analytics/data.py,sha256=yaa7Hi9bQMhFKt_7wWDFZYsfU-tAKoE-gsm97_5vSco,1032
311
311
  edc_analytics/row/__init__.py,sha256=oBRBcrTeWr77ZfvsSnCGKbrHS0-qY26yt4EDn_hxllA,192
312
312
  edc_analytics/row/row_definition.py,sha256=me9QGvDb7aijLPoRD_jey1cLkJ3TuBLkF6IpzoyV_Y4,1434
313
- edc_analytics/row/row_definitions.py,sha256=NuNWmXSxBY2PSKep9EV5bRYyKJ1sRAWjjcOevMTHAgQ,953
313
+ edc_analytics/row/row_definitions.py,sha256=7PW5wd-wiYaOeIJ_lqCi6bAzOQUWG_gyykKeqK0VZ-k,962
314
314
  edc_analytics/row/row_statistics.py,sha256=M5zjBCbguVOxOp2Lmla9JUkZA9bG3V8aanY6zswDRL0,2897
315
- edc_analytics/row/row_statistics_with_gender.py,sha256=P20IiMnz11AzmvtwUsAMGzn_lgPoyUdvigcr8lhIOzg,3210
315
+ edc_analytics/row/row_statistics_with_gender.py,sha256=ti16XFqDgjrMcd-1v5kMxc5X1R6UqHYVKQ8khKvFPAI,3211
316
316
  edc_analytics/stata/__init__.py,sha256=fzENxSjaoxaXbSeafnaNXOPO85aXWbObQBnYfhAJI6w,69
317
- edc_analytics/stata/get_stata_labels_from_model.py,sha256=3EjENwn-ACITNYQvkwahdvdMsefSR4V077EH43zF5ls,1463
317
+ edc_analytics/stata/get_stata_labels_from_model.py,sha256=Dsu0635K2F0xK3zrU-BmnuLV2ZD51mDqbTxJZWfgL4I,1431
318
318
  edc_analytics/styler.py,sha256=IvuKalHPSJLZTjzOKAt7wK2kCRRvZGL9_PxM70Vo68Q,3086
319
- edc_analytics/table.py,sha256=yt1XvKYJvk_VjsBl324iCRJNGofkq9yg0u0h5ZHVnvw,3752
319
+ edc_analytics/table.py,sha256=PJwlnURui-BXOqc9s0STA1Fs0FqbdAtNLKLnGFV_kS4,3753
320
320
  edc_analytics/urls.py,sha256=NR4c2ybRQNjbtJwi-PTsYB9oacVuVZsIRHo7ZnA0WIc,118
321
321
  edc_appconfig/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
322
322
  edc_appconfig/apps.py,sha256=eMQEa-pQzqWyuBGbpWWNJwgmbc7oSZ3d0FNFdd_08ik,11211
@@ -342,7 +342,7 @@ edc_appointment/constants.py,sha256=tsv0MV8Zo4e1aLnx00VYsM0EmyUUuTupeEFb2cko1N8,
342
342
  edc_appointment/context_processors.py,sha256=LTdhNLGDhsJmdobhnM4VwhZns-67jm5Culz8vxsErzI,387
343
343
  edc_appointment/creators/__init__.py,sha256=yv_eSyS8ogQjSe-Y3elxUMa3unIbgTMH3Tg9QSRLdho,425
344
344
  edc_appointment/creators/appointment_creator.py,sha256=HXB7tDyPHZ29MNqpDHuYXmJTRaWCxAtvU4KOypEc0o0,9757
345
- edc_appointment/creators/appointments_creator.py,sha256=K31XnlSVry7bJ8r2VCEgpwyneqa6laBSmw8nV2JTjEU,5687
345
+ edc_appointment/creators/appointments_creator.py,sha256=XH-pYfxtl5lS917mEGSMFWrwAVuOmjdB2Efw9ONo0jU,5638
346
346
  edc_appointment/creators/unscheduled_appointment_creator.py,sha256=m7yLmW2xcNGf5PhhTUc1PleI8JKlhaYSoMWVCBZDvis,10590
347
347
  edc_appointment/creators/utils.py,sha256=H4Q2_BVFqTX5ra8Aov061ItHLlusyGY2eOFoHC4taag,2568
348
348
  edc_appointment/exceptions.py,sha256=07KyrUsorB4ke1TToWx2BcRAAsJYMKkOAt8cjn93B3A,1214
@@ -874,10 +874,10 @@ edc_document_status/models/signals.py,sha256=yZmZiYD3c2QDqUUTdTN087Fc3_B2-Vi-983
874
874
  edc_document_status/urls.py,sha256=Hgg7_Kjo7c1aFcXBB8kuOJIvW5IY7Do7z5vkeBAWEDQ,213
875
875
  edc_dx/__init__.py,sha256=im7QJgvIrK5LLffGaTEL5v_TT81Io51SjF3cjPwPHnk,156
876
876
  edc_dx/apps.py,sha256=L9jTcNHWXli9vYd5FPnyc7SXOj-qs_3ntVJ549nJ0XE,109
877
- edc_dx/diagnoses.py,sha256=N99awNy9ZReb4Ko5oBqJN6CfF-jL3wIpnsPYe3Yttgs,9071
877
+ edc_dx/diagnoses.py,sha256=TWP5MaCGqBhZKvB_l-dFyMttL0mFV-2mUCYIE1sdO1U,9048
878
878
  edc_dx/form_validators/__init__.py,sha256=0Xdntahcc_2lYyYIdigkkcZMtfGTa1qX3C1zo0NW-eo,138
879
- edc_dx/form_validators/diagnosis_form_validator_mixin.py,sha256=a5pyop7VIjMe1Tjj_FY2TDtkyUmxk66Rms_4qvpIQSo,1925
880
- edc_dx/form_validators/result_form_validator_mixin.py,sha256=4Yby5_ZZyNmqAanJENcQh-c_yMybi7JjI20_SbtCHu4,2492
879
+ edc_dx/form_validators/diagnosis_form_validator_mixin.py,sha256=7XVTPPkVx-X3BW8XiBKRYGTeuPIYnt9Xsbcno8Q5ZOU,1926
880
+ edc_dx/form_validators/result_form_validator_mixin.py,sha256=Us_ZJrLc8HB1xw6Bu_ZM6gQhHJDEj3eusv2M7mlUUPs,2405
881
881
  edc_dx/utils.py,sha256=tHhSJgXYz_DwP9Ujhi4MVLFc2Sz-oxcOQEpDO9elz5w,1223
882
882
  edc_dx_review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
883
883
  edc_dx_review/apps.py,sha256=kgCI6K_HKHiKb1MfblBJSr-naZk7bABgMWhtpmIl1wg,116
@@ -887,42 +887,42 @@ edc_dx_review/choices.py,sha256=AufGpbPViQ6eA0TJxfod3g9Y8T-aLvr6tulAHL3AQtc,643
887
887
  edc_dx_review/constants.py,sha256=1l1n556hBzL5M_fbnQkxK4dXzYH_35pem42QZVWHslk,204
888
888
  edc_dx_review/fieldsets.py,sha256=aLgxJqfz8XsoK9dXI4wa8v9qS_GD1KQbO0b2pVg_ClA,1276
889
889
  edc_dx_review/form_mixins/__init__.py,sha256=BT941i0F_ahGzpZQtM-Jhd911XVFrxKDj87L0w-hCSM,112
890
- edc_dx_review/form_mixins/clinical_review_baseline_required_form_mixin.py,sha256=A9Q4aTzwPR4hg48LmUztoH56hsaRxsa93at8lJXGiMg,942
890
+ edc_dx_review/form_mixins/clinical_review_baseline_required_form_mixin.py,sha256=Goxl-DVRfAv2qtZlquN9-GJ31slylRPkoI07zyrYO3k,943
891
891
  edc_dx_review/form_validator_mixins/__init__.py,sha256=qlmi4uKl-Dqtliyw42ZfldMkYhdgAs4xOwK9isYGEMA,218
892
892
  edc_dx_review/form_validator_mixins/clinical_review_baseline_form_validator_mixin.py,sha256=KFc4PwH2__fo4OvHrBtNTuYApliVuiD-VT1RRnmqcCw,226
893
893
  edc_dx_review/form_validator_mixins/clinical_review_followup_form_validator_mixin.py,sha256=fNZF9TJrak9e9yUb8VJNLElLThWLGma3Wun5-DP6OtA,1097
894
894
  edc_dx_review/list_data.py,sha256=Igsmks2rDC95PDMWZmWLynhvQ6dBL2K0qazwtEDDXOc,711
895
- edc_dx_review/medical_date.py,sha256=CHRJsiKuZUpyJeEVOqc0A9jGFE2lsZByKTzzi9jl5DQ,6907
895
+ edc_dx_review/medical_date.py,sha256=I_TJ5tEY8Mkqgy3_z_pjKlHszNE5LWYSXiRMEXxpQMw,6896
896
896
  edc_dx_review/migrations/0001_initial.py,sha256=0BmL_Pj5j-lRlR_IBzwDqGH96pIOT8mlf3huCK43Jlk,11691
897
897
  edc_dx_review/migrations/0002_diagnosislocations_extra_value_and_more.py,sha256=I8wCv3MXFzDufDC9fksSfKjDNi5TK_whkWN0Jc6a4Pg,965
898
898
  edc_dx_review/migrations/0003_alter_diagnosislocations_options_and_more.py,sha256=xeaCnYnmqFnVemeqNhuIc8-o9SAKZpguZHd4QTinr7c,5011
899
899
  edc_dx_review/migrations/0004_remove_diagnosislocations_edc_dx_revi_name_a39b40_idx_and_more.py,sha256=a0yE-oHCphIt9-PjfMa5qLb1FNBoMbcq1qliyw6MGVQ,524
900
900
  edc_dx_review/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
901
901
  edc_dx_review/model_mixins/__init__.py,sha256=-Yga4EAzAyBO9Q6Wmtj9bfKujnVBFPxklp98pkhvijw,700
902
- edc_dx_review/model_mixins/clinical_review_baseline_model_mixin.py,sha256=Pt_P8wejad66krsuiYLjm6rWaWmdK8oM3xTfFjg3qHk,926
902
+ edc_dx_review/model_mixins/clinical_review_baseline_model_mixin.py,sha256=E_1UuQlJmBbiQnTAm5LugRplHRWe6rRVa26teCnlOtA,927
903
903
  edc_dx_review/model_mixins/clinical_review_followup/__init__.py,sha256=vhLjTnrXT89BYohT18JHRmxN8ua6I_KSBO-bkmJiP34,403
904
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_chol_model_mixin.py,sha256=0926O-SKcTRLv06bCx-6VZSQXFryH5-W818_vakDRDA,1677
905
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_dm_model_mixin.py,sha256=LanclLOeI_FE85YeyuQk4_fmTVCHg8qoo3SJ3lEHrGw,1653
906
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_hiv_model_mixin.py,sha256=JDQX4YoDCKbFOMRr2yB-G3qBaHuhr9PefFYgnHrzuu0,1680
907
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_htn_model_mixin.py,sha256=6NtMzddh3eQjGpZwU7TmXbvKQ_XB7jZ0tb3KteMyqQA,1738
908
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_model_mixin.py,sha256=UoH1P_MHw4XL46FTLa4juYK9iYbXeki0OdicdTkBeS8,892
909
- edc_dx_review/model_mixins/dx_location_model_mixin.py,sha256=aJYuHDUrJ8TPpMIuhBMT3QyIAVKua5bWrWyVhzKXYQE,425
904
+ edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_chol_model_mixin.py,sha256=fWV8YCXki24r-o5a2Zs2VGMgWeqXW7rd5ayzffwo82o,1678
905
+ edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_dm_model_mixin.py,sha256=ICoAmdtNpiGyuNN2-P0M9WyI2HsIW-tilwU3d3bnKrY,1654
906
+ edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_hiv_model_mixin.py,sha256=L0_RiaJWrKANwfFz7pU9ZvkVIRzek3_1ckOhk4BNPDo,1681
907
+ edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_htn_model_mixin.py,sha256=uC8W37NO0slc8a1xwRGsemdgbsM2VkYNRgyDZUTqU48,1739
908
+ edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_model_mixin.py,sha256=8SzfY9qBxQhc2RKPyhH-LLAS90FnDZbHoz_rTnw0Nkk,893
909
+ edc_dx_review/model_mixins/dx_location_model_mixin.py,sha256=XJZOh7ZJhJRM99DjhHSkipoGwfgKQqdhzJZRmUPDqjk,426
910
910
  edc_dx_review/model_mixins/factory/__init__.py,sha256=IdBPEgN-mJm9v0vjSDLGc10v12pIp-NFpBi-E0y5Zmk,348
911
- edc_dx_review/model_mixins/factory/baseline_review_model_mixin_factory.py,sha256=mPdS6lOhlZ1e21ikxBkKjjqjg4ba9Oad3tiCgfJ_l3E,1746
912
- edc_dx_review/model_mixins/factory/calculate_date.py,sha256=wBLnTj3-JN70fp58uLvukmr0zfzVlVpYFut3-cXNdjM,1526
913
- edc_dx_review/model_mixins/factory/dx_initial_review_model_mixin_factory.py,sha256=FVf2NcwpA0rDBJNX_f5KSv0i7VmsEM47kIn7Zf500kw,3222
914
- edc_dx_review/model_mixins/factory/followup_review_model_mixin_factory.py,sha256=rlsct84TTyfbHkB1hdsHKEHE_FC_0TA1f_xgc_n7ufs,1246
915
- edc_dx_review/model_mixins/factory/rx_initial_review_model_mixin_factory.py,sha256=a98icJL6J31xEyYQ1eppVYTte-jPQdo9C2KdgpmwZ4g,2376
911
+ edc_dx_review/model_mixins/factory/baseline_review_model_mixin_factory.py,sha256=5h8wWOlcB-CZqanTwk8LfCZdq9y3DaFSmOoCLv87j5g,1747
912
+ edc_dx_review/model_mixins/factory/calculate_date.py,sha256=Ytjlk2R7CsNWTYWVwzl17UuUHai8ZBKa4A0cB58XsqE,1527
913
+ edc_dx_review/model_mixins/factory/dx_initial_review_model_mixin_factory.py,sha256=fiq649IUg28cbi9KGGHtWQ6lntQpSy53mWJtXm1q7_o,3211
914
+ edc_dx_review/model_mixins/factory/followup_review_model_mixin_factory.py,sha256=QUq2cMo9IPWLnhXeyHg1qj6iRUuSAWY2m9oyHSh8cOQ,1247
915
+ edc_dx_review/model_mixins/factory/rx_initial_review_model_mixin_factory.py,sha256=DTO3NAN26VoZLQ0JQwF-N5pPo-Gxsw89m-iAkg7Wu3c,2377
916
916
  edc_dx_review/model_mixins/followup_review/__init__.py,sha256=2vTnIxKhFrmxXDVdngv5RumwycrpnWh7FaMU8CA99LU,139
917
- edc_dx_review/model_mixins/followup_review/followup_review_model_mixin.py,sha256=41DfNIvUUd6NUbbjCMbC-5dBA5KEV3JQfTmtob88oSM,673
918
- edc_dx_review/model_mixins/followup_review/hiv_followup_review_model_mixin.py,sha256=HLTl0wUV4FUY5_uwww79ZzwircfQyjZ7FbV29lenrmc,1034
917
+ edc_dx_review/model_mixins/followup_review/followup_review_model_mixin.py,sha256=HJJSqRxDj8wxxCRmOTsA_mz6l9Q5Fp5gpZYHHthMu0k,674
918
+ edc_dx_review/model_mixins/followup_review/hiv_followup_review_model_mixin.py,sha256=y0Ux_v55bfDtOIQO4MmLu-E00tRfSLmP5QMPixuUVtg,1035
919
919
  edc_dx_review/model_mixins/initial_review/__init__.py,sha256=nSRBcgZcJnbACdazzI_1oJ9B5YHjAbvpCO3KlFsn510,250
920
- edc_dx_review/model_mixins/initial_review/chol_initial_review_model_mixin.py,sha256=efe7lG6HiUPVROZNl9GYuUi_wRO0mBTkVHetwG48gCk,1000
921
- edc_dx_review/model_mixins/initial_review/hiv_initial_model_mixins.py,sha256=V99wib43CfTFdFB9-U7EAbpPbXDWHHJdHQcYkWODTX0,3641
922
- edc_dx_review/model_mixins/initial_review/ncd_initial_review_model_mixin.py,sha256=4EKYkCUGEBmLhH1gs2CikFAh7jP6aJWHfNBu9zc841o,1192
920
+ edc_dx_review/model_mixins/initial_review/chol_initial_review_model_mixin.py,sha256=sj3nBYKY4nY3arX6TcChN1KvjlKFr5216ijT4_b2Z6M,1001
921
+ edc_dx_review/model_mixins/initial_review/hiv_initial_model_mixins.py,sha256=ayOxv6D_sFC_5ttGYQigEMq2c-hC5823H7pPlDXVkJU,3642
922
+ edc_dx_review/model_mixins/initial_review/ncd_initial_review_model_mixin.py,sha256=UsE-z9qZzNnBj7v7dwaBqzIj24h63l_ywnYjvMzrT4c,1193
923
923
  edc_dx_review/models.py,sha256=j270tH76Fb-WYMc7nR_4JfIT9An5QQ-FSTiiGer4YUE,610
924
- edc_dx_review/radio_fields.py,sha256=bbQrzZoWtbQ7ywP3e8yR0d_uzW5rloqpgZT0zuLzbQ4,833
925
- edc_dx_review/utils.py,sha256=i3dmJLzyaGwLlCY9GMDpDx8zWMCcFzcWA5zqSXOSiKo,6985
924
+ edc_dx_review/radio_fields.py,sha256=sb8njIokL0v_kA-7_HZJtiUXdW2ILeOYOXMqd2XLsLU,810
925
+ edc_dx_review/utils.py,sha256=bTh1zcmSiEq27RlEaDd0Cp4NZ6jhg7gr_dZapsLk28U,6974
926
926
  edc_egfr/__init__.py,sha256=q9hW6cas07gJxgElFaN7AtUGzwTwHuXvexxGFej3_pQ,56
927
927
  edc_egfr/admin/__init__.py,sha256=X4SsX1UIcGwxzNHH2uG4rnLxLQiYcLJeGLi4agxnkEQ,79
928
928
  edc_egfr/admin/egfr_drop_notification_admin_mixin.py,sha256=Chet4mYDfZI4ohYrAyyf4CymZE6jCkYUB7y7uSAP-rs,2431
@@ -2795,12 +2795,12 @@ edc_randomization/models/edc_permissions.py,sha256=f6P4zXxLQdTAbn2KnFlwfE86Lmzj7
2795
2795
  edc_randomization/models/randomization_list.py,sha256=Hd5yUSZ5HBKhNzYdlbJufab8U2x3Dzw8n4WXnYzJxyc,253
2796
2796
  edc_randomization/randomization_list_importer.py,sha256=v8d1klIfAsf9CwR_4HLLzifDnJvFD2bXhDOe0XqznjM,12366
2797
2797
  edc_randomization/randomization_list_verifier.py,sha256=0JqlbVCBZzx18Vxp2gID4zHjZMyO1LaHzqe4zTzRUpY,6481
2798
- edc_randomization/randomizer.py,sha256=csUo5gdqrxdwj-2xyXNGWvUWSEBOKja3XUecXYg28_A,12989
2798
+ edc_randomization/randomizer.py,sha256=N57PvCGjsjlw4r4V-zE5K7uWnLUw4QwZREu-mQ-O_4U,12939
2799
2799
  edc_randomization/randomizers.py,sha256=JvMDAnsgvRZkl50Bawm2iLsyw3bfWfGwBafn54K5g44,297
2800
2800
  edc_randomization/site_randomizers.py,sha256=T6u1cNPZtoh5ajdsBciSVWzZYZyOPbiTqS9VBGKJ7H8,4077
2801
2801
  edc_randomization/system_checks.py,sha256=S_2Vkq3J3DQVEXOV3JNNlCzuZcl1l34-tTnaBaSZaBk,3025
2802
2802
  edc_randomization/urls.py,sha256=yQnOoy5U5uL00f7_JJcZk7toskmZunG_kJdl-epCV9U,223
2803
- edc_randomization/utils.py,sha256=rCEq6S08A2KosBpo_orFLEkoOWJ8PrX_Bk3CuzWqPt8,5527
2803
+ edc_randomization/utils.py,sha256=uRTtoyAuWo5D6y65ZHBWMSody8IMJBK6cS3mFmJGIMw,5730
2804
2804
  edc_randomization/views.py,sha256=mQnAoYzIbqXFXablJbPY3NdJc4K3fD4le-GOF2kpMmE,340
2805
2805
  edc_refusal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2806
2806
  edc_refusal/admin.py,sha256=upce4HEPyPYKTe_R_krQ9TaLufcJN50c0lw4JrzbTwE,2151
@@ -3319,18 +3319,18 @@ edc_visit_schedule/visit_schedule/schedules_collection.py,sha256=yujoCASDUWyFsqa
3319
3319
  edc_visit_schedule/visit_schedule/visit_schedule.py,sha256=s4pzIUMSDyGgj2wINI53qO3V_UFU_vhEX7lTBtz9UmE,5734
3320
3320
  edc_visit_schedule/wsgi.py,sha256=ToL_Ox7yQ30uUMBj5Ev2q_im2dl6mWKdqsUyJWeyFWU,397
3321
3321
  edc_visit_tracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3322
- edc_visit_tracking/action_items.py,sha256=dPrbvsbMzBTpIPfCeJupV2d2Na1BDqWUmgFTjtU0E6g,1252
3322
+ edc_visit_tracking/action_items.py,sha256=lWf2-lNgz3QxkvbHW3j7YpZFbwqZwUvI_qDaAR4SWWI,1216
3323
3323
  edc_visit_tracking/admin.py,sha256=YQ6gNY7oMgzfR-HAgMfeZ8DsH0Yhr8GvmUeQUb11eqQ,554
3324
3324
  edc_visit_tracking/admin_site.py,sha256=f_MgAkWy84hY48SOb5vT21IcSkebPm0j5b6ZmcuCjcw,187
3325
- edc_visit_tracking/apps.py,sha256=9Y0hcJkV6t0sNUWRqGnnMTzoYYbBd0wfPWVKXHCTI6I,529
3325
+ edc_visit_tracking/apps.py,sha256=WD1WUsqsYoH0arcRdx6eDyEU3pB4cwpjY3wvYvlpg7U,545
3326
3326
  edc_visit_tracking/choices.py,sha256=HVpd_Wr31NAG2kTKaF7TkPAgBk5PB_8hfQN3sAiNisA,2322
3327
3327
  edc_visit_tracking/constants.py,sha256=-XXCR2VzMxpZrCdGp4CB958j3E0llrPd5PKdrblsHNs,444
3328
- edc_visit_tracking/context_processors.py,sha256=dOLgB2WhD2W2YIO_KIf9gNDzdEmxG1vOhrjXddKXZyo,326
3329
- edc_visit_tracking/crf_date_validator.py,sha256=Nysm30fY971wB__tn9h7U0x_IlYzhrIMd6PU84Hny4c,3695
3328
+ edc_visit_tracking/context_processors.py,sha256=U4zznNUdTAxFNjr56nl13GXFQ2yM9VIochpGujf9bb0,312
3329
+ edc_visit_tracking/crf_date_validator.py,sha256=_UhYtNvzAyVdechjEmR-g9-SKBGb4aCW0DlK4WaY7rg,3737
3330
3330
  edc_visit_tracking/exceptions.py,sha256=trVxjsip2_Fl6y2e4gWts9XVVPcOqmJXvthuJSSFTMk,155
3331
3331
  edc_visit_tracking/form_validators/__init__.py,sha256=BQcVQ_BBlMb_x5unFYm2sy5fTGiI3pBnyxsgrebjIfU,119
3332
- edc_visit_tracking/form_validators/visit_form_validator.py,sha256=6A3Xrc_XGiqGKR1hPY6lovWxiskKcCQ4R8mok5iWag4,12052
3333
- edc_visit_tracking/form_validators/visit_missed_form_validator.py,sha256=Q-1pVZvxoE9k8d1QIEG_c-8uExPLnzSu74wYMuA8-Oo,1347
3332
+ edc_visit_tracking/form_validators/visit_form_validator.py,sha256=lRc1IA3ujgWqJL0VQbjPzJa8G6njXKogbOVoN_EzTGg,11972
3333
+ edc_visit_tracking/form_validators/visit_missed_form_validator.py,sha256=xJRVbAGcxXSvfb6ZkxGHfI01G1K7afuxF8wXa-smAMg,1449
3334
3334
  edc_visit_tracking/locale/sw/LC_MESSAGES/django.po,sha256=lRGiikykQlMhoS-SgN-Hzqgm3NpBuAoR9H_vsEt4-aI,3719
3335
3335
  edc_visit_tracking/managers.py,sha256=oQyaMAnUYYLqY9qwSU8rT2-KUvqu4M9t9J1YzIIdwFQ,4008
3336
3336
  edc_visit_tracking/migrations/0001_initial.py,sha256=R3MUszHP_y2zQAO9JXu16td3ZTkJreh6jCBOc6biALs,5655
@@ -3354,7 +3354,7 @@ edc_visit_tracking/model_mixins/crfs/visit_tracking_crf_model_mixin.py,sha256=5p
3354
3354
  edc_visit_tracking/model_mixins/requisitions/__init__.py,sha256=JZHxR8XADoba21z1JJsJyQattiRheoLj4mkCDk_HEKc,87
3355
3355
  edc_visit_tracking/model_mixins/requisitions/visit_tracking_requisition_model_mixin.py,sha256=I72j8p5SkYLDpxAAeRx76z_TMi-lIH0JxcbKY6OOALg,1186
3356
3356
  edc_visit_tracking/model_mixins/subject_visit_missed_model_mixin.py,sha256=BnPnpbNSVyJgbzxCyWAnoOXkjRUyciifFIR6kWNp_yA,3593
3357
- edc_visit_tracking/model_mixins/utils.py,sha256=pyQl3k0tzu1Amq9qBBWARu9bTRJasNbPU3dF8UZz2Ag,1537
3357
+ edc_visit_tracking/model_mixins/utils.py,sha256=yL3DsZbW5mG8kOo3r3D1J_dGhvYTpYb9x6FY0fQMrts,1554
3358
3358
  edc_visit_tracking/model_mixins/visit_model_mixin/__init__.py,sha256=YpThykSaGcXNz7rOH-aOOodhLFFhnR3vDwn40gsaxpc,248
3359
3359
  edc_visit_tracking/model_mixins/visit_model_mixin/caretaker_fields_mixin.py,sha256=4GDRDe6It-b1UNKatsdI_oyu1_47bpnJsuKv6yLQWZ8,1024
3360
3360
  edc_visit_tracking/model_mixins/visit_model_mixin/previous_visit_model_mixin.py,sha256=NZ5UDkZltDmqYu6uHOL4BWB5-tnwEyWmIHI_VYNY3Is,1875
@@ -3365,23 +3365,23 @@ edc_visit_tracking/modeladmin_mixins/crf_model_admin_mixin.py,sha256=KaShntUo5g0
3365
3365
  edc_visit_tracking/modeladmin_mixins/visit_model_admin_mixin.py,sha256=bnBzchsrRYZzq8-AGV7veWzXsLPHOx_bwFCHduUBLRM,5156
3366
3366
  edc_visit_tracking/modelform_mixins/__init__.py,sha256=YSmMaMAWmggeIoftggG_ij8H2kX-H3P5T8w5E0TCCZ0,194
3367
3367
  edc_visit_tracking/modelform_mixins/crf/__init__.py,sha256=bPOdhr4KLEBFo-SK1F8DWD6us7kFolU4qphjg5oTnTs,129
3368
- edc_visit_tracking/modelform_mixins/crf/visit_tracking_crf_modelform_mixin.py,sha256=2uDMX00sXNzlM_C8O6n53CRru8PXnEJ_JKjbiNBya38,4347
3369
- edc_visit_tracking/modelform_mixins/utils.py,sha256=XpGMbmF4V8-KffoHpq-pLOWe-9Tq0DRUNAPDe5EdHBk,1663
3368
+ edc_visit_tracking/modelform_mixins/crf/visit_tracking_crf_modelform_mixin.py,sha256=Ynl8z-rL1LwmYGtWMb-XbYwdcM7p_rX6tA5Fx6uSbfc,4373
3369
+ edc_visit_tracking/modelform_mixins/utils.py,sha256=PlmJw4enAaESEGkzooH0iOX9LtGZL7jasDKaHZ9IOdM,1669
3370
3370
  edc_visit_tracking/modelform_mixins/visit_tracking_modelform_mixin.py,sha256=pik_qYv2vJLd3HtYslZEn65_Cy0iHSs-rqkryoyjZg8,2580
3371
3371
  edc_visit_tracking/models/__init__.py,sha256=aB-hnevvK3ttednU3e3c5Fb8i2GIRK5p2zRRkgoSXSc,318
3372
- edc_visit_tracking/models/signals.py,sha256=0gIfrFWaQSAIyG_YEuQpHz-_czw9yceM03wosujlSDI,2786
3372
+ edc_visit_tracking/models/signals.py,sha256=AgdyrUfya4obxnOjoTl62IIRHMfuP-wMtMm6skwes5E,2800
3373
3373
  edc_visit_tracking/models/subject_visit.py,sha256=yrSV_pH-W_tw3S9Px2ELSTeFM82haNf0YaekWVaTaRI,1732
3374
3374
  edc_visit_tracking/models/subject_visit_missed.py,sha256=VsVF0eALA-UHTeWkaJT5xGCo8FYXqBiRxv-t61wIFtk,963
3375
3375
  edc_visit_tracking/models/subject_visit_missed_reasons.py,sha256=dGEUkZvD3wqyUk3joSmfdsEPEswZ6dsCQJAUZ69cewI,257
3376
3376
  edc_visit_tracking/models/visit_reasons.py,sha256=OWAL3iksKQlkp9zcD3OXDnjg03kS97hATMRjIZMVsU0,214
3377
3377
  edc_visit_tracking/stubs.py,sha256=4lGFKGJEckmnXbNdnQQwc_HcdQFSQxE1H2oQpDircBs,1164
3378
3378
  edc_visit_tracking/system_checks.py,sha256=rK1yGfp0H9nuQJBl1PA_RhvorBXMG-ve_Wc6jKXBmdQ,435
3379
- edc_visit_tracking/typing_stubs.py,sha256=RkwlPckyO9r-L51SAYmRtiB65AktEYd29W9JREb8-f4,2137
3379
+ edc_visit_tracking/typing_stubs.py,sha256=7LMpFxcJICBFY7fBFZ1z0JSwSBfYGE6K1whAE7BVb-U,2106
3380
3380
  edc_visit_tracking/urls.py,sha256=X9Wiy0mlDaCeiiOKNytfSNfVY6oeeJbaBPtHol81HD0,181
3381
- edc_visit_tracking/utils.py,sha256=z5ax1F_WCSyKWfapxQ3XolUduduX9MBqWmSrtExCRkc,3251
3381
+ edc_visit_tracking/utils.py,sha256=3Bamx6fWOISqN5_JhXU3R1eHwcBZ4f1KDdNu0t_aHEg,3258
3382
3382
  edc_visit_tracking/view_utils/__init__.py,sha256=ABuaZK3UEKHbLjaJv-0e0tovLI0xKG6wrxgElloTeZ4,87
3383
3383
  edc_visit_tracking/view_utils/related_visit_button.py,sha256=D0y6mxtY8mW5sQsczyC-GEl2rzc_n5cgTVCTKF-tdIQ,3385
3384
- edc_visit_tracking/visit_sequence.py,sha256=Mrneex0eZjb6YHFmKaJ9rzs-7iBVDbMeRH26q-7748E,4294
3384
+ edc_visit_tracking/visit_sequence.py,sha256=ppK53IVuFD1ZcDHSEeP_P1YfqCNSveznf9s_Rqg5CVY,4300
3385
3385
  edc_visit_tracking/wsgi.py,sha256=_CGuos4-Wxsv6M7sc_3MnHKbWlGEfqzJDUrO6JUHzIo,179
3386
3386
  edc_vitals/__init__.py,sha256=k7pm4XmS32dJU4XRYKHJrzEkA7CuQ75_Jj7nLkvUiDY,117
3387
3387
  edc_vitals/apps.py,sha256=AvV-Dwgo6KRe5VhriAP8CYM6OJG6kGh2_1BwQ6Wmc8o,145
@@ -3406,7 +3406,7 @@ edc_vitals/models/fields/waist_circumference.py,sha256=fZcHFDdEwWLjIVLktKrFCD9UU
3406
3406
  edc_vitals/models/fields/weight.py,sha256=zo9_9e3Cpu0UqoRbWS-iDkcDo6fK80b1dDQy4x4MyxE,921
3407
3407
  edc_vitals/utils.py,sha256=vXid44KUXxeaSyund_y5MNXc5DFJs052_PwUAjszE2k,1384
3408
3408
  edc_vitals/validators.py,sha256=vNiElWMs0rRnHRNuVoPLRf0rW_C_0xcfUyep1Y_Si5s,156
3409
- clinicedc-2.0.13.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
3410
- clinicedc-2.0.13.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
3411
- clinicedc-2.0.13.dist-info/METADATA,sha256=Qe7GZ-I1dQExgWXJk70U7IJrafjScA4vbPGAEDwP75s,15924
3412
- clinicedc-2.0.13.dist-info/RECORD,,
3409
+ clinicedc-2.0.15.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
3410
+ clinicedc-2.0.15.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
3411
+ clinicedc-2.0.15.dist-info/METADATA,sha256=OYiw0Dnv4nYU6PEi6gKTj-xbM-7sc6meDbengt5P2zY,15899
3412
+ clinicedc-2.0.15.dist-info/RECORD,,
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+
2
3
  from edc_constants.constants import FEMALE, MALE
3
4
 
4
5
  from ..constants import MEDIAN_RANGE, N_ONLY, N_WITH_COL_PROP, N_WITH_ROW_PROP
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+
2
3
  from edc_constants.constants import FEMALE, MALE
3
4
 
4
5
  from ..constants import N_ONLY, N_WITH_COL_PROP, N_WITH_ROW_PROP
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+
2
3
  from edc_constants.constants import FEMALE, MALE
3
4
 
4
5
  from ..constants import (
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+
2
3
  from edc_constants.constants import FEMALE, MALE
3
4
 
4
5
  from ..constants import N_ONLY, N_WITH_COL_PROP, N_WITH_ROW_PROP
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+
2
3
  from edc_constants.constants import FEMALE, MALE
3
4
 
4
5
  from ..constants import MEDIAN_IQR, N_ONLY, N_WITH_COL_PROP, N_WITH_ROW_PROP
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+
2
3
  from edc_constants.constants import FEMALE, MALE
3
4
 
4
5
  from ..constants import MEAN_95CI, N_ONLY, N_WITH_COL_PROP, N_WITH_ROW_PROP
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+
2
3
  from edc_constants.constants import FEMALE, MALE
3
4
 
4
5
  from ..constants import N_ONLY, N_WITH_COL_PROP, N_WITH_ROW_PROP
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+
2
3
  from edc_constants.constants import FEMALE, MALE
3
4
 
4
5
  from ..constants import MEAN_95CI, N_ONLY, N_WITH_COL_PROP, N_WITH_ROW_PROP
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+
2
3
  from edc_constants.constants import FEMALE, MALE
3
4
 
4
5
  from ..constants import MEAN_95CI, N_ONLY, N_WITH_COL_PROP, N_WITH_ROW_PROP
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+
2
3
  from edc_constants.constants import FEMALE, MALE
3
4
 
4
5
  from ..constants import (
@@ -1,4 +1,4 @@
1
- from typing import Iterable
1
+ from collections.abc import Iterable
2
2
 
3
3
  from .row_definition import RowDefinition
4
4
  from .row_statistics import RowStatistics
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+
2
3
  from edc_constants.constants import FEMALE, MALE
3
4
 
4
5
  from .row_statistics import RowStatistics
@@ -35,10 +35,9 @@ def get_stata_labels_from_model(
35
35
  if suffix:
36
36
  if f"{fld.name}_{suffix}" in df.columns:
37
37
  labels.update({f"{fld.name}_{suffix}": strip_html(str(fld.verbose_name)[:80])})
38
- else:
39
- if f"{fld.name}_{suffix}" in df.columns:
40
- try:
41
- labels.update({fld.name: strip_html(str(fld.verbose_name)[:80])})
42
- except AttributeError:
43
- pass
38
+ elif f"{fld.name}_{suffix}" in df.columns:
39
+ try:
40
+ labels.update({fld.name: strip_html(str(fld.verbose_name)[:80])})
41
+ except AttributeError:
42
+ pass
44
43
  return labels
edc_analytics/table.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+
2
3
  from edc_constants.constants import FEMALE, MALE
3
4
 
4
5
  from .constants import COUNT_COLUMN, N_ONLY, N_WITH_ROW_PROP, TITLE_COLUMN
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  from datetime import datetime
4
5
  from typing import TYPE_CHECKING
5
6
  from zoneinfo import ZoneInfo
@@ -39,7 +40,7 @@ class AppointmentsCreator:
39
40
  visit_schedule: VisitSchedule | None = None,
40
41
  schedule: Schedule | None = None,
41
42
  report_datetime: datetime | None = None,
42
- appointment_model: str = None,
43
+ appointment_model: str | None = None,
43
44
  site_id: int | None = None,
44
45
  skip_baseline: bool | None = None,
45
46
  ):
@@ -103,13 +104,12 @@ class AppointmentsCreator:
103
104
  else:
104
105
  self.delete_appointments_after_timepoint(last_timepoint)
105
106
 
106
- appointments = self.appointment_model_cls.objects.filter(
107
+ return self.appointment_model_cls.objects.filter(
107
108
  subject_identifier=self.subject_identifier,
108
109
  site_id=self.site_id,
109
110
  visit_schedule_name=self.visit_schedule.name,
110
111
  schedule_name=self.schedule.name,
111
112
  ).order_by("timepoint")
112
- return appointments
113
113
 
114
114
  def update_or_create_appointment(self, **kwargs) -> Appointment:
115
115
  """Updates or creates an appointment for this subject
@@ -133,10 +133,8 @@ class AppointmentsCreator:
133
133
  schedule_name=self.schedule.name,
134
134
  )
135
135
  for appointment in appointments:
136
- try:
136
+ with contextlib.suppress(ProtectedError):
137
137
  appointment.delete()
138
- except ProtectedError:
139
- pass
140
138
 
141
139
  def delete_appointments_after_timepoint(self, last_timepoint) -> None:
142
140
  """Delete appointments after a given timepoint.
@@ -151,7 +149,5 @@ class AppointmentsCreator:
151
149
  visit_schedule_name=self.visit_schedule.name,
152
150
  schedule_name=self.schedule.name,
153
151
  ):
154
- try:
152
+ with contextlib.suppress(ProtectedError):
155
153
  appointment.delete()
156
- except ProtectedError:
157
- pass
edc_dx/diagnoses.py CHANGED
@@ -1,9 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from datetime import date, datetime
4
- from typing import Dict
5
4
 
6
5
  from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
6
+
7
7
  from edc_constants.constants import YES
8
8
  from edc_dx_review.utils import (
9
9
  get_clinical_review_baseline_model_cls,
@@ -130,7 +130,7 @@ class Diagnoses:
130
130
 
131
131
  def report_datetime_opts(
132
132
  self, prefix: str = None, lte: bool = None
133
- ) -> Dict[str, datetime]:
133
+ ) -> dict[str, datetime]:
134
134
  opts = {}
135
135
  prefix = prefix.lower() or ""
136
136
  if self.report_datetime:
@@ -1,4 +1,5 @@
1
1
  from django import forms
2
+
2
3
  from edc_constants.constants import YES
3
4
 
4
5
  from ..diagnoses import (
@@ -1,9 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Tuple
4
-
5
3
  from django import forms
6
4
  from django.conf import settings
5
+
7
6
  from edc_dx_review.utils import raise_if_clinical_review_does_not_exist
8
7
  from edc_form_validators import INVALID_ERROR, FormValidator
9
8
  from edc_utils import convert_php_dateformat
@@ -13,7 +12,7 @@ from ..diagnoses import ClinicalReviewBaselineRequired, Diagnoses, InitialReview
13
12
 
14
13
 
15
14
  class ResultFormValidatorMixin(FormValidator):
16
- dx: Tuple[str, str] # e.g. (HIV, "HIV Infection")
15
+ dx: tuple[str, str] # e.g. (HIV, "HIV Infection")
17
16
 
18
17
  def clean(self):
19
18
  raise_if_baseline(self.cleaned_data.get("subject_visit"))
@@ -50,16 +49,15 @@ class ResultFormValidatorMixin(FormValidator):
50
49
  raise forms.ValidationError(
51
50
  f"A {dx_msg_label} diagnosis has not been reported for this subject."
52
51
  )
53
- else:
54
- if dx_date > self.cleaned_data.get(drawn_date_fld):
55
- formatted_date = dx_date.strftime(
56
- convert_php_dateformat(settings.SHORT_DATE_FORMAT)
57
- )
58
- raise forms.ValidationError(
59
- {
60
- "drawn_date": (
61
- "Invalid. Subject was diagnosed with "
62
- f"{dx_msg_label} on {formatted_date}."
63
- )
64
- }
65
- )
52
+ if dx_date > self.cleaned_data.get(drawn_date_fld):
53
+ formatted_date = dx_date.strftime(
54
+ convert_php_dateformat(settings.SHORT_DATE_FORMAT)
55
+ )
56
+ raise forms.ValidationError(
57
+ {
58
+ "drawn_date": (
59
+ "Invalid. Subject was diagnosed with "
60
+ f"{dx_msg_label} on {formatted_date}."
61
+ )
62
+ }
63
+ )
@@ -1,5 +1,6 @@
1
1
  from django import forms
2
2
  from django.core.exceptions import ObjectDoesNotExist
3
+
3
4
  from edc_model.utils import model_exists_or_raise
4
5
 
5
6
  from ..utils import get_clinical_review_baseline_model_cls
@@ -4,6 +4,7 @@ from datetime import date, datetime
4
4
 
5
5
  from django.conf import settings
6
6
  from django.core.exceptions import ValidationError
7
+
7
8
  from edc_constants.constants import GT, GTE, LT, LTE
8
9
  from edc_model import estimated_date_from_ago
9
10
  from edc_utils import convert_php_dateformat
@@ -23,7 +24,7 @@ class MedicalDateError(ValidationError):
23
24
 
24
25
  @property
25
26
  def message_dict(self):
26
- getattr(self, "error_dict")
27
+ self.error_dict
27
28
  return {k: v[0] for k, v in dict(self).items()}
28
29
 
29
30
 
@@ -1,4 +1,5 @@
1
1
  from django.db import models
2
+
2
3
  from edc_constants.choices import YES_NO
3
4
  from edc_constants.constants import YES
4
5
  from edc_crf.model_mixins import SingletonCrfModelMixin
@@ -1,6 +1,7 @@
1
1
  from django.db import models
2
2
  from django.utils.html import format_html
3
3
  from django.utils.safestring import mark_safe
4
+
4
5
  from edc_constants.choices import YES_NO_NA
5
6
  from edc_constants.constants import NOT_APPLICABLE
6
7
  from edc_model import models as edc_models
@@ -1,6 +1,7 @@
1
1
  from django.db import models
2
2
  from django.utils.html import format_html
3
3
  from django.utils.safestring import mark_safe
4
+
4
5
  from edc_constants.choices import YES_NO_NA
5
6
  from edc_constants.constants import NOT_APPLICABLE
6
7
  from edc_model import models as edc_models
@@ -1,6 +1,7 @@
1
1
  from django.db import models
2
2
  from django.utils.html import format_html
3
3
  from django.utils.safestring import mark_safe
4
+
4
5
  from edc_constants.choices import YES_NO_NA
5
6
  from edc_constants.constants import NOT_APPLICABLE
6
7
  from edc_model import models as edc_models
@@ -1,6 +1,7 @@
1
1
  from django.db import models
2
2
  from django.utils.html import format_html
3
3
  from django.utils.safestring import mark_safe
4
+
4
5
  from edc_constants.choices import YES_NO_NA
5
6
  from edc_constants.constants import NOT_APPLICABLE
6
7
  from edc_model import models as edc_models
@@ -1,4 +1,5 @@
1
1
  from django.db import models
2
+
2
3
  from edc_constants.choices import YES_NO
3
4
  from edc_constants.constants import YES
4
5
  from edc_crf.model_mixins import SingletonCrfModelMixin
@@ -1,4 +1,5 @@
1
1
  from django.db import models
2
+
2
3
  from edc_model import models as edc_models
3
4
 
4
5
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from django.db import models
4
+
4
5
  from edc_constants.choices import YES_NO
5
6
  from edc_constants.constants import CHOL, DM, HIV, HTN, NO
6
7
  from edc_dx import get_diagnosis_labels
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from datetime import date
4
4
 
5
5
  from django.db import models
6
+
6
7
  from edc_constants.constants import NO, NOT_APPLICABLE, YES
7
8
  from edc_model import duration_to_date
8
9
 
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from datetime import date
4
4
 
5
5
  from django.db import models
6
+
6
7
  from edc_constants.choices import YES_NO
7
8
  from edc_constants.constants import NOT_APPLICABLE, YES
8
9
  from edc_dx import Diagnoses
@@ -36,7 +37,7 @@ def dx_initial_review_methods_model_mixin_factory():
36
37
 
37
38
  @property
38
39
  def diagnoses(self):
39
- subject_identifier = getattr(self, "subject_identifier")
40
+ subject_identifier = self.subject_identifier
40
41
  return Diagnoses(
41
42
  subject_identifier=subject_identifier,
42
43
  report_datetime=getattr(self, get_report_datetime_field_name()),
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from django.db import models
4
+
4
5
  from edc_constants.choices import YES_NO_NA
5
6
  from edc_constants.constants import CHOL, DM, HIV, HTN, NOT_APPLICABLE
6
7
  from edc_dx import get_diagnosis_labels
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from django.db import models
4
+
4
5
  from edc_constants.choices import YES_NO, YES_NO_NA
5
6
  from edc_constants.constants import NOT_APPLICABLE, YES
6
7
  from edc_model.models import DurationYMDField
@@ -1,4 +1,5 @@
1
1
  from django.db import models
2
+
2
3
  from edc_constants.choices import YES_NO_NA
3
4
  from edc_constants.constants import NOT_APPLICABLE
4
5
 
@@ -1,4 +1,5 @@
1
1
  from django.db import models
2
+
2
3
  from edc_constants.choices import YES_NO_NA
3
4
  from edc_constants.constants import NOT_APPLICABLE
4
5
 
@@ -1,4 +1,5 @@
1
1
  from django.db import models
2
+
2
3
  from edc_constants.choices import YES_NO
3
4
  from edc_constants.constants import NOT_APPLICABLE
4
5
 
@@ -1,5 +1,6 @@
1
1
  from django.core.validators import MaxValueValidator, MinValueValidator
2
2
  from django.db import models
3
+
3
4
  from edc_constants.choices import YES_NO, YES_NO_NA, YES_NO_PENDING_NA
4
5
  from edc_constants.constants import NOT_APPLICABLE, YES
5
6
  from edc_lab.choices import VL_QUANTIFIER_NA
@@ -1,4 +1,5 @@
1
1
  from django.db import models
2
+
2
3
  from edc_constants.choices import YES_NO
3
4
  from edc_constants.constants import YES
4
5
  from edc_model import models as edc_models
@@ -1,10 +1,10 @@
1
- from typing import Dict
2
1
 
3
2
  from django.contrib import admin
3
+
4
4
  from edc_dx import get_diagnosis_labels_prefixes
5
5
 
6
6
 
7
- def get_clinical_review_cond_radio_fields() -> Dict[str, int]:
7
+ def get_clinical_review_cond_radio_fields() -> dict[str, int]:
8
8
  radio_fields = {}
9
9
  for prefix in get_diagnosis_labels_prefixes():
10
10
  cond = prefix.lower()
@@ -17,7 +17,7 @@ def get_clinical_review_cond_radio_fields() -> Dict[str, int]:
17
17
  return radio_fields
18
18
 
19
19
 
20
- def get_clinical_review_baseline_cond_radio_fields() -> Dict[str, int]:
20
+ def get_clinical_review_baseline_cond_radio_fields() -> dict[str, int]:
21
21
  radio_fields = {}
22
22
  for prefix in get_diagnosis_labels_prefixes():
23
23
  cond = prefix.lower()
edc_dx_review/utils.py CHANGED
@@ -7,6 +7,7 @@ from django import forms
7
7
  from django.apps import apps as django_apps
8
8
  from django.conf import settings
9
9
  from django.core.exceptions import ObjectDoesNotExist
10
+
10
11
  from edc_constants.constants import HIV, YES
11
12
  from edc_model.utils import model_exists_or_raise
12
13
  from edc_visit_schedule.baseline import VisitScheduleBaselineError
@@ -78,7 +79,7 @@ def get_extra_attrs():
78
79
  "review": "review",
79
80
  }
80
81
  try:
81
- data = getattr(settings, "EDC_DX_REVIEW_EXTRA_ATTRS")
82
+ data = settings.EDC_DX_REVIEW_EXTRA_ATTRS
82
83
  except AttributeError:
83
84
  pass
84
85
  else:
@@ -22,6 +22,7 @@ from .randomization_list_importer import (
22
22
  RandomizationListAlreadyImported,
23
23
  RandomizationListImporter,
24
24
  )
25
+ from .utils import get_randomization_list_path
25
26
 
26
27
  if TYPE_CHECKING:
27
28
  from edc_registration.models import RegisteredSubject
@@ -99,11 +100,7 @@ class Randomizer:
99
100
  DEFAULT_ASSIGNMENT_DESCRIPTION_MAP,
100
101
  )
101
102
  filename: str = "randomization_list.csv"
102
- randomizationlist_folder: Path | str = getattr(
103
- settings,
104
- "EDC_RANDOMIZATION_LIST_PATH",
105
- Path(settings.BASE_DIR).expanduser() / ".etc",
106
- )
103
+ randomizationlist_folder: Path | str = get_randomization_list_path()
107
104
  extra_csv_fieldnames: list[str] | None = None
108
105
  trial_is_blinded: bool = True
109
106
  importer_cls: Any = RandomizationListImporter
@@ -25,6 +25,16 @@ class SubjectNotRandomization(Exception): # noqa: N818
25
25
  pass
26
26
 
27
27
 
28
+ def get_randomization_list_path() -> Path:
29
+ return Path(
30
+ getattr(
31
+ settings,
32
+ "EDC_RANDOMIZATION_LIST_PATH",
33
+ settings.ETC_DIR,
34
+ )
35
+ ).expanduser()
36
+
37
+
28
38
  def get_assignment_for_subject(
29
39
  subject_identifier: str,
30
40
  randomizer_name: str,
@@ -26,12 +26,11 @@ class VisitMissedAction(ActionWithNotification):
26
26
 
27
27
  def get_next_actions(self) -> list[Action]:
28
28
  next_actions: list[Action] = []
29
- next_actions = self.append_to_next_if_required(
29
+ return self.append_to_next_if_required(
30
30
  next_actions=next_actions,
31
31
  action_name=self.get_loss_to_followup_action_name(),
32
32
  required=self.is_ltfu(),
33
33
  )
34
- return next_actions
35
34
 
36
35
 
37
36
  class MissedVisitAction(VisitMissedAction):
@@ -12,7 +12,7 @@ class AppConfig(DjangoAppConfig):
12
12
  verbose_name = "Edc Visit Tracking"
13
13
  report_datetime_allowance: int = 30
14
14
  allow_crf_report_datetime_before_visit: bool = False
15
- reason_field: dict = {}
15
+ reason_field: dict = {} # noqa: RUF012
16
16
 
17
17
  def ready(self):
18
18
  register(context_processors_check)
@@ -2,11 +2,10 @@ from .constants import DEFERRED_VISIT, LOST_VISIT, MISSED_VISIT, SCHEDULED, UNSC
2
2
 
3
3
 
4
4
  def constants(request) -> dict:
5
- dct = dict(
5
+ return dict(
6
6
  DEFERRED_VISIT=DEFERRED_VISIT,
7
7
  LOST_VISIT=LOST_VISIT,
8
8
  MISSED_VISIT=MISSED_VISIT,
9
9
  SCHEDULED=SCHEDULED,
10
10
  UNSCHEDULED=UNSCHEDULED,
11
11
  )
12
- return dct
@@ -14,11 +14,11 @@ class CrfReportDateAllowanceError(Exception):
14
14
  pass
15
15
 
16
16
 
17
- class CrfReportDateBeforeStudyStart(Exception):
17
+ class CrfReportDateBeforeStudyStart(Exception): # noqa: N818
18
18
  pass
19
19
 
20
20
 
21
- class CrfReportDateIsFuture(Exception):
21
+ class CrfReportDateIsFuture(Exception): # noqa: N818
22
22
  pass
23
23
 
24
24
 
@@ -58,13 +58,13 @@ class CrfDateValidator:
58
58
  datetime_not_before_study_start(self.report_datetime)
59
59
  except ValidationError as e:
60
60
  message = e.message if hasattr(e, "message") else str(e)
61
- raise CrfReportDateBeforeStudyStart(message)
61
+ raise CrfReportDateBeforeStudyStart(message) from e
62
62
  # datetime_not_future
63
63
  try:
64
64
  datetime_not_future(self.report_datetime)
65
65
  except ValidationError as e:
66
66
  message = e.message if hasattr(e, "message") else str(e)
67
- raise CrfReportDateIsFuture(message)
67
+ raise CrfReportDateIsFuture(message) from e
68
68
 
69
69
  formatted_visit_datetime = self.visit_report_datetime.strftime(
70
70
  convert_php_dateformat(settings.SHORT_DATE_FORMAT)
@@ -168,41 +168,44 @@ class VisitFormValidator(WindowPeriodFormValidatorMixin, FormValidator):
168
168
  """Asserts the report_datetime is not before the
169
169
  appt_datetime.
170
170
  """
171
- if report_datetime_local := self.report_datetime:
172
- if report_datetime_local.date() < self.appt_datetime_local.date():
173
- appt_datetime_str = formatted_datetime(
174
- self.appt_datetime_local, format_as_date=True
175
- )
176
- self.raise_validation_error(
177
- {
178
- "report_datetime": (
179
- "Invalid. Cannot be before appointment date. "
180
- f"Got appointment date {appt_datetime_str}"
181
- )
182
- },
183
- INVALID_ERROR,
184
- )
171
+ if (report_datetime_local := self.report_datetime) and (
172
+ report_datetime_local.date() < self.appt_datetime_local.date()
173
+ ):
174
+ appt_datetime_str = formatted_datetime(
175
+ self.appt_datetime_local, format_as_date=True
176
+ )
177
+ self.raise_validation_error(
178
+ {
179
+ "report_datetime": (
180
+ "Invalid. Cannot be before appointment date. "
181
+ f"Got appointment date {appt_datetime_str}"
182
+ )
183
+ },
184
+ INVALID_ERROR,
185
+ )
185
186
 
186
187
  def validate_visit_datetime_matches_appt_datetime_at_baseline(self) -> None:
187
188
  """Asserts the report_datetime matches the appt_datetime
188
189
  as baseline.
189
190
  """
190
- if is_baseline(instance=self.appointment):
191
- if report_datetime_local := self.report_datetime:
192
- if report_datetime_local.date() != self.appt_datetime_local.date():
193
- appt_datetime_str = formatted_datetime(
194
- self.appt_datetime_local, format_as_date=True
195
- )
196
- self.raise_validation_error(
197
- {
198
- "report_datetime": (
199
- "Invalid. Must match appointment date at baseline. "
200
- "If necessary, change the appointment date and "
201
- f"try again. Got appointment date {appt_datetime_str}"
202
- )
203
- },
204
- INVALID_ERROR,
191
+ if (
192
+ (is_baseline(instance=self.appointment))
193
+ and (report_datetime_local := self.report_datetime)
194
+ and (report_datetime_local.date() != self.appt_datetime_local.date())
195
+ ):
196
+ appt_datetime_str = formatted_datetime(
197
+ self.appt_datetime_local, format_as_date=True
198
+ )
199
+ self.raise_validation_error(
200
+ {
201
+ "report_datetime": (
202
+ "Invalid. Must match appointment date at baseline. "
203
+ "If necessary, change the appointment date and "
204
+ f"try again. Got appointment date {appt_datetime_str}"
205
205
  )
206
+ },
207
+ INVALID_ERROR,
208
+ )
206
209
 
207
210
  def validate_visits_completed_in_order(self) -> None:
208
211
  """Asserts visits are completed in order."""
@@ -210,7 +213,7 @@ class VisitFormValidator(WindowPeriodFormValidatorMixin, FormValidator):
210
213
  try:
211
214
  visit_sequence.enforce_sequence()
212
215
  except VisitSequenceError as e:
213
- raise forms.ValidationError(e, code=INVALID_ERROR)
216
+ raise forms.ValidationError(e, code=INVALID_ERROR) from e
214
217
 
215
218
  def validate_visit_code_sequence_and_reason(self) -> None:
216
219
  """Asserts the `reason` makes sense relative to the
@@ -234,7 +237,7 @@ class VisitFormValidator(WindowPeriodFormValidatorMixin, FormValidator):
234
237
  and EDC_VISIT_TRACKING_ALLOW_MISSED_UNSCHEDULED is False
235
238
  ):
236
239
  raise forms.ValidationError(
237
- {"reason": ("Invalid. This is an unscheduled visit. See appointment.")},
240
+ {"reason": "Invalid. This is an unscheduled visit. See appointment."},
238
241
  code=INVALID_ERROR,
239
242
  )
240
243
  # raise if CRF metadata exist
@@ -267,23 +270,25 @@ class VisitFormValidator(WindowPeriodFormValidatorMixin, FormValidator):
267
270
  field_required="reason_missed_other",
268
271
  )
269
272
 
270
- if self.validate_unscheduled_visit_reason:
271
- if "reason_unscheduled" in self.cleaned_data:
272
- self.applicable_if(
273
- UNSCHEDULED,
274
- field="reason",
275
- field_applicable="reason_unscheduled",
276
- )
273
+ if (
274
+ self.validate_unscheduled_visit_reason
275
+ and "reason_unscheduled" in self.cleaned_data
276
+ ):
277
+ self.applicable_if(
278
+ UNSCHEDULED,
279
+ field="reason",
280
+ field_applicable="reason_unscheduled",
281
+ )
277
282
 
278
- self.required_if(
279
- OTHER,
280
- field="reason_unscheduled",
281
- field_required="reason_unscheduled_other",
282
- )
283
+ self.required_if(
284
+ OTHER,
285
+ field="reason_unscheduled",
286
+ field_required="reason_unscheduled_other",
287
+ )
283
288
 
284
289
  def metadata_exists_for(
285
290
  self,
286
- entry_status: str = None,
291
+ entry_status: str | None = None,
287
292
  filter_models: list[str] | None = None,
288
293
  exclude_models: list[str] | None = None,
289
294
  ) -> int:
@@ -291,8 +296,9 @@ class VisitFormValidator(WindowPeriodFormValidatorMixin, FormValidator):
291
296
  the given entry_status.
292
297
  """
293
298
  exclude_opts: dict = {}
299
+ entry_status = entry_status or KEYED
294
300
  filter_opts = deepcopy(self.crf_filter_options)
295
- filter_opts.update(entry_status=entry_status or KEYED)
301
+ filter_opts.update(entry_status=entry_status)
296
302
  if filter_models:
297
303
  filter_opts.update(model__in=filter_models)
298
304
  if exclude_models:
@@ -3,6 +3,8 @@ from edc_form_validators import FormValidator
3
3
 
4
4
 
5
5
  class VisitMissedFormValidator(FormValidator):
6
+ min_contact_attempts_count = 3
7
+
6
8
  def clean(self) -> None:
7
9
  self.applicable_if(YES, field="contact_attempted", field_applicable="contact_made")
8
10
  self.required_if(
@@ -16,7 +18,10 @@ class VisitMissedFormValidator(FormValidator):
16
18
  if self.cleaned_data.get("contact_attempts_count") is None
17
19
  else self.cleaned_data.get("contact_attempts_count")
18
20
  )
19
- cond = self.cleaned_data.get("contact_made") == NO and contact_attempts_count < 3
21
+ cond = (
22
+ self.cleaned_data.get("contact_made") == NO
23
+ and contact_attempts_count < self.min_contact_attempts_count
24
+ )
20
25
  self.required_if_true(
21
26
  cond,
22
27
  field_required="contact_attempts_explained",
@@ -25,7 +25,7 @@ def get_related_visit_model_attr(model_cls) -> str:
25
25
  fld_cls.related_model is not None
26
26
  and fld_cls.related_model == related_visit_model_cls
27
27
  ):
28
- attrs.append(fld_cls.name)
28
+ attrs.append(fld_cls.name) # noqa: PERF401
29
29
  if len(attrs) > 1:
30
30
  raise RelatedVisitFieldError(
31
31
  f"More than one field is related to the visit model. See {model_cls}. "
@@ -76,11 +76,11 @@ class VisitTrackingCrfModelFormMixin:
76
76
  def related_visit_model_attr(self) -> str:
77
77
  try:
78
78
  return self._meta.model.related_visit_model_attr()
79
- except AttributeError:
79
+ except AttributeError as e:
80
80
  raise VisitTrackingCrfModelFormMixinError(
81
81
  "Expected method `related_visit_model_attr`. Is this a CRF? "
82
82
  f"See model {self._meta.model}"
83
- )
83
+ ) from e
84
84
 
85
85
  def validate_visit_tracking(self: Any) -> None:
86
86
  # trigger a validation error if visit field is None
@@ -104,7 +104,7 @@ class VisitTrackingCrfModelFormMixin:
104
104
  CrfReportDateBeforeStudyStart,
105
105
  CrfReportDateIsFuture,
106
106
  ) as e:
107
- raise forms.ValidationError({self.report_datetime_field_attr: str(e)})
107
+ raise forms.ValidationError({self.report_datetime_field_attr: str(e)}) from e
108
108
 
109
109
  def validate_visits_completed_in_order(self) -> None:
110
110
  """Asserts visits are completed in order."""
@@ -115,4 +115,4 @@ class VisitTrackingCrfModelFormMixin:
115
115
  try:
116
116
  visit_sequence.enforce_sequence(document_type="CRF")
117
117
  except VisitSequenceError as e:
118
- raise forms.ValidationError(str(e), code=INVALID_ERROR)
118
+ raise forms.ValidationError(str(e), code=INVALID_ERROR) from e
@@ -29,9 +29,9 @@ def get_related_visit(
29
29
  VisitTrackingCrfModelFormMixin
30
30
  | InlineCrfModelFormMixin
31
31
  | CrfFormValidator
32
- | CrfFormValidatorMixin,
32
+ | CrfFormValidatorMixin
33
33
  ),
34
- related_visit_model_attr: str = None,
34
+ related_visit_model_attr: str | None = None,
35
35
  ) -> RelatedVisitModel | None:
36
36
  """Returns the related visit model instance or None.
37
37
 
@@ -18,7 +18,7 @@ def visit_tracking_check_in_progress_on_post_save(
18
18
  """Calls method on the visit tracking instance"""
19
19
  if not raw and not update_fields:
20
20
  try:
21
- instance.appointment
21
+ instance.appointment # noqa: B018
22
22
  except AttributeError:
23
23
  pass
24
24
  else:
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from datetime import datetime
4
- from typing import Any, Generic, Protocol, Tuple, TypeVar
4
+ from typing import Any, Protocol, TypeVar
5
5
  from uuid import UUID
6
6
 
7
7
  from django.contrib.contenttypes.fields import GenericForeignKey
@@ -33,10 +33,10 @@ class ModelBase(type):
33
33
  def _base_manager(cls: type[_Self]) -> BaseManager[_Self]: ...
34
34
 
35
35
 
36
- class Options(Generic[_M]):
36
+ class Options([_M]):
37
37
  def label_lower(self) -> str: ...
38
38
 
39
- def fields(self) -> Tuple[Field]: ... # noqa
39
+ def fields(self) -> tuple[Field]: ...
40
40
 
41
41
  def get_fields(
42
42
  self, include_parents: bool = ..., include_hidden: bool = ...
@@ -63,7 +63,7 @@ def get_subject_visit_missed_model() -> str:
63
63
  try:
64
64
  model = settings.SUBJECT_VISIT_MISSED_MODEL
65
65
  except AttributeError as e:
66
- raise ImproperlyConfigured(f"{error_msg} Got {e}.")
66
+ raise ImproperlyConfigured(f"{error_msg} Got {e}.") from e
67
67
  else:
68
68
  if not model:
69
69
  raise ImproperlyConfigured(f"{error_msg} Got None.")
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  from typing import TYPE_CHECKING
4
5
 
5
6
  from django.utils.translation import gettext_lazy as _
@@ -39,7 +40,7 @@ class VisitSequence:
39
40
  self.visit_code = self.appointment.visit_code
40
41
  self.visit_code_sequence = self.appointment.visit_code_sequence
41
42
 
42
- def enforce_sequence(self, document_type: str = None) -> None:
43
+ def enforce_sequence(self, document_type: str | None = None) -> None:
43
44
  """Raises an exception if sequence is not adhered to; that is,
44
45
  the visit reports are not completed in order.
45
46
 
@@ -80,10 +81,8 @@ class VisitSequence:
80
81
  previous_visit_code = self.visit_code
81
82
  else:
82
83
  previous = self.appointment.schedule.visits.previous(self.visit_code)
83
- try:
84
+ with contextlib.suppress(AttributeError):
84
85
  previous_visit_code = previous.code
85
- except AttributeError:
86
- pass
87
86
  return previous_visit_code
88
87
 
89
88
  @property