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_model/utils.py
CHANGED
|
@@ -15,7 +15,6 @@ from django.urls import reverse
|
|
|
15
15
|
from .constants import REPORT_DATETIME_FIELD_NAME
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
|
-
|
|
19
18
|
from edc_visit_tracking.model_mixins import VisitTrackingCrfModelMixin
|
|
20
19
|
|
|
21
20
|
from .models import BaseUuidModel
|
|
@@ -136,7 +135,6 @@ def raise_on_invalid_field_name(data: dict | models.Model, attrname: str) -> Non
|
|
|
136
135
|
raise InvalidFieldName(f"{e} Got {data}")
|
|
137
136
|
else:
|
|
138
137
|
raise InvalidFieldName(f"Field name cannot be None. Got {data}")
|
|
139
|
-
return None
|
|
140
138
|
|
|
141
139
|
|
|
142
140
|
def model_exists_or_raise(
|
|
@@ -85,7 +85,5 @@ class ModelAdminDashboardMixin:
|
|
|
85
85
|
if callable(super().view_on_site):
|
|
86
86
|
url = super().view_on_site(obj)
|
|
87
87
|
else:
|
|
88
|
-
raise NoReverseMatch(
|
|
89
|
-
f"{e}. See subject_dashboard_url_name for {repr(self)}."
|
|
90
|
-
)
|
|
88
|
+
raise NoReverseMatch(f"{e}. See subject_dashboard_url_name for {self!r}.")
|
|
91
89
|
return url
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Optional, Tuple
|
|
2
|
-
|
|
3
1
|
from django.apps import apps as django_apps
|
|
4
2
|
from django.contrib import admin
|
|
5
3
|
from django.utils.functional import lazy
|
|
@@ -12,7 +10,7 @@ format_html_lazy = lazy(format_html, str)
|
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
class SimpleHistoryAdmin(BaseSimpleHistoryAdmin):
|
|
15
|
-
history_list_display:
|
|
13
|
+
history_list_display: tuple[str, ...] = ("dashboard", "change_message")
|
|
16
14
|
object_history_template = "edc_model_admin/admin/object_history.html"
|
|
17
15
|
object_history_form_template = "edc_model_admin/admin/object_history_form.html"
|
|
18
16
|
|
|
@@ -20,7 +18,7 @@ class SimpleHistoryAdmin(BaseSimpleHistoryAdmin):
|
|
|
20
18
|
save_as_continue = False
|
|
21
19
|
|
|
22
20
|
@admin.display(description=_("Change Message"))
|
|
23
|
-
def change_message(self, obj) ->
|
|
21
|
+
def change_message(self, obj) -> str | None:
|
|
24
22
|
log_entry_model_cls = django_apps.get_model("admin.logentry")
|
|
25
23
|
log_entry = (
|
|
26
24
|
log_entry_model_cls.objects.filter(
|
|
@@ -36,7 +34,7 @@ class SimpleHistoryAdmin(BaseSimpleHistoryAdmin):
|
|
|
36
34
|
)
|
|
37
35
|
return None
|
|
38
36
|
|
|
39
|
-
def dashboard(self, obj) ->
|
|
37
|
+
def dashboard(self, obj) -> str | None:
|
|
40
38
|
if callable(self.view_on_site):
|
|
41
39
|
return format_html_lazy(
|
|
42
40
|
'<A href="{url}">Dashboard</A>',
|
|
@@ -44,16 +42,16 @@ class SimpleHistoryAdmin(BaseSimpleHistoryAdmin):
|
|
|
44
42
|
)
|
|
45
43
|
return None
|
|
46
44
|
|
|
47
|
-
def get_list_display(self, request) ->
|
|
45
|
+
def get_list_display(self, request) -> tuple[str, ...]:
|
|
48
46
|
return tuple(super().get_list_display(request))
|
|
49
47
|
|
|
50
|
-
def get_list_filter(self, request) ->
|
|
48
|
+
def get_list_filter(self, request) -> tuple[str, ...]:
|
|
51
49
|
return tuple(super().get_list_filter(request))
|
|
52
50
|
|
|
53
|
-
def get_search_fields(self, request) ->
|
|
51
|
+
def get_search_fields(self, request) -> tuple[str, ...]:
|
|
54
52
|
return tuple(super().get_search_fields(request))
|
|
55
53
|
|
|
56
|
-
def get_readonly_fields(self, request, obj=None) ->
|
|
54
|
+
def get_readonly_fields(self, request, obj=None) -> tuple[str, ...]:
|
|
57
55
|
return tuple(super().get_readonly_fields(request, obj=obj))
|
|
58
56
|
|
|
59
57
|
def history_view_title(self, request, obj):
|
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
1
|
from django.http.response import HttpResponseRedirect
|
|
4
2
|
|
|
5
3
|
|
|
6
4
|
class BaseModelAdminRedirectMixin:
|
|
7
5
|
"""Redirect on add, change, or delete."""
|
|
8
6
|
|
|
9
|
-
def redirect_url(self, request, obj, post_url_continue=None) ->
|
|
7
|
+
def redirect_url(self, request, obj, post_url_continue=None) -> str | None:
|
|
10
8
|
pass
|
|
11
9
|
|
|
12
|
-
def redirect_url_on_add(self, request, obj, post_url_continue=None) ->
|
|
10
|
+
def redirect_url_on_add(self, request, obj, post_url_continue=None) -> str | None:
|
|
13
11
|
return self.redirect_url(request, obj, post_url_continue=post_url_continue)
|
|
14
12
|
|
|
15
|
-
def redirect_url_on_change(self, request, obj, post_url_continue=None) ->
|
|
13
|
+
def redirect_url_on_change(self, request, obj, post_url_continue=None) -> str | None:
|
|
16
14
|
return self.redirect_url(request, obj, post_url_continue=post_url_continue)
|
|
17
15
|
|
|
18
|
-
def redirect_url_on_delete(self, request, obj_display, obj_id) ->
|
|
16
|
+
def redirect_url_on_delete(self, request, obj_display, obj_id) -> str | None:
|
|
19
17
|
pass
|
|
20
18
|
|
|
21
19
|
def response_add(self, request, obj, post_url_continue=None):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any
|
|
2
2
|
|
|
3
3
|
from django.core.handlers.wsgi import WSGIRequest
|
|
4
4
|
|
|
@@ -11,7 +11,7 @@ class ModelAdminLimitToSelectedForeignkeyError(Exception):
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class ModelAdminLimitToSelectedForeignkey:
|
|
14
|
-
limit_fk_field_to_selected: list[
|
|
14
|
+
limit_fk_field_to_selected: list[tuple[str, Any]] = None
|
|
15
15
|
|
|
16
16
|
def formfield_for_foreignkey(self, db_field, request: WSGIRequest, **kwargs):
|
|
17
17
|
db = kwargs.get("using")
|
|
@@ -29,11 +29,7 @@ class ModelAdminModelRedirectMixin(BaseModelAdminRedirectMixin):
|
|
|
29
29
|
namespace = namespace or self.redirect_namespace
|
|
30
30
|
return "{}?q={}".format(
|
|
31
31
|
reverse(
|
|
32
|
-
"{namespace}:{
|
|
33
|
-
namespace=namespace,
|
|
34
|
-
app_label=self.redirect_app_label,
|
|
35
|
-
model_name=self.redirect_model_name,
|
|
36
|
-
)
|
|
32
|
+
f"{namespace}:{self.redirect_app_label}_{self.redirect_model_name}_changelist"
|
|
37
33
|
),
|
|
38
34
|
self.search_value(obj) or "",
|
|
39
35
|
)
|
|
@@ -41,9 +37,5 @@ class ModelAdminModelRedirectMixin(BaseModelAdminRedirectMixin):
|
|
|
41
37
|
def redirect_url_on_delete(self, request, obj_display, obj_id, namespace=None):
|
|
42
38
|
namespace = namespace or self.redirect_namespace
|
|
43
39
|
return reverse(
|
|
44
|
-
"{namespace}:{
|
|
45
|
-
namespace=namespace,
|
|
46
|
-
app_label=self.redirect_app_label,
|
|
47
|
-
model_name=self.redirect_model_name,
|
|
48
|
-
)
|
|
40
|
+
f"{namespace}:{self.redirect_app_label}_{self.redirect_model_name}_changelist"
|
|
49
41
|
)
|
|
@@ -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 urllib.parse import urlencode
|
|
5
5
|
|
|
6
6
|
from django.apps import apps as django_apps
|
|
@@ -82,15 +82,15 @@ class ModelAdminNextUrlRedirectMixin(BaseModelAdminRedirectMixin):
|
|
|
82
82
|
extra_context.update(show_cancel=self.show_cancel)
|
|
83
83
|
return extra_context
|
|
84
84
|
|
|
85
|
-
def redirect_url(self, request, obj, post_url_continue=None) ->
|
|
85
|
+
def redirect_url(self, request, obj, post_url_continue=None) -> str | None:
|
|
86
86
|
redirect_url = None
|
|
87
87
|
if self.show_save_next and request.POST.get("_savenext"):
|
|
88
88
|
redirect_url = self.get_savenext_redirect_url(request=request, obj=obj)
|
|
89
89
|
if not redirect_url:
|
|
90
90
|
redirect_url = self.get_next_redirect_url(request=request, obj=obj)
|
|
91
|
-
elif self.show_cancel and request.POST.get("_cancel")
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
elif (self.show_cancel and request.POST.get("_cancel")) or request.GET.get(
|
|
92
|
+
self.next_querystring_attr
|
|
93
|
+
):
|
|
94
94
|
redirect_url = self.get_next_redirect_url(request=request, obj=obj)
|
|
95
95
|
if not redirect_url:
|
|
96
96
|
redirect_url = super().redirect_url(
|
|
@@ -98,7 +98,7 @@ class ModelAdminNextUrlRedirectMixin(BaseModelAdminRedirectMixin):
|
|
|
98
98
|
)
|
|
99
99
|
return redirect_url
|
|
100
100
|
|
|
101
|
-
def get_next_redirect_url(self, request=None, **kwargs) ->
|
|
101
|
+
def get_next_redirect_url(self, request=None, **kwargs) -> str | None:
|
|
102
102
|
"""Returns a redirect url determined from the "next" attr
|
|
103
103
|
in the querystring.
|
|
104
104
|
"""
|
|
@@ -120,7 +120,7 @@ class ModelAdminNextUrlRedirectMixin(BaseModelAdminRedirectMixin):
|
|
|
120
120
|
redirect_url = f"{redirect_url}?q={options['q']}"
|
|
121
121
|
return redirect_url
|
|
122
122
|
|
|
123
|
-
def get_savenext_redirect_url(self, request=None, obj=None) ->
|
|
123
|
+
def get_savenext_redirect_url(self, request=None, obj=None) -> str | None:
|
|
124
124
|
"""Returns a redirect_url for the next form in
|
|
125
125
|
the visit schedule.
|
|
126
126
|
|
|
@@ -159,8 +159,7 @@ class ModelAdminNextUrlRedirectMixin(BaseModelAdminRedirectMixin):
|
|
|
159
159
|
querystring = urlencode(querystring_opts)
|
|
160
160
|
if redirect_url:
|
|
161
161
|
return (
|
|
162
|
-
f"{redirect_url}?{self.next_querystring_attr}="
|
|
163
|
-
f"{next_querystring}&{querystring}"
|
|
162
|
+
f"{redirect_url}?{self.next_querystring_attr}={next_querystring}&{querystring}"
|
|
164
163
|
)
|
|
165
164
|
return None
|
|
166
165
|
|
edc_model_admin/utils.py
CHANGED
|
@@ -41,15 +41,12 @@ def get_value_from_lookup_string(search_field_name: str = None, obj=None, reques
|
|
|
41
41
|
if request:
|
|
42
42
|
value = request.GET.get(field, "")
|
|
43
43
|
break
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
)
|
|
51
|
-
if value is None:
|
|
52
|
-
break
|
|
44
|
+
try:
|
|
45
|
+
value = getattr(value or obj, field)
|
|
46
|
+
except AttributeError as e:
|
|
47
|
+
raise SearchTermLookupError(f"Invalid search term. `{search_field_name}`. Got {e}")
|
|
48
|
+
if value is None:
|
|
49
|
+
break
|
|
53
50
|
return value
|
|
54
51
|
|
|
55
52
|
|
|
@@ -2,8 +2,8 @@ from .base_model_form_mixin import BaseModelFormMixin, BaseModelFormMixinError
|
|
|
2
2
|
from .inline_model_form_mixin import InlineModelFormMixin, InlineModelFormMixinError
|
|
3
3
|
|
|
4
4
|
__all__ = [
|
|
5
|
-
"BaseModelFormMixinError",
|
|
6
5
|
"BaseModelFormMixin",
|
|
6
|
+
"BaseModelFormMixinError",
|
|
7
7
|
"InlineModelFormMixin",
|
|
8
8
|
"InlineModelFormMixinError",
|
|
9
9
|
]
|
|
@@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
|
|
24
24
|
pass
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
__all__ = ["
|
|
27
|
+
__all__ = ["ModelToDataframe", "ModelToDataframeError"]
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class ModelToDataframeError(Exception):
|
|
@@ -124,9 +124,7 @@ class ModelToDataframe:
|
|
|
124
124
|
try:
|
|
125
125
|
row_count = self.queryset.count()
|
|
126
126
|
except OperationalError as e:
|
|
127
|
-
if "The user specified as a definer" in str(e) and
|
|
128
|
-
self.model_cls, "recreate_db_view"
|
|
129
|
-
):
|
|
127
|
+
if "The user specified as a definer" in str(e) and self.model_cls.recreate_db_view:
|
|
130
128
|
self.model_cls.recreate_db_view()
|
|
131
129
|
row_count = self.queryset.count()
|
|
132
130
|
else:
|
edc_navbar/site_navbars.py
CHANGED
|
@@ -52,14 +52,14 @@ class NavbarCollection:
|
|
|
52
52
|
except KeyError:
|
|
53
53
|
raise NavbarError(
|
|
54
54
|
f"Navbar '{name}' does not exist. Expected one of "
|
|
55
|
-
f"{list(self.registry.keys())}. See {
|
|
55
|
+
f"{list(self.registry.keys())}. See {self!r}."
|
|
56
56
|
)
|
|
57
57
|
else:
|
|
58
58
|
# does the navbar have items?
|
|
59
59
|
if not navbar.navbar_items:
|
|
60
60
|
raise NavbarError(
|
|
61
61
|
f"Navbar '{navbar.name}' has no navbar_item. Expected "
|
|
62
|
-
f"'{selected_item}'. See {
|
|
62
|
+
f"'{selected_item}'. See {self!r}"
|
|
63
63
|
)
|
|
64
64
|
# does the selected item exist?
|
|
65
65
|
if selected_item:
|
edc_navbar/system_checks.py
CHANGED
|
@@ -25,7 +25,7 @@ def edc_navbar_checks(app_configs, **kwargs) -> list[CheckMessage]:
|
|
|
25
25
|
msg = (
|
|
26
26
|
f"Invalid app_label in codename. Expected format "
|
|
27
27
|
f"'<app_label>.<some_codename>'. Got {navbar_item.codename}. "
|
|
28
|
-
f"See {
|
|
28
|
+
f"See {navbar_item!r}."
|
|
29
29
|
)
|
|
30
30
|
errors.append(Error(msg, id="edc_navbar.E002"))
|
|
31
31
|
try:
|
|
@@ -47,8 +47,7 @@ class MailingListManager:
|
|
|
47
47
|
"""Returns the api_url or None."""
|
|
48
48
|
if not self._api_url:
|
|
49
49
|
error_msg = (
|
|
50
|
-
f"Email is enabled but API_URL is not set. "
|
|
51
|
-
f"See settings.{self.api_url_attr}"
|
|
50
|
+
f"Email is enabled but API_URL is not set. See settings.{self.api_url_attr}"
|
|
52
51
|
)
|
|
53
52
|
try:
|
|
54
53
|
self._api_url = getattr(settings, self.api_url_attr)
|
|
@@ -64,8 +63,7 @@ class MailingListManager:
|
|
|
64
63
|
"""Returns the api_key or None."""
|
|
65
64
|
if not self._api_key:
|
|
66
65
|
error_msg = (
|
|
67
|
-
f"Email is enabled but API_KEY is not set. "
|
|
68
|
-
f"See settings.{self.api_key_attr}"
|
|
66
|
+
f"Email is enabled but API_KEY is not set. See settings.{self.api_key_attr}"
|
|
69
67
|
)
|
|
70
68
|
try:
|
|
71
69
|
self._api_key = getattr(settings, self.api_key_attr)
|
|
@@ -91,7 +89,7 @@ class MailingListManager:
|
|
|
91
89
|
"subscribed": True,
|
|
92
90
|
"address": user.email,
|
|
93
91
|
"name": f"{user.first_name} {user.last_name}",
|
|
94
|
-
"description": f
|
|
92
|
+
"description": f"{user.userprofile.job_title or ''}",
|
|
95
93
|
"upsert": "yes",
|
|
96
94
|
},
|
|
97
95
|
timeout=10,
|
|
@@ -124,7 +122,7 @@ class MailingListManager:
|
|
|
124
122
|
except KeyError:
|
|
125
123
|
sys.stdout.write(
|
|
126
124
|
f"{action.title()} failed. Got response={response.status_code} "
|
|
127
|
-
f"{
|
|
125
|
+
f"{response.json()!s}"
|
|
128
126
|
)
|
|
129
127
|
else:
|
|
130
128
|
sys.stdout.write(
|
|
@@ -149,8 +147,7 @@ class MailingListManager:
|
|
|
149
147
|
)
|
|
150
148
|
if verbose:
|
|
151
149
|
sys.stdout.write(
|
|
152
|
-
f"Creating mailing list {self.address}. "
|
|
153
|
-
f"Got response={response.status_code}.\n"
|
|
150
|
+
f"Creating mailing list {self.address}. Got response={response.status_code}.\n"
|
|
154
151
|
)
|
|
155
152
|
return response
|
|
156
153
|
|
|
@@ -9,7 +9,7 @@ class Command(BaseCommand):
|
|
|
9
9
|
def handle(self, *args, **options):
|
|
10
10
|
for notification_cls in site_notifications.registry.values():
|
|
11
11
|
notification = notification_cls()
|
|
12
|
-
print(
|
|
12
|
+
print()
|
|
13
13
|
print(notification.name)
|
|
14
14
|
print(notification.display_name)
|
|
15
15
|
print(f"email: {notification.email_to}")
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
from .notification import Notification
|
|
2
|
-
from .signals import
|
|
3
|
-
|
|
2
|
+
from .signals import (
|
|
3
|
+
manage_mailists_on_userprofile_m2m_changed,
|
|
4
|
+
notification_on_post_create_historical_record,
|
|
5
|
+
)
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
1
|
from .model_notification import ModelNotification
|
|
4
2
|
|
|
5
3
|
|
|
6
4
|
class GradedEventNotification(ModelNotification):
|
|
7
|
-
grade:
|
|
8
|
-
model:
|
|
5
|
+
grade: int | None = None
|
|
6
|
+
model: str | None = None
|
|
9
7
|
create_fields = ["ae_grade"]
|
|
10
8
|
update_fields = ["ae_grade"]
|
|
11
9
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
1
|
from django.apps import apps as django_apps
|
|
4
2
|
|
|
5
3
|
from edc_model.stubs import BaseUuidHistoryModelStub
|
|
@@ -17,7 +15,7 @@ class ModelNotification(Notification):
|
|
|
17
15
|
modification or deletion.
|
|
18
16
|
"""
|
|
19
17
|
|
|
20
|
-
model:
|
|
18
|
+
model: str | None = None # label_lower format
|
|
21
19
|
|
|
22
20
|
model_operations = [CREATE, UPDATE, DELETE]
|
|
23
21
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
2
|
-
|
|
3
1
|
from django.apps import apps as django_apps
|
|
4
2
|
from django.conf import settings
|
|
5
3
|
from django.core.exceptions import ObjectDoesNotExist
|
|
@@ -27,13 +25,13 @@ class Notification:
|
|
|
27
25
|
"""A generic class to generate a notification on a condition"""
|
|
28
26
|
|
|
29
27
|
# app_name: str = None
|
|
30
|
-
name:
|
|
31
|
-
display_name:
|
|
28
|
+
name: str | None = None
|
|
29
|
+
display_name: str | None = None
|
|
32
30
|
|
|
33
31
|
sms_client = Client
|
|
34
32
|
|
|
35
|
-
email_from:
|
|
36
|
-
email_to:
|
|
33
|
+
email_from: list[str] = get_email_contacts("data_manager")
|
|
34
|
+
email_to: list[str] | None = None # usually a mailing list address
|
|
37
35
|
email_message_cls = EmailMessage
|
|
38
36
|
|
|
39
37
|
email_body_template: str = (
|
|
@@ -50,7 +48,7 @@ class Notification:
|
|
|
50
48
|
"Thanks."
|
|
51
49
|
)
|
|
52
50
|
email_subject_template: str = (
|
|
53
|
-
"{test_subject_line}{protocol_name}:
|
|
51
|
+
"{test_subject_line}{protocol_name}: {display_name} for {subject_identifier}"
|
|
54
52
|
)
|
|
55
53
|
email_footer_template: str = (
|
|
56
54
|
"\n\n-----------------\n"
|
|
@@ -72,7 +70,7 @@ class Notification:
|
|
|
72
70
|
sms_test_line: str = "TEST MESSAGE. NO ACTION REQUIRED - "
|
|
73
71
|
|
|
74
72
|
def __init__(self) -> None:
|
|
75
|
-
self._notification_enabled:
|
|
73
|
+
self._notification_enabled: bool | None = None
|
|
76
74
|
self._template_opts: dict = {}
|
|
77
75
|
self.email_to = self.email_to or self.default_email_to
|
|
78
76
|
self.test_message: bool = False
|
|
@@ -90,7 +88,7 @@ class Notification:
|
|
|
90
88
|
return f"{self.name}: {self.display_name}"
|
|
91
89
|
|
|
92
90
|
@property
|
|
93
|
-
def default_email_to(self) ->
|
|
91
|
+
def default_email_to(self) -> list[str]:
|
|
94
92
|
return [f"{self.name}.{settings.APP_NAME}@mg.clinicedc.org"]
|
|
95
93
|
|
|
96
94
|
def notify(
|
|
@@ -113,8 +111,8 @@ class Notification:
|
|
|
113
111
|
* instance
|
|
114
112
|
* user
|
|
115
113
|
"""
|
|
116
|
-
email_sent:
|
|
117
|
-
sms_sent:
|
|
114
|
+
email_sent: int | None = None
|
|
115
|
+
sms_sent: dict | None = None
|
|
118
116
|
use_email = use_email or get_email_enabled()
|
|
119
117
|
use_sms = use_sms or getattr(settings, "TWILIO_ENABLED", False)
|
|
120
118
|
if force_notify or self._notify_on_condition(**kwargs):
|
|
@@ -140,8 +138,7 @@ class Notification:
|
|
|
140
138
|
"""Returns the value of `notify_on_condition` or False."""
|
|
141
139
|
if test_message:
|
|
142
140
|
return True
|
|
143
|
-
|
|
144
|
-
return self.enabled and self.notify_on_condition(**kwargs)
|
|
141
|
+
return self.enabled and self.notify_on_condition(**kwargs)
|
|
145
142
|
|
|
146
143
|
def post_notification_actions(self, **kwargs):
|
|
147
144
|
pass
|
|
@@ -217,8 +214,8 @@ class Notification:
|
|
|
217
214
|
|
|
218
215
|
def send_email(
|
|
219
216
|
self,
|
|
220
|
-
fail_silently:
|
|
221
|
-
email_to:
|
|
217
|
+
fail_silently: bool | None = None,
|
|
218
|
+
email_to: list[str] = None,
|
|
222
219
|
email_body_template: str = None,
|
|
223
220
|
**kwargs,
|
|
224
221
|
) -> int:
|
|
@@ -232,8 +229,8 @@ class Notification:
|
|
|
232
229
|
|
|
233
230
|
def send_sms(
|
|
234
231
|
self,
|
|
235
|
-
fail_silently:
|
|
236
|
-
sms_recipient:
|
|
232
|
+
fail_silently: bool | None = None,
|
|
233
|
+
sms_recipient: str | None = None,
|
|
237
234
|
**kwargs,
|
|
238
235
|
) -> dict:
|
|
239
236
|
status = {}
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import copy
|
|
4
4
|
import sys
|
|
5
5
|
from json.decoder import JSONDecodeError
|
|
6
|
-
from typing import TYPE_CHECKING, Any
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
7
|
|
|
8
8
|
from django.apps import apps as django_apps
|
|
9
9
|
from django.conf import settings
|
|
@@ -39,7 +39,7 @@ class NotificationNotRegistered(Exception):
|
|
|
39
39
|
|
|
40
40
|
class SiteNotifications:
|
|
41
41
|
def __init__(self):
|
|
42
|
-
self._registry: dict[str,
|
|
42
|
+
self._registry: dict[str, type[ActionItemNotification | Notification]] = {}
|
|
43
43
|
self.loaded = False
|
|
44
44
|
self.models = {}
|
|
45
45
|
|
|
@@ -55,7 +55,7 @@ class SiteNotifications:
|
|
|
55
55
|
)
|
|
56
56
|
return self._registry
|
|
57
57
|
|
|
58
|
-
def get(self, name) ->
|
|
58
|
+
def get(self, name) -> type[Notification]:
|
|
59
59
|
"""Returns a Notification by name."""
|
|
60
60
|
if not self.loaded:
|
|
61
61
|
raise RegistryNotLoaded(self)
|
|
@@ -63,7 +63,7 @@ class SiteNotifications:
|
|
|
63
63
|
raise NotificationNotRegistered(f"Notification not registered. Got '{name}'.")
|
|
64
64
|
return self._registry.get(name)
|
|
65
65
|
|
|
66
|
-
def register(self, notification_cls:
|
|
66
|
+
def register(self, notification_cls: type[Notification] = None):
|
|
67
67
|
"""Registers a Notification class unique by name."""
|
|
68
68
|
self.loaded = True
|
|
69
69
|
display_names = [n.display_name for n in self.registry.values()]
|
|
@@ -75,7 +75,7 @@ class SiteNotifications:
|
|
|
75
75
|
|
|
76
76
|
models = getattr(notification_cls, "models", [])
|
|
77
77
|
if not models and getattr(notification_cls, "model", None):
|
|
78
|
-
models = [
|
|
78
|
+
models = [notification_cls.model]
|
|
79
79
|
for model in models:
|
|
80
80
|
try:
|
|
81
81
|
if notification_cls.name not in [n.name for n in self.models[model]]:
|
|
@@ -104,7 +104,7 @@ class SiteNotifications:
|
|
|
104
104
|
|
|
105
105
|
def update_notification_list(
|
|
106
106
|
self, apps: django_apps = None, schema_editor=None, verbose=False
|
|
107
|
-
):
|
|
107
|
+
):
|
|
108
108
|
"""Updates the notification model to ensure all registered
|
|
109
109
|
notifications classes are listed.
|
|
110
110
|
|
|
@@ -174,7 +174,7 @@ class SiteNotifications:
|
|
|
174
174
|
response = manager.create()
|
|
175
175
|
except ConnectionError as e:
|
|
176
176
|
sys.stdout.write(
|
|
177
|
-
style.ERROR(f" * Failed to create mailing list {name}.
|
|
177
|
+
style.ERROR(f" * Failed to create mailing list {name}. Got {e}\n")
|
|
178
178
|
)
|
|
179
179
|
else:
|
|
180
180
|
if verbose:
|
edc_notification/stubs.py
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from typing import List,
|
|
2
|
+
from typing import List, Protocol
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class BaseNotificationStub(Protocol):
|
|
6
|
-
display_name:
|
|
6
|
+
display_name: str | None
|
|
7
7
|
email_body_template: str
|
|
8
8
|
email_footer_template: str
|
|
9
|
-
email_from:
|
|
9
|
+
email_from: list[str]
|
|
10
10
|
# email_message_cls = Type[EmailMessage]
|
|
11
11
|
email_subject_template: str
|
|
12
12
|
email_test_body_line: str
|
|
13
|
-
email_to:
|
|
14
|
-
name:
|
|
13
|
+
email_to: list[str] | None
|
|
14
|
+
name: str | None
|
|
15
15
|
# sms_client: Type[Client]
|
|
16
16
|
sms_template: str
|
|
17
17
|
sms_test_line: str
|
|
@@ -29,7 +29,7 @@ class BaseNotificationStub(Protocol):
|
|
|
29
29
|
) -> bool: ...
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
class NotificationStub(BaseNotificationStub, Protocol): ...
|
|
32
|
+
class NotificationStub(BaseNotificationStub, Protocol): ...
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
class NotificationModelStub(BaseNotificationStub, Protocol):
|
|
@@ -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.apps import apps as django_apps
|
|
6
6
|
|
|
@@ -38,7 +38,7 @@ def update_mailing_lists_in_m2m(
|
|
|
38
38
|
and site_notifications.loaded
|
|
39
39
|
and userprofile.email_notifications.through == sender
|
|
40
40
|
):
|
|
41
|
-
notification_model_cls:
|
|
41
|
+
notification_model_cls: type[Notification] = django_apps.get_model(
|
|
42
42
|
"edc_notification.Notification"
|
|
43
43
|
)
|
|
44
44
|
for notification_obj in notification_model_cls.objects.filter(
|
edc_offstudy/action_items.py
CHANGED
|
@@ -14,11 +14,11 @@ class EndOfStudyAction(ActionWithNotification):
|
|
|
14
14
|
name = END_OF_STUDY_ACTION
|
|
15
15
|
display_name = "Submit End of Study Report"
|
|
16
16
|
notification_display_name = "End of Study Report"
|
|
17
|
-
parent_action_names =
|
|
17
|
+
parent_action_names = (
|
|
18
18
|
UNBLINDING_REVIEW_ACTION,
|
|
19
19
|
DEATH_REPORT_ACTION,
|
|
20
20
|
LTFU_ACTION,
|
|
21
|
-
|
|
21
|
+
)
|
|
22
22
|
show_link_to_changelist = True
|
|
23
23
|
priority = HIGH_PRIORITY
|
|
24
24
|
singleton = True
|
edc_offstudy/models.py
CHANGED
|
@@ -25,4 +25,4 @@ class SubjectOffstudy(
|
|
|
25
25
|
class Meta(BaseUuidModel.Meta):
|
|
26
26
|
verbose_name = "Subject Offstudy"
|
|
27
27
|
verbose_name_plural = "Subject Offstudy"
|
|
28
|
-
indexes = ActionNoManagersModelMixin.Meta.indexes
|
|
28
|
+
indexes = (*ActionNoManagersModelMixin.Meta.indexes, *BaseUuidModel.Meta.indexes)
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
from textwrap import fill
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from bs4 import BeautifulSoup
|
|
8
8
|
from django.apps import apps as django_apps
|
|
@@ -131,7 +131,7 @@ class CrfPdfReport(Report):
|
|
|
131
131
|
return f"{slugify(cls.get_verbose_name().lower())}s.pdf"
|
|
132
132
|
|
|
133
133
|
@classmethod
|
|
134
|
-
def get_model_cls(cls) ->
|
|
134
|
+
def get_model_cls(cls) -> type[CrfModelMixin | UniqueSubjectIdentifierModelMixin]:
|
|
135
135
|
return django_apps.get_model(cls.model)
|
|
136
136
|
|
|
137
137
|
@classmethod
|