clinicedc 2.0.3__py3-none-any.whl → 2.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of clinicedc might be problematic. Click here for more details.
- {clinicedc-2.0.3.dist-info → clinicedc-2.0.5.dist-info}/METADATA +41 -45
- {clinicedc-2.0.3.dist-info → clinicedc-2.0.5.dist-info}/RECORD +478 -478
- edc_action_item/action.py +76 -54
- edc_action_item/action_item_notification.py +1 -3
- edc_action_item/action_with_notification.py +4 -4
- edc_action_item/create_action_item.py +2 -2
- edc_action_item/create_or_update_action_type.py +1 -1
- edc_action_item/data_fixers.py +4 -5
- edc_action_item/delete_action_item.py +0 -1
- edc_action_item/modeladmin_mixins.py +2 -4
- edc_action_item/modelform_mixins/modelform_mixins.py +1 -1
- edc_action_item/models/action_item.py +53 -38
- edc_action_item/models/action_model_mixin.py +17 -17
- edc_action_item/models/action_type.py +33 -13
- edc_action_item/models/reference.py +3 -2
- edc_action_item/site_action_items.py +13 -14
- edc_action_item/stubs.py +9 -9
- edc_action_item/templatetags/action_item_extras.py +2 -2
- edc_action_item/view_utils/action_item_button.py +3 -3
- edc_action_item/view_utils/action_item_popover_list_item.py +4 -4
- edc_adherence/form_validator_mixin.py +1 -1
- edc_adverse_event/action_items/ae_followup_action.py +1 -1
- edc_adverse_event/action_items/ae_initial_action.py +1 -1
- edc_adverse_event/action_items/ae_susar_action.py +1 -1
- edc_adverse_event/action_items/ae_tmg_action.py +1 -1
- edc_adverse_event/action_items/death_report_action.py +1 -2
- edc_adverse_event/action_items/death_report_tmg_action.py +1 -1
- edc_adverse_event/action_items/death_report_tmg_second_action.py +1 -1
- edc_adverse_event/action_items/hospitalization_action.py +1 -1
- edc_adverse_event/form_validator_mixins/death_report_form_validator.py +3 -3
- edc_adverse_event/form_validator_mixins/requires_death_report_form_validator_mixin.py +3 -4
- edc_adverse_event/form_validators/death_report_tmg.py +1 -1
- edc_adverse_event/model_mixins/ae_followup/ae_followup_methods_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/ae_followup/ae_followup_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/ae_initial/ae_initial_fields_model_mixin.py +0 -1
- edc_adverse_event/model_mixins/ae_initial/ae_initial_methods_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/ae_initial/ae_initial_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/ae_special_interest/aesi_methods_model_mixin.py +4 -4
- edc_adverse_event/model_mixins/ae_special_interest/aesi_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/ae_susar/ae_susar_methods_model_mixin.py +4 -4
- edc_adverse_event/model_mixins/ae_susar/ae_susar_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/ae_tmg/ae_tmg_fields_model_mixin.py +4 -5
- edc_adverse_event/model_mixins/ae_tmg/ae_tmg_methods_model_mixin.py +4 -4
- edc_adverse_event/model_mixins/ae_tmg/ae_tmg_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/death_report/death_report_extra_fields_model_mixin.py +2 -3
- edc_adverse_event/model_mixins/death_report/death_report_model_mixin.py +11 -11
- edc_adverse_event/model_mixins/death_report/death_report_tmg_model_mixin.py +9 -11
- edc_adverse_event/model_mixins/death_report/simple_death_report_model_mixin.py +7 -6
- edc_adverse_event/model_mixins/hospitaization/hospitalization_model_mixin.py +0 -1
- edc_adverse_event/modeladmin_mixins/ae_followup_admin_mixin.py +9 -11
- edc_adverse_event/modeladmin_mixins/ae_initial_admin_mixin.py +4 -6
- edc_adverse_event/modeladmin_mixins/modeladmin_mixins.py +1 -1
- edc_adverse_event/modeladmin_mixins/utils.py +4 -4
- edc_adverse_event/models/signals.py +7 -19
- edc_adverse_event/pdf_reports/death_pdf_report.py +8 -9
- edc_adverse_event/templatetags/edc_adverse_event_extras.py +10 -20
- edc_adverse_event/urls.py +7 -3
- edc_adverse_event/utils.py +2 -2
- edc_adverse_event/view_utils/tmg_button.py +4 -4
- edc_appconfig/system_checks.py +1 -1
- edc_appointment/appointment_status_updater.py +14 -15
- edc_appointment/creators/appointment_creator.py +7 -11
- edc_appointment/creators/appointments_creator.py +1 -1
- edc_appointment/creators/unscheduled_appointment_creator.py +12 -13
- edc_appointment/form_validator_mixins/next_appointment_crf_form_validator_mixin.py +1 -3
- edc_appointment/form_validator_mixins/window_period_form_validator_mixin.py +7 -6
- edc_appointment/form_validators/appointment_form_validator.py +2 -4
- edc_appointment/form_validators/utils.py +4 -4
- edc_appointment/managers.py +4 -4
- edc_appointment/model_mixins/appointment_methods_model_mixin.py +2 -2
- edc_appointment/model_mixins/appointment_model_mixin.py +5 -5
- edc_appointment/model_mixins/window_period_model_mixin.py +1 -1
- edc_appointment/models/appointment.py +1 -1
- edc_appointment/skip_appointments.py +5 -6
- edc_appointment/stubs.py +12 -12
- edc_appointment/utils.py +41 -47
- edc_appointment/view_utils/appointment_button.py +3 -5
- edc_auth/admin/role_admin.py +5 -7
- edc_auth/auth_objects/default_roles.py +1 -3
- edc_auth/auth_updater/auth_updater.py +5 -7
- edc_auth/auth_updater/group_updater.py +8 -8
- edc_auth/auth_updater/role_updater.py +1 -2
- edc_auth/get_app_codenames.py +1 -3
- edc_auth/import_users.py +19 -19
- edc_auth/models/role.py +4 -3
- edc_auth/password_setter.py +6 -7
- edc_auth/send_new_credentials_to_user.py +2 -3
- edc_auth/site_auths.py +3 -3
- edc_consent/actions.py +4 -5
- edc_consent/consent_definition.py +8 -11
- edc_consent/consent_definition_extension.py +3 -3
- edc_consent/form_validators/__init__.py +1 -1
- edc_consent/model_mixins/__init__.py +2 -2
- edc_consent/model_mixins/consent_version_model_mixin.py +1 -1
- edc_consent/modeladmin_mixins/consent_model_admin_mixin.py +1 -2
- edc_consent/modelform_mixins/consent_modelform_mixin/consent_modelform_validation_mixin.py +3 -5
- edc_consent/site_consents.py +10 -15
- edc_consent/stubs.py +2 -2
- edc_consent/utils.py +1 -1
- edc_constants/utils.py +2 -4
- edc_crf/crf_form_validator_mixins.py +2 -2
- edc_crf/model_mixins/crf_no_manager_model_mixin.py +2 -2
- edc_crf/model_mixins/crf_status_model_mixin.py +1 -1
- edc_crf/models/crf_status.py +10 -12
- edc_crf/update_crf_status_command.py +1 -3
- edc_dashboard/management/commands/update_search_slugs.py +8 -11
- edc_dashboard/templatetags/edc_dashboard_extras.py +1 -1
- edc_dashboard/url_config.py +3 -3
- edc_dashboard/utils.py +3 -4
- edc_dashboard/views/dashboard_view.py +1 -1
- edc_data_manager/models/data_dictionary.py +1 -2
- edc_data_manager/models/data_query.py +3 -3
- edc_data_manager/models/query_rule.py +2 -2
- edc_data_manager/tasks.py +0 -2
- edc_device/device.py +1 -1
- edc_device/view_mixins.py +1 -1
- edc_document_status/fieldsets.py +1 -3
- edc_document_status/model_mixins.py +2 -2
- edc_document_status/modeladmin_mixins.py +1 -4
- edc_egfr/admin/egfr_drop_notification_admin_mixin.py +5 -7
- edc_egfr/egfr.py +6 -7
- edc_egfr/get_drop_notification_model.py +1 -1
- edc_egfr/model_mixins/egfr_drop_notification_model_mixin.py +1 -1
- edc_export/archive_exporter.py +26 -27
- edc_export/exportables.py +2 -3
- edc_export/management/commands/import_receipts.py +6 -6
- edc_export/model_exporter/file_history_updater.py +1 -1
- edc_export/model_exporter/model_exporter.py +1 -1
- edc_export/model_exporter/value_getter.py +6 -6
- edc_export/model_mixins/notification_model_mixin.py +4 -4
- edc_export/models/data_request.py +3 -3
- edc_export/models/data_request_history.py +2 -2
- edc_export/models/export_receipt.py +1 -1
- edc_export/models/file_history.py +5 -5
- edc_export/models/plan.py +4 -4
- edc_export/models/upload_export_receipt_file.py +2 -2
- edc_export/utils.py +1 -1
- edc_facility/facility.py +6 -6
- edc_facility/holidays.py +2 -5
- edc_facility/import_holidays.py +6 -6
- edc_facility/model_mixins.py +1 -1
- edc_facility/models/holiday.py +1 -1
- edc_facility/utils.py +3 -3
- edc_fieldsets/fieldsets.py +1 -1
- edc_form_describer/forms_reference.py +5 -8
- edc_form_describer/make_forms_reference.py +1 -1
- edc_form_describer/management/commands/make_forms_reference.py +1 -1
- edc_form_label/custom_label_condition.py +1 -1
- edc_form_label/form_label.py +2 -2
- edc_form_runners/exceptions.py +1 -1
- edc_form_runners/models/issue.py +3 -2
- edc_form_runners/site.py +2 -2
- edc_form_runners/templatetags/form_runners_extras.py +1 -1
- edc_form_runners/utils.py +5 -5
- edc_form_validators/applicable_field_validator.py +32 -32
- edc_form_validators/base_form_validator.py +1 -1
- edc_form_validators/extra_mixins/study_day_form_validator.py +1 -1
- edc_form_validators/many_to_many_field_validator.py +38 -43
- edc_form_validators/other_specify_field_validator.py +9 -17
- edc_form_validators/required_field_validator.py +34 -39
- edc_form_validators/test_case_mixin.py +5 -5
- edc_identifier/admin.py +1 -3
- edc_identifier/identifier.py +2 -3
- edc_identifier/model_mixins.py +5 -8
- edc_identifier/research_identifier.py +10 -12
- edc_identifier/short_identifier.py +1 -1
- edc_identifier/simple_identifier.py +22 -22
- edc_identifier/utils.py +1 -1
- edc_lab/admin/fieldsets.py +7 -9
- edc_lab/admin/modeladmin_mixins.py +5 -6
- edc_lab/form_validators/requisition_form_validator_mixin.py +2 -3
- edc_lab/forms/box_form.py +5 -11
- edc_lab/identifiers/aliquot_identifier.py +5 -8
- edc_lab/identifiers/prefix.py +2 -5
- edc_lab/lab/aliquot_creator.py +1 -2
- edc_lab/lab/aliquot_type.py +2 -4
- edc_lab/lab/manifest.py +5 -7
- edc_lab/lab/requisition_panel.py +2 -4
- edc_lab/lab/requisition_panel_group.py +5 -7
- edc_lab/model_mixins/requisition/requisition_model_mixin.py +5 -4
- edc_lab/model_mixins/shipping/manifest_model_mixin.py +6 -6
- edc_lab/model_mixins/shipping/verify_model_mixin.py +4 -3
- edc_lab/models/aliquot.py +1 -1
- edc_lab/models/box.py +2 -2
- edc_lab/models/box_item.py +1 -1
- edc_lab/models/box_type.py +1 -1
- edc_lab/models/manifest/manifest_item.py +1 -1
- edc_lab/pdf_reports/manifest_pdf_report.py +3 -7
- edc_lab/site_labs.py +3 -3
- edc_lab_dashboard/dashboard_templates.py +1 -1
- edc_lab_dashboard/view_mixins/box_view_mixin.py +4 -9
- edc_lab_dashboard/views/action_views/action_view.py +1 -2
- edc_lab_dashboard/views/action_views/manage_manifest_view.py +1 -1
- edc_lab_dashboard/views/action_views/manifest_view.py +9 -11
- edc_lab_dashboard/views/action_views/pack_view.py +11 -14
- edc_lab_results/calculate_missing.py +2 -2
- edc_lab_results/fieldsets.py +6 -6
- edc_lab_results/model_mixin_factories/__init__.py +2 -2
- edc_lab_results/model_mixin_factories/reportable_result_model_mixin_factory.py +7 -9
- edc_lab_results/model_mixin_factories/result_model_mixin_factory.py +7 -9
- edc_lab_results/model_mixins/fbg_model_mixin.py +1 -1
- edc_lab_results/model_mixins/glucose_model_mixin.py +1 -1
- edc_label/admin.py +1 -3
- edc_label/label.py +2 -4
- edc_label/label_template.py +1 -1
- edc_label/subject_label.py +1 -3
- edc_list_data/admin.py +3 -5
- edc_list_data/load_list_data.py +1 -1
- edc_list_data/load_model_data.py +1 -3
- edc_list_data/model_mixins.py +3 -5
- edc_list_data/preload_data.py +2 -4
- edc_list_data/site_list_data.py +6 -7
- edc_listboard/filters/listboard_filter.py +2 -2
- edc_listboard/views/listboard_view.py +1 -3
- edc_locator/models.py +2 -2
- edc_locator/view_mixins/subject_locator_view_mixins.py +2 -2
- edc_ltfu/action_items.py +1 -1
- edc_ltfu/model_mixins.py +0 -2
- edc_ltfu/models.py +4 -4
- edc_metadata/admin/modeladmin_mixins.py +1 -1
- edc_metadata/admin/requisition_metadata.py +2 -4
- edc_metadata/metadata/crf_metadata_getter.py +1 -3
- edc_metadata/metadata/requisition_metadata_getter.py +1 -3
- edc_metadata/metadata_handler.py +2 -2
- edc_metadata/metadata_helper/metadata_helper_mixin.py +2 -3
- edc_metadata/metadata_mixins/source_model_metadata_mixin.py +2 -2
- edc_metadata/metadata_refresher.py +1 -1
- edc_metadata/metadata_rules/crf/crf_rule_group.py +3 -5
- edc_metadata/metadata_rules/logic.py +1 -1
- edc_metadata/metadata_rules/requisition/requisition_rule.py +4 -4
- edc_metadata/metadata_rules/requisition/requisition_rule_group.py +2 -2
- edc_metadata/metadata_rules/rule_evaluator.py +5 -6
- edc_metadata/metadata_rules/rule_group_metaclass.py +2 -3
- edc_metadata/metadata_rules/site.py +6 -7
- edc_metadata/metadata_updater.py +2 -2
- edc_metadata/metadata_wrappers/metadata_wrapper.py +4 -4
- edc_metadata/model_mixins/creates/creates_metadata_model_mixin.py +6 -7
- edc_metadata/model_mixins/updates/updates_crf_metadata_model_mixin.py +2 -2
- edc_metadata/model_mixins/updates/updates_metadata_model_mixin.py +2 -2
- edc_metadata/model_mixins/updates/updates_requisition_metadata_model_mixin.py +2 -2
- edc_metadata/models/crf_metadata.py +27 -29
- edc_metadata/models/crf_metadata_model_mixin.py +1 -1
- edc_metadata/models/requisition_metadata.py +29 -31
- edc_metadata/stubs.py +17 -17
- edc_metadata/utils.py +4 -4
- edc_model/__init__.py +2 -2
- edc_model/models/historical_records.py +2 -2
- edc_model/models/signals.py +1 -1
- edc_model/models/url_model_mixin.py +1 -1
- edc_model/utils.py +0 -2
- edc_model_admin/dashboard/model_admin_dashboard_mixin.py +1 -3
- edc_model_admin/history/model_admin_simple_history.py +7 -9
- edc_model_admin/mixins/base_model_admin_redirect_mixin.py +4 -6
- edc_model_admin/mixins/model_admin_limit_to_selected_foreignkey.py +2 -2
- edc_model_admin/mixins/model_admin_model_redirect_mixin.py +2 -10
- edc_model_admin/mixins/model_admin_next_url_redirect_mixin.py +8 -9
- edc_model_admin/utils.py +6 -9
- edc_model_form/mixins/__init__.py +1 -1
- edc_model_form/mixins/base_model_form_mixin.py +1 -1
- edc_model_to_dataframe/model_to_dataframe.py +2 -4
- edc_navbar/site_navbars.py +2 -2
- edc_navbar/system_checks.py +1 -1
- edc_notification/mailing_list_manager.py +5 -8
- edc_notification/management/commands/list_recipients_by_notification.py +1 -1
- edc_notification/models/__init__.py +4 -2
- edc_notification/notification/graded_event_notification.py +2 -4
- edc_notification/notification/model_notification.py +1 -3
- edc_notification/notification/notification.py +14 -17
- edc_notification/site_notifications.py +7 -7
- edc_notification/stubs.py +6 -6
- edc_notification/update_mailing_lists_in_m2m.py +2 -2
- edc_offstudy/action_items.py +2 -2
- edc_offstudy/model_mixins/offstudy_model_mixin.py +1 -1
- edc_offstudy/models.py +1 -1
- edc_pdf_reports/crf_pdf_report.py +2 -2
- edc_pdf_reports/model_mixins.py +2 -2
- edc_pdf_reports/report.py +4 -5
- edc_pdf_reports/utils.py +1 -1
- edc_pdutils/dataframes/get_subject_consent.py +1 -3
- edc_pdutils/df_exporters/csv_exporter.py +5 -7
- edc_pdutils/df_exporters/tables_exporter.py +2 -2
- edc_pdutils/df_handlers/crf_df_handler.py +1 -2
- edc_pdutils/dialects/crf_dialect.py +3 -3
- edc_pdutils/management/commands/export_models.py +6 -7
- edc_pdutils/site_values_mappings.py +2 -2
- edc_pdutils/utils/datetime_to_date.py +1 -2
- edc_pdutils/utils/refresh_model_from_dataframe.py +1 -3
- edc_pdutils/utils/table_names.py +2 -4
- edc_pdutils/utils/undash.py +1 -1
- edc_pharmacy/admin/medication/assignment_admin.py +2 -4
- edc_pharmacy/admin/medication/dosage_guideline_admin.py +2 -4
- edc_pharmacy/admin/medication/formulation_admin.py +3 -5
- edc_pharmacy/admin/medication/medication_admin.py +3 -5
- edc_pharmacy/admin/prescription/rx_refill_admin.py +7 -9
- edc_pharmacy/admin/stock/lot_admin.py +5 -7
- edc_pharmacy/admin/stock/order_admin.py +2 -2
- edc_pharmacy/admin/stock/order_item_admin.py +6 -6
- edc_pharmacy/admin/stock/receive_admin.py +1 -1
- edc_pharmacy/admin/stock/receive_item_admin.py +2 -2
- edc_pharmacy/admin/stock/stock_request_admin.py +4 -6
- edc_pharmacy/dosage_calculator.py +4 -4
- edc_pharmacy/form_validators/crf/study_medication_form_validator.py +8 -8
- edc_pharmacy/forms/stock/stock_request_form.py +2 -4
- edc_pharmacy/model_mixins/study_medication_crf_model_mixin.py +2 -2
- edc_pharmacy/models/medication/formulation.py +3 -4
- edc_pharmacy/models/medication/medication.py +1 -2
- edc_pharmacy/models/prescription/rx.py +2 -2
- edc_pharmacy/models/prescription/rx_refill.py +7 -9
- edc_pharmacy/models/reports/stock_availability.py +3 -4
- edc_pharmacy/models/stock/confirmation_at_site.py +1 -3
- edc_pharmacy/models/stock/order.py +1 -2
- edc_pharmacy/models/stock/receive.py +3 -4
- edc_pharmacy/models/stock/receive_item.py +2 -3
- edc_pharmacy/models/stock/stock.py +1 -2
- edc_pharmacy/models/stock/stock_adjustment.py +1 -2
- edc_pharmacy/models/stock/stock_request.py +4 -5
- edc_pharmacy/models/storage/box.py +1 -1
- edc_pharmacy/models/storage/items/container_model_mixin.py +1 -1
- edc_pharmacy/models/storage/room.py +1 -1
- edc_pharmacy/models/storage/shelf.py +1 -1
- edc_pharmacy/models/storage/utils.py +15 -16
- edc_pharmacy/prescribe/create_prescription.py +5 -5
- edc_pharmacy/refill/refill_creator.py +1 -1
- edc_pharmacy/sample_usb_printing/usb_printing.py +1 -1
- edc_pharmacy/utils/__init__.py +1 -1
- edc_pharmacy/utils/confirm_stock.py +3 -3
- edc_pharmacy/utils/confirm_stock_at_site.py +9 -8
- edc_pharmacy/utils/dispense.py +5 -8
- edc_pharmacy/utils/format_qty.py +3 -3
- edc_pharmacy/utils/get_codenames.py +1 -1
- edc_pharmacy/utils/miscellaneous.py +1 -1
- edc_pharmacy/utils/process_repack_request.py +18 -19
- edc_pharmacy/utils/process_repack_request_queryset.py +0 -2
- edc_pharmacy/utils/stock_request/bulk_create_stock_request_items.py +2 -3
- edc_pharmacy/views/add_to_storage_bin_view.py +1 -1
- edc_pharmacy/views/allocate_to_subject_view.py +2 -6
- edc_pharmacy/views/confirm_stock_from_instance_view.py +4 -4
- edc_pharmacy/views/confirm_stock_from_queryset_view.py +1 -5
- edc_pharmacy/views/confirmation_at_site_view.py +2 -3
- edc_pharmacy/views/dispense_view.py +1 -1
- edc_pharmacy/views/move_to_storage_bin_view.py +2 -3
- edc_pharmacy/views/print_labels_view.py +30 -31
- edc_pharmacy/views/transfer_stock_view.py +1 -1
- edc_prn/prn.py +1 -1
- edc_prn/site_prn_forms.py +1 -2
- edc_protocol_incident/action_items.py +2 -2
- edc_protocol_incident/model_mixins/protocol_deviation_violation_model_mixin.py +3 -3
- edc_protocol_incident/model_mixins/protocol_incident_model_mixin.py +3 -5
- edc_protocol_incident/modeladmin_mixins.py +3 -5
- edc_protocol_incident/models/protocol_deviation_violation.py +5 -5
- edc_protocol_incident/models/protocol_incident.py +5 -5
- edc_pylabels/site_label_configs.py +5 -5
- edc_qareports/model_mixins/qa_report_model_mixin.py +4 -2
- edc_qareports/models/qa_reports_log.py +1 -2
- edc_qareports/utils.py +1 -2
- edc_randomization/admin.py +3 -5
- edc_randomization/auth_objects.py +2 -3
- edc_randomization/blinding.py +1 -1
- edc_randomization/constants.py +2 -4
- edc_randomization/model_mixins.py +3 -3
- edc_randomization/randomization_list_importer.py +13 -14
- edc_randomization/randomization_list_verifier.py +11 -13
- edc_randomization/randomizer.py +2 -2
- edc_randomization/system_checks.py +1 -2
- edc_randomization/utils.py +5 -5
- edc_refusal/admin.py +1 -1
- edc_refusal/model_mixins.py +1 -1
- edc_refusal/utils.py +1 -1
- edc_registration/model_mixins/updates_or_creates_registered_subject_model_mixin.py +2 -2
- edc_registration/modeladmin_mixins.py +4 -7
- edc_registration/models/registered_subject.py +7 -9
- edc_registration/utils.py +3 -4
- edc_reportable/data/grading_data/daids_july_2017.py +3 -3
- edc_reportable/evaluator.py +5 -5
- edc_reportable/formula.py +1 -1
- edc_reportable/reference_range_evaluator.py +3 -3
- edc_reportable/utils/convert_units.py +10 -12
- edc_reportable/utils/grading_data_model_cls.py +2 -2
- edc_reportable/utils/grading_exception_model_cls.py +2 -2
- edc_reportable/utils/molecular_weight_model_cls.py +2 -2
- edc_reportable/utils/normal_data_model_cls.py +2 -2
- edc_reportable/utils/reference_range_colllection_model_cls.py +2 -2
- edc_screening/age_evaluator.py +2 -4
- edc_screening/eligibility.py +1 -1
- edc_screening/form_validator_mixins.py +2 -2
- edc_screening/model_mixins/screening_methods_model_mixin.py +1 -1
- edc_screening/screening_eligibility.py +1 -1
- edc_screening/utils.py +4 -6
- edc_search/model_mixins.py +1 -1
- edc_search/search_slug.py +1 -3
- edc_search/updater.py +1 -1
- edc_sites/admin/site_model_admin_mixin.py +2 -2
- edc_sites/model_mixins/site_model_mixin.py +1 -2
- edc_sites/modelform_mixins.py +2 -2
- edc_sites/models/__init__.py +1 -1
- edc_sites/models/site_profile.py +4 -4
- edc_sites/site.py +24 -32
- edc_sites/system_checks.py +1 -1
- edc_sites/utils/get_message_text.py +1 -1
- edc_sites/utils/valid_site_for_subject_or_raise.py +5 -6
- edc_subject_dashboard/requisition_report.py +4 -5
- edc_subject_dashboard/requisition_verifier.py +4 -2
- edc_subject_dashboard/templatetags/edc_subject_dashboard_extras.py +16 -17
- edc_subject_dashboard/view_utils/__init__.py +1 -1
- edc_subject_dashboard/view_utils/crf_button.py +1 -3
- edc_subject_dashboard/view_utils/subject_consent_dashboard_button.py +2 -2
- edc_subject_dashboard/view_utils/subject_consent_listboard_button.py +2 -2
- edc_subject_dashboard/view_utils/timepoint_status_button.py +1 -4
- edc_subject_dashboard/views/requisition_print_actions_view.py +2 -2
- edc_subject_dashboard/views/subject_dashboard_view.py +1 -1
- edc_timepoint/model_mixins.py +2 -2
- edc_transfer/action_items.py +1 -1
- edc_transfer/model_mixins.py +4 -3
- edc_transfer/modeladmin_mixins.py +3 -6
- edc_unblinding/action_items.py +2 -2
- edc_unblinding/models/unblinding_request.py +3 -3
- edc_unblinding/models/unblinding_review.py +3 -3
- edc_utils/__init__.py +6 -5
- edc_utils/age.py +1 -1
- edc_utils/celery.py +3 -8
- edc_utils/get_static_file.py +1 -1
- edc_utils/text.py +5 -6
- edc_view_utils/__init__.py +3 -3
- edc_view_utils/dashboard_model_button.py +4 -4
- edc_view_utils/model_button.py +7 -8
- edc_view_utils/perms.py +3 -3
- edc_visit_schedule/action_items.py +2 -2
- edc_visit_schedule/admin/list_filters.py +11 -9
- edc_visit_schedule/admin/subject_schedule_history_admin.py +13 -15
- edc_visit_schedule/admin/visit_schedule_admin.py +7 -7
- edc_visit_schedule/fieldsets.py +4 -6
- edc_visit_schedule/management/commands/find_invalid_onschedules.py +1 -1
- edc_visit_schedule/model_mixins/off_schedule_model_mixin.py +3 -1
- edc_visit_schedule/model_mixins/on_schedule_model_mixin.py +1 -1
- edc_visit_schedule/modelform_mixins/__init__.py +1 -1
- edc_visit_schedule/modelform_mixins/off_schedule_modelform_mixin.py +1 -2
- edc_visit_schedule/models/subject_schedule_history.py +2 -1
- edc_visit_schedule/models/visit_schedule.py +2 -2
- edc_visit_schedule/schedule/schedule.py +11 -12
- edc_visit_schedule/schedule/visit_collection.py +4 -3
- edc_visit_schedule/schedule/window.py +19 -12
- edc_visit_schedule/site_visit_schedules.py +9 -9
- edc_visit_schedule/subject_schedule.py +6 -7
- edc_visit_schedule/system_checks.py +8 -4
- edc_visit_schedule/view_mixins.py +1 -1
- edc_visit_schedule/visit/crf.py +3 -7
- edc_visit_schedule/visit/requisition.py +2 -2
- edc_visit_schedule/visit/visit.py +6 -7
- edc_visit_schedule/visit/window_period.py +8 -8
- edc_visit_schedule/visit_schedule/schedules_collection.py +2 -5
- edc_visit_tracking/action_items.py +11 -13
- edc_visit_tracking/form_validators/visit_form_validator.py +5 -5
- edc_visit_tracking/model_mixins/base/visit_methods_model_mixin.py +3 -4
- edc_visit_tracking/model_mixins/crfs/visit_tracking_crf_model_mixin.py +2 -2
- edc_visit_tracking/model_mixins/subject_visit_missed_model_mixin.py +2 -3
- edc_visit_tracking/model_mixins/utils.py +1 -1
- edc_visit_tracking/model_mixins/visit_model_mixin/previous_visit_model_mixin.py +2 -2
- edc_visit_tracking/model_mixins/visit_model_mixin/visit_model_mixin.py +3 -3
- edc_visit_tracking/modeladmin_mixins/crf_model_admin_mixin.py +6 -4
- edc_visit_tracking/modeladmin_mixins/visit_model_admin_mixin.py +6 -7
- edc_visit_tracking/modelform_mixins/crf/visit_tracking_crf_modelform_mixin.py +4 -5
- edc_visit_tracking/modelform_mixins/visit_tracking_modelform_mixin.py +3 -4
- edc_visit_tracking/models/subject_visit.py +1 -1
- edc_visit_tracking/models/subject_visit_missed.py +1 -1
- edc_visit_tracking/stubs.py +7 -7
- edc_visit_tracking/typing_stubs.py +11 -11
- edc_visit_tracking/utils.py +5 -5
- edc_visit_tracking/view_utils/related_visit_button.py +5 -5
- edc_vitals/calculators/bmi.py +1 -1
- edc_vitals/form_validators/blood_pressure_form_validator_mixin.py +8 -12
- edc_vitals/form_validators/bmi_form_validator_mixin.py +1 -1
- edc_vitals/form_validators/weight_height_with_bmi_form_validator_mixin.py +5 -2
- edc_vitals/model_mixins/blood_pressure_model_mixin.py +3 -4
- edc_vitals/model_mixins/weight_height_bmi_model_mixin.py +4 -4
- edc_vitals/utils.py +1 -1
- edc_vitals/validators.py +1 -1
- {clinicedc-2.0.3.dist-info → clinicedc-2.0.5.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.3.dist-info → clinicedc-2.0.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -86,26 +86,22 @@ class AppointmentCreator:
|
|
|
86
86
|
try:
|
|
87
87
|
if is_naive(timepoint_datetime):
|
|
88
88
|
raise ValueError(
|
|
89
|
-
f"Naive datetime not allowed. {
|
|
89
|
+
f"Naive datetime not allowed. {self!r}. Got {timepoint_datetime}"
|
|
90
90
|
)
|
|
91
|
-
|
|
92
|
-
self.timepoint_datetime = timepoint_datetime
|
|
91
|
+
self.timepoint_datetime = timepoint_datetime
|
|
93
92
|
except AttributeError:
|
|
94
93
|
raise AppointmentCreatorError(
|
|
95
|
-
f"Expected 'timepoint_datetime'. Got None. {
|
|
94
|
+
f"Expected 'timepoint_datetime'. Got None. {self!r}."
|
|
96
95
|
)
|
|
97
96
|
# suggested_datetime (defaults to timepoint_datetime)
|
|
98
97
|
# If provided, the rules for window period/rdelta relative
|
|
99
98
|
# to timepoint_datetime still apply.
|
|
100
99
|
if suggested_datetime and is_naive(suggested_datetime):
|
|
101
|
-
raise ValueError(
|
|
102
|
-
|
|
103
|
-
)
|
|
104
|
-
else:
|
|
105
|
-
self.suggested_datetime = suggested_datetime or self.timepoint_datetime
|
|
100
|
+
raise ValueError(f"Naive datetime not allowed. {self!r}. Got {suggested_datetime}")
|
|
101
|
+
self.suggested_datetime = suggested_datetime or self.timepoint_datetime
|
|
106
102
|
self.facility = facility or visit.facility
|
|
107
103
|
if not self.facility:
|
|
108
|
-
raise AppointmentCreatorError(f"facility_name not defined. See {
|
|
104
|
+
raise AppointmentCreatorError(f"facility_name not defined. See {visit!r}")
|
|
109
105
|
self.get_appointment()
|
|
110
106
|
|
|
111
107
|
def __repr__(self):
|
|
@@ -217,7 +213,7 @@ class AppointmentCreator:
|
|
|
217
213
|
)
|
|
218
214
|
except FacilityError as e:
|
|
219
215
|
raise CreateAppointmentDateError(
|
|
220
|
-
f"{e} Visit={
|
|
216
|
+
f"{e} Visit={self.visit!r}. "
|
|
221
217
|
f"Try setting 'best_effort_available_datetime=True' on facility."
|
|
222
218
|
)
|
|
223
219
|
else:
|
|
@@ -84,7 +84,7 @@ class AppointmentsCreator:
|
|
|
84
84
|
facility = get_facility(visit.facility_name)
|
|
85
85
|
except FacilityError as e:
|
|
86
86
|
raise CreateAppointmentError(
|
|
87
|
-
f"{e} See {
|
|
87
|
+
f"{e} See {visit!r}. Got facility_name={visit.facility_name}"
|
|
88
88
|
)
|
|
89
89
|
appointment = self.update_or_create_appointment(
|
|
90
90
|
visit=visit,
|
|
@@ -100,7 +100,7 @@ class UnscheduledAppointmentCreator:
|
|
|
100
100
|
f"schedule_name='{self.schedule_name}',"
|
|
101
101
|
f"visit_code='{self.visit_code}'" + "}"
|
|
102
102
|
)
|
|
103
|
-
|
|
103
|
+
if not value.allow_unscheduled:
|
|
104
104
|
raise UnscheduledAppointmentNotAllowed(
|
|
105
105
|
f"Not allowed. Visit {self.visit_code} is not configured for "
|
|
106
106
|
"unscheduled appointments."
|
|
@@ -241,18 +241,17 @@ class UnscheduledAppointmentCreator:
|
|
|
241
241
|
"visit form is not submitted. "
|
|
242
242
|
f"Got appointment '{self.visit_code}.0'."
|
|
243
243
|
)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
)
|
|
244
|
+
if self._parent_appointment.appt_status not in [
|
|
245
|
+
COMPLETE_APPT,
|
|
246
|
+
INCOMPLETE_APPT,
|
|
247
|
+
]:
|
|
248
|
+
raise InvalidParentAppointmentStatusError(
|
|
249
|
+
"Unable to create unscheduled appointment. An unscheduled "
|
|
250
|
+
"appointment cannot be created if the parent appointment "
|
|
251
|
+
"is 'new' or 'in progress'. Got appointment "
|
|
252
|
+
f"'{self.visit_code}' is "
|
|
253
|
+
f"{self._parent_appointment.get_appt_status_display().lower()}."
|
|
254
|
+
)
|
|
256
255
|
|
|
257
256
|
return self._parent_appointment
|
|
258
257
|
|
|
@@ -24,7 +24,6 @@ class NextAppointmentCrfFormValidatorMixin(FormValidator):
|
|
|
24
24
|
super().__init__(**kwargs)
|
|
25
25
|
|
|
26
26
|
def clean(self):
|
|
27
|
-
|
|
28
27
|
self.required_if(
|
|
29
28
|
NO,
|
|
30
29
|
field="offschedule_today",
|
|
@@ -119,8 +118,7 @@ class NextAppointmentCrfFormValidatorMixin(FormValidator):
|
|
|
119
118
|
except ValidationError as e:
|
|
120
119
|
if e.message_dict.get("appt_datetime"):
|
|
121
120
|
raise ValidationError({"appt_date": e.message_dict["appt_datetime"]})
|
|
122
|
-
|
|
123
|
-
raise
|
|
121
|
+
raise
|
|
124
122
|
|
|
125
123
|
@property
|
|
126
124
|
def appt_datetime(self) -> datetime:
|
|
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
from dateutil.relativedelta import relativedelta
|
|
7
7
|
from django.utils.translation import gettext as _
|
|
8
8
|
|
|
9
|
-
from edc_utils import formatted_date
|
|
9
|
+
from edc_utils import formatted_date
|
|
10
10
|
from edc_utils.date import floor_secs, to_local
|
|
11
11
|
from edc_visit_schedule.exceptions import (
|
|
12
12
|
ScheduledVisitWindowError,
|
|
@@ -38,7 +38,7 @@ class WindowPeriodFormValidatorMixin:
|
|
|
38
38
|
and appointment.visit_code_sequence > 0
|
|
39
39
|
and appointment.next
|
|
40
40
|
and appointment.next.appt_status in [INCOMPLETE_APPT, COMPLETE_APPT]
|
|
41
|
-
and
|
|
41
|
+
and to_local(proposed_appt_datetime) < to_local(appointment.next.appt_datetime)
|
|
42
42
|
):
|
|
43
43
|
value = True
|
|
44
44
|
return value
|
|
@@ -50,14 +50,15 @@ class WindowPeriodFormValidatorMixin:
|
|
|
50
50
|
form_field: str,
|
|
51
51
|
):
|
|
52
52
|
if proposed_appt_datetime:
|
|
53
|
-
proposed_appt_datetime = to_utc(proposed_appt_datetime)
|
|
54
53
|
try:
|
|
55
54
|
appointment.schedule.datetime_in_window(
|
|
56
|
-
timepoint_datetime=appointment.timepoint_datetime,
|
|
57
|
-
dt=proposed_appt_datetime,
|
|
55
|
+
timepoint_datetime=to_local(appointment.timepoint_datetime),
|
|
56
|
+
dt=to_local(proposed_appt_datetime),
|
|
58
57
|
visit_code=appointment.visit_code,
|
|
59
58
|
visit_code_sequence=appointment.visit_code_sequence,
|
|
60
|
-
baseline_timepoint_datetime=
|
|
59
|
+
baseline_timepoint_datetime=to_local(
|
|
60
|
+
self.baseline_timepoint_datetime(appointment)
|
|
61
|
+
),
|
|
61
62
|
)
|
|
62
63
|
except UnScheduledVisitWindowError:
|
|
63
64
|
if not self.ignore_window_period_for_unscheduled(
|
|
@@ -236,10 +236,8 @@ class AppointmentFormValidator(
|
|
|
236
236
|
self.raise_validation_error(
|
|
237
237
|
{
|
|
238
238
|
"appt_datetime": (
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
f"Got consented on {formatted_date}"
|
|
242
|
-
)
|
|
239
|
+
"Invalid. Cannot be before consent date. "
|
|
240
|
+
f"Got consented on {formatted_date}"
|
|
243
241
|
)
|
|
244
242
|
},
|
|
245
243
|
INVALID_APPT_DATE,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
3
|
from zoneinfo import ZoneInfo
|
|
4
4
|
|
|
5
5
|
from django.urls import reverse
|
|
@@ -16,7 +16,7 @@ def get_appointment_url(appointment_pk: str, subject_identifier: str) -> str:
|
|
|
16
16
|
args=(appointment_pk,),
|
|
17
17
|
)
|
|
18
18
|
rev_url = (
|
|
19
|
-
f
|
|
19
|
+
f"{rev_url}?next={url_names.get('subject_dashboard_url')},"
|
|
20
20
|
f"subject_identifier"
|
|
21
21
|
f"&subject_identifier={subject_identifier}"
|
|
22
22
|
)
|
|
@@ -27,7 +27,7 @@ def validate_appt_datetime_unique(
|
|
|
27
27
|
form_validator: Any,
|
|
28
28
|
appointment: Any,
|
|
29
29
|
appt_datetime: datetime,
|
|
30
|
-
form_field:
|
|
30
|
+
form_field: str | None = None,
|
|
31
31
|
):
|
|
32
32
|
"""Assert one visit report per day"""
|
|
33
33
|
if appt_datetime:
|
|
@@ -45,7 +45,7 @@ def validate_appt_datetime_unique(
|
|
|
45
45
|
{form_field: "An appointment already exists for this date (M)"},
|
|
46
46
|
INVALID_ERROR,
|
|
47
47
|
)
|
|
48
|
-
|
|
48
|
+
if other_appts.count() == 1:
|
|
49
49
|
if appointment and other_appts[0].id != appointment.id:
|
|
50
50
|
appointment_url = get_appointment_url(
|
|
51
51
|
(
|
edc_appointment/managers.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, Any
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
5
|
from django.db import models, transaction
|
|
6
6
|
from django.db.models.deletion import ProtectedError
|
|
@@ -43,7 +43,7 @@ class AppointmentManager(models.Manager):
|
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
@staticmethod
|
|
46
|
-
def get_query_options(**kwargs) ->
|
|
46
|
+
def get_query_options(**kwargs) -> dict[Any]:
|
|
47
47
|
"""Returns a dictionary or options.
|
|
48
48
|
|
|
49
49
|
Dictionary is based on the appointment instance or everything
|
|
@@ -53,7 +53,7 @@ class AppointmentManager(models.Manager):
|
|
|
53
53
|
schedule_name = kwargs.get("schedule_name")
|
|
54
54
|
subject_identifier = kwargs.get("subject_identifier")
|
|
55
55
|
visit_schedule_name = kwargs.get("visit_schedule_name")
|
|
56
|
-
options:
|
|
56
|
+
options: dict[Any] = dict(visit_code_sequence=0)
|
|
57
57
|
try:
|
|
58
58
|
options.update(
|
|
59
59
|
subject_identifier=appointment.subject_identifier,
|
|
@@ -76,7 +76,7 @@ class AppointmentManager(models.Manager):
|
|
|
76
76
|
f"Expected visit_schedule_name for schedule_name "
|
|
77
77
|
f"'{schedule_name}'. Got {visit_schedule_name}"
|
|
78
78
|
)
|
|
79
|
-
|
|
79
|
+
if schedule_name:
|
|
80
80
|
options.update(schedule_name=schedule_name)
|
|
81
81
|
return options
|
|
82
82
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING,
|
|
3
|
+
from typing import TYPE_CHECKING, TypeVar
|
|
4
4
|
|
|
5
5
|
from django.core.exceptions import ObjectDoesNotExist
|
|
6
6
|
from django.db import models
|
|
@@ -48,7 +48,7 @@ class AppointmentMethodsModelMixin(models.Model):
|
|
|
48
48
|
return get_related_visit_model_attr(cls)
|
|
49
49
|
|
|
50
50
|
@classmethod
|
|
51
|
-
def related_visit_model_cls(cls: Appointment) ->
|
|
51
|
+
def related_visit_model_cls(cls: Appointment) -> type[VisitModel]:
|
|
52
52
|
return getattr(cls, cls.related_visit_model_attr()).related.related_model
|
|
53
53
|
|
|
54
54
|
@property
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import uuid
|
|
4
4
|
from datetime import datetime
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
6
|
from uuid import UUID
|
|
7
7
|
|
|
8
8
|
from dateutil.relativedelta import relativedelta
|
|
@@ -67,7 +67,7 @@ class AppointmentModelMixin(
|
|
|
67
67
|
return f"{self.subject_identifier} {self.visit_code}.{self.visit_code_sequence}"
|
|
68
68
|
|
|
69
69
|
def save(self: Appointment, *args, **kwargs):
|
|
70
|
-
if not kwargs.get("update_fields"
|
|
70
|
+
if not kwargs.get("update_fields"):
|
|
71
71
|
if self.id and is_baseline(instance=self):
|
|
72
72
|
visit_schedule = site_visit_schedules.get_visit_schedule(
|
|
73
73
|
self.visit_schedule_name
|
|
@@ -116,7 +116,7 @@ class AppointmentModelMixin(
|
|
|
116
116
|
)
|
|
117
117
|
|
|
118
118
|
@property
|
|
119
|
-
def str_pk(self: Appointment) ->
|
|
119
|
+
def str_pk(self: Appointment) -> str | uuid.UUID:
|
|
120
120
|
if isinstance(self.id, UUID):
|
|
121
121
|
return str(self.pk)
|
|
122
122
|
return self.pk
|
|
@@ -193,7 +193,7 @@ class AppointmentModelMixin(
|
|
|
193
193
|
name="unique_%(app_label)s_%(class)s_200",
|
|
194
194
|
),
|
|
195
195
|
]
|
|
196
|
-
indexes =
|
|
196
|
+
indexes = (
|
|
197
197
|
models.Index(fields=["appt_datetime"]),
|
|
198
198
|
models.Index(fields=["appt_status"]),
|
|
199
199
|
models.Index(fields=["timepoint", "visit_code_sequence"]),
|
|
@@ -217,4 +217,4 @@ class AppointmentModelMixin(
|
|
|
217
217
|
"visit_code_sequence",
|
|
218
218
|
]
|
|
219
219
|
),
|
|
220
|
-
|
|
220
|
+
)
|
|
@@ -20,7 +20,7 @@ class WindowPeriodModelMixin(models.Model):
|
|
|
20
20
|
window_period_checks_enabled: bool = True
|
|
21
21
|
|
|
22
22
|
def save(self: Any, *args, **kwargs) -> None:
|
|
23
|
-
if not kwargs.get("update_fields"
|
|
23
|
+
if not kwargs.get("update_fields"):
|
|
24
24
|
self.raise_on_appt_datetime_not_in_window()
|
|
25
25
|
super().save(*args, **kwargs)
|
|
26
26
|
|
|
@@ -26,4 +26,4 @@ class Appointment(AppointmentModelMixin, SiteModelMixin, BaseUuidModel):
|
|
|
26
26
|
natural_key.dependencies = ["sites.Site"] # type: ignore
|
|
27
27
|
|
|
28
28
|
class Meta(AppointmentModelMixin.Meta, SiteModelMixin.Meta, BaseUuidModel.Meta):
|
|
29
|
-
indexes = AppointmentModelMixin.Meta.indexes
|
|
29
|
+
indexes = (*AppointmentModelMixin.Meta.indexes, *BaseUuidModel.Meta.indexes)
|
|
@@ -177,12 +177,11 @@ class SkipAppointments:
|
|
|
177
177
|
raise AppointmentAlreadyStarted(
|
|
178
178
|
f"Unable update as next. Appointment already started. Got {appointment}."
|
|
179
179
|
)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
appointment.save(update_fields=["appt_status", "appt_datetime", "comment"])
|
|
180
|
+
appointment.appt_status = NEW_APPT
|
|
181
|
+
appointment.appt_datetime = self.next_appt_datetime
|
|
182
|
+
appointment.comment = ""
|
|
183
|
+
self.validate_appointment_as_next(appointment)
|
|
184
|
+
appointment.save(update_fields=["appt_status", "appt_datetime", "comment"])
|
|
186
185
|
|
|
187
186
|
@property
|
|
188
187
|
def last_crf_obj(self):
|
edc_appointment/stubs.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from typing import Any,
|
|
2
|
+
from typing import Any, Protocol, TypeVar
|
|
3
3
|
from uuid import UUID
|
|
4
4
|
|
|
5
5
|
from django.db import models
|
|
@@ -8,23 +8,23 @@ from edc_visit_schedule.schedule import Schedule
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class AppointmentModelStub(Protocol):
|
|
11
|
-
id:
|
|
12
|
-
pk:
|
|
13
|
-
subject_identifier:
|
|
14
|
-
appt_datetime:
|
|
15
|
-
visit_code:
|
|
16
|
-
visit_code_sequence:
|
|
17
|
-
visit_schedule_name:
|
|
18
|
-
schedule_name:
|
|
19
|
-
facility_name:
|
|
20
|
-
timepoint:
|
|
11
|
+
id: UUID | models.UUIDField
|
|
12
|
+
pk: UUID | models.UUIDField
|
|
13
|
+
subject_identifier: str | models.CharField
|
|
14
|
+
appt_datetime: datetime | models.DateTimeField
|
|
15
|
+
visit_code: str | models.CharField
|
|
16
|
+
visit_code_sequence: int | models.IntegerField
|
|
17
|
+
visit_schedule_name: str | models.CharField
|
|
18
|
+
schedule_name: str | models.CharField
|
|
19
|
+
facility_name: str | models.CharField
|
|
20
|
+
timepoint: int | models.IntegerField
|
|
21
21
|
timepoint_datetime: datetime
|
|
22
22
|
schedule: Schedule
|
|
23
23
|
_meta: Any
|
|
24
24
|
|
|
25
25
|
objects: models.Manager
|
|
26
26
|
|
|
27
|
-
last_visit_code_sequence:
|
|
27
|
+
last_visit_code_sequence: int | None
|
|
28
28
|
next: "AppointmentModelStub"
|
|
29
29
|
previous: "AppointmentModelStub"
|
|
30
30
|
get_next: "AppointmentModelStub"
|
edc_appointment/utils.py
CHANGED
|
@@ -4,7 +4,7 @@ import calendar
|
|
|
4
4
|
import sys
|
|
5
5
|
import warnings
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from typing import TYPE_CHECKING, Any
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
9
|
from dateutil.relativedelta import relativedelta
|
|
10
10
|
from django.apps import apps as django_apps
|
|
@@ -23,9 +23,8 @@ from django.db.models import Count, ProtectedError
|
|
|
23
23
|
from django.urls import reverse
|
|
24
24
|
from django.utils.translation import gettext as _
|
|
25
25
|
|
|
26
|
-
from edc_constants.constants import CLINIC
|
|
26
|
+
from edc_constants.constants import CLINIC, NOT_APPLICABLE, OK
|
|
27
27
|
from edc_constants.constants import ERROR as ERROR_CODE
|
|
28
|
-
from edc_constants.constants import NOT_APPLICABLE, OK
|
|
29
28
|
from edc_dashboard.url_names import url_names
|
|
30
29
|
from edc_form_validators import INVALID_ERROR
|
|
31
30
|
from edc_metadata.constants import CRF, REQUIRED, REQUISITION
|
|
@@ -89,7 +88,7 @@ def get_appointment_model_name() -> str:
|
|
|
89
88
|
return "edc_appointment.appointment"
|
|
90
89
|
|
|
91
90
|
|
|
92
|
-
def get_appointment_model_cls() ->
|
|
91
|
+
def get_appointment_model_cls() -> type[Appointment]:
|
|
93
92
|
return django_apps.get_model(get_appointment_model_name())
|
|
94
93
|
|
|
95
94
|
|
|
@@ -97,7 +96,7 @@ def get_appointment_type_model_name() -> str:
|
|
|
97
96
|
return "edc_appointment.appointmenttype"
|
|
98
97
|
|
|
99
98
|
|
|
100
|
-
def get_appointment_type_model_cls() ->
|
|
99
|
+
def get_appointment_type_model_cls() -> type[AppointmentType]:
|
|
101
100
|
return django_apps.get_model(get_appointment_type_model_name())
|
|
102
101
|
|
|
103
102
|
|
|
@@ -369,7 +368,6 @@ def delete_appointment_in_sequence(appointment: Any, from_post_delete=None) -> N
|
|
|
369
368
|
schedule_name=appointment.schedule_name,
|
|
370
369
|
visit_code=appointment.visit_code,
|
|
371
370
|
)
|
|
372
|
-
return None
|
|
373
371
|
|
|
374
372
|
|
|
375
373
|
def update_appt_status(appointment: Appointment, save: bool | None = None):
|
|
@@ -383,14 +381,13 @@ def update_appt_status(appointment: Appointment, save: bool | None = None):
|
|
|
383
381
|
pass
|
|
384
382
|
elif not appointment.related_visit:
|
|
385
383
|
appointment.appt_status = NEW_APPT
|
|
384
|
+
elif (
|
|
385
|
+
appointment.crf_metadata_required_exists
|
|
386
|
+
or appointment.requisition_metadata_required_exists
|
|
387
|
+
):
|
|
388
|
+
appointment.appt_status = INCOMPLETE_APPT
|
|
386
389
|
else:
|
|
387
|
-
|
|
388
|
-
appointment.crf_metadata_required_exists
|
|
389
|
-
or appointment.requisition_metadata_required_exists
|
|
390
|
-
):
|
|
391
|
-
appointment.appt_status = INCOMPLETE_APPT
|
|
392
|
-
else:
|
|
393
|
-
appointment.appt_status = COMPLETE_APPT
|
|
390
|
+
appointment.appt_status = COMPLETE_APPT
|
|
394
391
|
if save:
|
|
395
392
|
appointment.save_base(update_fields=["appt_status"])
|
|
396
393
|
appointment.refresh_from_db()
|
|
@@ -578,7 +575,7 @@ def appt_datetime_in_next_window_adjusted_for_gap(
|
|
|
578
575
|
in_window = False
|
|
579
576
|
gap_days = get_window_gap_days(appointment)
|
|
580
577
|
max_gap = get_max_window_gap_to_lower(appointment)
|
|
581
|
-
gap_days =
|
|
578
|
+
gap_days = min(gap_days, max_gap)
|
|
582
579
|
if gap_days > 0:
|
|
583
580
|
next_lower_datetime = (
|
|
584
581
|
appointment.next.timepoint_datetime
|
|
@@ -639,22 +636,21 @@ def get_appointment_by_datetime(
|
|
|
639
636
|
f"Date falls in a `window period gap` between {appointment.visit_code} "
|
|
640
637
|
f"and {appointment.next.visit_code}. Got {dt}."
|
|
641
638
|
)
|
|
642
|
-
|
|
639
|
+
if (
|
|
643
640
|
in_gap
|
|
644
641
|
and in_next_window_adjusted
|
|
645
642
|
and appointment.next.visit.add_window_gap_to_lower
|
|
646
643
|
):
|
|
647
644
|
appointment = appointment.next
|
|
648
645
|
break
|
|
649
|
-
|
|
646
|
+
if (
|
|
650
647
|
in_gap
|
|
651
648
|
and not in_next_window_adjusted
|
|
652
649
|
and appointment.next.visit.add_window_gap_to_lower
|
|
653
650
|
):
|
|
654
651
|
appointment = None
|
|
655
652
|
break
|
|
656
|
-
|
|
657
|
-
appointment = appointment.next
|
|
653
|
+
appointment = appointment.next
|
|
658
654
|
else:
|
|
659
655
|
break
|
|
660
656
|
return appointment
|
|
@@ -674,27 +670,26 @@ def reset_appointment(appointment: Appointment, **kwargs):
|
|
|
674
670
|
raise AppointmentAlreadyStarted(
|
|
675
671
|
f"Unable to reset. Appointment already started. Got {appointment}."
|
|
676
672
|
)
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
673
|
+
defaults = dict(
|
|
674
|
+
appt_status=appointment._meta.get_field("appt_status").default,
|
|
675
|
+
appt_timing=appointment._meta.get_field("appt_timing").default,
|
|
676
|
+
appt_type=None,
|
|
677
|
+
appt_type_other=None,
|
|
678
|
+
appt_datetime=appointment.timepoint_datetime,
|
|
679
|
+
comment="",
|
|
680
|
+
)
|
|
681
|
+
defaults.update(**kwargs)
|
|
682
|
+
for k, v in defaults.items():
|
|
683
|
+
try:
|
|
684
|
+
related_model = get_model_from_relation(appointment._meta.get_field(k))
|
|
685
|
+
except NotRelationField:
|
|
686
|
+
setattr(appointment, k, v)
|
|
687
|
+
else:
|
|
688
688
|
try:
|
|
689
|
-
|
|
690
|
-
except
|
|
691
|
-
setattr(appointment, k,
|
|
692
|
-
|
|
693
|
-
try:
|
|
694
|
-
setattr(appointment, k, related_model.objects.get(name=v))
|
|
695
|
-
except ObjectDoesNotExist:
|
|
696
|
-
setattr(appointment, k, None)
|
|
697
|
-
appointment.save_base(update_fields=[*defaults.keys()])
|
|
689
|
+
setattr(appointment, k, related_model.objects.get(name=v))
|
|
690
|
+
except ObjectDoesNotExist:
|
|
691
|
+
setattr(appointment, k, None)
|
|
692
|
+
appointment.save_base(update_fields=[*defaults.keys()])
|
|
698
693
|
|
|
699
694
|
|
|
700
695
|
def skip_appointment(appointment: Appointment, comment: str | None = None):
|
|
@@ -705,14 +700,13 @@ def skip_appointment(appointment: Appointment, comment: str | None = None):
|
|
|
705
700
|
raise AppointmentAlreadyStarted(
|
|
706
701
|
f"Unable to skip. Appointment already started. Got {appointment}."
|
|
707
702
|
)
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
)
|
|
703
|
+
reset_appointment(
|
|
704
|
+
appointment,
|
|
705
|
+
appt_status=SKIPPED_APPT,
|
|
706
|
+
appt_timing=NOT_APPLICABLE,
|
|
707
|
+
appt_type=NOT_APPLICABLE,
|
|
708
|
+
comment=comment,
|
|
709
|
+
)
|
|
716
710
|
|
|
717
711
|
|
|
718
712
|
def get_unscheduled_appointment_url(appointment: Appointment = None) -> str:
|
|
@@ -820,7 +814,7 @@ def validate_date_is_on_clinic_day(
|
|
|
820
814
|
raise raise_validation_error(
|
|
821
815
|
{"appt_date": "Cannot be equal to the report datetime"}, INVALID_ERROR
|
|
822
816
|
)
|
|
823
|
-
|
|
817
|
+
if appt_date <= report_date:
|
|
824
818
|
raise raise_validation_error(
|
|
825
819
|
{"appt_date": "Cannot be before the report datetime"}, INVALID_ERROR
|
|
826
820
|
)
|
|
@@ -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
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
7
|
from django.apps import apps as django_apps
|
|
@@ -24,7 +24,7 @@ __all__ = ["AppointmentButton"]
|
|
|
24
24
|
class AppointmentButton(DashboardModelButton):
|
|
25
25
|
model_obj: Appointment = None
|
|
26
26
|
colors: tuple[str, str, str] = field(default=(3 * ("default",)))
|
|
27
|
-
model_cls:
|
|
27
|
+
model_cls: type[Appointment] = field(default=None, init=False)
|
|
28
28
|
appointment: Appointment = field(default=None, init=False)
|
|
29
29
|
|
|
30
30
|
def __post_init__(self):
|
|
@@ -33,9 +33,7 @@ class AppointmentButton(DashboardModelButton):
|
|
|
33
33
|
@property
|
|
34
34
|
def disabled(self) -> str:
|
|
35
35
|
disabled = "disabled"
|
|
36
|
-
if self.model_obj.appt_status == IN_PROGRESS_APPT and self.perms.change
|
|
37
|
-
disabled = ""
|
|
38
|
-
elif (
|
|
36
|
+
if (self.model_obj.appt_status == IN_PROGRESS_APPT and self.perms.change) or (
|
|
39
37
|
self.model_obj.appt_status != NEW_APPT
|
|
40
38
|
and not self.perms.add
|
|
41
39
|
and not self.perms.change
|
edc_auth/admin/role_admin.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Tuple
|
|
2
|
-
|
|
3
1
|
from django.contrib import admin
|
|
4
2
|
from django.utils.html import format_html
|
|
5
3
|
from django.utils.safestring import mark_safe
|
|
@@ -14,15 +12,15 @@ from ..models import Role
|
|
|
14
12
|
class RoleAdmin(TemplatesModelAdminMixin, admin.ModelAdmin):
|
|
15
13
|
fieldsets = ((None, ({"fields": ("display_name", "name", "display_index", "groups")})),)
|
|
16
14
|
|
|
17
|
-
list_display_links:
|
|
15
|
+
list_display_links: tuple[str, ...] = ("display_name", "group_list")
|
|
18
16
|
|
|
19
|
-
list_display:
|
|
17
|
+
list_display: tuple[str, ...] = ("display_name", "name", "group_list")
|
|
20
18
|
|
|
21
|
-
filter_horizontal:
|
|
19
|
+
filter_horizontal: tuple[str, ...] = ("groups",)
|
|
22
20
|
|
|
23
|
-
search_fields:
|
|
21
|
+
search_fields: tuple[str, ...] = ("display_name", "name", "groups__name")
|
|
24
22
|
|
|
25
|
-
ordering:
|
|
23
|
+
ordering: tuple[str, ...] = ("display_index", "display_name")
|
|
26
24
|
|
|
27
25
|
list_filter = ("groups__name",)
|
|
28
26
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Dict, List
|
|
2
|
-
|
|
3
1
|
from ..constants import (
|
|
4
2
|
ACCOUNT_MANAGER,
|
|
5
3
|
ACCOUNT_MANAGER_ROLE,
|
|
@@ -20,7 +18,7 @@ from ..constants import (
|
|
|
20
18
|
)
|
|
21
19
|
|
|
22
20
|
# Format {ROLE_NAME: [GROUP_NAME, GROUP_NAME, ...]
|
|
23
|
-
default_roles:
|
|
21
|
+
default_roles: dict[str, list[str]] = {
|
|
24
22
|
ACCOUNT_MANAGER_ROLE: [ACCOUNT_MANAGER],
|
|
25
23
|
AUDITOR_ROLE: [
|
|
26
24
|
AUDITOR,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Callable
|
|
5
5
|
|
|
6
6
|
from django.apps import apps as django_apps
|
|
7
7
|
from django.conf import settings
|
|
@@ -89,9 +89,8 @@ class AuthUpdater:
|
|
|
89
89
|
for func in pre_updates:
|
|
90
90
|
sys.stdout.write(f" * {func.__name__}\n")
|
|
91
91
|
func(self)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
sys.stdout.write(" * nothing to do\n")
|
|
92
|
+
elif self.verbose:
|
|
93
|
+
sys.stdout.write(" * nothing to do\n")
|
|
95
94
|
if self.verbose:
|
|
96
95
|
sys.stdout.write(" Done.\n")
|
|
97
96
|
|
|
@@ -103,9 +102,8 @@ class AuthUpdater:
|
|
|
103
102
|
for app_label, func in post_updates:
|
|
104
103
|
sys.stdout.write(f" * {func.__name__}({app_label})\n")
|
|
105
104
|
func(self, app_label)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
sys.stdout.write(" * nothing to do\n")
|
|
105
|
+
elif self.verbose:
|
|
106
|
+
sys.stdout.write(" * nothing to do\n")
|
|
109
107
|
if self.verbose:
|
|
110
108
|
sys.stdout.write(" Done.\n")
|
|
111
109
|
|