clinicedc 2.0.4__py3-none-any.whl → 2.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of clinicedc might be problematic. Click here for more details.

Files changed (478) hide show
  1. {clinicedc-2.0.4.dist-info → clinicedc-2.0.6.dist-info}/METADATA +41 -45
  2. {clinicedc-2.0.4.dist-info → clinicedc-2.0.6.dist-info}/RECORD +478 -478
  3. edc_action_item/action.py +76 -54
  4. edc_action_item/action_item_notification.py +1 -3
  5. edc_action_item/action_with_notification.py +4 -4
  6. edc_action_item/create_action_item.py +2 -2
  7. edc_action_item/create_or_update_action_type.py +1 -1
  8. edc_action_item/data_fixers.py +4 -5
  9. edc_action_item/delete_action_item.py +0 -1
  10. edc_action_item/modeladmin_mixins.py +2 -4
  11. edc_action_item/modelform_mixins/modelform_mixins.py +1 -1
  12. edc_action_item/models/action_item.py +53 -38
  13. edc_action_item/models/action_model_mixin.py +17 -17
  14. edc_action_item/models/action_type.py +33 -13
  15. edc_action_item/models/reference.py +3 -2
  16. edc_action_item/site_action_items.py +13 -14
  17. edc_action_item/stubs.py +9 -9
  18. edc_action_item/templatetags/action_item_extras.py +2 -2
  19. edc_action_item/view_utils/action_item_button.py +3 -3
  20. edc_action_item/view_utils/action_item_popover_list_item.py +4 -4
  21. edc_adherence/form_validator_mixin.py +1 -1
  22. edc_adverse_event/action_items/ae_followup_action.py +1 -1
  23. edc_adverse_event/action_items/ae_initial_action.py +1 -1
  24. edc_adverse_event/action_items/ae_susar_action.py +1 -1
  25. edc_adverse_event/action_items/ae_tmg_action.py +1 -1
  26. edc_adverse_event/action_items/death_report_action.py +1 -2
  27. edc_adverse_event/action_items/death_report_tmg_action.py +1 -1
  28. edc_adverse_event/action_items/death_report_tmg_second_action.py +1 -1
  29. edc_adverse_event/action_items/hospitalization_action.py +1 -1
  30. edc_adverse_event/form_validator_mixins/death_report_form_validator.py +3 -3
  31. edc_adverse_event/form_validator_mixins/requires_death_report_form_validator_mixin.py +3 -4
  32. edc_adverse_event/form_validators/death_report_tmg.py +1 -1
  33. edc_adverse_event/model_mixins/ae_followup/ae_followup_methods_model_mixin.py +3 -3
  34. edc_adverse_event/model_mixins/ae_followup/ae_followup_model_mixin.py +3 -3
  35. edc_adverse_event/model_mixins/ae_initial/ae_initial_fields_model_mixin.py +0 -1
  36. edc_adverse_event/model_mixins/ae_initial/ae_initial_methods_model_mixin.py +3 -3
  37. edc_adverse_event/model_mixins/ae_initial/ae_initial_model_mixin.py +3 -3
  38. edc_adverse_event/model_mixins/ae_special_interest/aesi_methods_model_mixin.py +4 -4
  39. edc_adverse_event/model_mixins/ae_special_interest/aesi_model_mixin.py +3 -3
  40. edc_adverse_event/model_mixins/ae_susar/ae_susar_methods_model_mixin.py +4 -4
  41. edc_adverse_event/model_mixins/ae_susar/ae_susar_model_mixin.py +3 -3
  42. edc_adverse_event/model_mixins/ae_tmg/ae_tmg_fields_model_mixin.py +4 -5
  43. edc_adverse_event/model_mixins/ae_tmg/ae_tmg_methods_model_mixin.py +4 -4
  44. edc_adverse_event/model_mixins/ae_tmg/ae_tmg_model_mixin.py +3 -3
  45. edc_adverse_event/model_mixins/death_report/death_report_extra_fields_model_mixin.py +2 -3
  46. edc_adverse_event/model_mixins/death_report/death_report_model_mixin.py +11 -11
  47. edc_adverse_event/model_mixins/death_report/death_report_tmg_model_mixin.py +9 -11
  48. edc_adverse_event/model_mixins/death_report/simple_death_report_model_mixin.py +7 -6
  49. edc_adverse_event/model_mixins/hospitaization/hospitalization_model_mixin.py +0 -1
  50. edc_adverse_event/modeladmin_mixins/ae_followup_admin_mixin.py +9 -11
  51. edc_adverse_event/modeladmin_mixins/ae_initial_admin_mixin.py +4 -6
  52. edc_adverse_event/modeladmin_mixins/modeladmin_mixins.py +1 -1
  53. edc_adverse_event/modeladmin_mixins/utils.py +4 -4
  54. edc_adverse_event/models/signals.py +7 -19
  55. edc_adverse_event/pdf_reports/death_pdf_report.py +8 -9
  56. edc_adverse_event/templatetags/edc_adverse_event_extras.py +10 -20
  57. edc_adverse_event/urls.py +7 -3
  58. edc_adverse_event/utils.py +2 -2
  59. edc_adverse_event/view_utils/tmg_button.py +4 -4
  60. edc_appconfig/system_checks.py +1 -1
  61. edc_appointment/appointment_status_updater.py +14 -15
  62. edc_appointment/creators/appointment_creator.py +7 -11
  63. edc_appointment/creators/appointments_creator.py +1 -1
  64. edc_appointment/creators/unscheduled_appointment_creator.py +12 -13
  65. edc_appointment/form_validator_mixins/next_appointment_crf_form_validator_mixin.py +1 -3
  66. edc_appointment/form_validator_mixins/window_period_form_validator_mixin.py +7 -6
  67. edc_appointment/form_validators/appointment_form_validator.py +2 -4
  68. edc_appointment/form_validators/utils.py +4 -4
  69. edc_appointment/managers.py +4 -4
  70. edc_appointment/model_mixins/appointment_methods_model_mixin.py +2 -2
  71. edc_appointment/model_mixins/appointment_model_mixin.py +5 -5
  72. edc_appointment/model_mixins/window_period_model_mixin.py +1 -1
  73. edc_appointment/models/appointment.py +1 -1
  74. edc_appointment/skip_appointments.py +5 -6
  75. edc_appointment/stubs.py +12 -12
  76. edc_appointment/utils.py +41 -47
  77. edc_appointment/view_utils/appointment_button.py +3 -5
  78. edc_auth/admin/role_admin.py +5 -7
  79. edc_auth/auth_objects/default_roles.py +1 -3
  80. edc_auth/auth_updater/auth_updater.py +5 -7
  81. edc_auth/auth_updater/group_updater.py +8 -8
  82. edc_auth/auth_updater/role_updater.py +1 -2
  83. edc_auth/get_app_codenames.py +1 -3
  84. edc_auth/import_users.py +19 -19
  85. edc_auth/models/role.py +4 -3
  86. edc_auth/password_setter.py +6 -7
  87. edc_auth/send_new_credentials_to_user.py +2 -3
  88. edc_auth/site_auths.py +3 -3
  89. edc_consent/actions.py +4 -5
  90. edc_consent/consent_definition.py +8 -11
  91. edc_consent/consent_definition_extension.py +3 -3
  92. edc_consent/form_validators/__init__.py +1 -1
  93. edc_consent/model_mixins/__init__.py +2 -2
  94. edc_consent/model_mixins/consent_version_model_mixin.py +1 -1
  95. edc_consent/modeladmin_mixins/consent_model_admin_mixin.py +1 -2
  96. edc_consent/modelform_mixins/consent_modelform_mixin/consent_modelform_validation_mixin.py +3 -5
  97. edc_consent/site_consents.py +10 -15
  98. edc_consent/stubs.py +2 -2
  99. edc_consent/utils.py +1 -1
  100. edc_constants/utils.py +2 -4
  101. edc_crf/crf_form_validator_mixins.py +2 -2
  102. edc_crf/model_mixins/crf_no_manager_model_mixin.py +2 -2
  103. edc_crf/model_mixins/crf_status_model_mixin.py +1 -1
  104. edc_crf/models/crf_status.py +10 -12
  105. edc_crf/update_crf_status_command.py +1 -3
  106. edc_dashboard/management/commands/update_search_slugs.py +8 -11
  107. edc_dashboard/templatetags/edc_dashboard_extras.py +1 -1
  108. edc_dashboard/url_config.py +3 -3
  109. edc_dashboard/utils.py +3 -4
  110. edc_dashboard/views/dashboard_view.py +1 -1
  111. edc_data_manager/models/data_dictionary.py +1 -2
  112. edc_data_manager/models/data_query.py +3 -3
  113. edc_data_manager/models/query_rule.py +2 -2
  114. edc_data_manager/tasks.py +0 -2
  115. edc_device/device.py +1 -1
  116. edc_device/view_mixins.py +1 -1
  117. edc_document_status/fieldsets.py +1 -3
  118. edc_document_status/model_mixins.py +2 -2
  119. edc_document_status/modeladmin_mixins.py +1 -4
  120. edc_egfr/admin/egfr_drop_notification_admin_mixin.py +5 -7
  121. edc_egfr/egfr.py +6 -7
  122. edc_egfr/get_drop_notification_model.py +1 -1
  123. edc_egfr/model_mixins/egfr_drop_notification_model_mixin.py +1 -1
  124. edc_export/archive_exporter.py +26 -27
  125. edc_export/exportables.py +2 -3
  126. edc_export/management/commands/import_receipts.py +6 -6
  127. edc_export/model_exporter/file_history_updater.py +1 -1
  128. edc_export/model_exporter/model_exporter.py +1 -1
  129. edc_export/model_exporter/value_getter.py +6 -6
  130. edc_export/model_mixins/notification_model_mixin.py +4 -4
  131. edc_export/models/data_request.py +3 -3
  132. edc_export/models/data_request_history.py +2 -2
  133. edc_export/models/export_receipt.py +1 -1
  134. edc_export/models/file_history.py +5 -5
  135. edc_export/models/plan.py +4 -4
  136. edc_export/models/upload_export_receipt_file.py +2 -2
  137. edc_export/utils.py +1 -1
  138. edc_facility/facility.py +6 -6
  139. edc_facility/holidays.py +2 -5
  140. edc_facility/import_holidays.py +6 -6
  141. edc_facility/model_mixins.py +1 -1
  142. edc_facility/models/holiday.py +1 -1
  143. edc_facility/utils.py +3 -3
  144. edc_fieldsets/fieldsets.py +1 -1
  145. edc_form_describer/forms_reference.py +5 -8
  146. edc_form_describer/make_forms_reference.py +1 -1
  147. edc_form_describer/management/commands/make_forms_reference.py +1 -1
  148. edc_form_label/custom_label_condition.py +1 -1
  149. edc_form_label/form_label.py +2 -2
  150. edc_form_runners/exceptions.py +1 -1
  151. edc_form_runners/models/issue.py +3 -2
  152. edc_form_runners/site.py +2 -2
  153. edc_form_runners/templatetags/form_runners_extras.py +1 -1
  154. edc_form_runners/utils.py +5 -5
  155. edc_form_validators/applicable_field_validator.py +32 -32
  156. edc_form_validators/base_form_validator.py +1 -1
  157. edc_form_validators/extra_mixins/study_day_form_validator.py +1 -1
  158. edc_form_validators/many_to_many_field_validator.py +38 -43
  159. edc_form_validators/other_specify_field_validator.py +9 -17
  160. edc_form_validators/required_field_validator.py +34 -39
  161. edc_form_validators/test_case_mixin.py +5 -5
  162. edc_identifier/admin.py +1 -3
  163. edc_identifier/identifier.py +2 -3
  164. edc_identifier/model_mixins.py +5 -8
  165. edc_identifier/research_identifier.py +10 -12
  166. edc_identifier/short_identifier.py +1 -1
  167. edc_identifier/simple_identifier.py +22 -22
  168. edc_identifier/utils.py +1 -1
  169. edc_lab/admin/fieldsets.py +7 -9
  170. edc_lab/admin/modeladmin_mixins.py +5 -6
  171. edc_lab/form_validators/requisition_form_validator_mixin.py +2 -3
  172. edc_lab/forms/box_form.py +5 -11
  173. edc_lab/identifiers/aliquot_identifier.py +5 -8
  174. edc_lab/identifiers/prefix.py +2 -5
  175. edc_lab/lab/aliquot_creator.py +1 -2
  176. edc_lab/lab/aliquot_type.py +2 -4
  177. edc_lab/lab/manifest.py +5 -7
  178. edc_lab/lab/requisition_panel.py +2 -4
  179. edc_lab/lab/requisition_panel_group.py +5 -7
  180. edc_lab/model_mixins/requisition/requisition_model_mixin.py +5 -4
  181. edc_lab/model_mixins/shipping/manifest_model_mixin.py +6 -6
  182. edc_lab/model_mixins/shipping/verify_model_mixin.py +4 -3
  183. edc_lab/models/aliquot.py +1 -1
  184. edc_lab/models/box.py +2 -2
  185. edc_lab/models/box_item.py +1 -1
  186. edc_lab/models/box_type.py +1 -1
  187. edc_lab/models/manifest/manifest_item.py +1 -1
  188. edc_lab/pdf_reports/manifest_pdf_report.py +3 -7
  189. edc_lab/site_labs.py +3 -3
  190. edc_lab_dashboard/dashboard_templates.py +1 -1
  191. edc_lab_dashboard/view_mixins/box_view_mixin.py +4 -9
  192. edc_lab_dashboard/views/action_views/action_view.py +1 -2
  193. edc_lab_dashboard/views/action_views/manage_manifest_view.py +1 -1
  194. edc_lab_dashboard/views/action_views/manifest_view.py +9 -11
  195. edc_lab_dashboard/views/action_views/pack_view.py +11 -14
  196. edc_lab_results/calculate_missing.py +2 -2
  197. edc_lab_results/fieldsets.py +6 -6
  198. edc_lab_results/model_mixin_factories/__init__.py +2 -2
  199. edc_lab_results/model_mixin_factories/reportable_result_model_mixin_factory.py +7 -9
  200. edc_lab_results/model_mixin_factories/result_model_mixin_factory.py +7 -9
  201. edc_lab_results/model_mixins/fbg_model_mixin.py +1 -1
  202. edc_lab_results/model_mixins/glucose_model_mixin.py +1 -1
  203. edc_label/admin.py +1 -3
  204. edc_label/label.py +2 -4
  205. edc_label/label_template.py +1 -1
  206. edc_label/subject_label.py +1 -3
  207. edc_list_data/admin.py +3 -5
  208. edc_list_data/load_list_data.py +1 -1
  209. edc_list_data/load_model_data.py +1 -3
  210. edc_list_data/model_mixins.py +3 -5
  211. edc_list_data/preload_data.py +2 -4
  212. edc_list_data/site_list_data.py +6 -7
  213. edc_listboard/filters/listboard_filter.py +2 -2
  214. edc_listboard/views/listboard_view.py +1 -3
  215. edc_locator/models.py +2 -2
  216. edc_locator/view_mixins/subject_locator_view_mixins.py +2 -2
  217. edc_ltfu/action_items.py +1 -1
  218. edc_ltfu/model_mixins.py +0 -2
  219. edc_ltfu/models.py +4 -4
  220. edc_metadata/admin/modeladmin_mixins.py +1 -1
  221. edc_metadata/admin/requisition_metadata.py +2 -4
  222. edc_metadata/metadata/crf_metadata_getter.py +1 -3
  223. edc_metadata/metadata/requisition_metadata_getter.py +1 -3
  224. edc_metadata/metadata_handler.py +2 -2
  225. edc_metadata/metadata_helper/metadata_helper_mixin.py +2 -3
  226. edc_metadata/metadata_mixins/source_model_metadata_mixin.py +2 -2
  227. edc_metadata/metadata_refresher.py +1 -1
  228. edc_metadata/metadata_rules/crf/crf_rule_group.py +3 -5
  229. edc_metadata/metadata_rules/logic.py +1 -1
  230. edc_metadata/metadata_rules/requisition/requisition_rule.py +4 -4
  231. edc_metadata/metadata_rules/requisition/requisition_rule_group.py +2 -2
  232. edc_metadata/metadata_rules/rule_evaluator.py +5 -6
  233. edc_metadata/metadata_rules/rule_group_metaclass.py +2 -3
  234. edc_metadata/metadata_rules/site.py +6 -7
  235. edc_metadata/metadata_updater.py +2 -2
  236. edc_metadata/metadata_wrappers/metadata_wrapper.py +4 -4
  237. edc_metadata/model_mixins/creates/creates_metadata_model_mixin.py +6 -7
  238. edc_metadata/model_mixins/updates/updates_crf_metadata_model_mixin.py +2 -2
  239. edc_metadata/model_mixins/updates/updates_metadata_model_mixin.py +2 -2
  240. edc_metadata/model_mixins/updates/updates_requisition_metadata_model_mixin.py +2 -2
  241. edc_metadata/models/crf_metadata.py +27 -29
  242. edc_metadata/models/crf_metadata_model_mixin.py +1 -1
  243. edc_metadata/models/requisition_metadata.py +29 -31
  244. edc_metadata/stubs.py +17 -17
  245. edc_metadata/utils.py +4 -4
  246. edc_model/__init__.py +2 -2
  247. edc_model/models/historical_records.py +2 -2
  248. edc_model/models/signals.py +1 -1
  249. edc_model/models/url_model_mixin.py +1 -1
  250. edc_model/utils.py +0 -2
  251. edc_model_admin/dashboard/model_admin_dashboard_mixin.py +1 -3
  252. edc_model_admin/history/model_admin_simple_history.py +7 -9
  253. edc_model_admin/mixins/base_model_admin_redirect_mixin.py +4 -6
  254. edc_model_admin/mixins/model_admin_limit_to_selected_foreignkey.py +2 -2
  255. edc_model_admin/mixins/model_admin_model_redirect_mixin.py +2 -10
  256. edc_model_admin/mixins/model_admin_next_url_redirect_mixin.py +8 -9
  257. edc_model_admin/utils.py +6 -9
  258. edc_model_form/mixins/__init__.py +1 -1
  259. edc_model_form/mixins/base_model_form_mixin.py +1 -1
  260. edc_model_to_dataframe/model_to_dataframe.py +2 -4
  261. edc_navbar/site_navbars.py +2 -2
  262. edc_navbar/system_checks.py +1 -1
  263. edc_notification/mailing_list_manager.py +5 -8
  264. edc_notification/management/commands/list_recipients_by_notification.py +1 -1
  265. edc_notification/models/__init__.py +4 -2
  266. edc_notification/notification/graded_event_notification.py +2 -4
  267. edc_notification/notification/model_notification.py +1 -3
  268. edc_notification/notification/notification.py +14 -17
  269. edc_notification/site_notifications.py +7 -7
  270. edc_notification/stubs.py +6 -6
  271. edc_notification/update_mailing_lists_in_m2m.py +2 -2
  272. edc_offstudy/action_items.py +2 -2
  273. edc_offstudy/model_mixins/offstudy_model_mixin.py +1 -1
  274. edc_offstudy/models.py +1 -1
  275. edc_pdf_reports/crf_pdf_report.py +2 -2
  276. edc_pdf_reports/model_mixins.py +2 -2
  277. edc_pdf_reports/report.py +4 -5
  278. edc_pdf_reports/utils.py +1 -1
  279. edc_pdutils/dataframes/get_subject_consent.py +1 -3
  280. edc_pdutils/df_exporters/csv_exporter.py +5 -7
  281. edc_pdutils/df_exporters/tables_exporter.py +2 -2
  282. edc_pdutils/df_handlers/crf_df_handler.py +1 -2
  283. edc_pdutils/dialects/crf_dialect.py +3 -3
  284. edc_pdutils/management/commands/export_models.py +6 -7
  285. edc_pdutils/site_values_mappings.py +2 -2
  286. edc_pdutils/utils/datetime_to_date.py +1 -2
  287. edc_pdutils/utils/refresh_model_from_dataframe.py +1 -3
  288. edc_pdutils/utils/table_names.py +2 -4
  289. edc_pdutils/utils/undash.py +1 -1
  290. edc_pharmacy/admin/medication/assignment_admin.py +2 -4
  291. edc_pharmacy/admin/medication/dosage_guideline_admin.py +2 -4
  292. edc_pharmacy/admin/medication/formulation_admin.py +3 -5
  293. edc_pharmacy/admin/medication/medication_admin.py +3 -5
  294. edc_pharmacy/admin/prescription/rx_refill_admin.py +7 -9
  295. edc_pharmacy/admin/stock/lot_admin.py +5 -7
  296. edc_pharmacy/admin/stock/order_admin.py +2 -2
  297. edc_pharmacy/admin/stock/order_item_admin.py +6 -6
  298. edc_pharmacy/admin/stock/receive_admin.py +1 -1
  299. edc_pharmacy/admin/stock/receive_item_admin.py +2 -2
  300. edc_pharmacy/admin/stock/stock_request_admin.py +4 -6
  301. edc_pharmacy/dosage_calculator.py +4 -4
  302. edc_pharmacy/form_validators/crf/study_medication_form_validator.py +8 -8
  303. edc_pharmacy/forms/stock/stock_request_form.py +2 -4
  304. edc_pharmacy/model_mixins/study_medication_crf_model_mixin.py +2 -2
  305. edc_pharmacy/models/medication/formulation.py +3 -4
  306. edc_pharmacy/models/medication/medication.py +1 -2
  307. edc_pharmacy/models/prescription/rx.py +2 -2
  308. edc_pharmacy/models/prescription/rx_refill.py +7 -9
  309. edc_pharmacy/models/reports/stock_availability.py +3 -4
  310. edc_pharmacy/models/stock/confirmation_at_site.py +1 -3
  311. edc_pharmacy/models/stock/order.py +1 -2
  312. edc_pharmacy/models/stock/receive.py +3 -4
  313. edc_pharmacy/models/stock/receive_item.py +2 -3
  314. edc_pharmacy/models/stock/stock.py +1 -2
  315. edc_pharmacy/models/stock/stock_adjustment.py +1 -2
  316. edc_pharmacy/models/stock/stock_request.py +4 -5
  317. edc_pharmacy/models/storage/box.py +1 -1
  318. edc_pharmacy/models/storage/items/container_model_mixin.py +1 -1
  319. edc_pharmacy/models/storage/room.py +1 -1
  320. edc_pharmacy/models/storage/shelf.py +1 -1
  321. edc_pharmacy/models/storage/utils.py +15 -16
  322. edc_pharmacy/prescribe/create_prescription.py +5 -5
  323. edc_pharmacy/refill/refill_creator.py +1 -1
  324. edc_pharmacy/sample_usb_printing/usb_printing.py +1 -1
  325. edc_pharmacy/utils/__init__.py +1 -1
  326. edc_pharmacy/utils/confirm_stock.py +3 -3
  327. edc_pharmacy/utils/confirm_stock_at_site.py +9 -8
  328. edc_pharmacy/utils/dispense.py +5 -8
  329. edc_pharmacy/utils/format_qty.py +3 -3
  330. edc_pharmacy/utils/get_codenames.py +1 -1
  331. edc_pharmacy/utils/miscellaneous.py +1 -1
  332. edc_pharmacy/utils/process_repack_request.py +18 -19
  333. edc_pharmacy/utils/process_repack_request_queryset.py +0 -2
  334. edc_pharmacy/utils/stock_request/bulk_create_stock_request_items.py +2 -3
  335. edc_pharmacy/views/add_to_storage_bin_view.py +1 -1
  336. edc_pharmacy/views/allocate_to_subject_view.py +2 -6
  337. edc_pharmacy/views/confirm_stock_from_instance_view.py +4 -4
  338. edc_pharmacy/views/confirm_stock_from_queryset_view.py +1 -5
  339. edc_pharmacy/views/confirmation_at_site_view.py +2 -3
  340. edc_pharmacy/views/dispense_view.py +1 -1
  341. edc_pharmacy/views/move_to_storage_bin_view.py +2 -3
  342. edc_pharmacy/views/print_labels_view.py +30 -31
  343. edc_pharmacy/views/transfer_stock_view.py +1 -1
  344. edc_prn/prn.py +1 -1
  345. edc_prn/site_prn_forms.py +1 -2
  346. edc_protocol_incident/action_items.py +2 -2
  347. edc_protocol_incident/model_mixins/protocol_deviation_violation_model_mixin.py +3 -3
  348. edc_protocol_incident/model_mixins/protocol_incident_model_mixin.py +3 -5
  349. edc_protocol_incident/modeladmin_mixins.py +3 -5
  350. edc_protocol_incident/models/protocol_deviation_violation.py +5 -5
  351. edc_protocol_incident/models/protocol_incident.py +5 -5
  352. edc_pylabels/site_label_configs.py +5 -5
  353. edc_qareports/model_mixins/qa_report_model_mixin.py +4 -2
  354. edc_qareports/models/qa_reports_log.py +1 -2
  355. edc_qareports/utils.py +1 -2
  356. edc_randomization/admin.py +3 -5
  357. edc_randomization/auth_objects.py +2 -3
  358. edc_randomization/blinding.py +1 -1
  359. edc_randomization/constants.py +2 -4
  360. edc_randomization/model_mixins.py +3 -3
  361. edc_randomization/randomization_list_importer.py +13 -14
  362. edc_randomization/randomization_list_verifier.py +11 -13
  363. edc_randomization/randomizer.py +2 -2
  364. edc_randomization/system_checks.py +1 -2
  365. edc_randomization/utils.py +5 -5
  366. edc_refusal/admin.py +1 -1
  367. edc_refusal/model_mixins.py +1 -1
  368. edc_refusal/utils.py +1 -1
  369. edc_registration/model_mixins/updates_or_creates_registered_subject_model_mixin.py +2 -2
  370. edc_registration/modeladmin_mixins.py +4 -7
  371. edc_registration/models/registered_subject.py +7 -9
  372. edc_registration/utils.py +3 -4
  373. edc_reportable/data/grading_data/daids_july_2017.py +3 -3
  374. edc_reportable/evaluator.py +5 -5
  375. edc_reportable/formula.py +1 -1
  376. edc_reportable/reference_range_evaluator.py +3 -3
  377. edc_reportable/utils/convert_units.py +10 -12
  378. edc_reportable/utils/grading_data_model_cls.py +2 -2
  379. edc_reportable/utils/grading_exception_model_cls.py +2 -2
  380. edc_reportable/utils/molecular_weight_model_cls.py +2 -2
  381. edc_reportable/utils/normal_data_model_cls.py +2 -2
  382. edc_reportable/utils/reference_range_colllection_model_cls.py +2 -2
  383. edc_screening/age_evaluator.py +2 -4
  384. edc_screening/eligibility.py +1 -1
  385. edc_screening/form_validator_mixins.py +2 -2
  386. edc_screening/model_mixins/screening_methods_model_mixin.py +1 -1
  387. edc_screening/screening_eligibility.py +1 -1
  388. edc_screening/utils.py +4 -6
  389. edc_search/model_mixins.py +1 -1
  390. edc_search/search_slug.py +1 -3
  391. edc_search/updater.py +1 -1
  392. edc_sites/admin/site_model_admin_mixin.py +2 -2
  393. edc_sites/model_mixins/site_model_mixin.py +1 -2
  394. edc_sites/modelform_mixins.py +2 -2
  395. edc_sites/models/__init__.py +1 -1
  396. edc_sites/models/site_profile.py +4 -4
  397. edc_sites/site.py +24 -32
  398. edc_sites/system_checks.py +1 -1
  399. edc_sites/utils/get_message_text.py +1 -1
  400. edc_sites/utils/valid_site_for_subject_or_raise.py +5 -6
  401. edc_subject_dashboard/requisition_report.py +4 -5
  402. edc_subject_dashboard/requisition_verifier.py +4 -2
  403. edc_subject_dashboard/templatetags/edc_subject_dashboard_extras.py +16 -17
  404. edc_subject_dashboard/view_utils/__init__.py +1 -1
  405. edc_subject_dashboard/view_utils/crf_button.py +1 -3
  406. edc_subject_dashboard/view_utils/subject_consent_dashboard_button.py +2 -2
  407. edc_subject_dashboard/view_utils/subject_consent_listboard_button.py +2 -2
  408. edc_subject_dashboard/view_utils/timepoint_status_button.py +1 -4
  409. edc_subject_dashboard/views/requisition_print_actions_view.py +2 -2
  410. edc_subject_dashboard/views/subject_dashboard_view.py +1 -1
  411. edc_timepoint/model_mixins.py +2 -2
  412. edc_transfer/action_items.py +1 -1
  413. edc_transfer/model_mixins.py +4 -3
  414. edc_transfer/modeladmin_mixins.py +3 -6
  415. edc_unblinding/action_items.py +2 -2
  416. edc_unblinding/models/unblinding_request.py +3 -3
  417. edc_unblinding/models/unblinding_review.py +3 -3
  418. edc_utils/__init__.py +6 -5
  419. edc_utils/age.py +1 -1
  420. edc_utils/celery.py +3 -8
  421. edc_utils/get_static_file.py +1 -1
  422. edc_utils/text.py +5 -6
  423. edc_view_utils/__init__.py +3 -3
  424. edc_view_utils/dashboard_model_button.py +4 -4
  425. edc_view_utils/model_button.py +7 -8
  426. edc_view_utils/perms.py +3 -3
  427. edc_visit_schedule/action_items.py +2 -2
  428. edc_visit_schedule/admin/list_filters.py +11 -9
  429. edc_visit_schedule/admin/subject_schedule_history_admin.py +13 -15
  430. edc_visit_schedule/admin/visit_schedule_admin.py +7 -7
  431. edc_visit_schedule/fieldsets.py +4 -6
  432. edc_visit_schedule/management/commands/find_invalid_onschedules.py +1 -1
  433. edc_visit_schedule/model_mixins/off_schedule_model_mixin.py +3 -1
  434. edc_visit_schedule/model_mixins/on_schedule_model_mixin.py +1 -1
  435. edc_visit_schedule/modelform_mixins/__init__.py +1 -1
  436. edc_visit_schedule/modelform_mixins/off_schedule_modelform_mixin.py +1 -2
  437. edc_visit_schedule/models/subject_schedule_history.py +2 -1
  438. edc_visit_schedule/models/visit_schedule.py +2 -2
  439. edc_visit_schedule/schedule/schedule.py +11 -12
  440. edc_visit_schedule/schedule/visit_collection.py +4 -3
  441. edc_visit_schedule/schedule/window.py +19 -12
  442. edc_visit_schedule/site_visit_schedules.py +9 -9
  443. edc_visit_schedule/subject_schedule.py +6 -7
  444. edc_visit_schedule/system_checks.py +1 -1
  445. edc_visit_schedule/view_mixins.py +1 -1
  446. edc_visit_schedule/visit/crf.py +3 -7
  447. edc_visit_schedule/visit/requisition.py +2 -2
  448. edc_visit_schedule/visit/visit.py +6 -7
  449. edc_visit_schedule/visit/window_period.py +8 -8
  450. edc_visit_schedule/visit_schedule/schedules_collection.py +2 -5
  451. edc_visit_tracking/action_items.py +11 -13
  452. edc_visit_tracking/form_validators/visit_form_validator.py +5 -5
  453. edc_visit_tracking/model_mixins/base/visit_methods_model_mixin.py +3 -4
  454. edc_visit_tracking/model_mixins/crfs/visit_tracking_crf_model_mixin.py +2 -2
  455. edc_visit_tracking/model_mixins/subject_visit_missed_model_mixin.py +2 -3
  456. edc_visit_tracking/model_mixins/utils.py +1 -1
  457. edc_visit_tracking/model_mixins/visit_model_mixin/previous_visit_model_mixin.py +2 -2
  458. edc_visit_tracking/model_mixins/visit_model_mixin/visit_model_mixin.py +3 -3
  459. edc_visit_tracking/modeladmin_mixins/crf_model_admin_mixin.py +6 -4
  460. edc_visit_tracking/modeladmin_mixins/visit_model_admin_mixin.py +6 -7
  461. edc_visit_tracking/modelform_mixins/crf/visit_tracking_crf_modelform_mixin.py +4 -5
  462. edc_visit_tracking/modelform_mixins/visit_tracking_modelform_mixin.py +3 -4
  463. edc_visit_tracking/models/subject_visit.py +1 -1
  464. edc_visit_tracking/models/subject_visit_missed.py +1 -1
  465. edc_visit_tracking/stubs.py +7 -7
  466. edc_visit_tracking/typing_stubs.py +11 -11
  467. edc_visit_tracking/utils.py +5 -5
  468. edc_visit_tracking/view_utils/related_visit_button.py +5 -5
  469. edc_vitals/calculators/bmi.py +1 -1
  470. edc_vitals/form_validators/blood_pressure_form_validator_mixin.py +8 -10
  471. edc_vitals/form_validators/bmi_form_validator_mixin.py +1 -1
  472. edc_vitals/form_validators/weight_height_with_bmi_form_validator_mixin.py +5 -2
  473. edc_vitals/model_mixins/blood_pressure_model_mixin.py +3 -4
  474. edc_vitals/model_mixins/weight_height_bmi_model_mixin.py +4 -4
  475. edc_vitals/utils.py +1 -1
  476. edc_vitals/validators.py +1 -1
  477. {clinicedc-2.0.4.dist-info → clinicedc-2.0.6.dist-info}/WHEEL +0 -0
  478. {clinicedc-2.0.4.dist-info → clinicedc-2.0.6.dist-info}/licenses/LICENSE +0 -0
