clinicedc 2.0.4__py3-none-any.whl → 2.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of clinicedc might be problematic. Click here for more details.
- {clinicedc-2.0.4.dist-info → clinicedc-2.0.6.dist-info}/METADATA +41 -45
- {clinicedc-2.0.4.dist-info → clinicedc-2.0.6.dist-info}/RECORD +478 -478
- edc_action_item/action.py +76 -54
- edc_action_item/action_item_notification.py +1 -3
- edc_action_item/action_with_notification.py +4 -4
- edc_action_item/create_action_item.py +2 -2
- edc_action_item/create_or_update_action_type.py +1 -1
- edc_action_item/data_fixers.py +4 -5
- edc_action_item/delete_action_item.py +0 -1
- edc_action_item/modeladmin_mixins.py +2 -4
- edc_action_item/modelform_mixins/modelform_mixins.py +1 -1
- edc_action_item/models/action_item.py +53 -38
- edc_action_item/models/action_model_mixin.py +17 -17
- edc_action_item/models/action_type.py +33 -13
- edc_action_item/models/reference.py +3 -2
- edc_action_item/site_action_items.py +13 -14
- edc_action_item/stubs.py +9 -9
- edc_action_item/templatetags/action_item_extras.py +2 -2
- edc_action_item/view_utils/action_item_button.py +3 -3
- edc_action_item/view_utils/action_item_popover_list_item.py +4 -4
- edc_adherence/form_validator_mixin.py +1 -1
- edc_adverse_event/action_items/ae_followup_action.py +1 -1
- edc_adverse_event/action_items/ae_initial_action.py +1 -1
- edc_adverse_event/action_items/ae_susar_action.py +1 -1
- edc_adverse_event/action_items/ae_tmg_action.py +1 -1
- edc_adverse_event/action_items/death_report_action.py +1 -2
- edc_adverse_event/action_items/death_report_tmg_action.py +1 -1
- edc_adverse_event/action_items/death_report_tmg_second_action.py +1 -1
- edc_adverse_event/action_items/hospitalization_action.py +1 -1
- edc_adverse_event/form_validator_mixins/death_report_form_validator.py +3 -3
- edc_adverse_event/form_validator_mixins/requires_death_report_form_validator_mixin.py +3 -4
- edc_adverse_event/form_validators/death_report_tmg.py +1 -1
- edc_adverse_event/model_mixins/ae_followup/ae_followup_methods_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/ae_followup/ae_followup_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/ae_initial/ae_initial_fields_model_mixin.py +0 -1
- edc_adverse_event/model_mixins/ae_initial/ae_initial_methods_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/ae_initial/ae_initial_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/ae_special_interest/aesi_methods_model_mixin.py +4 -4
- edc_adverse_event/model_mixins/ae_special_interest/aesi_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/ae_susar/ae_susar_methods_model_mixin.py +4 -4
- edc_adverse_event/model_mixins/ae_susar/ae_susar_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/ae_tmg/ae_tmg_fields_model_mixin.py +4 -5
- edc_adverse_event/model_mixins/ae_tmg/ae_tmg_methods_model_mixin.py +4 -4
- edc_adverse_event/model_mixins/ae_tmg/ae_tmg_model_mixin.py +3 -3
- edc_adverse_event/model_mixins/death_report/death_report_extra_fields_model_mixin.py +2 -3
- edc_adverse_event/model_mixins/death_report/death_report_model_mixin.py +11 -11
- edc_adverse_event/model_mixins/death_report/death_report_tmg_model_mixin.py +9 -11
- edc_adverse_event/model_mixins/death_report/simple_death_report_model_mixin.py +7 -6
- edc_adverse_event/model_mixins/hospitaization/hospitalization_model_mixin.py +0 -1
- edc_adverse_event/modeladmin_mixins/ae_followup_admin_mixin.py +9 -11
- edc_adverse_event/modeladmin_mixins/ae_initial_admin_mixin.py +4 -6
- edc_adverse_event/modeladmin_mixins/modeladmin_mixins.py +1 -1
- edc_adverse_event/modeladmin_mixins/utils.py +4 -4
- edc_adverse_event/models/signals.py +7 -19
- edc_adverse_event/pdf_reports/death_pdf_report.py +8 -9
- edc_adverse_event/templatetags/edc_adverse_event_extras.py +10 -20
- edc_adverse_event/urls.py +7 -3
- edc_adverse_event/utils.py +2 -2
- edc_adverse_event/view_utils/tmg_button.py +4 -4
- edc_appconfig/system_checks.py +1 -1
- edc_appointment/appointment_status_updater.py +14 -15
- edc_appointment/creators/appointment_creator.py +7 -11
- edc_appointment/creators/appointments_creator.py +1 -1
- edc_appointment/creators/unscheduled_appointment_creator.py +12 -13
- edc_appointment/form_validator_mixins/next_appointment_crf_form_validator_mixin.py +1 -3
- edc_appointment/form_validator_mixins/window_period_form_validator_mixin.py +7 -6
- edc_appointment/form_validators/appointment_form_validator.py +2 -4
- edc_appointment/form_validators/utils.py +4 -4
- edc_appointment/managers.py +4 -4
- edc_appointment/model_mixins/appointment_methods_model_mixin.py +2 -2
- edc_appointment/model_mixins/appointment_model_mixin.py +5 -5
- edc_appointment/model_mixins/window_period_model_mixin.py +1 -1
- edc_appointment/models/appointment.py +1 -1
- edc_appointment/skip_appointments.py +5 -6
- edc_appointment/stubs.py +12 -12
- edc_appointment/utils.py +41 -47
- edc_appointment/view_utils/appointment_button.py +3 -5
- edc_auth/admin/role_admin.py +5 -7
- edc_auth/auth_objects/default_roles.py +1 -3
- edc_auth/auth_updater/auth_updater.py +5 -7
- edc_auth/auth_updater/group_updater.py +8 -8
- edc_auth/auth_updater/role_updater.py +1 -2
- edc_auth/get_app_codenames.py +1 -3
- edc_auth/import_users.py +19 -19
- edc_auth/models/role.py +4 -3
- edc_auth/password_setter.py +6 -7
- edc_auth/send_new_credentials_to_user.py +2 -3
- edc_auth/site_auths.py +3 -3
- edc_consent/actions.py +4 -5
- edc_consent/consent_definition.py +8 -11
- edc_consent/consent_definition_extension.py +3 -3
- edc_consent/form_validators/__init__.py +1 -1
- edc_consent/model_mixins/__init__.py +2 -2
- edc_consent/model_mixins/consent_version_model_mixin.py +1 -1
- edc_consent/modeladmin_mixins/consent_model_admin_mixin.py +1 -2
- edc_consent/modelform_mixins/consent_modelform_mixin/consent_modelform_validation_mixin.py +3 -5
- edc_consent/site_consents.py +10 -15
- edc_consent/stubs.py +2 -2
- edc_consent/utils.py +1 -1
- edc_constants/utils.py +2 -4
- edc_crf/crf_form_validator_mixins.py +2 -2
- edc_crf/model_mixins/crf_no_manager_model_mixin.py +2 -2
- edc_crf/model_mixins/crf_status_model_mixin.py +1 -1
- edc_crf/models/crf_status.py +10 -12
- edc_crf/update_crf_status_command.py +1 -3
- edc_dashboard/management/commands/update_search_slugs.py +8 -11
- edc_dashboard/templatetags/edc_dashboard_extras.py +1 -1
- edc_dashboard/url_config.py +3 -3
- edc_dashboard/utils.py +3 -4
- edc_dashboard/views/dashboard_view.py +1 -1
- edc_data_manager/models/data_dictionary.py +1 -2
- edc_data_manager/models/data_query.py +3 -3
- edc_data_manager/models/query_rule.py +2 -2
- edc_data_manager/tasks.py +0 -2
- edc_device/device.py +1 -1
- edc_device/view_mixins.py +1 -1
- edc_document_status/fieldsets.py +1 -3
- edc_document_status/model_mixins.py +2 -2
- edc_document_status/modeladmin_mixins.py +1 -4
- edc_egfr/admin/egfr_drop_notification_admin_mixin.py +5 -7
- edc_egfr/egfr.py +6 -7
- edc_egfr/get_drop_notification_model.py +1 -1
- edc_egfr/model_mixins/egfr_drop_notification_model_mixin.py +1 -1
- edc_export/archive_exporter.py +26 -27
- edc_export/exportables.py +2 -3
- edc_export/management/commands/import_receipts.py +6 -6
- edc_export/model_exporter/file_history_updater.py +1 -1
- edc_export/model_exporter/model_exporter.py +1 -1
- edc_export/model_exporter/value_getter.py +6 -6
- edc_export/model_mixins/notification_model_mixin.py +4 -4
- edc_export/models/data_request.py +3 -3
- edc_export/models/data_request_history.py +2 -2
- edc_export/models/export_receipt.py +1 -1
- edc_export/models/file_history.py +5 -5
- edc_export/models/plan.py +4 -4
- edc_export/models/upload_export_receipt_file.py +2 -2
- edc_export/utils.py +1 -1
- edc_facility/facility.py +6 -6
- edc_facility/holidays.py +2 -5
- edc_facility/import_holidays.py +6 -6
- edc_facility/model_mixins.py +1 -1
- edc_facility/models/holiday.py +1 -1
- edc_facility/utils.py +3 -3
- edc_fieldsets/fieldsets.py +1 -1
- edc_form_describer/forms_reference.py +5 -8
- edc_form_describer/make_forms_reference.py +1 -1
- edc_form_describer/management/commands/make_forms_reference.py +1 -1
- edc_form_label/custom_label_condition.py +1 -1
- edc_form_label/form_label.py +2 -2
- edc_form_runners/exceptions.py +1 -1
- edc_form_runners/models/issue.py +3 -2
- edc_form_runners/site.py +2 -2
- edc_form_runners/templatetags/form_runners_extras.py +1 -1
- edc_form_runners/utils.py +5 -5
- edc_form_validators/applicable_field_validator.py +32 -32
- edc_form_validators/base_form_validator.py +1 -1
- edc_form_validators/extra_mixins/study_day_form_validator.py +1 -1
- edc_form_validators/many_to_many_field_validator.py +38 -43
- edc_form_validators/other_specify_field_validator.py +9 -17
- edc_form_validators/required_field_validator.py +34 -39
- edc_form_validators/test_case_mixin.py +5 -5
- edc_identifier/admin.py +1 -3
- edc_identifier/identifier.py +2 -3
- edc_identifier/model_mixins.py +5 -8
- edc_identifier/research_identifier.py +10 -12
- edc_identifier/short_identifier.py +1 -1
- edc_identifier/simple_identifier.py +22 -22
- edc_identifier/utils.py +1 -1
- edc_lab/admin/fieldsets.py +7 -9
- edc_lab/admin/modeladmin_mixins.py +5 -6
- edc_lab/form_validators/requisition_form_validator_mixin.py +2 -3
- edc_lab/forms/box_form.py +5 -11
- edc_lab/identifiers/aliquot_identifier.py +5 -8
- edc_lab/identifiers/prefix.py +2 -5
- edc_lab/lab/aliquot_creator.py +1 -2
- edc_lab/lab/aliquot_type.py +2 -4
- edc_lab/lab/manifest.py +5 -7
- edc_lab/lab/requisition_panel.py +2 -4
- edc_lab/lab/requisition_panel_group.py +5 -7
- edc_lab/model_mixins/requisition/requisition_model_mixin.py +5 -4
- edc_lab/model_mixins/shipping/manifest_model_mixin.py +6 -6
- edc_lab/model_mixins/shipping/verify_model_mixin.py +4 -3
- edc_lab/models/aliquot.py +1 -1
- edc_lab/models/box.py +2 -2
- edc_lab/models/box_item.py +1 -1
- edc_lab/models/box_type.py +1 -1
- edc_lab/models/manifest/manifest_item.py +1 -1
- edc_lab/pdf_reports/manifest_pdf_report.py +3 -7
- edc_lab/site_labs.py +3 -3
- edc_lab_dashboard/dashboard_templates.py +1 -1
- edc_lab_dashboard/view_mixins/box_view_mixin.py +4 -9
- edc_lab_dashboard/views/action_views/action_view.py +1 -2
- edc_lab_dashboard/views/action_views/manage_manifest_view.py +1 -1
- edc_lab_dashboard/views/action_views/manifest_view.py +9 -11
- edc_lab_dashboard/views/action_views/pack_view.py +11 -14
- edc_lab_results/calculate_missing.py +2 -2
- edc_lab_results/fieldsets.py +6 -6
- edc_lab_results/model_mixin_factories/__init__.py +2 -2
- edc_lab_results/model_mixin_factories/reportable_result_model_mixin_factory.py +7 -9
- edc_lab_results/model_mixin_factories/result_model_mixin_factory.py +7 -9
- edc_lab_results/model_mixins/fbg_model_mixin.py +1 -1
- edc_lab_results/model_mixins/glucose_model_mixin.py +1 -1
- edc_label/admin.py +1 -3
- edc_label/label.py +2 -4
- edc_label/label_template.py +1 -1
- edc_label/subject_label.py +1 -3
- edc_list_data/admin.py +3 -5
- edc_list_data/load_list_data.py +1 -1
- edc_list_data/load_model_data.py +1 -3
- edc_list_data/model_mixins.py +3 -5
- edc_list_data/preload_data.py +2 -4
- edc_list_data/site_list_data.py +6 -7
- edc_listboard/filters/listboard_filter.py +2 -2
- edc_listboard/views/listboard_view.py +1 -3
- edc_locator/models.py +2 -2
- edc_locator/view_mixins/subject_locator_view_mixins.py +2 -2
- edc_ltfu/action_items.py +1 -1
- edc_ltfu/model_mixins.py +0 -2
- edc_ltfu/models.py +4 -4
- edc_metadata/admin/modeladmin_mixins.py +1 -1
- edc_metadata/admin/requisition_metadata.py +2 -4
- edc_metadata/metadata/crf_metadata_getter.py +1 -3
- edc_metadata/metadata/requisition_metadata_getter.py +1 -3
- edc_metadata/metadata_handler.py +2 -2
- edc_metadata/metadata_helper/metadata_helper_mixin.py +2 -3
- edc_metadata/metadata_mixins/source_model_metadata_mixin.py +2 -2
- edc_metadata/metadata_refresher.py +1 -1
- edc_metadata/metadata_rules/crf/crf_rule_group.py +3 -5
- edc_metadata/metadata_rules/logic.py +1 -1
- edc_metadata/metadata_rules/requisition/requisition_rule.py +4 -4
- edc_metadata/metadata_rules/requisition/requisition_rule_group.py +2 -2
- edc_metadata/metadata_rules/rule_evaluator.py +5 -6
- edc_metadata/metadata_rules/rule_group_metaclass.py +2 -3
- edc_metadata/metadata_rules/site.py +6 -7
- edc_metadata/metadata_updater.py +2 -2
- edc_metadata/metadata_wrappers/metadata_wrapper.py +4 -4
- edc_metadata/model_mixins/creates/creates_metadata_model_mixin.py +6 -7
- edc_metadata/model_mixins/updates/updates_crf_metadata_model_mixin.py +2 -2
- edc_metadata/model_mixins/updates/updates_metadata_model_mixin.py +2 -2
- edc_metadata/model_mixins/updates/updates_requisition_metadata_model_mixin.py +2 -2
- edc_metadata/models/crf_metadata.py +27 -29
- edc_metadata/models/crf_metadata_model_mixin.py +1 -1
- edc_metadata/models/requisition_metadata.py +29 -31
- edc_metadata/stubs.py +17 -17
- edc_metadata/utils.py +4 -4
- edc_model/__init__.py +2 -2
- edc_model/models/historical_records.py +2 -2
- edc_model/models/signals.py +1 -1
- edc_model/models/url_model_mixin.py +1 -1
- edc_model/utils.py +0 -2
- edc_model_admin/dashboard/model_admin_dashboard_mixin.py +1 -3
- edc_model_admin/history/model_admin_simple_history.py +7 -9
- edc_model_admin/mixins/base_model_admin_redirect_mixin.py +4 -6
- edc_model_admin/mixins/model_admin_limit_to_selected_foreignkey.py +2 -2
- edc_model_admin/mixins/model_admin_model_redirect_mixin.py +2 -10
- edc_model_admin/mixins/model_admin_next_url_redirect_mixin.py +8 -9
- edc_model_admin/utils.py +6 -9
- edc_model_form/mixins/__init__.py +1 -1
- edc_model_form/mixins/base_model_form_mixin.py +1 -1
- edc_model_to_dataframe/model_to_dataframe.py +2 -4
- edc_navbar/site_navbars.py +2 -2
- edc_navbar/system_checks.py +1 -1
- edc_notification/mailing_list_manager.py +5 -8
- edc_notification/management/commands/list_recipients_by_notification.py +1 -1
- edc_notification/models/__init__.py +4 -2
- edc_notification/notification/graded_event_notification.py +2 -4
- edc_notification/notification/model_notification.py +1 -3
- edc_notification/notification/notification.py +14 -17
- edc_notification/site_notifications.py +7 -7
- edc_notification/stubs.py +6 -6
- edc_notification/update_mailing_lists_in_m2m.py +2 -2
- edc_offstudy/action_items.py +2 -2
- edc_offstudy/model_mixins/offstudy_model_mixin.py +1 -1
- edc_offstudy/models.py +1 -1
- edc_pdf_reports/crf_pdf_report.py +2 -2
- edc_pdf_reports/model_mixins.py +2 -2
- edc_pdf_reports/report.py +4 -5
- edc_pdf_reports/utils.py +1 -1
- edc_pdutils/dataframes/get_subject_consent.py +1 -3
- edc_pdutils/df_exporters/csv_exporter.py +5 -7
- edc_pdutils/df_exporters/tables_exporter.py +2 -2
- edc_pdutils/df_handlers/crf_df_handler.py +1 -2
- edc_pdutils/dialects/crf_dialect.py +3 -3
- edc_pdutils/management/commands/export_models.py +6 -7
- edc_pdutils/site_values_mappings.py +2 -2
- edc_pdutils/utils/datetime_to_date.py +1 -2
- edc_pdutils/utils/refresh_model_from_dataframe.py +1 -3
- edc_pdutils/utils/table_names.py +2 -4
- edc_pdutils/utils/undash.py +1 -1
- edc_pharmacy/admin/medication/assignment_admin.py +2 -4
- edc_pharmacy/admin/medication/dosage_guideline_admin.py +2 -4
- edc_pharmacy/admin/medication/formulation_admin.py +3 -5
- edc_pharmacy/admin/medication/medication_admin.py +3 -5
- edc_pharmacy/admin/prescription/rx_refill_admin.py +7 -9
- edc_pharmacy/admin/stock/lot_admin.py +5 -7
- edc_pharmacy/admin/stock/order_admin.py +2 -2
- edc_pharmacy/admin/stock/order_item_admin.py +6 -6
- edc_pharmacy/admin/stock/receive_admin.py +1 -1
- edc_pharmacy/admin/stock/receive_item_admin.py +2 -2
- edc_pharmacy/admin/stock/stock_request_admin.py +4 -6
- edc_pharmacy/dosage_calculator.py +4 -4
- edc_pharmacy/form_validators/crf/study_medication_form_validator.py +8 -8
- edc_pharmacy/forms/stock/stock_request_form.py +2 -4
- edc_pharmacy/model_mixins/study_medication_crf_model_mixin.py +2 -2
- edc_pharmacy/models/medication/formulation.py +3 -4
- edc_pharmacy/models/medication/medication.py +1 -2
- edc_pharmacy/models/prescription/rx.py +2 -2
- edc_pharmacy/models/prescription/rx_refill.py +7 -9
- edc_pharmacy/models/reports/stock_availability.py +3 -4
- edc_pharmacy/models/stock/confirmation_at_site.py +1 -3
- edc_pharmacy/models/stock/order.py +1 -2
- edc_pharmacy/models/stock/receive.py +3 -4
- edc_pharmacy/models/stock/receive_item.py +2 -3
- edc_pharmacy/models/stock/stock.py +1 -2
- edc_pharmacy/models/stock/stock_adjustment.py +1 -2
- edc_pharmacy/models/stock/stock_request.py +4 -5
- edc_pharmacy/models/storage/box.py +1 -1
- edc_pharmacy/models/storage/items/container_model_mixin.py +1 -1
- edc_pharmacy/models/storage/room.py +1 -1
- edc_pharmacy/models/storage/shelf.py +1 -1
- edc_pharmacy/models/storage/utils.py +15 -16
- edc_pharmacy/prescribe/create_prescription.py +5 -5
- edc_pharmacy/refill/refill_creator.py +1 -1
- edc_pharmacy/sample_usb_printing/usb_printing.py +1 -1
- edc_pharmacy/utils/__init__.py +1 -1
- edc_pharmacy/utils/confirm_stock.py +3 -3
- edc_pharmacy/utils/confirm_stock_at_site.py +9 -8
- edc_pharmacy/utils/dispense.py +5 -8
- edc_pharmacy/utils/format_qty.py +3 -3
- edc_pharmacy/utils/get_codenames.py +1 -1
- edc_pharmacy/utils/miscellaneous.py +1 -1
- edc_pharmacy/utils/process_repack_request.py +18 -19
- edc_pharmacy/utils/process_repack_request_queryset.py +0 -2
- edc_pharmacy/utils/stock_request/bulk_create_stock_request_items.py +2 -3
- edc_pharmacy/views/add_to_storage_bin_view.py +1 -1
- edc_pharmacy/views/allocate_to_subject_view.py +2 -6
- edc_pharmacy/views/confirm_stock_from_instance_view.py +4 -4
- edc_pharmacy/views/confirm_stock_from_queryset_view.py +1 -5
- edc_pharmacy/views/confirmation_at_site_view.py +2 -3
- edc_pharmacy/views/dispense_view.py +1 -1
- edc_pharmacy/views/move_to_storage_bin_view.py +2 -3
- edc_pharmacy/views/print_labels_view.py +30 -31
- edc_pharmacy/views/transfer_stock_view.py +1 -1
- edc_prn/prn.py +1 -1
- edc_prn/site_prn_forms.py +1 -2
- edc_protocol_incident/action_items.py +2 -2
- edc_protocol_incident/model_mixins/protocol_deviation_violation_model_mixin.py +3 -3
- edc_protocol_incident/model_mixins/protocol_incident_model_mixin.py +3 -5
- edc_protocol_incident/modeladmin_mixins.py +3 -5
- edc_protocol_incident/models/protocol_deviation_violation.py +5 -5
- edc_protocol_incident/models/protocol_incident.py +5 -5
- edc_pylabels/site_label_configs.py +5 -5
- edc_qareports/model_mixins/qa_report_model_mixin.py +4 -2
- edc_qareports/models/qa_reports_log.py +1 -2
- edc_qareports/utils.py +1 -2
- edc_randomization/admin.py +3 -5
- edc_randomization/auth_objects.py +2 -3
- edc_randomization/blinding.py +1 -1
- edc_randomization/constants.py +2 -4
- edc_randomization/model_mixins.py +3 -3
- edc_randomization/randomization_list_importer.py +13 -14
- edc_randomization/randomization_list_verifier.py +11 -13
- edc_randomization/randomizer.py +2 -2
- edc_randomization/system_checks.py +1 -2
- edc_randomization/utils.py +5 -5
- edc_refusal/admin.py +1 -1
- edc_refusal/model_mixins.py +1 -1
- edc_refusal/utils.py +1 -1
- edc_registration/model_mixins/updates_or_creates_registered_subject_model_mixin.py +2 -2
- edc_registration/modeladmin_mixins.py +4 -7
- edc_registration/models/registered_subject.py +7 -9
- edc_registration/utils.py +3 -4
- edc_reportable/data/grading_data/daids_july_2017.py +3 -3
- edc_reportable/evaluator.py +5 -5
- edc_reportable/formula.py +1 -1
- edc_reportable/reference_range_evaluator.py +3 -3
- edc_reportable/utils/convert_units.py +10 -12
- edc_reportable/utils/grading_data_model_cls.py +2 -2
- edc_reportable/utils/grading_exception_model_cls.py +2 -2
- edc_reportable/utils/molecular_weight_model_cls.py +2 -2
- edc_reportable/utils/normal_data_model_cls.py +2 -2
- edc_reportable/utils/reference_range_colllection_model_cls.py +2 -2
- edc_screening/age_evaluator.py +2 -4
- edc_screening/eligibility.py +1 -1
- edc_screening/form_validator_mixins.py +2 -2
- edc_screening/model_mixins/screening_methods_model_mixin.py +1 -1
- edc_screening/screening_eligibility.py +1 -1
- edc_screening/utils.py +4 -6
- edc_search/model_mixins.py +1 -1
- edc_search/search_slug.py +1 -3
- edc_search/updater.py +1 -1
- edc_sites/admin/site_model_admin_mixin.py +2 -2
- edc_sites/model_mixins/site_model_mixin.py +1 -2
- edc_sites/modelform_mixins.py +2 -2
- edc_sites/models/__init__.py +1 -1
- edc_sites/models/site_profile.py +4 -4
- edc_sites/site.py +24 -32
- edc_sites/system_checks.py +1 -1
- edc_sites/utils/get_message_text.py +1 -1
- edc_sites/utils/valid_site_for_subject_or_raise.py +5 -6
- edc_subject_dashboard/requisition_report.py +4 -5
- edc_subject_dashboard/requisition_verifier.py +4 -2
- edc_subject_dashboard/templatetags/edc_subject_dashboard_extras.py +16 -17
- edc_subject_dashboard/view_utils/__init__.py +1 -1
- edc_subject_dashboard/view_utils/crf_button.py +1 -3
- edc_subject_dashboard/view_utils/subject_consent_dashboard_button.py +2 -2
- edc_subject_dashboard/view_utils/subject_consent_listboard_button.py +2 -2
- edc_subject_dashboard/view_utils/timepoint_status_button.py +1 -4
- edc_subject_dashboard/views/requisition_print_actions_view.py +2 -2
- edc_subject_dashboard/views/subject_dashboard_view.py +1 -1
- edc_timepoint/model_mixins.py +2 -2
- edc_transfer/action_items.py +1 -1
- edc_transfer/model_mixins.py +4 -3
- edc_transfer/modeladmin_mixins.py +3 -6
- edc_unblinding/action_items.py +2 -2
- edc_unblinding/models/unblinding_request.py +3 -3
- edc_unblinding/models/unblinding_review.py +3 -3
- edc_utils/__init__.py +6 -5
- edc_utils/age.py +1 -1
- edc_utils/celery.py +3 -8
- edc_utils/get_static_file.py +1 -1
- edc_utils/text.py +5 -6
- edc_view_utils/__init__.py +3 -3
- edc_view_utils/dashboard_model_button.py +4 -4
- edc_view_utils/model_button.py +7 -8
- edc_view_utils/perms.py +3 -3
- edc_visit_schedule/action_items.py +2 -2
- edc_visit_schedule/admin/list_filters.py +11 -9
- edc_visit_schedule/admin/subject_schedule_history_admin.py +13 -15
- edc_visit_schedule/admin/visit_schedule_admin.py +7 -7
- edc_visit_schedule/fieldsets.py +4 -6
- edc_visit_schedule/management/commands/find_invalid_onschedules.py +1 -1
- edc_visit_schedule/model_mixins/off_schedule_model_mixin.py +3 -1
- edc_visit_schedule/model_mixins/on_schedule_model_mixin.py +1 -1
- edc_visit_schedule/modelform_mixins/__init__.py +1 -1
- edc_visit_schedule/modelform_mixins/off_schedule_modelform_mixin.py +1 -2
- edc_visit_schedule/models/subject_schedule_history.py +2 -1
- edc_visit_schedule/models/visit_schedule.py +2 -2
- edc_visit_schedule/schedule/schedule.py +11 -12
- edc_visit_schedule/schedule/visit_collection.py +4 -3
- edc_visit_schedule/schedule/window.py +19 -12
- edc_visit_schedule/site_visit_schedules.py +9 -9
- edc_visit_schedule/subject_schedule.py +6 -7
- edc_visit_schedule/system_checks.py +1 -1
- edc_visit_schedule/view_mixins.py +1 -1
- edc_visit_schedule/visit/crf.py +3 -7
- edc_visit_schedule/visit/requisition.py +2 -2
- edc_visit_schedule/visit/visit.py +6 -7
- edc_visit_schedule/visit/window_period.py +8 -8
- edc_visit_schedule/visit_schedule/schedules_collection.py +2 -5
- edc_visit_tracking/action_items.py +11 -13
- edc_visit_tracking/form_validators/visit_form_validator.py +5 -5
- edc_visit_tracking/model_mixins/base/visit_methods_model_mixin.py +3 -4
- edc_visit_tracking/model_mixins/crfs/visit_tracking_crf_model_mixin.py +2 -2
- edc_visit_tracking/model_mixins/subject_visit_missed_model_mixin.py +2 -3
- edc_visit_tracking/model_mixins/utils.py +1 -1
- edc_visit_tracking/model_mixins/visit_model_mixin/previous_visit_model_mixin.py +2 -2
- edc_visit_tracking/model_mixins/visit_model_mixin/visit_model_mixin.py +3 -3
- edc_visit_tracking/modeladmin_mixins/crf_model_admin_mixin.py +6 -4
- edc_visit_tracking/modeladmin_mixins/visit_model_admin_mixin.py +6 -7
- edc_visit_tracking/modelform_mixins/crf/visit_tracking_crf_modelform_mixin.py +4 -5
- edc_visit_tracking/modelform_mixins/visit_tracking_modelform_mixin.py +3 -4
- edc_visit_tracking/models/subject_visit.py +1 -1
- edc_visit_tracking/models/subject_visit_missed.py +1 -1
- edc_visit_tracking/stubs.py +7 -7
- edc_visit_tracking/typing_stubs.py +11 -11
- edc_visit_tracking/utils.py +5 -5
- edc_visit_tracking/view_utils/related_visit_button.py +5 -5
- edc_vitals/calculators/bmi.py +1 -1
- edc_vitals/form_validators/blood_pressure_form_validator_mixin.py +8 -10
- edc_vitals/form_validators/bmi_form_validator_mixin.py +1 -1
- edc_vitals/form_validators/weight_height_with_bmi_form_validator_mixin.py +5 -2
- edc_vitals/model_mixins/blood_pressure_model_mixin.py +3 -4
- edc_vitals/model_mixins/weight_height_bmi_model_mixin.py +4 -4
- edc_vitals/utils.py +1 -1
- edc_vitals/validators.py +1 -1
- {clinicedc-2.0.4.dist-info → clinicedc-2.0.6.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.4.dist-info → clinicedc-2.0.6.dist-info}/licenses/LICENSE +0 -0
edc_utils/text.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import random
|
|
2
2
|
import re
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import Optional
|
|
5
4
|
from zoneinfo import ZoneInfo
|
|
6
5
|
|
|
7
6
|
from django.conf import settings
|
|
@@ -12,7 +11,7 @@ safe_allowed_chars = "ABCDEFGHKMNPRTUVWXYZ2346789"
|
|
|
12
11
|
def get_safe_random_string(length=12, safe=None, allowed_chars=None):
|
|
13
12
|
safe = True if safe is None else safe
|
|
14
13
|
allowed_chars = allowed_chars or (
|
|
15
|
-
"
|
|
14
|
+
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTUVWXYZ012346789!@#%^&*()?<>.,[]{}"
|
|
16
15
|
)
|
|
17
16
|
if safe:
|
|
18
17
|
allowed_chars = "ABCDEFGHKMNPRTUVWXYZ2346789"
|
|
@@ -64,10 +63,10 @@ def convert_from_camel(name):
|
|
|
64
63
|
|
|
65
64
|
|
|
66
65
|
def formatted_datetime(
|
|
67
|
-
aware_datetime:
|
|
68
|
-
php_dateformat:
|
|
69
|
-
tz:
|
|
70
|
-
format_as_date:
|
|
66
|
+
aware_datetime: datetime | None,
|
|
67
|
+
php_dateformat: str | None = None,
|
|
68
|
+
tz: str | None = None,
|
|
69
|
+
format_as_date: bool | None = None,
|
|
71
70
|
):
|
|
72
71
|
"""Returns a formatted datetime string, localized by default.
|
|
73
72
|
|
edc_view_utils/__init__.py
CHANGED
|
@@ -8,6 +8,9 @@ from .query_button import QueryButton
|
|
|
8
8
|
from .render_history_and_query_buttons import render_history_and_query_buttons
|
|
9
9
|
|
|
10
10
|
__all__ = [
|
|
11
|
+
"ADD",
|
|
12
|
+
"CHANGE",
|
|
13
|
+
"VIEW",
|
|
11
14
|
"DashboardModelButton",
|
|
12
15
|
"HistoryButton",
|
|
13
16
|
"ModelButton",
|
|
@@ -16,7 +19,4 @@ __all__ = [
|
|
|
16
19
|
"PrnButton",
|
|
17
20
|
"QueryButton",
|
|
18
21
|
"render_history_and_query_buttons",
|
|
19
|
-
"ADD",
|
|
20
|
-
"CHANGE",
|
|
21
|
-
"VIEW",
|
|
22
22
|
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
7
|
from .model_button import ModelButton
|
|
@@ -15,9 +15,9 @@ if TYPE_CHECKING:
|
|
|
15
15
|
from edc_metadata.models import CrfMetadata, RequisitionMetadata
|
|
16
16
|
from edc_model.models import BaseUuidModel
|
|
17
17
|
|
|
18
|
-
class CrfModel(CrfModelMixin, BaseUuidModel): ...
|
|
18
|
+
class CrfModel(CrfModelMixin, BaseUuidModel): ...
|
|
19
19
|
|
|
20
|
-
class RequisitionModel(RequisitionModelMixin, BaseUuidModel): ...
|
|
20
|
+
class RequisitionModel(RequisitionModelMixin, BaseUuidModel): ...
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
__all__ = ["DashboardModelButton"]
|
|
@@ -38,7 +38,7 @@ class DashboardModelButton(ModelButton):
|
|
|
38
38
|
metadata_model_obj: CrfMetadata | RequisitionMetadata = None
|
|
39
39
|
appointment: Appointment = None
|
|
40
40
|
next_url_name: str = field(default="subject_dashboard_url")
|
|
41
|
-
model_cls:
|
|
41
|
+
model_cls: type[CrfModel | RequisitionModel] = field(default=None, init=False)
|
|
42
42
|
|
|
43
43
|
def __post_init__(self):
|
|
44
44
|
self.model_cls = self.metadata_model_obj.model_cls
|
edc_view_utils/model_button.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
5
|
from uuid import uuid4
|
|
6
6
|
|
|
7
7
|
from django.core.handlers.wsgi import WSGIRequest
|
|
@@ -28,7 +28,7 @@ ADD: int = 0
|
|
|
28
28
|
CHANGE = 1
|
|
29
29
|
VIEW = 2
|
|
30
30
|
|
|
31
|
-
__all__ = ["
|
|
31
|
+
__all__ = ["ADD", "CHANGE", "VIEW", "ModelButton"]
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class ModelButtonError(Exception):
|
|
@@ -39,7 +39,7 @@ class ModelButtonError(Exception):
|
|
|
39
39
|
class ModelButton:
|
|
40
40
|
user: User = None
|
|
41
41
|
model_obj: Model = None
|
|
42
|
-
model_cls:
|
|
42
|
+
model_cls: type[Model] = field(default=None)
|
|
43
43
|
current_site: Site = None
|
|
44
44
|
subject_identifier: str | None = None
|
|
45
45
|
request: WSGIRequestObject | None = None
|
|
@@ -104,11 +104,10 @@ class ModelButton:
|
|
|
104
104
|
@property
|
|
105
105
|
def disabled(self) -> str:
|
|
106
106
|
disabled = "disabled"
|
|
107
|
-
if not self.model_obj and self.perms.add
|
|
107
|
+
if (not self.model_obj and self.perms.add) or (
|
|
108
|
+
self.model_obj and (self.perms.change or self.perms.view)
|
|
109
|
+
):
|
|
108
110
|
disabled = ""
|
|
109
|
-
else:
|
|
110
|
-
if self.model_obj and (self.perms.change or self.perms.view):
|
|
111
|
-
disabled = ""
|
|
112
111
|
return disabled
|
|
113
112
|
|
|
114
113
|
@property
|
|
@@ -116,7 +115,7 @@ class ModelButton:
|
|
|
116
115
|
btn_id = f"{self.model_cls._meta.label_lower.split('.')[1]}-{uuid4().hex}"
|
|
117
116
|
if self.model_obj:
|
|
118
117
|
btn_id = (
|
|
119
|
-
f"{self.model_cls._meta.label_lower.split('.')[1]}-
|
|
118
|
+
f"{self.model_cls._meta.label_lower.split('.')[1]}-{self.model_obj.id.hex}"
|
|
120
119
|
)
|
|
121
120
|
return btn_id
|
|
122
121
|
|
edc_view_utils/perms.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import InitVar, dataclass, field
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from django.contrib.auth import get_permission_codename
|
|
7
7
|
|
|
@@ -60,7 +60,7 @@ class Perms:
|
|
|
60
60
|
"""
|
|
61
61
|
|
|
62
62
|
user: User = None
|
|
63
|
-
model_cls: InitVar[
|
|
63
|
+
model_cls: InitVar[type[models.Model]] = None
|
|
64
64
|
current_site: Site = None
|
|
65
65
|
site: Site = None
|
|
66
66
|
add: bool = field(default=False, init=False)
|
|
@@ -70,7 +70,7 @@ class Perms:
|
|
|
70
70
|
view_only: bool = field(default=False, init=False)
|
|
71
71
|
_site_perms: SitePerms = field(default=None, init=False)
|
|
72
72
|
|
|
73
|
-
def __post_init__(self, model_cls:
|
|
73
|
+
def __post_init__(self, model_cls: type[models.Model]):
|
|
74
74
|
# self.user = get_object_or_404(User, pk=self.user.id)
|
|
75
75
|
# set add, change, delete, view attrs for this user
|
|
76
76
|
# based on the model class
|
|
@@ -11,10 +11,10 @@ class OffscheduleAction(ActionWithNotification):
|
|
|
11
11
|
name = OFFSCHEDULE_ACTION
|
|
12
12
|
display_name = "Submit Off-Schedule"
|
|
13
13
|
notification_display_name = "Off-Schedule"
|
|
14
|
-
parent_action_names =
|
|
14
|
+
parent_action_names = (
|
|
15
15
|
DEATH_REPORT_ACTION,
|
|
16
16
|
LTFU_ACTION,
|
|
17
|
-
|
|
17
|
+
)
|
|
18
18
|
reference_model = "edc_visit_schedule.offschedule"
|
|
19
19
|
show_link_to_changelist = True
|
|
20
20
|
admin_site_name = "edc_visit_schedule_admin"
|
|
@@ -11,7 +11,7 @@ class ScheduleStatusListFilter(SimpleListFilter):
|
|
|
11
11
|
title = "Schedule status"
|
|
12
12
|
parameter_name = "schedule_status"
|
|
13
13
|
|
|
14
|
-
def lookups(self, request, model_admin):
|
|
14
|
+
def lookups(self, request, model_admin): # noqa: ARG002
|
|
15
15
|
names = []
|
|
16
16
|
qs = (
|
|
17
17
|
SubjectScheduleHistory.objects.values(
|
|
@@ -20,13 +20,15 @@ class ScheduleStatusListFilter(SimpleListFilter):
|
|
|
20
20
|
.order_by("schedule_name", "onschedule_model", "offschedule_model")
|
|
21
21
|
.annotate(cnt=Count("schedule_name"))
|
|
22
22
|
)
|
|
23
|
-
for
|
|
24
|
-
names.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
for s in ["on", "off"]:
|
|
24
|
+
names.extend(
|
|
25
|
+
[
|
|
26
|
+
(
|
|
27
|
+
f"{obj.get('schedule_name')}__{s}",
|
|
28
|
+
f"{s.title()}: {obj.get('schedule_name')}",
|
|
29
|
+
)
|
|
30
|
+
for obj in qs
|
|
31
|
+
]
|
|
30
32
|
)
|
|
31
33
|
return tuple(names)
|
|
32
34
|
|
|
@@ -37,7 +39,7 @@ class ScheduleStatusListFilter(SimpleListFilter):
|
|
|
37
39
|
schedule_name=schedule_name, offschedule_datetime__isnull=status == "on"
|
|
38
40
|
).values_list("subject_identifier", flat=True)
|
|
39
41
|
|
|
40
|
-
def queryset(self, request, queryset):
|
|
42
|
+
def queryset(self, request, queryset): # noqa: ARG002
|
|
41
43
|
if self.value() and self.value() != "none":
|
|
42
44
|
queryset = queryset.filter(subject_identifier__in=self.subject_identifiers)
|
|
43
45
|
return queryset
|
|
@@ -61,25 +61,23 @@ class SubjectScheduleHistoryAdmin(
|
|
|
61
61
|
"offschedule_datetime",
|
|
62
62
|
"visit_schedule_name",
|
|
63
63
|
"schedule_name",
|
|
64
|
-
|
|
64
|
+
*list_filter,
|
|
65
|
+
)
|
|
65
66
|
|
|
66
67
|
def get_readonly_fields(self, request, obj=None) -> tuple:
|
|
67
68
|
fields = super().get_readonly_fields(request, obj=obj)
|
|
68
|
-
|
|
69
|
-
fields
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
+ audit_fields
|
|
69
|
+
return (
|
|
70
|
+
*fields,
|
|
71
|
+
"subject_identifier",
|
|
72
|
+
"visit_schedule_name",
|
|
73
|
+
"schedule_name",
|
|
74
|
+
"schedule_status",
|
|
75
|
+
"onschedule_datetime",
|
|
76
|
+
"offschedule_datetime",
|
|
77
|
+
"onschedule_model",
|
|
78
|
+
"offschedule_model",
|
|
79
|
+
*audit_fields,
|
|
81
80
|
)
|
|
82
|
-
return fields
|
|
83
81
|
|
|
84
82
|
def dashboard(self, obj=None, label=None) -> str:
|
|
85
83
|
try:
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Tuple
|
|
2
|
-
|
|
3
1
|
from django.contrib.admin.decorators import register
|
|
4
2
|
from django_audit_fields.admin import audit_fieldset_tuple
|
|
5
3
|
|
|
@@ -39,7 +37,7 @@ class VisitScheduleAdmin(SimpleHistoryAdmin):
|
|
|
39
37
|
"visit_name",
|
|
40
38
|
)
|
|
41
39
|
|
|
42
|
-
def get_list_display(self, request) ->
|
|
40
|
+
def get_list_display(self, request) -> tuple[str, ...]:
|
|
43
41
|
list_display = super().get_list_display(request)
|
|
44
42
|
return (
|
|
45
43
|
"visit_schedule_name",
|
|
@@ -49,18 +47,20 @@ class VisitScheduleAdmin(SimpleHistoryAdmin):
|
|
|
49
47
|
"visit_name",
|
|
50
48
|
"timepoint",
|
|
51
49
|
"active",
|
|
52
|
-
|
|
50
|
+
*list_display,
|
|
51
|
+
)
|
|
53
52
|
|
|
54
|
-
def get_list_filter(self, request) ->
|
|
53
|
+
def get_list_filter(self, request) -> tuple[str, ...]:
|
|
55
54
|
list_filter = super().get_list_filter(request)
|
|
56
55
|
return (
|
|
57
56
|
"active",
|
|
58
57
|
"visit_schedule_name",
|
|
59
58
|
"schedule_name",
|
|
60
59
|
"visit_code",
|
|
61
|
-
|
|
60
|
+
*list_filter,
|
|
61
|
+
)
|
|
62
62
|
|
|
63
63
|
@staticmethod
|
|
64
|
-
def populate_visit_schedule(request, queryset) -> None:
|
|
64
|
+
def populate_visit_schedule(request, queryset) -> None: # noqa: ARG004
|
|
65
65
|
VisitSchedule.objects.update(active=False)
|
|
66
66
|
site_visit_schedules.to_model(VisitSchedule)
|
edc_visit_schedule/fieldsets.py
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
visit_schedule_fields: Tuple[str, ...] = (
|
|
1
|
+
visit_schedule_fields: tuple[str, ...] = (
|
|
4
2
|
"visit_schedule_name",
|
|
5
3
|
"schedule_name",
|
|
6
4
|
"visit_code",
|
|
7
5
|
)
|
|
8
6
|
|
|
9
|
-
visit_schedule_fieldset_tuple:
|
|
7
|
+
visit_schedule_fieldset_tuple: tuple[str, dict[str, tuple[str, ...]]] = (
|
|
10
8
|
"Visit Schedule",
|
|
11
9
|
{"classes": ("collapse",), "fields": visit_schedule_fields},
|
|
12
10
|
)
|
|
13
11
|
|
|
14
|
-
visit_schedule_only_fields:
|
|
12
|
+
visit_schedule_only_fields: tuple[str, ...] = (
|
|
15
13
|
"visit_schedule_name",
|
|
16
14
|
"schedule_name",
|
|
17
15
|
)
|
|
18
16
|
|
|
19
|
-
visit_schedule_only_fieldset_tuple:
|
|
17
|
+
visit_schedule_only_fieldset_tuple: tuple[str, dict[str, tuple[str, ...]]] = (
|
|
20
18
|
"Visit Schedule",
|
|
21
19
|
{"classes": ("collapse",), "fields": visit_schedule_only_fields},
|
|
22
20
|
)
|
|
@@ -27,7 +27,7 @@ class Command(BaseCommand):
|
|
|
27
27
|
for visit_schedule in site_visit_schedules.visit_schedules.values():
|
|
28
28
|
for schedule in visit_schedule.schedules.values():
|
|
29
29
|
try:
|
|
30
|
-
onschedule_model_cls =
|
|
30
|
+
onschedule_model_cls = schedule.onschedule_model_cls
|
|
31
31
|
except LookupError:
|
|
32
32
|
pass
|
|
33
33
|
else:
|
|
@@ -89,4 +89,6 @@ class OffScheduleModelMixin(UniqueSubjectIdentifierFieldMixin, models.Model):
|
|
|
89
89
|
|
|
90
90
|
class Meta:
|
|
91
91
|
abstract = True
|
|
92
|
-
indexes =
|
|
92
|
+
indexes = (
|
|
93
|
+
models.Index(fields=["subject_identifier", "offschedule_datetime", "site"]),
|
|
94
|
+
)
|
|
@@ -89,4 +89,4 @@ class OnScheduleModelMixin(UniqueSubjectIdentifierFieldMixin, models.Model):
|
|
|
89
89
|
|
|
90
90
|
class Meta:
|
|
91
91
|
abstract = True
|
|
92
|
-
indexes =
|
|
92
|
+
indexes = (models.Index(fields=["subject_identifier", "onschedule_datetime", "site"]),)
|
|
@@ -3,7 +3,7 @@ from .off_schedule_modelform_mixin import OffScheduleModelFormMixin
|
|
|
3
3
|
from .visit_schedule_non_crf_modelform_mixin import VisitScheduleNonCrfModelFormMixin
|
|
4
4
|
|
|
5
5
|
__all__ = [
|
|
6
|
-
"VisitScheduleCrfModelFormMixin",
|
|
7
6
|
"OffScheduleModelFormMixin",
|
|
7
|
+
"VisitScheduleCrfModelFormMixin",
|
|
8
8
|
"VisitScheduleNonCrfModelFormMixin",
|
|
9
9
|
]
|
|
@@ -43,8 +43,7 @@ class OffScheduleModelFormMixin(VisitScheduleNonCrfModelFormMixin):
|
|
|
43
43
|
def offschedule_datetime(self) -> datetime | None:
|
|
44
44
|
if self.offschedule_datetime_field_attr in self.cleaned_data:
|
|
45
45
|
return to_utc(self.cleaned_data.get(self.offschedule_datetime_field_attr))
|
|
46
|
-
|
|
47
|
-
return getattr(self.instance, self.offschedule_datetime_field_attr)
|
|
46
|
+
return getattr(self.instance, self.offschedule_datetime_field_attr)
|
|
48
47
|
|
|
49
48
|
@property
|
|
50
49
|
def offschedule_compare_dates_as_datetimes(self):
|
|
@@ -56,7 +56,7 @@ class VisitSchedule(VisitScheduleMethodsModelMixin, edc_models.BaseUuidModel):
|
|
|
56
56
|
("visit_schedule_name", "schedule_name", "visit_code"),
|
|
57
57
|
("visit_schedule_name", "schedule_name", "timepoint"),
|
|
58
58
|
)
|
|
59
|
-
indexes =
|
|
59
|
+
indexes = (
|
|
60
60
|
models.Index(
|
|
61
61
|
fields=[
|
|
62
62
|
"visit_schedule_name",
|
|
@@ -67,4 +67,4 @@ class VisitSchedule(VisitScheduleMethodsModelMixin, edc_models.BaseUuidModel):
|
|
|
67
67
|
]
|
|
68
68
|
),
|
|
69
69
|
models.Index(fields=["visit_schedule_name", "schedule_name", "timepoint"]),
|
|
70
|
-
|
|
70
|
+
)
|
|
@@ -4,7 +4,7 @@ import re
|
|
|
4
4
|
from copy import deepcopy
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
from decimal import Decimal
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
8
|
|
|
9
9
|
from django.apps import apps as django_apps
|
|
10
10
|
|
|
@@ -64,7 +64,7 @@ class Schedule:
|
|
|
64
64
|
|
|
65
65
|
name_regex = r"[a-z0-9\_\-]+$"
|
|
66
66
|
visit_cls = Visit
|
|
67
|
-
visit_collection_cls:
|
|
67
|
+
visit_collection_cls: type[VisitCollection] = VisitCollection
|
|
68
68
|
window_cls = Window
|
|
69
69
|
|
|
70
70
|
def __init__(
|
|
@@ -87,8 +87,7 @@ class Schedule:
|
|
|
87
87
|
f"Invalid name. Got '{name}'. May only contains numbers, "
|
|
88
88
|
"lower case letters and '_'."
|
|
89
89
|
)
|
|
90
|
-
|
|
91
|
-
self.name = name
|
|
90
|
+
self.name = name
|
|
92
91
|
|
|
93
92
|
self.consent_definitions = consent_definitions
|
|
94
93
|
|
|
@@ -250,8 +249,8 @@ class Schedule:
|
|
|
250
249
|
if schedule.name != self.name:
|
|
251
250
|
raise ValueError(
|
|
252
251
|
f"Site visit schedules return the wrong schedule object. "
|
|
253
|
-
f"Expected {
|
|
254
|
-
f"Got {
|
|
252
|
+
f"Expected {self!r} for onschedule_model={self.onschedule_model}. "
|
|
253
|
+
f"Got {schedule!r}."
|
|
255
254
|
)
|
|
256
255
|
return SubjectSchedule(
|
|
257
256
|
subject_identifier, visit_schedule=visit_schedule, schedule=self
|
|
@@ -283,7 +282,7 @@ class Schedule:
|
|
|
283
282
|
]
|
|
284
283
|
if not consent_definition:
|
|
285
284
|
raise ScheduleError(
|
|
286
|
-
"Consent definition may not be None. Expected one of
|
|
285
|
+
f"Consent definition may not be None. Expected one of {formatted_cdefs}."
|
|
287
286
|
)
|
|
288
287
|
|
|
289
288
|
if consent_definition not in self.consent_definitions:
|
|
@@ -324,11 +323,11 @@ class Schedule:
|
|
|
324
323
|
return self.window_cls(name=self.name, visits=self.visits, **kwargs).datetime_in_window
|
|
325
324
|
|
|
326
325
|
@property
|
|
327
|
-
def onschedule_model_cls(self) ->
|
|
326
|
+
def onschedule_model_cls(self) -> type[OnSchedule]:
|
|
328
327
|
return django_apps.get_model(self.onschedule_model)
|
|
329
328
|
|
|
330
329
|
@property
|
|
331
|
-
def offschedule_model_cls(self) ->
|
|
330
|
+
def offschedule_model_cls(self) -> type[OffSchedule]:
|
|
332
331
|
return django_apps.get_model(self.offschedule_model)
|
|
333
332
|
|
|
334
333
|
@property
|
|
@@ -340,15 +339,15 @@ class Schedule:
|
|
|
340
339
|
return self.loss_to_followup_model_cls
|
|
341
340
|
|
|
342
341
|
@property
|
|
343
|
-
def history_model_cls(self) ->
|
|
342
|
+
def history_model_cls(self) -> type[SubjectScheduleHistory]:
|
|
344
343
|
return django_apps.get_model(self.history_model)
|
|
345
344
|
|
|
346
345
|
@property
|
|
347
|
-
def appointment_model_cls(self) ->
|
|
346
|
+
def appointment_model_cls(self) -> type[Appointment]:
|
|
348
347
|
return django_apps.get_model(self.appointment_model)
|
|
349
348
|
|
|
350
349
|
@property
|
|
351
|
-
def visit_model_cls(self) ->
|
|
350
|
+
def visit_model_cls(self) -> type[RelatedVisitModel]:
|
|
352
351
|
return self.appointment_model_cls.related_visit_model_cls()
|
|
353
352
|
|
|
354
353
|
def get_consent_definition(
|
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
|
+
from edc_utils import to_local
|
|
7
|
+
|
|
6
8
|
from ..ordered_collection import OrderedCollection
|
|
7
9
|
|
|
8
10
|
if TYPE_CHECKING:
|
|
@@ -33,11 +35,10 @@ class VisitCollection(OrderedCollection):
|
|
|
33
35
|
timepoint_dates = {}
|
|
34
36
|
for visit in self.values():
|
|
35
37
|
try:
|
|
36
|
-
timepoint_datetime = dt + visit.rbase
|
|
38
|
+
timepoint_datetime = to_local(dt) + visit.rbase
|
|
37
39
|
except TypeError as e:
|
|
38
40
|
raise VisitCollectionError(
|
|
39
|
-
f"Invalid visit.rbase. visit.rbase={visit.rbase}. "
|
|
40
|
-
f"See {repr(visit)}. Got {e}."
|
|
41
|
+
f"Invalid visit.rbase. visit.rbase={visit.rbase}. See {visit!r}. Got {e}."
|
|
41
42
|
)
|
|
42
43
|
else:
|
|
43
44
|
visit.timepoint_datetime = timepoint_datetime
|
|
@@ -2,8 +2,13 @@ from dateutil.relativedelta import relativedelta
|
|
|
2
2
|
from django.conf import settings
|
|
3
3
|
from django.utils.translation import gettext as _
|
|
4
4
|
|
|
5
|
-
from edc_utils import
|
|
6
|
-
|
|
5
|
+
from edc_utils import (
|
|
6
|
+
ceil_secs,
|
|
7
|
+
convert_php_dateformat,
|
|
8
|
+
floor_secs,
|
|
9
|
+
formatted_date,
|
|
10
|
+
to_local,
|
|
11
|
+
)
|
|
7
12
|
|
|
8
13
|
from ..exceptions import (
|
|
9
14
|
ScheduledVisitWindowError,
|
|
@@ -30,11 +35,11 @@ class Window:
|
|
|
30
35
|
):
|
|
31
36
|
self.name = name
|
|
32
37
|
self.visits = visits
|
|
33
|
-
self.timepoint_datetime =
|
|
34
|
-
self.dt =
|
|
38
|
+
self.timepoint_datetime = to_local(timepoint_datetime)
|
|
39
|
+
self.dt = to_local(dt)
|
|
35
40
|
self.visit_code = visit_code
|
|
36
41
|
self.visit_code_sequence = visit_code_sequence
|
|
37
|
-
self.baseline_timepoint_datetime =
|
|
42
|
+
self.baseline_timepoint_datetime = to_local(baseline_timepoint_datetime)
|
|
38
43
|
|
|
39
44
|
@property
|
|
40
45
|
def datetime_in_window(self):
|
|
@@ -71,19 +76,21 @@ class Window:
|
|
|
71
76
|
window period for a scheduled `visit` otherwise
|
|
72
77
|
raises an exception.
|
|
73
78
|
|
|
79
|
+
Ensure all datetimes in UTC before comparison.
|
|
80
|
+
|
|
74
81
|
In this case, `visit` is the object from schedule and
|
|
75
82
|
not a model instance.
|
|
76
83
|
"""
|
|
77
84
|
visit = self.visits.get(self.visit_code)
|
|
78
85
|
visit.timepoint_datetime = self.timepoint_datetime
|
|
79
86
|
gap_days = self.get_window_gap_days()
|
|
80
|
-
lower = floor_secs(
|
|
81
|
-
upper =
|
|
82
|
-
if not (lower <= floor_secs(
|
|
83
|
-
lower_date = to_local(
|
|
87
|
+
lower = floor_secs(to_local(visit.dates.lower) - relativedelta(days=gap_days))
|
|
88
|
+
upper = ceil_secs(to_local(visit.dates.upper))
|
|
89
|
+
if not (lower <= floor_secs(to_local(self.dt)) <= upper):
|
|
90
|
+
lower_date = to_local(lower).strftime(
|
|
84
91
|
convert_php_dateformat(settings.SHORT_DATETIME_FORMAT)
|
|
85
92
|
)
|
|
86
|
-
upper_date = to_local(
|
|
93
|
+
upper_date = to_local(upper).strftime(
|
|
87
94
|
convert_php_dateformat(settings.SHORT_DATETIME_FORMAT)
|
|
88
95
|
)
|
|
89
96
|
dt = to_local(self.dt).strftime(
|
|
@@ -114,8 +121,8 @@ class Window:
|
|
|
114
121
|
dt=self.baseline_timepoint_datetime
|
|
115
122
|
).get(next_visit)
|
|
116
123
|
if not (
|
|
117
|
-
floor_secs(
|
|
118
|
-
< floor_secs(
|
|
124
|
+
floor_secs(to_local(self.dt))
|
|
125
|
+
< floor_secs(to_local(next_timepoint_datetime - next_visit.rlower))
|
|
119
126
|
):
|
|
120
127
|
dt_lower = formatted_date(next_timepoint_datetime - next_visit.rlower)
|
|
121
128
|
dt = formatted_date(self.dt)
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
4
|
import sys
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from django.apps import apps as django_apps
|
|
8
8
|
from django.core.exceptions import ObjectDoesNotExist
|
|
@@ -77,7 +77,7 @@ class SiteVisitSchedules:
|
|
|
77
77
|
visit_schedule_names = "', '".join(self.registry.keys())
|
|
78
78
|
raise SiteVisitScheduleError(
|
|
79
79
|
f"Invalid visit schedule name. Got '{visit_schedule_name}'. "
|
|
80
|
-
f"Expected one of '{visit_schedule_names}'. See {
|
|
80
|
+
f"Expected one of '{visit_schedule_names}'. See {self!r}."
|
|
81
81
|
)
|
|
82
82
|
return visit_schedule
|
|
83
83
|
|
|
@@ -116,7 +116,7 @@ class SiteVisitSchedules:
|
|
|
116
116
|
raise SiteVisitScheduleError(
|
|
117
117
|
f"Schedule not found. No schedule exists for {attr}={cdef}."
|
|
118
118
|
)
|
|
119
|
-
|
|
119
|
+
if len(ret) > 1:
|
|
120
120
|
raise SiteVisitScheduleError(
|
|
121
121
|
f"Schedule is ambiguous. More than one schedule exists for "
|
|
122
122
|
f"{attr}={cdef}. Got {ret}"
|
|
@@ -126,7 +126,7 @@ class SiteVisitSchedules:
|
|
|
126
126
|
|
|
127
127
|
def get_by_onschedule_model(
|
|
128
128
|
self, onschedule_model: str = None
|
|
129
|
-
) ->
|
|
129
|
+
) -> tuple[VisitSchedule, Schedule]:
|
|
130
130
|
"""Returns a tuple of (visit_schedule, schedule)
|
|
131
131
|
for the given onschedule model.
|
|
132
132
|
|
|
@@ -136,7 +136,7 @@ class SiteVisitSchedules:
|
|
|
136
136
|
|
|
137
137
|
def get_by_offschedule_model(
|
|
138
138
|
self, offschedule_model: str = None
|
|
139
|
-
) ->
|
|
139
|
+
) -> tuple[VisitSchedule, Schedule]:
|
|
140
140
|
"""Returns a tuple of visit_schedule, schedule
|
|
141
141
|
for the given offschedule model.
|
|
142
142
|
|
|
@@ -146,7 +146,7 @@ class SiteVisitSchedules:
|
|
|
146
146
|
|
|
147
147
|
def get_by_loss_to_followup_model(
|
|
148
148
|
self, loss_to_followup_model: str = None
|
|
149
|
-
) ->
|
|
149
|
+
) -> tuple[VisitSchedule, Schedule]:
|
|
150
150
|
"""Returns a tuple of visit_schedule, schedule
|
|
151
151
|
for the given loss_to_followup model.
|
|
152
152
|
|
|
@@ -156,7 +156,7 @@ class SiteVisitSchedules:
|
|
|
156
156
|
|
|
157
157
|
def get_by_model(
|
|
158
158
|
self, attr: str = None, model: str = None
|
|
159
|
-
) ->
|
|
159
|
+
) -> tuple[VisitSchedule, Schedule]:
|
|
160
160
|
ret = []
|
|
161
161
|
model = model.lower()
|
|
162
162
|
for visit_schedule in self.visit_schedules.values():
|
|
@@ -173,7 +173,7 @@ class SiteVisitSchedules:
|
|
|
173
173
|
raise SiteVisitScheduleError(
|
|
174
174
|
f"Schedule not found. No schedule exists for {attr}={model}."
|
|
175
175
|
)
|
|
176
|
-
|
|
176
|
+
if len(ret) > 1:
|
|
177
177
|
raise SiteVisitScheduleError(
|
|
178
178
|
f"Schedule is ambiguous. More than one schedule exists for "
|
|
179
179
|
f"{attr}={model}. Got {ret}"
|
|
@@ -313,7 +313,7 @@ class SiteVisitSchedules:
|
|
|
313
313
|
before_import_registry = copy.copy(site_visit_schedules._registry)
|
|
314
314
|
import_module(f"{app}.{module_name}")
|
|
315
315
|
if verbose:
|
|
316
|
-
sys.stdout.write(" - registered visit schedule from
|
|
316
|
+
sys.stdout.write(f" - registered visit schedule from '{app}'\n")
|
|
317
317
|
except Exception as e:
|
|
318
318
|
if f"No module named '{app}.{module_name}'" not in str(e):
|
|
319
319
|
raise
|