clinicedc 2.0.4__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.4.dist-info → clinicedc-2.0.5.dist-info}/METADATA +41 -45
- {clinicedc-2.0.4.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 +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 -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.4.dist-info → clinicedc-2.0.5.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.4.dist-info → clinicedc-2.0.5.dist-info}/licenses/LICENSE +0 -0
edc_action_item/action.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
from django.apps import apps as django_apps
|
|
7
8
|
from django.conf import settings
|
|
@@ -27,6 +28,29 @@ logger = logging.getLogger(__name__)
|
|
|
27
28
|
style = color_style()
|
|
28
29
|
|
|
29
30
|
REFERENCE_MODEL_ERROR_CODE = "reference_model"
|
|
31
|
+
ACTION_CLASS_MISMATCH = (
|
|
32
|
+
"Action class mismatch for given ActionItem. "
|
|
33
|
+
"{action_item_action_cls} incorrectly passed "
|
|
34
|
+
"to Action {action_cls}."
|
|
35
|
+
)
|
|
36
|
+
ACTION_GOT_UNLISTED_PARENT_ACTION_ITEM = (
|
|
37
|
+
"Action class received an unlisted parent_action_item. "
|
|
38
|
+
"Expected one of {parent_action_names}. "
|
|
39
|
+
"Got '{parent_action_cls_name}' "
|
|
40
|
+
"subject_identifier `{subject_identifier}`."
|
|
41
|
+
"See Action {action_cls}."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
ACTION_EXPECTS_RELATED_ACTION_ITEM = (
|
|
45
|
+
"Action class expects a related_action_item. "
|
|
46
|
+
"related_reference_fk_attr=`{related_reference_fk_attr}`. "
|
|
47
|
+
"Got None for action based on action_item `{action_item}`. "
|
|
48
|
+
"See Action {action_cls}."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
ACTION_ITEM_DOES_NOT_EXIST = "{err} Got action_identifier={action_identifier}."
|
|
52
|
+
ACTION_ITEM_GET_OR_CREATE_FAILED = "Unable to get or create ActionItem. Got {opts}."
|
|
53
|
+
ACTION_NAME_IS_NONE = "Action name cannot be None. See {action_cls}."
|
|
30
54
|
|
|
31
55
|
|
|
32
56
|
class Action:
|
|
@@ -45,7 +69,7 @@ class Action:
|
|
|
45
69
|
help_text: str = None
|
|
46
70
|
instructions = None
|
|
47
71
|
name: str = None
|
|
48
|
-
parent_action_names:
|
|
72
|
+
parent_action_names: tuple[str] | None = None
|
|
49
73
|
enforce_parent_action_names: bool = True
|
|
50
74
|
priority: bool = None
|
|
51
75
|
reference_model: str = None
|
|
@@ -63,23 +87,23 @@ class Action:
|
|
|
63
87
|
action_type_model: str = "edc_action_item.actiontype"
|
|
64
88
|
next_actions: list[str] | None = None # a list of Action classes which may include 'self'
|
|
65
89
|
|
|
66
|
-
def __init__(
|
|
90
|
+
def __init__( # noqa: PLR0913
|
|
67
91
|
self,
|
|
68
92
|
action_item: ActionItemStub = None,
|
|
69
93
|
reference_obj: models.Model = None,
|
|
70
|
-
subject_identifier: str = None,
|
|
71
|
-
action_identifier: str = None,
|
|
72
|
-
parent_action_item:
|
|
73
|
-
related_action_item:
|
|
74
|
-
using:
|
|
75
|
-
readonly:
|
|
94
|
+
subject_identifier: str | None = None,
|
|
95
|
+
action_identifier: str | None = None,
|
|
96
|
+
parent_action_item: ActionItemStub | None = None,
|
|
97
|
+
related_action_item: ActionItemStub | None = None,
|
|
98
|
+
using: str | None = None,
|
|
99
|
+
readonly: bool | None = None,
|
|
76
100
|
skip_get_current_site: bool | None = None,
|
|
77
101
|
site_id: int | None = None,
|
|
78
102
|
) -> None:
|
|
79
103
|
self._action_item = action_item
|
|
80
104
|
self._reference_obj = reference_obj
|
|
81
105
|
|
|
82
|
-
self.parent_action_names = self.parent_action_names or
|
|
106
|
+
self.parent_action_names = self.parent_action_names or ()
|
|
83
107
|
|
|
84
108
|
self.messages: dict = {}
|
|
85
109
|
|
|
@@ -94,9 +118,10 @@ class Action:
|
|
|
94
118
|
|
|
95
119
|
if self.action_item.action_cls != self.__class__:
|
|
96
120
|
raise ActionError(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
121
|
+
ACTION_CLASS_MISMATCH.format(
|
|
122
|
+
action_item_action_cls=self.action_item.action_cls,
|
|
123
|
+
action_cls=self.__class__,
|
|
124
|
+
),
|
|
100
125
|
code="class type mismatch",
|
|
101
126
|
)
|
|
102
127
|
self.action_identifier = self.action_item.action_identifier
|
|
@@ -112,18 +137,20 @@ class Action:
|
|
|
112
137
|
and self.parent_action_item.action_cls.name not in self.parent_action_names
|
|
113
138
|
):
|
|
114
139
|
raise ActionError(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
140
|
+
ACTION_GOT_UNLISTED_PARENT_ACTION_ITEM.format(
|
|
141
|
+
parent_action_names=self.parent_action_names,
|
|
142
|
+
parent_action_cls_name=self.parent_action_item.action_cls.name,
|
|
143
|
+
subject_identifier=self.subject_identifier,
|
|
144
|
+
action_cls=self.__class__,
|
|
145
|
+
)
|
|
120
146
|
)
|
|
121
147
|
if not self.related_action_item and self.related_reference_fk_attr:
|
|
122
148
|
raise ActionError(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
149
|
+
ACTION_EXPECTS_RELATED_ACTION_ITEM.format(
|
|
150
|
+
related_reference_fk_attr=self.related_reference_fk_attr,
|
|
151
|
+
action_item=self.action_item,
|
|
152
|
+
action_cls=self.__class__,
|
|
153
|
+
)
|
|
127
154
|
)
|
|
128
155
|
|
|
129
156
|
if self.reference_obj and not self.readonly:
|
|
@@ -157,19 +184,18 @@ class Action:
|
|
|
157
184
|
The reference model instance "should" exist if
|
|
158
185
|
action item exists and is CLOSED. If not, re-open.
|
|
159
186
|
"""
|
|
160
|
-
if not self._reference_obj:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
self.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
self.action_item.refresh_from_db()
|
|
187
|
+
if not self._reference_obj and self.action_identifier:
|
|
188
|
+
try:
|
|
189
|
+
self._reference_obj = (
|
|
190
|
+
self.reference_model_cls()
|
|
191
|
+
.objects.using(self.using)
|
|
192
|
+
.get(action_identifier=self.action_identifier)
|
|
193
|
+
)
|
|
194
|
+
except ObjectDoesNotExist:
|
|
195
|
+
if self.action_item and self.action_item.status == CLOSED:
|
|
196
|
+
self.action_item.status = OPEN
|
|
197
|
+
self.action_item.save(update_fields=["status"], using=self.using)
|
|
198
|
+
self.action_item.refresh_from_db()
|
|
173
199
|
return self._reference_obj
|
|
174
200
|
|
|
175
201
|
@property
|
|
@@ -188,8 +214,10 @@ class Action:
|
|
|
188
214
|
)
|
|
189
215
|
except ObjectDoesNotExist as e:
|
|
190
216
|
raise ObjectDoesNotExist(
|
|
191
|
-
|
|
192
|
-
|
|
217
|
+
ACTION_ITEM_DOES_NOT_EXIST.format(
|
|
218
|
+
err=str(e), action_identifier=self.action_identifier
|
|
219
|
+
)
|
|
220
|
+
) from e
|
|
193
221
|
elif self.reference_obj:
|
|
194
222
|
self._action_item = self.reference_obj.action_item
|
|
195
223
|
else:
|
|
@@ -221,10 +249,10 @@ class Action:
|
|
|
221
249
|
parent_action_item=self.parent_action_item,
|
|
222
250
|
related_action_item=self.related_action_item,
|
|
223
251
|
)
|
|
224
|
-
raise ActionError(
|
|
252
|
+
raise ActionError(ACTION_ITEM_GET_OR_CREATE_FAILED.format(opts=opts))
|
|
225
253
|
return self._action_item
|
|
226
254
|
|
|
227
|
-
def _create_new_action_item(self, subject_identifier: str = None, **opts):
|
|
255
|
+
def _create_new_action_item(self, subject_identifier: str | None = None, **opts):
|
|
228
256
|
"""Create a new action item.
|
|
229
257
|
|
|
230
258
|
Called only after checking.
|
|
@@ -254,12 +282,12 @@ class Action:
|
|
|
254
282
|
)
|
|
255
283
|
|
|
256
284
|
@classmethod
|
|
257
|
-
def action_item_model_cls(cls) ->
|
|
285
|
+
def action_item_model_cls(cls) -> type[ActionItem]:
|
|
258
286
|
"""Returns the ActionItem model class."""
|
|
259
287
|
return django_apps.get_model(cls.action_item_model)
|
|
260
288
|
|
|
261
289
|
@classmethod
|
|
262
|
-
def action_type_model_cls(cls) ->
|
|
290
|
+
def action_type_model_cls(cls) -> type[ActionType]:
|
|
263
291
|
"""Returns the ActionType model class."""
|
|
264
292
|
return django_apps.get_model(cls.action_type_model)
|
|
265
293
|
|
|
@@ -282,14 +310,10 @@ class Action:
|
|
|
282
310
|
def as_dict(cls) -> dict:
|
|
283
311
|
"""Returns select class attrs as a dictionary."""
|
|
284
312
|
dct = {k: v for k, v in cls.__dict__.items() if not k.startswith("_")}
|
|
285
|
-
|
|
313
|
+
with suppress(AttributeError):
|
|
286
314
|
dct.update(reference_model=cls.get_reference_model().lower())
|
|
287
|
-
|
|
288
|
-
pass
|
|
289
|
-
try:
|
|
315
|
+
with suppress(AttributeError):
|
|
290
316
|
dct.update(related_reference_model=cls.related_reference_model.lower())
|
|
291
|
-
except AttributeError:
|
|
292
|
-
pass
|
|
293
317
|
dct.update(
|
|
294
318
|
name=cls.name,
|
|
295
319
|
display_name=cls.display_name,
|
|
@@ -305,7 +329,7 @@ class Action:
|
|
|
305
329
|
)
|
|
306
330
|
return dct
|
|
307
331
|
|
|
308
|
-
def get_next_actions(self) ->
|
|
332
|
+
def get_next_actions(self) -> list[str]:
|
|
309
333
|
"""Returns a list of action classes to be created
|
|
310
334
|
again by this model if the first has been closed on post_save.
|
|
311
335
|
"""
|
|
@@ -376,7 +400,7 @@ class Action:
|
|
|
376
400
|
self.reference_obj.action_identifier
|
|
377
401
|
)
|
|
378
402
|
)
|
|
379
|
-
| models.Q(related_action_item=self.reference_obj.action_item)
|
|
403
|
+
| models.Q(related_action_item=self.reference_obj.action_item)
|
|
380
404
|
),
|
|
381
405
|
status=CLOSED,
|
|
382
406
|
)
|
|
@@ -445,12 +469,10 @@ class Action:
|
|
|
445
469
|
Will not append if the ActionItem for the next action
|
|
446
470
|
already exists.
|
|
447
471
|
"""
|
|
448
|
-
|
|
472
|
+
with suppress(AttributeError):
|
|
449
473
|
action_name = action_cls.name
|
|
450
|
-
except AttributeError:
|
|
451
|
-
pass
|
|
452
474
|
if not action_name:
|
|
453
|
-
raise ActionError(
|
|
475
|
+
raise ActionError(ACTION_NAME_IS_NONE.format(action_cls=self))
|
|
454
476
|
next_actions = next_actions or []
|
|
455
477
|
required = True if required is None else required
|
|
456
478
|
opts = dict(
|
|
@@ -491,7 +513,7 @@ class Action:
|
|
|
491
513
|
parent_action_item=parent_action_item,
|
|
492
514
|
status=NEW,
|
|
493
515
|
)
|
|
494
|
-
for index, obj in enumerate(
|
|
516
|
+
for index, obj in enumerate( # noqa: B007
|
|
495
517
|
self.action_item_model_cls().objects.using(self.using).filter(**opts)
|
|
496
518
|
):
|
|
497
519
|
obj.delete(using=self.using)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
|
|
3
1
|
from django.core.exceptions import ObjectDoesNotExist
|
|
4
2
|
|
|
5
3
|
from edc_constants.constants import CLOSED, NEW, OPEN
|
|
@@ -35,7 +33,7 @@ class ActionItemNotification(Notification):
|
|
|
35
33
|
notification_action_name: str = None
|
|
36
34
|
# set by class user, list of field(s) from
|
|
37
35
|
# the action_item.reference_obj
|
|
38
|
-
notification_fields:
|
|
36
|
+
notification_fields: list[str] = []
|
|
39
37
|
|
|
40
38
|
# TODO: not sure what this does?
|
|
41
39
|
notify_on_new: bool = False
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import cast
|
|
2
2
|
|
|
3
3
|
from django.db import models
|
|
4
4
|
|
|
@@ -14,7 +14,7 @@ class ActionWithNotification(Action):
|
|
|
14
14
|
notification_email_to: list[str] = None
|
|
15
15
|
notification_display_name: str = None
|
|
16
16
|
notification_fields: list[str] = None
|
|
17
|
-
notification_super_cls:
|
|
17
|
+
notification_super_cls: type[ActionItemNotification] = ActionItemNotification
|
|
18
18
|
notify_on_changed_reference_obj: models.Model = True
|
|
19
19
|
notify_on_close: bool = False
|
|
20
20
|
notify_on_new: bool = False
|
|
@@ -22,10 +22,10 @@ class ActionWithNotification(Action):
|
|
|
22
22
|
notify_on_open: bool = False
|
|
23
23
|
|
|
24
24
|
@classmethod
|
|
25
|
-
def notification_cls(cls) ->
|
|
25
|
+
def notification_cls(cls) -> type[ActionItemNotification]:
|
|
26
26
|
"""Returns a subclass of ActionItemModelNotification."""
|
|
27
27
|
return cast(
|
|
28
|
-
|
|
28
|
+
type[ActionItemNotification],
|
|
29
29
|
type(
|
|
30
30
|
f"{cls.__name__}Notification",
|
|
31
31
|
(cls.notification_super_cls,),
|
|
@@ -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.core.exceptions import ObjectDoesNotExist
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def create_action_item(
|
|
16
|
-
action_cls:
|
|
16
|
+
action_cls: type[Action],
|
|
17
17
|
subject_identifier: str = None,
|
|
18
18
|
action_identifier: str | None = None,
|
|
19
19
|
related_action_item: ActionItem | None = None,
|
|
@@ -27,7 +27,7 @@ def create_or_update_action_type(
|
|
|
27
27
|
if options:
|
|
28
28
|
fields = [f.name for f in action_type_model_cls._meta.get_fields() if f.name != "name"]
|
|
29
29
|
for attr, value in options.items():
|
|
30
|
-
if attr in fields:
|
|
30
|
+
if attr in fields and value is not None:
|
|
31
31
|
opts.update({attr: value})
|
|
32
32
|
try:
|
|
33
33
|
action_type = action_type_model_cls.objects.get(name=name)
|
edc_action_item/data_fixers.py
CHANGED
|
@@ -64,7 +64,7 @@ def fix_null_action_item_fk(apps, app_label, models):
|
|
|
64
64
|
try:
|
|
65
65
|
obj.save()
|
|
66
66
|
except MultipleObjectsReturned:
|
|
67
|
-
sys.stdout.write(f"Failed to resave {
|
|
67
|
+
sys.stdout.write(f"Failed to resave {obj!r}")
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
def fix_null_action_items(apps):
|
|
@@ -114,8 +114,7 @@ def fix_null_action_items(apps):
|
|
|
114
114
|
.update(related_action_item=related_action_item)
|
|
115
115
|
)
|
|
116
116
|
sys.stdout.write(
|
|
117
|
-
f" setting related_action_items for "
|
|
118
|
-
f"{related_action_item}. Got {updated}\n"
|
|
117
|
+
f" setting related_action_items for {related_action_item}. Got {updated}\n"
|
|
119
118
|
)
|
|
120
119
|
|
|
121
120
|
|
|
@@ -157,7 +156,7 @@ def fix_null_related_action_items(apps): # noqa
|
|
|
157
156
|
action_item.related_action_item = related_action_item
|
|
158
157
|
action_item.save()
|
|
159
158
|
if reference_obj:
|
|
160
|
-
reference_obj.related_action_item = related_action_item
|
|
159
|
+
reference_obj.related_action_item = related_action_item
|
|
161
160
|
reference_obj.save()
|
|
162
161
|
if (
|
|
163
162
|
action_item_cls.objects.filter(
|
|
@@ -239,7 +238,7 @@ def fix_duplicate_singleton_action_items(apps, name=None):
|
|
|
239
238
|
).delete()
|
|
240
239
|
|
|
241
240
|
|
|
242
|
-
def fix_null_related_action_items2(delete_orphans=None):
|
|
241
|
+
def fix_null_related_action_items2(delete_orphans=None):
|
|
243
242
|
"""Used for early action items where related_action_item
|
|
244
243
|
was not set, e.g. v0.1.1
|
|
245
244
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
from typing import Tuple
|
|
2
|
-
|
|
3
1
|
from .fieldsets import action_fields
|
|
4
2
|
|
|
5
3
|
|
|
6
4
|
class ActionItemModelAdminMixin:
|
|
7
|
-
def get_readonly_fields(self, request, obj=None) ->
|
|
5
|
+
def get_readonly_fields(self, request, obj=None) -> tuple[str, ...]:
|
|
8
6
|
"""
|
|
9
7
|
Returns a list of readonly field names.
|
|
10
8
|
|
|
@@ -15,7 +13,7 @@ class ActionItemModelAdminMixin:
|
|
|
15
13
|
fields += action_fields
|
|
16
14
|
return tuple(f for f in fields if f != "action_identifier")
|
|
17
15
|
|
|
18
|
-
def get_search_fields(self, request) ->
|
|
16
|
+
def get_search_fields(self, request) -> tuple[str, ...]:
|
|
19
17
|
search_fields = super().get_search_fields(request)
|
|
20
18
|
custom_fields = ("action_identifier",)
|
|
21
19
|
if "subject_identifier" in [f.name for f in self.model._meta.fields]:
|
|
@@ -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.core.exceptions import ObjectDoesNotExist
|
|
@@ -29,9 +29,27 @@ if TYPE_CHECKING:
|
|
|
29
29
|
|
|
30
30
|
class PrnModel(BaseUuidModel):
|
|
31
31
|
subject_identifier: str
|
|
32
|
-
...
|
|
32
|
+
...
|
|
33
33
|
|
|
34
|
-
class CrfModel(CrfModelMixin): ...
|
|
34
|
+
class CrfModel(CrfModelMixin): ...
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
RELATED_ACTION_ITEM_DOES_NOT_EXIST = (
|
|
38
|
+
"Related ActionItem does not exist. Got {action_identifier}."
|
|
39
|
+
)
|
|
40
|
+
PARENT_ACTION_ITEM_DOES_NOT_EXIST = (
|
|
41
|
+
"Parent ActionItem does not exist. Got {action_identifier}."
|
|
42
|
+
)
|
|
43
|
+
INVALID_ACTION_ITEM_STATUS = (
|
|
44
|
+
"Invalid action item status. Reference model exists. "
|
|
45
|
+
"Got `{status}`. Perhaps catch this in the form"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
INVALID_SUBJECT_IDENTIFIER = (
|
|
49
|
+
"Invalid subject identifier. Attempt to create {class_name} failed. "
|
|
50
|
+
"Subject does not exist in '{subject_identifier_model}'. "
|
|
51
|
+
"Got '{subject_identifier}'."
|
|
52
|
+
)
|
|
35
53
|
|
|
36
54
|
|
|
37
55
|
class CurrentSiteManager(BaseCurrentSiteManager):
|
|
@@ -82,17 +100,14 @@ class ActionItem(
|
|
|
82
100
|
|
|
83
101
|
related_action_identifier = models.CharField(
|
|
84
102
|
max_length=50,
|
|
85
|
-
null=True,
|
|
86
103
|
blank=True,
|
|
87
104
|
help_text=(
|
|
88
|
-
"May be left blank. e.g. action identifier from "
|
|
89
|
-
"source model that opened the item."
|
|
105
|
+
"May be left blank. e.g. action identifier from source model that opened the item."
|
|
90
106
|
),
|
|
91
107
|
)
|
|
92
108
|
|
|
93
109
|
parent_action_identifier = models.CharField(
|
|
94
110
|
max_length=50,
|
|
95
|
-
null=True,
|
|
96
111
|
blank=True,
|
|
97
112
|
help_text=(
|
|
98
113
|
"May be left blank. e.g. action identifier from "
|
|
@@ -103,7 +118,6 @@ class ActionItem(
|
|
|
103
118
|
priority = models.CharField(
|
|
104
119
|
max_length=25,
|
|
105
120
|
choices=PRIORITY,
|
|
106
|
-
null=True,
|
|
107
121
|
blank=True,
|
|
108
122
|
help_text="Leave blank to use default for this action type.",
|
|
109
123
|
)
|
|
@@ -128,13 +142,11 @@ class ActionItem(
|
|
|
128
142
|
|
|
129
143
|
status = models.CharField(max_length=25, default=NEW, choices=ACTION_STATUS)
|
|
130
144
|
|
|
131
|
-
instructions = models.TextField(
|
|
132
|
-
null=True, blank=True, help_text="populated by action class"
|
|
133
|
-
)
|
|
145
|
+
instructions = models.TextField(blank=True, help_text="populated by action class")
|
|
134
146
|
|
|
135
147
|
auto_created = models.BooleanField(default=False)
|
|
136
148
|
|
|
137
|
-
auto_created_comment = models.CharField(max_length=25,
|
|
149
|
+
auto_created_comment = models.CharField(max_length=25, blank=True)
|
|
138
150
|
|
|
139
151
|
objects = ActionItemManager()
|
|
140
152
|
|
|
@@ -168,29 +180,28 @@ class ActionItem(
|
|
|
168
180
|
subject_identifier_model_cls.objects.get(
|
|
169
181
|
subject_identifier=self.subject_identifier
|
|
170
182
|
)
|
|
171
|
-
except ObjectDoesNotExist:
|
|
183
|
+
except ObjectDoesNotExist as e:
|
|
172
184
|
raise SubjectDoesNotExist(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
INVALID_SUBJECT_IDENTIFIER.format(
|
|
186
|
+
class_name=self.__class__.__name__,
|
|
187
|
+
subject_identifier_model=self.subject_identifier_model,
|
|
188
|
+
subject_identifier=self.subject_identifier,
|
|
189
|
+
)
|
|
190
|
+
) from e
|
|
178
191
|
self.priority = self.priority or self.action_type.priority
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
f"Got `{self.get_status_display()}`. Perhaps catch this in the form"
|
|
189
|
-
)
|
|
192
|
+
elif (
|
|
193
|
+
self.status in [NEW, CANCELLED]
|
|
194
|
+
and django_apps.get_model(self.reference_model)
|
|
195
|
+
.objects.filter(action_identifier=self.action_identifier)
|
|
196
|
+
.exists()
|
|
197
|
+
):
|
|
198
|
+
raise ActionItemStatusError(
|
|
199
|
+
INVALID_ACTION_ITEM_STATUS.format(status=self.get_status_display())
|
|
200
|
+
)
|
|
190
201
|
super().save(*args, **kwargs)
|
|
191
202
|
|
|
192
203
|
def natural_key(self) -> tuple[str]:
|
|
193
|
-
return (self.action_identifier,)
|
|
204
|
+
return (self.action_identifier,)
|
|
194
205
|
|
|
195
206
|
@property
|
|
196
207
|
def last_updated(self) -> datetime | str:
|
|
@@ -201,7 +212,7 @@ class ActionItem(
|
|
|
201
212
|
return None if self.status == NEW else self.user_modified or self.user_created
|
|
202
213
|
|
|
203
214
|
@property
|
|
204
|
-
def action_cls(self) ->
|
|
215
|
+
def action_cls(self) -> type[Action]:
|
|
205
216
|
"""Returns the action_cls."""
|
|
206
217
|
return site_action_items.get(self.action_type.name)
|
|
207
218
|
|
|
@@ -219,7 +230,7 @@ class ActionItem(
|
|
|
219
230
|
return self.action_type.reference_model
|
|
220
231
|
|
|
221
232
|
@property
|
|
222
|
-
def reference_model_cls(self) ->
|
|
233
|
+
def reference_model_cls(self) -> type[PrnModel | CrfModel]:
|
|
223
234
|
return django_apps.get_model(self.action_type.reference_model)
|
|
224
235
|
|
|
225
236
|
@property
|
|
@@ -230,7 +241,9 @@ class ActionItem(
|
|
|
230
241
|
def parent_reference_obj(self) -> PrnModel | CrfModel:
|
|
231
242
|
if not self.parent_action_item:
|
|
232
243
|
raise ObjectDoesNotExist(
|
|
233
|
-
|
|
244
|
+
PARENT_ACTION_ITEM_DOES_NOT_EXIST.format(
|
|
245
|
+
action_identifier=self.action_identifier
|
|
246
|
+
)
|
|
234
247
|
)
|
|
235
248
|
return self.parent_action_item.reference_obj
|
|
236
249
|
|
|
@@ -242,7 +255,9 @@ class ActionItem(
|
|
|
242
255
|
def related_reference_obj(self) -> PrnModel | CrfModel:
|
|
243
256
|
if not self.related_action_item:
|
|
244
257
|
raise ObjectDoesNotExist(
|
|
245
|
-
|
|
258
|
+
RELATED_ACTION_ITEM_DOES_NOT_EXIST.format(
|
|
259
|
+
action_identifier=self.action_identifier
|
|
260
|
+
)
|
|
246
261
|
)
|
|
247
262
|
return self.related_action_item.reference_obj
|
|
248
263
|
|
|
@@ -266,9 +281,9 @@ class ActionItem(
|
|
|
266
281
|
verbose_name = "Action Item"
|
|
267
282
|
verbose_name_plural = "Action Items"
|
|
268
283
|
indexes = (
|
|
269
|
-
BaseUuidModel.Meta.indexes
|
|
270
|
-
|
|
271
|
-
|
|
284
|
+
*BaseUuidModel.Meta.indexes,
|
|
285
|
+
*NonUniqueSubjectIdentifierFieldMixin.Meta.indexes,
|
|
286
|
+
*[
|
|
272
287
|
models.Index(
|
|
273
288
|
fields=[
|
|
274
289
|
"subject_identifier",
|
|
@@ -279,5 +294,5 @@ class ActionItem(
|
|
|
279
294
|
"report_datetime",
|
|
280
295
|
],
|
|
281
296
|
),
|
|
282
|
-
]
|
|
297
|
+
],
|
|
283
298
|
)
|
|
@@ -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
|
|
6
6
|
from django.db.models.deletion import PROTECT
|
|
@@ -53,6 +53,19 @@ class ActionNoManagersModelMixin(models.Model):
|
|
|
53
53
|
|
|
54
54
|
action_item_reason = models.TextField(null=True, editable=False)
|
|
55
55
|
|
|
56
|
+
class Meta:
|
|
57
|
+
abstract = True
|
|
58
|
+
indexes = (
|
|
59
|
+
models.Index(
|
|
60
|
+
fields=[
|
|
61
|
+
"action_identifier",
|
|
62
|
+
"action_item",
|
|
63
|
+
"related_action_item",
|
|
64
|
+
"parent_action_item",
|
|
65
|
+
]
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
|
|
56
69
|
def __str__(self) -> str:
|
|
57
70
|
if self.action_identifier:
|
|
58
71
|
return f"{self.action_identifier[-9:]}"
|
|
@@ -61,7 +74,7 @@ class ActionNoManagersModelMixin(models.Model):
|
|
|
61
74
|
def save(self: Any, *args, **kwargs):
|
|
62
75
|
# ensure action class is defined
|
|
63
76
|
if not self.get_action_cls():
|
|
64
|
-
raise ActionClassNotDefined(f"Action class name not defined. See {
|
|
77
|
+
raise ActionClassNotDefined(f"Action class name not defined. See {self!r}")
|
|
65
78
|
|
|
66
79
|
# ensure subject_identifier
|
|
67
80
|
if not self.subject_identifier:
|
|
@@ -98,13 +111,13 @@ class ActionNoManagersModelMixin(models.Model):
|
|
|
98
111
|
super().save(*args, **kwargs) # type: ignore
|
|
99
112
|
|
|
100
113
|
def natural_key(self: Any) -> tuple:
|
|
101
|
-
return (self.action_identifier,)
|
|
114
|
+
return (self.action_identifier,)
|
|
102
115
|
|
|
103
116
|
# noinspection PyTypeHints
|
|
104
117
|
natural_key.dependencies = ["edc_action_item.actionitem"] # type:ignore
|
|
105
118
|
|
|
106
119
|
@classmethod
|
|
107
|
-
def get_action_cls(cls) ->
|
|
120
|
+
def get_action_cls(cls) -> type[Action]:
|
|
108
121
|
return site_action_items.get(cls.action_name)
|
|
109
122
|
|
|
110
123
|
@property
|
|
@@ -123,19 +136,6 @@ class ActionNoManagersModelMixin(models.Model):
|
|
|
123
136
|
"""Returns a shortened action_identifier"""
|
|
124
137
|
return self.action_identifier[-9:]
|
|
125
138
|
|
|
126
|
-
class Meta:
|
|
127
|
-
abstract = True
|
|
128
|
-
indexes = [
|
|
129
|
-
models.Index(
|
|
130
|
-
fields=[
|
|
131
|
-
"action_identifier",
|
|
132
|
-
"action_item",
|
|
133
|
-
"related_action_item",
|
|
134
|
-
"parent_action_item",
|
|
135
|
-
]
|
|
136
|
-
)
|
|
137
|
-
]
|
|
138
|
-
|
|
139
139
|
|
|
140
140
|
class ActionModelMixin(ActionNoManagersModelMixin):
|
|
141
141
|
objects = ActionIdentifierModelManager()
|