edc_utils/text.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import random
2
2
  import re
3
3
  from datetime import datetime
4
- from typing import Optional
5
4
  from zoneinfo import ZoneInfo
6
5
 
7
6
  from django.conf import settings
@@ -12,7 +11,7 @@ safe_allowed_chars = "ABCDEFGHKMNPRTUVWXYZ2346789"
12
11
  def get_safe_random_string(length=12, safe=None, allowed_chars=None):
13
12
  safe = True if safe is None else safe
14
13
  allowed_chars = allowed_chars or (
15
- "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL" "MNOPQRTUVWXYZ012346789!@#%^&*()?<>.,[]{}"
14
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTUVWXYZ012346789!@#%^&*()?<>.,[]{}"
16
15
  )
17
16
  if safe:
18
17
  allowed_chars = "ABCDEFGHKMNPRTUVWXYZ2346789"
@@ -64,10 +63,10 @@ def convert_from_camel(name):
64
63
 
65
64
 
66
65
  def formatted_datetime(
67
- aware_datetime: Optional[datetime],
68
- php_dateformat: Optional[str] = None,
69
- tz: Optional[str] = None,
70
- format_as_date: Optional[bool] = None,
66
+ aware_datetime: datetime | None,
67
+ php_dateformat: str | None = None,
68
+ tz: str | None = None,
69
+ format_as_date: bool | None = None,
71
70
  ):
