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.
- {clinicedc-2.0.4.dist-info → clinicedc-2.0.6.dist-info}/METADATA +41 -45
- {clinicedc-2.0.4.dist-info → clinicedc-2.0.6.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 +1 -1
- 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 -10
- 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.4.dist-info → clinicedc-2.0.6.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.4.dist-info → clinicedc-2.0.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from django.apps import apps as django_apps
|
|
7
7
|
from django.conf import settings
|
|
@@ -41,7 +41,7 @@ if TYPE_CHECKING:
|
|
|
41
41
|
class RelatedVisitModel(SiteModelMixin, Base, BaseUuidModel):
|
|
42
42
|
pass
|
|
43
43
|
|
|
44
|
-
class OnScheduleLikeModel(OnScheduleModelMixin): ...
|
|
44
|
+
class OnScheduleLikeModel(OnScheduleModelMixin): ...
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class SubjectSchedule:
|
|
@@ -80,19 +80,19 @@ class SubjectSchedule:
|
|
|
80
80
|
return f"{self.subject_identifier} {self.visit_schedule_name}.{self.schedule_name}"
|
|
81
81
|
|
|
82
82
|
@property
|
|
83
|
-
def onschedule_model_cls(self) ->
|
|
83
|
+
def onschedule_model_cls(self) -> type[OnSchedule]:
|
|
84
84
|
return django_apps.get_model(self.onschedule_model)
|
|
85
85
|
|
|
86
86
|
@property
|
|
87
|
-
def offschedule_model_cls(self) ->
|
|
87
|
+
def offschedule_model_cls(self) -> type[OffSchedule]:
|
|
88
88
|
return django_apps.get_model(self.offschedule_model)
|
|
89
89
|
|
|
90
90
|
@property
|
|
91
|
-
def history_model_cls(self) ->
|
|
91
|
+
def history_model_cls(self) -> type[SubjectScheduleHistory]:
|
|
92
92
|
return django_apps.get_model(self.history_model)
|
|
93
93
|
|
|
94
94
|
@property
|
|
95
|
-
def appointment_model_cls(self) ->
|
|
95
|
+
def appointment_model_cls(self) -> type[Appointment]:
|
|
96
96
|
return django_apps.get_model(self.appointment_model)
|
|
97
97
|
|
|
98
98
|
def put_on_schedule(
|
|
@@ -397,4 +397,3 @@ class SubjectSchedule:
|
|
|
397
397
|
f"Got '{self.subject_identifier}' was taken "
|
|
398
398
|
f"off this schedule on '{formatted_offschedule_datetime}'."
|
|
399
399
|
)
|
|
400
|
-
return None
|
|
@@ -86,7 +86,7 @@ def check_onschedule_exists_in_subject_schedule_history(app_configs, **kwargs) -
|
|
|
86
86
|
for visit_schedule in site_visit_schedules.visit_schedules.values():
|
|
87
87
|
for schedule in visit_schedule.schedules.values():
|
|
88
88
|
try:
|
|
89
|
-
onschedule_model_cls =
|
|
89
|
+
onschedule_model_cls = schedule.onschedule_model_cls
|
|
90
90
|
except LookupError:
|
|
91
91
|
pass
|
|
92
92
|
else:
|
edc_visit_schedule/visit/crf.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Type
|
|
4
|
-
|
|
5
3
|
from django.apps import apps as django_apps
|
|
6
4
|
from django.db import models
|
|
7
5
|
|
|
@@ -38,9 +36,7 @@ class Crf:
|
|
|
38
36
|
)
|
|
39
37
|
|
|
40
38
|
def __repr__(self) -> str:
|
|
41
|
-
return (
|
|
42
|
-
f"{self.__class__.__name__}({self.show_order}, " f"{self.model}, {self.required})"
|
|
43
|
-
)
|
|
39
|
+
return f"{self.__class__.__name__}({self.show_order}, {self.model}, {self.required})"
|
|
44
40
|
|
|
45
41
|
def __str__(self) -> str:
|
|
46
42
|
required = "Required" if self.required else ""
|
|
@@ -57,11 +53,11 @@ class Crf:
|
|
|
57
53
|
except LookupError as e:
|
|
58
54
|
raise CrfLookupError(e) from e
|
|
59
55
|
|
|
60
|
-
def get_model_cls(self) ->
|
|
56
|
+
def get_model_cls(self) -> type[models.Model]:
|
|
61
57
|
return self.model_cls
|
|
62
58
|
|
|
63
59
|
@property
|
|
64
|
-
def model_cls(self) ->
|
|
60
|
+
def model_cls(self) -> type[models.Model]:
|
|
65
61
|
return django_apps.get_model(self.model)
|
|
66
62
|
|
|
67
63
|
@property
|
|
@@ -20,7 +20,7 @@ class Requisition(Crf):
|
|
|
20
20
|
if not self.panel.requisition_model:
|
|
21
21
|
raise RequisitionError(
|
|
22
22
|
f"Invalid requisition model. Got None. "
|
|
23
|
-
f"See {
|
|
23
|
+
f"See {panel!r}. "
|
|
24
24
|
f"Was the panel referred to by this schedule's requisition "
|
|
25
25
|
f"added to a lab profile and registered with site_labs?"
|
|
26
26
|
)
|
|
@@ -67,5 +67,5 @@ class Requisition(Crf):
|
|
|
67
67
|
for panel in panels:
|
|
68
68
|
if panel.name not in lab_profile.panels:
|
|
69
69
|
raise ScheduledRequisitionError(
|
|
70
|
-
f"Panel does not exist in lab profiles.
|
|
70
|
+
f"Panel does not exist in lab profiles. Got {panel!r}"
|
|
71
71
|
)
|
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
from django.apps import apps as django_apps
|
|
8
8
|
|
|
9
9
|
from edc_facility.utils import get_default_facility_name, get_facility
|
|
10
|
-
from edc_utils import get_utcnow,
|
|
10
|
+
from edc_utils import get_utcnow, to_local
|
|
11
11
|
|
|
12
12
|
from .crf_collection import CrfCollection
|
|
13
13
|
from .forms_collection import FormsCollection
|
|
@@ -73,14 +73,14 @@ class VisitDate:
|
|
|
73
73
|
|
|
74
74
|
@base.setter
|
|
75
75
|
def base(self, dt: datetime = None):
|
|
76
|
-
self._base =
|
|
76
|
+
self._base = to_local(dt)
|
|
77
77
|
self._lower, self._upper = self._window_period.get_window(dt=self._base)
|
|
78
78
|
|
|
79
79
|
@property
|
|
80
80
|
def lower(self) -> datetime:
|
|
81
81
|
if not self.base:
|
|
82
82
|
raise BaseDatetimeNotSet(
|
|
83
|
-
"Base datetime is None, set the base datetime
|
|
83
|
+
"Base datetime is None, set the base datetime before accessing attr lower"
|
|
84
84
|
)
|
|
85
85
|
return self._lower
|
|
86
86
|
|
|
@@ -88,7 +88,7 @@ class VisitDate:
|
|
|
88
88
|
def upper(self) -> datetime:
|
|
89
89
|
if not self.base:
|
|
90
90
|
raise BaseDatetimeNotSet(
|
|
91
|
-
"Base datetime is None, set the base datetime
|
|
91
|
+
"Base datetime is None, set the base datetime before accessing attr upper"
|
|
92
92
|
)
|
|
93
93
|
return self._upper
|
|
94
94
|
|
|
@@ -159,8 +159,7 @@ class Visit:
|
|
|
159
159
|
self.grouping = grouping
|
|
160
160
|
if not code or isinstance(code, int) or not re.match(self.code_regex, code):
|
|
161
161
|
raise VisitCodeError(f"Invalid visit code. Got '{code}'")
|
|
162
|
-
|
|
163
|
-
self.code = code # unique
|
|
162
|
+
self.code = code # unique
|
|
164
163
|
self.dates = self.visit_date_cls(
|
|
165
164
|
rlower=rlower,
|
|
166
165
|
rupper=rupper,
|
|
@@ -272,7 +271,7 @@ class Visit:
|
|
|
272
271
|
|
|
273
272
|
@timepoint_datetime.setter
|
|
274
273
|
def timepoint_datetime(self, dt=None):
|
|
275
|
-
self.dates.base =
|
|
274
|
+
self.dates.base = to_local(dt)
|
|
276
275
|
|
|
277
276
|
def to_dict(self):
|
|
278
277
|
return dict(
|
|
@@ -3,12 +3,12 @@ from __future__ import annotations
|
|
|
3
3
|
from collections import namedtuple
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from decimal import Decimal
|
|
6
|
-
from typing import Tuple
|
|
7
6
|
from zoneinfo import ZoneInfo
|
|
8
7
|
|
|
9
8
|
from dateutil.relativedelta import relativedelta
|
|
9
|
+
from django.conf import settings
|
|
10
10
|
|
|
11
|
-
from edc_utils import
|
|
11
|
+
from edc_utils import to_local
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class WindowPeriod:
|
|
@@ -30,21 +30,21 @@ class WindowPeriod:
|
|
|
30
30
|
if self.timepoint == base_timepoint:
|
|
31
31
|
self.no_floor = True
|
|
32
32
|
|
|
33
|
-
def get_window(self, dt=None) ->
|
|
34
|
-
"""Returns a tuple of the lower and upper datetimes in
|
|
33
|
+
def get_window(self, dt=None) -> tuple[datetime, datetime]:
|
|
34
|
+
"""Returns a tuple of the lower and upper datetimes in local time."""
|
|
35
35
|
|
|
36
36
|
dt_floor = (
|
|
37
|
-
|
|
37
|
+
to_local(dt)
|
|
38
38
|
if self.no_floor
|
|
39
39
|
else dt.replace(hour=0, minute=0, second=0, microsecond=0).astimezone(
|
|
40
|
-
ZoneInfo(
|
|
40
|
+
ZoneInfo(settings.TIME_ZONE)
|
|
41
41
|
)
|
|
42
42
|
)
|
|
43
43
|
dt_ceil = (
|
|
44
|
-
|
|
44
|
+
to_local(dt)
|
|
45
45
|
if self.no_ceil
|
|
46
46
|
else dt.replace(hour=23, minute=59, second=59, microsecond=999999).astimezone(
|
|
47
|
-
ZoneInfo(
|
|
47
|
+
ZoneInfo(settings.TIME_ZONE)
|
|
48
48
|
)
|
|
49
49
|
)
|
|
50
50
|
window = namedtuple("Window", ["lower", "upper"])
|
|
@@ -22,9 +22,7 @@ class SchedulesCollection(OrderedCollection):
|
|
|
22
22
|
if model:
|
|
23
23
|
model = model.lower()
|
|
24
24
|
for item in self.values():
|
|
25
|
-
if item.onschedule_model == model:
|
|
26
|
-
schedule = item
|
|
27
|
-
elif item.offschedule_model == model:
|
|
25
|
+
if item.onschedule_model == model or item.offschedule_model == model:
|
|
28
26
|
schedule = item
|
|
29
27
|
if schedule:
|
|
30
28
|
break
|
|
@@ -32,8 +30,7 @@ class SchedulesCollection(OrderedCollection):
|
|
|
32
30
|
schedule = self.get(schedule_name)
|
|
33
31
|
if not schedule:
|
|
34
32
|
raise SchedulesCollectionError(
|
|
35
|
-
f"Schedule does not exist. Using model={model}, "
|
|
36
|
-
f"schedule_name={schedule_name}."
|
|
33
|
+
f"Schedule does not exist. Using model={model}, schedule_name={schedule_name}."
|
|
37
34
|
)
|
|
38
35
|
return schedule
|
|
39
36
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
|
|
3
1
|
from edc_action_item.action import Action
|
|
4
2
|
from edc_action_item.action_with_notification import ActionWithNotification
|
|
5
3
|
from edc_constants.constants import HIGH_PRIORITY, YES
|
|
@@ -9,16 +7,16 @@ from .constants import VISIT_MISSED_ACTION
|
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
class VisitMissedAction(ActionWithNotification):
|
|
12
|
-
name
|
|
13
|
-
display_name
|
|
14
|
-
notification_display_name
|
|
15
|
-
parent_action_names
|
|
16
|
-
show_link_to_changelist
|
|
17
|
-
priority
|
|
18
|
-
loss_to_followup_action_name
|
|
10
|
+
name = VISIT_MISSED_ACTION
|
|
11
|
+
display_name = "Submit Missed Visit"
|
|
12
|
+
notification_display_name = " Submit Missed Visit"
|
|
13
|
+
parent_action_names = ()
|
|
14
|
+
show_link_to_changelist = True
|
|
15
|
+
priority = HIGH_PRIORITY
|
|
16
|
+
loss_to_followup_action_name = LTFU_ACTION
|
|
19
17
|
|
|
20
|
-
reference_model
|
|
21
|
-
admin_site_name
|
|
18
|
+
reference_model = None # "inte_subject.subjectvisitmissed"
|
|
19
|
+
admin_site_name = None # "inte_prn_admin"
|
|
22
20
|
|
|
23
21
|
def get_loss_to_followup_action_name(self) -> str:
|
|
24
22
|
return self.loss_to_followup_action_name
|
|
@@ -26,8 +24,8 @@ class VisitMissedAction(ActionWithNotification):
|
|
|
26
24
|
def is_ltfu(self) -> bool:
|
|
27
25
|
return self.reference_obj.ltfu == YES
|
|
28
26
|
|
|
29
|
-
def get_next_actions(self) ->
|
|
30
|
-
next_actions:
|
|
27
|
+
def get_next_actions(self) -> list[Action]:
|
|
28
|
+
next_actions: list[Action] = []
|
|
31
29
|
next_actions = self.append_to_next_if_required(
|
|
32
30
|
next_actions=next_actions,
|
|
33
31
|
action_name=self.get_loss_to_followup_action_name(),
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from copy import deepcopy
|
|
4
4
|
from datetime import datetime
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
6
|
from zoneinfo import ZoneInfo
|
|
7
7
|
|
|
8
8
|
from django import forms
|
|
@@ -144,14 +144,14 @@ class VisitFormValidator(WindowPeriodFormValidatorMixin, FormValidator):
|
|
|
144
144
|
visit_schedule_name=self.instance.visit_schedule_name,
|
|
145
145
|
schedule_name=self.instance.schedule_name,
|
|
146
146
|
)
|
|
147
|
-
if
|
|
147
|
+
if self.instance.id:
|
|
148
148
|
qs = qs.exclude(id=self.instance.id)
|
|
149
149
|
if qs.count() > 1:
|
|
150
150
|
raise self.raise_validation_error(
|
|
151
151
|
{"report_datetime": "Visit report already exist for this date (M)"},
|
|
152
152
|
INVALID_ERROR,
|
|
153
153
|
)
|
|
154
|
-
|
|
154
|
+
if qs.count() == 1:
|
|
155
155
|
raise self.raise_validation_error(
|
|
156
156
|
{
|
|
157
157
|
"report_datetime": (
|
|
@@ -284,8 +284,8 @@ class VisitFormValidator(WindowPeriodFormValidatorMixin, FormValidator):
|
|
|
284
284
|
def metadata_exists_for(
|
|
285
285
|
self,
|
|
286
286
|
entry_status: str = None,
|
|
287
|
-
filter_models:
|
|
288
|
-
exclude_models:
|
|
287
|
+
filter_models: list[str] | None = None,
|
|
288
|
+
exclude_models: list[str] | None = None,
|
|
289
289
|
) -> int:
|
|
290
290
|
"""Returns True if metadata exists for this visit for
|
|
291
291
|
the given entry_status.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from django.conf import settings
|
|
6
6
|
from django.contrib.admin.utils import NotRelationField, get_model_from_relation
|
|
@@ -57,7 +57,7 @@ class VisitMethodsModelMixin(models.Model):
|
|
|
57
57
|
return related_visit_field_cls
|
|
58
58
|
|
|
59
59
|
@classmethod
|
|
60
|
-
def related_visit_model_cls(cls) ->
|
|
60
|
+
def related_visit_model_cls(cls) -> type[VisitModelMixin]:
|
|
61
61
|
"""Returns the 'model' class of the related visit foreign
|
|
62
62
|
key attribute.
|
|
63
63
|
"""
|
|
@@ -109,8 +109,7 @@ class VisitMethodsModelMixin(models.Model):
|
|
|
109
109
|
"Perhaps catch this in the form."
|
|
110
110
|
)
|
|
111
111
|
break
|
|
112
|
-
|
|
113
|
-
related_model = None
|
|
112
|
+
related_model = None
|
|
114
113
|
if not related_model:
|
|
115
114
|
raise ImproperlyConfigured(
|
|
116
115
|
f"Model is missing a FK to a related visit model. See {self.__class__}."
|
|
@@ -55,7 +55,7 @@ class VisitTrackingCrfModelMixin(VisitMethodsModelMixin, CrfScheduleModelMixin,
|
|
|
55
55
|
class Meta:
|
|
56
56
|
abstract = True
|
|
57
57
|
# assuming subject_visit
|
|
58
|
-
indexes =
|
|
58
|
+
indexes = (
|
|
59
59
|
models.Index(fields=["subject_visit", "site", "id"]),
|
|
60
60
|
models.Index(fields=["subject_visit", "report_datetime"]),
|
|
61
|
-
|
|
61
|
+
)
|
|
@@ -58,8 +58,7 @@ class SubjectVisitMissedModelMixin(models.Model):
|
|
|
58
58
|
|
|
59
59
|
contact_attempts_count = models.IntegerField(
|
|
60
60
|
verbose_name=_(
|
|
61
|
-
"Number of attempts made to contact
|
|
62
|
-
"since the expected appointment date"
|
|
61
|
+
"Number of attempts made to contact participantsince the expected appointment date"
|
|
63
62
|
),
|
|
64
63
|
validators=[MinValueValidator(1)],
|
|
65
64
|
help_text=_(
|
|
@@ -112,4 +111,4 @@ class SubjectVisitMissedModelMixin(models.Model):
|
|
|
112
111
|
|
|
113
112
|
class Meta:
|
|
114
113
|
abstract = True
|
|
115
|
-
indexes =
|
|
114
|
+
indexes = (models.Index(fields=["action_identifier", "site", "id"]),)
|
|
@@ -31,7 +31,7 @@ def get_related_visit_model_attr(model_cls) -> str:
|
|
|
31
31
|
f"More than one field is related to the visit model. See {model_cls}. "
|
|
32
32
|
f"Got {attrs}. "
|
|
33
33
|
)
|
|
34
|
-
|
|
34
|
+
if len(attrs) == 0:
|
|
35
35
|
raise RelatedVisitFieldError(
|
|
36
36
|
f"{model_cls} has no related visit model. "
|
|
37
37
|
f"Expected the related visit model to be an instance "
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, Any, Self
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
4
4
|
|
|
5
5
|
from django.db import models
|
|
6
6
|
|
|
@@ -24,7 +24,7 @@ class PreviousVisitModelMixin(models.Model):
|
|
|
24
24
|
* If the visit is the first in the sequence, save() is allowed.
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
-
visit_sequence_cls:
|
|
27
|
+
visit_sequence_cls: type[VisitSequence] = VisitSequence
|
|
28
28
|
|
|
29
29
|
def save(self, *args, **kwargs) -> None:
|
|
30
30
|
self.validate_visit_sequence()
|
|
@@ -116,7 +116,7 @@ class VisitModelMixin(
|
|
|
116
116
|
"report_datetime", # implies one visit per day!
|
|
117
117
|
),
|
|
118
118
|
)
|
|
119
|
-
indexes =
|
|
119
|
+
indexes = (
|
|
120
120
|
models.Index(
|
|
121
121
|
fields=[
|
|
122
122
|
"subject_identifier",
|
|
@@ -126,5 +126,5 @@ class VisitModelMixin(
|
|
|
126
126
|
"visit_code_sequence",
|
|
127
127
|
"report_datetime",
|
|
128
128
|
]
|
|
129
|
-
)
|
|
130
|
-
|
|
129
|
+
),
|
|
130
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
6
|
from edc_consent.fieldsets import REQUIRES_CONSENT_FIELDS
|
|
@@ -42,8 +42,10 @@ class CrfModelAdminMixin:
|
|
|
42
42
|
"visit_code",
|
|
43
43
|
"visit_reason",
|
|
44
44
|
)
|
|
45
|
-
return
|
|
46
|
-
|
|
45
|
+
return (
|
|
46
|
+
*fields_first,
|
|
47
|
+
*[f for f in list_display if f not in fields_first],
|
|
48
|
+
"__str__",
|
|
47
49
|
)
|
|
48
50
|
|
|
49
51
|
def get_search_fields(self, request: WSGIRequest) -> tuple[str, ...]:
|
|
@@ -76,7 +78,7 @@ class CrfModelAdminMixin:
|
|
|
76
78
|
return self.model.related_visit_model_attr()
|
|
77
79
|
|
|
78
80
|
@property
|
|
79
|
-
def related_visit_model_cls(self) ->
|
|
81
|
+
def related_visit_model_cls(self) -> type[VisitModelMixin]:
|
|
80
82
|
return self.model.related_visit_model_cls()
|
|
81
83
|
|
|
82
84
|
def related_visit(self, request: WSGIRequest, obj=None) -> VisitModelMixin | None:
|
|
@@ -56,7 +56,7 @@ class VisitModelAdminMixin(DocumentStatusModelAdminMixin):
|
|
|
56
56
|
audit_fieldset_tuple,
|
|
57
57
|
)
|
|
58
58
|
|
|
59
|
-
radio_fields = {
|
|
59
|
+
radio_fields = { # noqa: RUF012
|
|
60
60
|
"reason": admin.VERTICAL,
|
|
61
61
|
"reason_unscheduled": admin.VERTICAL,
|
|
62
62
|
"reason_missed": admin.VERTICAL,
|
|
@@ -79,11 +79,10 @@ class VisitModelAdminMixin(DocumentStatusModelAdminMixin):
|
|
|
79
79
|
def visit_reason(obj=None) -> str:
|
|
80
80
|
if obj.reason != UNSCHEDULED:
|
|
81
81
|
visit_reason = obj.get_reason_display()
|
|
82
|
+
elif obj.reason_unscheduled == OTHER:
|
|
83
|
+
visit_reason = obj.reason_unscheduled_other
|
|
82
84
|
else:
|
|
83
|
-
|
|
84
|
-
visit_reason = obj.reason_unscheduled_other
|
|
85
|
-
else:
|
|
86
|
-
visit_reason = obj.get_reason_unscheduled_display()
|
|
85
|
+
visit_reason = obj.get_reason_unscheduled_display()
|
|
87
86
|
return visit_reason
|
|
88
87
|
|
|
89
88
|
@staticmethod
|
|
@@ -114,7 +113,7 @@ class VisitModelAdminMixin(DocumentStatusModelAdminMixin):
|
|
|
114
113
|
"status",
|
|
115
114
|
"scheduled_data",
|
|
116
115
|
)
|
|
117
|
-
return custom_fields
|
|
116
|
+
return *custom_fields, *tuple(f for f in list_display if f not in custom_fields)
|
|
118
117
|
|
|
119
118
|
def get_list_filter(self, request: WSGIRequest) -> tuple[str, ...]:
|
|
120
119
|
list_filter = super().get_list_filter(request)
|
|
@@ -125,7 +124,7 @@ class VisitModelAdminMixin(DocumentStatusModelAdminMixin):
|
|
|
125
124
|
"reason",
|
|
126
125
|
"require_crfs",
|
|
127
126
|
)
|
|
128
|
-
return custom_fields
|
|
127
|
+
return *custom_fields, *tuple(f for f in list_filter if f not in custom_fields)
|
|
129
128
|
|
|
130
129
|
def get_readonly_fields(self, request, obj=None) -> tuple[str, ...]:
|
|
131
130
|
readonly_fields = super().get_readonly_fields(request, obj=obj)
|
|
@@ -89,11 +89,10 @@ class VisitTrackingCrfModelFormMixin:
|
|
|
89
89
|
if not self.related_visit:
|
|
90
90
|
if self.related_visit_model_attr in self.cleaned_data:
|
|
91
91
|
raise forms.ValidationError({self.related_visit_model_attr: ""})
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
elif self.cleaned_data.get(self.report_datetime_field_attr):
|
|
92
|
+
raise forms.ValidationError(
|
|
93
|
+
f"Field `{self.related_visit_model_attr}` is required (1)."
|
|
94
|
+
)
|
|
95
|
+
if self.cleaned_data.get(self.report_datetime_field_attr):
|
|
97
96
|
try:
|
|
98
97
|
self.crf_date_validator_cls(
|
|
99
98
|
report_datetime_allowance=self.report_datetime_allowance,
|
|
@@ -31,11 +31,11 @@ class VisitTrackingModelFormMixin(SiteModelFormMixin):
|
|
|
31
31
|
raise forms.ValidationError(
|
|
32
32
|
{
|
|
33
33
|
"reason": (
|
|
34
|
-
"Invalid. Appointment is missed.
|
|
34
|
+
"Invalid. Appointment is missed. Expected visit to be missed also."
|
|
35
35
|
)
|
|
36
36
|
}
|
|
37
37
|
)
|
|
38
|
-
|
|
38
|
+
if (
|
|
39
39
|
cleaned_data.get("reason")
|
|
40
40
|
and self.appointment.appt_timing == MISSED_APPT
|
|
41
41
|
and cleaned_data.get("reason") != MISSED_VISIT
|
|
@@ -43,8 +43,7 @@ class VisitTrackingModelFormMixin(SiteModelFormMixin):
|
|
|
43
43
|
raise forms.ValidationError(
|
|
44
44
|
{
|
|
45
45
|
"reason": (
|
|
46
|
-
"Invalid. Appointment is not missed. "
|
|
47
|
-
"Did not expected a missed visit."
|
|
46
|
+
"Invalid. Appointment is not missed. Did not expected a missed visit."
|
|
48
47
|
)
|
|
49
48
|
}
|
|
50
49
|
)
|
|
@@ -27,4 +27,4 @@ class SubjectVisitMissed(
|
|
|
27
27
|
class Meta(CrfModelMixin.Meta, BaseUuidModel.Meta):
|
|
28
28
|
verbose_name = _("Missed Visit Report")
|
|
29
29
|
verbose_name_plural = _("Missed Visit Report")
|
|
30
|
-
indexes = CrfModelMixin.Meta.indexes
|
|
30
|
+
indexes = (*CrfModelMixin.Meta.indexes, *BaseUuidModel.Meta.indexes)
|
edc_visit_tracking/stubs.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from typing import Protocol, TypeVar
|
|
2
|
+
from typing import Protocol, TypeVar
|
|
3
3
|
|
|
4
4
|
from django.db import models
|
|
5
5
|
|
|
@@ -7,15 +7,15 @@ from edc_model.stubs import ModelMetaStub
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class RelatedVisitModelStub(Protocol):
|
|
10
|
-
report_datetime:
|
|
11
|
-
subject_identifier:
|
|
10
|
+
report_datetime: datetime | models.DateTimeField
|
|
11
|
+
subject_identifier: str | models.CharField
|
|
12
12
|
reason: str
|
|
13
13
|
reason_unscheduled: str
|
|
14
14
|
reason_unscheduled_other: str
|
|
15
|
-
visit_code:
|
|
16
|
-
visit_code_sequence:
|
|
17
|
-
visit_schedule:
|
|
18
|
-
schedule:
|
|
15
|
+
visit_code: str | models.CharField
|
|
16
|
+
visit_code_sequence: int | models.IntegerField
|
|
17
|
+
visit_schedule: str | models.CharField
|
|
18
|
+
schedule: str | models.CharField
|
|
19
19
|
study_status: str
|
|
20
20
|
require_crfs: bool
|
|
21
21
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import Any, Generic, Protocol, Tuple,
|
|
4
|
+
from typing import Any, Generic, Protocol, Tuple, TypeVar
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
7
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
@@ -24,23 +24,23 @@ _Self = TypeVar("_Self", bound=Model)
|
|
|
24
24
|
|
|
25
25
|
class ModelBase(type):
|
|
26
26
|
@property
|
|
27
|
-
def objects(cls: type[_Self]) -> BaseManager[_Self]: ...
|
|
27
|
+
def objects(cls: type[_Self]) -> BaseManager[_Self]: ...
|
|
28
28
|
|
|
29
29
|
@property
|
|
30
|
-
def _default_manager(cls: type[_Self]) -> BaseManager[_Self]: ...
|
|
30
|
+
def _default_manager(cls: type[_Self]) -> BaseManager[_Self]: ...
|
|
31
31
|
|
|
32
32
|
@property
|
|
33
|
-
def _base_manager(cls: type[_Self]) -> BaseManager[_Self]: ...
|
|
33
|
+
def _base_manager(cls: type[_Self]) -> BaseManager[_Self]: ...
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class Options(Generic[_M]):
|
|
37
|
-
def label_lower(self) -> str: ...
|
|
37
|
+
def label_lower(self) -> str: ...
|
|
38
38
|
|
|
39
39
|
def fields(self) -> Tuple[Field]: ... # noqa
|
|
40
40
|
|
|
41
41
|
def get_fields(
|
|
42
|
-
self, include_parents: bool = ..., include_hidden: bool = ...
|
|
43
|
-
) -> list[Field | ForeignObjectRel | GenericForeignKey]: ...
|
|
42
|
+
self, include_parents: bool = ..., include_hidden: bool = ...
|
|
43
|
+
) -> list[Field | ForeignObjectRel | GenericForeignKey]: ...
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class SiteFieldsProtocol(Protocol):
|
|
@@ -49,11 +49,11 @@ class SiteFieldsProtocol(Protocol):
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
class RelatedVisitProtocol(VisitScheduleFieldsProtocol, Protocol):
|
|
52
|
-
metadata_cls:
|
|
53
|
-
metadata_destroyer_cls:
|
|
54
|
-
metadata_rule_evaluator_cls:
|
|
52
|
+
metadata_cls: type[Metadata]
|
|
53
|
+
metadata_destroyer_cls: type[Destroyer]
|
|
54
|
+
metadata_rule_evaluator_cls: type[MetadataRuleEvaluator]
|
|
55
55
|
|
|
56
|
-
class Meta: ...
|
|
56
|
+
class Meta: ...
|
|
57
57
|
|
|
58
58
|
_meta: Options[Any]
|
|
59
59
|
|