72
71
  """Returns a formatted datetime string, localized by default.
73
72
 
@@ -8,6 +8,9 @@ from .query_button import QueryButton
8
8
  from .render_history_and_query_buttons import render_history_and_query_buttons
9
9
 
10
10
  __all__ = [
11
+ "ADD",
12
+ "CHANGE",
13
+ "VIEW",
11
14
  "DashboardModelButton",
12
15
  "HistoryButton",
13
16
  "ModelButton",
@@ -16,7 +19,4 @@ __all__ = [
16
19
  "PrnButton",
17
20
  "QueryButton",
18
21
  "render_history_and_query_buttons",
19
- "ADD",
20
- "CHANGE",
21
- "VIEW",
22
22
  ]
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Type
4
+ from typing import TYPE_CHECKING
5
5
  from uuid import UUID
6
6
 
7
7
  from .model_button import ModelButton
@@ -15,9 +15,9 @@ if TYPE_CHECKING:
15
15
  from edc_metadata.models import CrfMetadata, RequisitionMetadata
16
16
  from edc_model.models import BaseUuidModel
17
17
 
18
- class CrfModel(CrfModelMixin, BaseUuidModel): ... # noqa
18
+ class CrfModel(CrfModelMixin, BaseUuidModel): ...
19
19
 
20
- class RequisitionModel(RequisitionModelMixin, BaseUuidModel): ... # noqa
20
+ class RequisitionModel(RequisitionModelMixin, BaseUuidModel): ...
21
21
 
22
22
 
23
23
  __all__ = ["DashboardModelButton"]
@@ -38,7 +38,7 @@ class DashboardModelButton(ModelButton):
38
38
  metadata_model_obj: CrfMetadata | RequisitionMetadata = None
39
39
  appointment: Appointment = None
40
40
  next_url_name: str = field(default="subject_dashboard_url")
41
- model_cls: Type[CrfModel | RequisitionModel] = field(default=None, init=False)
41
+ model_cls: type[CrfModel | RequisitionModel] = field(default=None, init=False)
42
42
 
43
43
  def __post_init__(self):
44
44
  self.model_cls = self.metadata_model_obj.model_cls
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Type
4
+ from typing import TYPE_CHECKING
5
5
  from uuid import uuid4
6
6
 
7
7
  from django.core.handlers.wsgi import WSGIRequest
@@ -28,7 +28,7 @@ ADD: int = 0
28
28
  CHANGE = 1
29
29
  VIEW = 2
30
30
 
31
- __all__ = ["ModelButton", "ADD", "CHANGE", "VIEW"]
31
+ __all__ = ["ADD", "CHANGE", "VIEW", "ModelButton"]
32
32
 
33
33
 
34
34
  class ModelButtonError(Exception):
@@ -39,7 +39,7 @@ class ModelButtonError(Exception):
39
39
  class ModelButton:
40
40
  user: User = None
41
41
  model_obj: Model = None
42
- model_cls: Type[Model] = field(default=None)
42
+ model_cls: type[Model] = field(default=None)
43
43
  current_site: Site = None
44
44
  subject_identifier: str | None = None
45
45
  request: WSGIRequestObject | None = None
@@ -104,11 +104,10 @@ class ModelButton:
104
104
  @property
105
105
  def disabled(self) -> str:
106
106
  disabled = "disabled"
107
- if not self.model_obj and self.perms.add:
107
+ if (not self.model_obj and self.perms.add) or (
108
+ self.model_obj and (self.perms.change or self.perms.view)
109
+ ):
108
110
  disabled = ""
109
- else:
110
- if self.model_obj and (self.perms.change or self.perms.view):
111
- disabled = ""
112
111
  return disabled
113
112
 
114
113
  @property
@@ -116,7 +115,7 @@ class ModelButton:
116
115
  btn_id = f"{self.model_cls._meta.label_lower.split('.')[1]}-{uuid4().hex}"
117
116
  if self.model_obj:
118
117
  btn_id = (
119
- f"{self.model_cls._meta.label_lower.split('.')[1]}-" f"{self.model_obj.id.hex}"
118
+ f"{self.model_cls._meta.label_lower.split('.')[1]}-{self.model_obj.id.hex}"
120
119
  )
121
120
  return btn_id
122
121
 
edc_view_utils/perms.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import InitVar, dataclass, field
4
- from typing import TYPE_CHECKING, Type
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  from django.contrib.auth import get_permission_codename
7
7
 
@@ -60,7 +60,7 @@ class Perms:
60
60
  """
61
61
 
62
62
  user: User = None
63
- model_cls: InitVar[Type[models.Model]] = None
63
+ model_cls: InitVar[type[models.Model]] = None
64
64
  current_site: Site = None
65
65
  site: Site = None
66
66
  add: bool = field(default=False, init=False)
@@ -70,7 +70,7 @@ class Perms:
70
70
  view_only: bool = field(default=False, init=False)
71
71
  _site_perms: SitePerms = field(default=None, init=False)
72
72
 
73
- def __post_init__(self, model_cls: Type[models.Model]):
73
+ def __post_init__(self, model_cls: type[models.Model]):
74
74
  # self.user = get_object_or_404(User, pk=self.user.id)
75
75
  # set add, change, delete, view attrs for this user
76
76
  # based on the model class
@@ -11,10 +11,10 @@ class OffscheduleAction(ActionWithNotification):
11
11
  name = OFFSCHEDULE_ACTION
12
12
  display_name = "Submit Off-Schedule"
13
13
  notification_display_name = "Off-Schedule"
14
- parent_action_names = [
14
+ parent_action_names = (
15
15
  DEATH_REPORT_ACTION,
16
16
  LTFU_ACTION,
17
- ]
17
+ )
18
18
  reference_model = "edc_visit_schedule.offschedule"
19
19
  show_link_to_changelist = True
20
20
  admin_site_name = "edc_visit_schedule_admin"
@@ -11,7 +11,7 @@ class ScheduleStatusListFilter(SimpleListFilter):
11
11
  title = "Schedule status"
12
12
  parameter_name = "schedule_status"
13
13
 
14
- def lookups(self, request, model_admin):
14
+ def lookups(self, request, model_admin): # noqa: ARG002
15
15
  names = []
16
16
  qs = (
17
17
  SubjectScheduleHistory.objects.values(
@@ -20,13 +20,15 @@ class ScheduleStatusListFilter(SimpleListFilter):
20
20
  .order_by("schedule_name", "onschedule_model", "offschedule_model")
21
21
  .annotate(cnt=Count("schedule_name"))
22
22
  )
23
- for obj in qs:
24
- names.append(
25
- (f"{obj.get('schedule_name')}__on", f"On: {obj.get('schedule_name')}")
26
- )
27
- for obj in qs:
28
- names.append(
29
- (f"{obj.get('schedule_name')}__off", f"Off: {obj.get('schedule_name')}")
23
+ for s in ["on", "off"]:
24
+ names.extend(
25
+ [
26
+ (
27
+ f"{obj.get('schedule_name')}__{s}",
28
+ f"{s.title()}: {obj.get('schedule_name')}",
29
+ )
30
+ for obj in qs
31
+ ]
30
32
  )
31
33
  return tuple(names)
32
34
 
@@ -37,7 +39,7 @@ class ScheduleStatusListFilter(SimpleListFilter):
37
39
  schedule_name=schedule_name, offschedule_datetime__isnull=status == "on"
38
40
  ).values_list("subject_identifier", flat=True)
39
41
 
40
- def queryset(self, request, queryset):
42
+ def queryset(self, request, queryset): # noqa: ARG002
41
43
  if self.value() and self.value() != "none":
42
44
  queryset = queryset.filter(subject_identifier__in=self.subject_identifiers)
43
45
  return queryset
@@ -61,25 +61,23 @@ class SubjectScheduleHistoryAdmin(
61
61
  "offschedule_datetime",
62
62
  "visit_schedule_name",
63
63
  "schedule_name",
64
- ) + list_filter
64
+ *list_filter,
65
+ )
65
66
 
66
67
  def get_readonly_fields(self, request, obj=None) -> tuple:
67
68
  fields = super().get_readonly_fields(request, obj=obj)
68
- fields = (
69
- fields
70
- + (
71
- "subject_identifier",
72
- "visit_schedule_name",
73
- "schedule_name",
74
- "schedule_status",
75
- "onschedule_datetime",
76
- "offschedule_datetime",
77
- "onschedule_model",
78
- "offschedule_model",
79
- )
80
- + audit_fields
69
+ return (
70
+ *fields,
71
+ "subject_identifier",
72
+ "visit_schedule_name",
73
+ "schedule_name",
74
+ "schedule_status",
75
+ "onschedule_datetime",
76
+ "offschedule_datetime",
77
+ "onschedule_model",
78
+ "offschedule_model",
79
+ *audit_fields,
81
80
  )
82
- return fields
83
81
 
84
82
  def dashboard(self, obj=None, label=None) -> str:
85
83
  try:
@@ -1,5 +1,3 @@
1
- from typing import Tuple
2
-
3
1
  from django.contrib.admin.decorators import register
4
2
  from django_audit_fields.admin import audit_fieldset_tuple
5
3
 
@@ -39,7 +37,7 @@ class VisitScheduleAdmin(SimpleHistoryAdmin):
39
37
  "visit_name",
40
38
  )
41
39
 
42
- def get_list_display(self, request) -> Tuple[str, ...]:
40
+ def get_list_display(self, request) -> tuple[str, ...]:
43
41
  list_display = super().get_list_display(request)
44
42
  return (
45
43
  "visit_schedule_name",
@@ -49,18 +47,20 @@ class VisitScheduleAdmin(SimpleHistoryAdmin):
49
47
  "visit_name",
50
48
  "timepoint",
51
49
  "active",
52
- ) + list_display
50
+ *list_display,
51
+ )
53
52
 
54
- def get_list_filter(self, request) -> Tuple[str, ...]:
53
+ def get_list_filter(self, request) -> tuple[str, ...]:
55
54
  list_filter = super().get_list_filter(request)
56
55
  return (
57
56
  "active",
58
57
  "visit_schedule_name",
59
58
  "schedule_name",
60
59
  "visit_code",
61
- ) + list_filter
60
+ *list_filter,
61
+ )
62
62
 
63
63
  @staticmethod
64
- def populate_visit_schedule(request, queryset) -> None:
64
+ def populate_visit_schedule(request, queryset) -> None: # noqa: ARG004
65
65
  VisitSchedule.objects.update(active=False)
66
66
  site_visit_schedules.to_model(VisitSchedule)
@@ -1,22 +1,20 @@
1
- from typing import Dict, Tuple
2
-
3
- visit_schedule_fields: Tuple[str, ...] = (
1
+ visit_schedule_fields: tuple[str, ...] = (
4
2
  "visit_schedule_name",
5
3
  "schedule_name",
6
4
  "visit_code",
7
5
  )
8
6
 
9
- visit_schedule_fieldset_tuple: Tuple[str, Dict[str, Tuple[str, ...]]] = (
7
+ visit_schedule_fieldset_tuple: tuple[str, dict[str, tuple[str, ...]]] = (
10
8
  "Visit Schedule",
11
9
  {"classes": ("collapse",), "fields": visit_schedule_fields},
12
10
  )
13
11
 
14
- visit_schedule_only_fields: Tuple[str, ...] = (
12
+ visit_schedule_only_fields: tuple[str, ...] = (
15
13
  "visit_schedule_name",
16
14
  "schedule_name",
17
15
  )
18
16
 
19
- visit_schedule_only_fieldset_tuple: Tuple[str, Dict[str, Tuple[str, ...]]] = (
17
+ visit_schedule_only_fieldset_tuple: tuple[str, dict[str, tuple[str, ...]]] = (
20
18
  "Visit Schedule",
21
19
  {"classes": ("collapse",), "fields": visit_schedule_only_fields},
22
20
  )
@@ -27,7 +27,7 @@ class Command(BaseCommand):
27
27
  for visit_schedule in site_visit_schedules.visit_schedules.values():
28
28
  for schedule in visit_schedule.schedules.values():
29
29
  try:
30
- onschedule_model_cls = getattr(schedule, "onschedule_model_cls")
30
+ onschedule_model_cls = schedule.onschedule_model_cls
31
31
  except LookupError:
32
32
  pass
33
33
  else:
@@ -89,4 +89,6 @@ class OffScheduleModelMixin(UniqueSubjectIdentifierFieldMixin, models.Model):
89
89
 
90
90
  class Meta:
91
91
  abstract = True
92
- indexes = [models.Index(fields=["subject_identifier", "offschedule_datetime", "site"])]
92
+ indexes = (
93
+ models.Index(fields=["subject_identifier", "offschedule_datetime", "site"]),
94
+ )
@@ -89,4 +89,4 @@ class OnScheduleModelMixin(UniqueSubjectIdentifierFieldMixin, models.Model):
89
89
 
90
90
  class Meta:
91
91
  abstract = True
92
- indexes = [models.Index(fields=["subject_identifier", "onschedule_datetime", "site"])]
92
+ indexes = (models.Index(fields=["subject_identifier", "onschedule_datetime", "site"]),)
@@ -3,7 +3,7 @@ from .off_schedule_modelform_mixin import OffScheduleModelFormMixin
3
3
  from .visit_schedule_non_crf_modelform_mixin import VisitScheduleNonCrfModelFormMixin
4
4
 
5
5
  __all__ = [
6
- "VisitScheduleCrfModelFormMixin",
7
6
  "OffScheduleModelFormMixin",
7
+ "VisitScheduleCrfModelFormMixin",
8
8
  "VisitScheduleNonCrfModelFormMixin",
9
9
  ]
@@ -43,8 +43,7 @@ class OffScheduleModelFormMixin(VisitScheduleNonCrfModelFormMixin):
43
43
  def offschedule_datetime(self) -> datetime | None:
44
44
  if self.offschedule_datetime_field_attr in self.cleaned_data:
45
45
  return to_utc(self.cleaned_data.get(self.offschedule_datetime_field_attr))
46
- else:
47
- return getattr(self.instance, self.offschedule_datetime_field_attr)
46
+ return getattr(self.instance, self.offschedule_datetime_field_attr)
48
47
 
49
48
  @property
50
49
  def offschedule_compare_dates_as_datetimes(self):
@@ -108,5 +108,6 @@ class SubjectScheduleHistory(
108
108
  )
109
109
  ]
110
110
  indexes = (
111
- BaseUuidModel.Meta.indexes + NonUniqueSubjectIdentifierFieldMixin.Meta.indexes
111
+ *BaseUuidModel.Meta.indexes,
112
+ *NonUniqueSubjectIdentifierFieldMixin.Meta.indexes,
112
113
  )
@@ -56,7 +56,7 @@ class VisitSchedule(VisitScheduleMethodsModelMixin, edc_models.BaseUuidModel):
56
56
  ("visit_schedule_name", "schedule_name", "visit_code"),
57
57
  ("visit_schedule_name", "schedule_name", "timepoint"),
58
58
  )
59
- indexes = [
59
+ indexes = (
60
60
  models.Index(
61
61
  fields=[
62
62
  "visit_schedule_name",
@@ -67,4 +67,4 @@ class VisitSchedule(VisitScheduleMethodsModelMixin, edc_models.BaseUuidModel):
67
67
  ]
68
68
  ),
69
69
  models.Index(fields=["visit_schedule_name", "schedule_name", "timepoint"]),
70
- ]
70
+ )
@@ -4,7 +4,7 @@ import re
4
4
  from copy import deepcopy
5
5
  from datetime import datetime
6
6
  from decimal import Decimal
7
- from typing import TYPE_CHECKING, Type
7
+ from typing import TYPE_CHECKING
8
8
 
9
9
  from django.apps import apps as django_apps
10
10
 
@@ -64,7 +64,7 @@ class Schedule:
64
64
 
65
65
  name_regex = r"[a-z0-9\_\-]+$"
66
66
  visit_cls = Visit
67
- visit_collection_cls: Type[VisitCollection] = VisitCollection
67
+ visit_collection_cls: type[VisitCollection] = VisitCollection
68
68
  window_cls = Window
69
69
 
70
70
  def __init__(
@@ -87,8 +87,7 @@ class Schedule:
87
87
  f"Invalid name. Got '{name}'. May only contains numbers, "
88
88
  "lower case letters and '_'."
89
89
  )
90
- else:
91
- self.name = name
90
+ self.name = name
92
91
 
93
92
  self.consent_definitions = consent_definitions
94
93
 
@@ -250,8 +249,8 @@ class Schedule:
250
249
  if schedule.name != self.name:
251
250
  raise ValueError(
252
251
  f"Site visit schedules return the wrong schedule object. "
253
- f"Expected {repr(self)} for onschedule_model={self.onschedule_model}. "
254
- f"Got {repr(schedule)}."
252
+ f"Expected {self!r} for onschedule_model={self.onschedule_model}. "
253
+ f"Got {schedule!r}."
255
254
  )
256
255
  return SubjectSchedule(
257
256
  subject_identifier, visit_schedule=visit_schedule, schedule=self
@@ -283,7 +282,7 @@ class Schedule:
283
282
  ]
284
283
  if not consent_definition:
285
284
  raise ScheduleError(
286
- "Consent definition may not be None. Expected one of " f"{formatted_cdefs}."
285
+ f"Consent definition may not be None. Expected one of {formatted_cdefs}."
287
286
  )
288
287
 
289
288
  if consent_definition not in self.consent_definitions:
@@ -324,11 +323,11 @@ class Schedule:
324
323
  return self.window_cls(name=self.name, visits=self.visits, **kwargs).datetime_in_window
325
324
 
326
325
  @property
327
- def onschedule_model_cls(self) -> Type[OnSchedule]:
326
+ def onschedule_model_cls(self) -> type[OnSchedule]:
328
327
  return django_apps.get_model(self.onschedule_model)
329
328
 
330
329
  @property
331
- def offschedule_model_cls(self) -> Type[OffSchedule]:
330
+ def offschedule_model_cls(self) -> type[OffSchedule]:
332
331
  return django_apps.get_model(self.offschedule_model)
333
332
 
334
333
  @property
@@ -340,15 +339,15 @@ class Schedule:
340
339
  return self.loss_to_followup_model_cls
341
340
 
342
341
  @property
343
- def history_model_cls(self) -> Type[SubjectScheduleHistory]:
342
+ def history_model_cls(self) -> type[SubjectScheduleHistory]:
344
343
  return django_apps.get_model(self.history_model)
345
344
 
346
345
  @property
347
- def appointment_model_cls(self) -> Type[Appointment]:
346
+ def appointment_model_cls(self) -> type[Appointment]:
348
347
  return django_apps.get_model(self.appointment_model)
349
348
 
350
349
  @property
351
- def visit_model_cls(self) -> Type[RelatedVisitModel]:
350
+ def visit_model_cls(self) -> type[RelatedVisitModel]:
352
351
  return self.appointment_model_cls.related_visit_model_cls()
353
352
 
354
353
  def get_consent_definition(
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  from datetime import datetime
4
4
  from typing import TYPE_CHECKING
5
5
 
6
+ from edc_utils import to_local
7
+
6
8
  from ..ordered_collection import OrderedCollection
7
9
 
8
10
  if TYPE_CHECKING:
@@ -33,11 +35,10 @@ class VisitCollection(OrderedCollection):
33
35
  timepoint_dates = {}
34
36
  for visit in self.values():
35
37
  try:
36
- timepoint_datetime = dt + visit.rbase
38
+ timepoint_datetime = to_local(dt) + visit.rbase
37
39
  except TypeError as e:
38
40
  raise VisitCollectionError(
39
- f"Invalid visit.rbase. visit.rbase={visit.rbase}. "
40
- f"See {repr(visit)}. Got {e}."
41
+ f"Invalid visit.rbase. visit.rbase={visit.rbase}. See {visit!r}. Got {e}."
41
42
  )
42
43
  else:
43
44
  visit.timepoint_datetime = timepoint_datetime
@@ -2,8 +2,13 @@ from dateutil.relativedelta import relativedelta
2
2
  from django.conf import settings
3
3
  from django.utils.translation import gettext as _
4
4
 
5
- from edc_utils import convert_php_dateformat, floor_secs, formatted_date, to_utc
6
- from edc_utils.date import to_local
5
+ from edc_utils import (
6
+ ceil_secs,
7
+ convert_php_dateformat,
8
+ floor_secs,
9
+ formatted_date,
10
+ to_local,
11
+ )
7
12
 
8
13
  from ..exceptions import (
9
14
  ScheduledVisitWindowError,
@@ -30,11 +35,11 @@ class Window:
30
35
  ):
31
36
  self.name = name
32
37
  self.visits = visits
33
- self.timepoint_datetime = to_utc(timepoint_datetime)
34
- self.dt = to_utc(dt)
38
+ self.timepoint_datetime = to_local(timepoint_datetime)
39
+ self.dt = to_local(dt)
35
40
  self.visit_code = visit_code
36
41
  self.visit_code_sequence = visit_code_sequence
37
- self.baseline_timepoint_datetime = to_utc(baseline_timepoint_datetime)
42
+ self.baseline_timepoint_datetime = to_local(baseline_timepoint_datetime)
38
43
 
39
44
  @property
40
45
  def datetime_in_window(self):
@@ -71,19 +76,21 @@ class Window:
71
76
  window period for a scheduled `visit` otherwise
72
77
  raises an exception.
73
78
 
79
+ Ensure all datetimes in UTC before comparison.
80
+
74
81
  In this case, `visit` is the object from schedule and
75
82
  not a model instance.
76
83
  """
77
84
  visit = self.visits.get(self.visit_code)
78
85
  visit.timepoint_datetime = self.timepoint_datetime
79
86
  gap_days = self.get_window_gap_days()
80
- lower = floor_secs(to_utc(visit.dates.lower) - relativedelta(days=gap_days))
81
- upper = floor_secs(to_utc(visit.dates.upper))
82
- if not (lower <= floor_secs(to_utc(self.dt)) <= upper):
83
- lower_date = to_local(visit.dates.lower).strftime(
87
+ lower = floor_secs(to_local(visit.dates.lower) - relativedelta(days=gap_days))
88
+ upper = ceil_secs(to_local(visit.dates.upper))
89
+ if not (lower <= floor_secs(to_local(self.dt)) <= upper):
90
+ lower_date = to_local(lower).strftime(
84
91
  convert_php_dateformat(settings.SHORT_DATETIME_FORMAT)
85
92
  )
86
- upper_date = to_local(visit.dates.upper).strftime(
93
+ upper_date = to_local(upper).strftime(
87
94
  convert_php_dateformat(settings.SHORT_DATETIME_FORMAT)
88
95
  )
89
96
  dt = to_local(self.dt).strftime(
@@ -114,8 +121,8 @@ class Window:
114
121
  dt=self.baseline_timepoint_datetime
115
122
  ).get(next_visit)
116
123
  if not (
117
- floor_secs(to_utc(self.dt))
118
- < floor_secs(to_utc(next_timepoint_datetime - next_visit.rlower))
124
+ floor_secs(to_local(self.dt))
125
+ < floor_secs(to_local(next_timepoint_datetime - next_visit.rlower))
119
126
  ):
120
127
  dt_lower = formatted_date(next_timepoint_datetime - next_visit.rlower)
121
128
  dt = formatted_date(self.dt)
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import copy
4
4
  import sys
5
- from typing import TYPE_CHECKING, Tuple
5
+ from typing import TYPE_CHECKING
6
6
 
7
7
  from django.apps import apps as django_apps
8
8
  from django.core.exceptions import ObjectDoesNotExist
@@ -77,7 +77,7 @@ class SiteVisitSchedules:
77
77
  visit_schedule_names = "', '".join(self.registry.keys())
78
78
  raise SiteVisitScheduleError(
79
79
  f"Invalid visit schedule name. Got '{visit_schedule_name}'. "
80
- f"Expected one of '{visit_schedule_names}'. See {repr(self)}."
80
+ f"Expected one of '{visit_schedule_names}'. See {self!r}."
81
81
  )
82
82
  return visit_schedule
83
83
 
@@ -116,7 +116,7 @@ class SiteVisitSchedules:
116
116
  raise SiteVisitScheduleError(
117
117
  f"Schedule not found. No schedule exists for {attr}={cdef}."
118
118
  )
119
- elif len(ret) > 1:
119
+ if len(ret) > 1:
120
120
  raise SiteVisitScheduleError(
121
121
  f"Schedule is ambiguous. More than one schedule exists for "
122
122
  f"{attr}={cdef}. Got {ret}"
@@ -126,7 +126,7 @@ class SiteVisitSchedules:
126
126
 
127
127
  def get_by_onschedule_model(
128
128
  self, onschedule_model: str = None
129
- ) -> Tuple[VisitSchedule, Schedule]:
129
+ ) -> tuple[VisitSchedule, Schedule]:
130
130
  """Returns a tuple of (visit_schedule, schedule)
131
131
  for the given onschedule model.
132
132
 
@@ -136,7 +136,7 @@ class SiteVisitSchedules:
136
136
 
137
137
  def get_by_offschedule_model(
138
138
  self, offschedule_model: str = None
139
- ) -> Tuple[VisitSchedule, Schedule]:
139
+ ) -> tuple[VisitSchedule, Schedule]:
140
140
  """Returns a tuple of visit_schedule, schedule
141
141
  for the given offschedule model.
142
142
 
@@ -146,7 +146,7 @@ class SiteVisitSchedules:
146
146
 
147
147
  def get_by_loss_to_followup_model(
148
148
  self, loss_to_followup_model: str = None
149
- ) -> Tuple[VisitSchedule, Schedule]:
149
+ ) -> tuple[VisitSchedule, Schedule]:
150
150
  """Returns a tuple of visit_schedule, schedule
151
151
  for the given loss_to_followup model.
152
152
 
@@ -156,7 +156,7 @@ class SiteVisitSchedules:
156
156
 
157
157
  def get_by_model(
158
158
  self, attr: str = None, model: str = None
159
- ) -> Tuple[VisitSchedule, Schedule]:
159
+ ) -> tuple[VisitSchedule, Schedule]:
160
160
  ret = []
161
161
  model = model.lower()
162
162
  for visit_schedule in self.visit_schedules.values():
@@ -173,7 +173,7 @@ class SiteVisitSchedules:
173
173
  raise SiteVisitScheduleError(
174
174
  f"Schedule not found. No schedule exists for {attr}={model}."
175
175
  )
176
- elif len(ret) > 1:
176
+ if len(ret) > 1:
177
177
  raise SiteVisitScheduleError(
178
178
  f"Schedule is ambiguous. More than one schedule exists for "
179
179
  f"{attr}={model}. Got {ret}"
@@ -313,7 +313,7 @@ class SiteVisitSchedules:
313
313
  before_import_registry = copy.copy(site_visit_schedules._registry)
314
314
  import_module(f"{app}.{module_name}")
315
315
  if verbose:
316
- sys.stdout.write(" - registered visit schedule from " f"'{app}'\n")
316
+ sys.stdout.write(f" - registered visit schedule from '{app}'\n")
317
317
  except Exception as e:
318
318
  if f"No module named '{app}.{module_name}'" not in str(e):
319
319
  raise