clinicedc 2.0.39__py3-none-any.whl → 2.0.41__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.39.dist-info → clinicedc-2.0.41.dist-info}/METADATA +3 -12
- {clinicedc-2.0.39.dist-info → clinicedc-2.0.41.dist-info}/RECORD +145 -151
- {clinicedc-2.0.39.dist-info → clinicedc-2.0.41.dist-info}/WHEEL +1 -1
- edc_adverse_event/dashboard_urls.py +2 -0
- edc_adverse_event/middleware.py +7 -6
- edc_adverse_event/navbars.py +4 -8
- edc_adverse_event/urls.py +14 -6
- edc_adverse_event/view_mixins/ae/ae_listboard_view_mixin.py +6 -8
- edc_adverse_event/view_mixins/ae/death_report_listboard_view_mixin.py +2 -4
- edc_adverse_event/view_mixins/tmg/tmg_ae_listboard_view_mixin.py +2 -3
- edc_adverse_event/views/home_view.py +1 -2
- edc_adverse_event/views/tmg/death_listboard_view.py +8 -6
- edc_adverse_event/views/tmg/home_view.py +4 -3
- edc_adverse_event/views/tmg/summary_listboard_view.py +4 -4
- edc_appointment/utils.py +3 -6
- edc_appointment/views/unscheduled_appointment_view.py +1 -1
- edc_consent/form_validators/consent_definition_form_validator_mixin.py +5 -2
- edc_consent/model_mixins/consent_version_model_mixin.py +1 -1
- edc_consent/navbars.py +2 -1
- edc_crf/model_mixins/crf_model_mixin.py +5 -1
- edc_crf/model_mixins/crf_no_manager_model_mixin.py +2 -2
- edc_crf/model_mixins/singleton_crf_model_mixin.py +1 -1
- edc_dashboard/middleware.py +10 -16
- edc_dashboard/middleware_mixins.py +10 -0
- edc_dashboard/navbars.py +1 -1
- edc_dashboard/url_config.py +50 -31
- edc_dashboard/url_names.py +23 -17
- edc_dashboard/utils.py +4 -4
- edc_dashboard/view_mixins/template_request_context_mixin.py +5 -8
- edc_dashboard/view_mixins/url_request_context_mixin.py +38 -26
- edc_dashboard/views/administration_view.py +2 -2
- edc_dashboard/views/dashboard_view.py +5 -10
- edc_data_manager/handlers/handlers.py +17 -5
- edc_data_manager/migrations/0043_alter_historicalqueryrule_comment_and_more.py +51 -0
- edc_data_manager/models/query_rule.py +7 -7
- edc_data_manager/navbar_item.py +1 -1
- edc_data_manager/rule/query_rule_wrapper.py +1 -1
- edc_data_manager/rule/rule_runner.py +6 -6
- edc_device/navbars.py +1 -1
- edc_export/navbars.py +2 -2
- edc_glucose/model_mixin_factories/fasting_model_mixin_factory.py +1 -1
- edc_identifier/identifier.py +6 -9
- edc_lab_dashboard/dashboard_urls.py +7 -5
- edc_lab_dashboard/middleware.py +10 -17
- edc_lab_dashboard/navbars.py +9 -9
- edc_lab_dashboard/templates/edc_lab_dashboard/listboard/tags/status_column.html +7 -0
- edc_lab_dashboard/urls.py +2 -5
- edc_lab_dashboard/view_mixins/form_action_view_mixin.py +1 -2
- edc_lab_dashboard/views/action_views/action_view.py +6 -6
- edc_lab_dashboard/views/action_views/aliquot_view.py +1 -1
- edc_lab_dashboard/views/action_views/manage_box_item_view.py +2 -3
- edc_lab_dashboard/views/action_views/manage_manifest_view.py +1 -1
- edc_lab_dashboard/views/action_views/manifest_view.py +2 -2
- edc_lab_dashboard/views/action_views/pack_view.py +2 -2
- edc_lab_dashboard/views/action_views/process_view.py +1 -1
- edc_lab_dashboard/views/action_views/receive_view.py +1 -1
- edc_lab_dashboard/views/action_views/requisition_view.py +1 -1
- edc_lab_dashboard/views/action_views/verify_box_item_view.py +1 -1
- edc_lab_dashboard/views/listboard_views/manage_box_listboard_view.py +4 -5
- edc_lab_dashboard/views/listboard_views/manifest_listboard_view.py +5 -6
- edc_lab_dashboard/views/listboard_views/process_listboard_view.py +4 -5
- edc_lab_dashboard/views/listboard_views/receive_listboard_view.py +5 -6
- edc_lab_dashboard/views/listboard_views/verify_box_listboard_view.py +5 -6
- edc_label/navbars.py +1 -1
- edc_list_data/admin.py +3 -3
- edc_list_data/load_model_data.py +1 -1
- edc_list_data/management/commands/load_list_data.py +2 -2
- edc_list_data/site_list_data.py +4 -4
- edc_listboard/middleware.py +9 -8
- edc_listboard/templates/edc_listboard/listboard.html +1 -1
- edc_listboard/view_mixins/listboard_filter_view_mixin.py +1 -1
- edc_listboard/view_mixins/search_form_view_mixin.py +1 -1
- edc_listboard/views/listboard_view.py +16 -25
- edc_listboard/views/screen/screening_listboard_view.py +2 -2
- edc_listboard/views/subject/subject_listboard_view.py +2 -2
- edc_locator/forms/subject_locator_form_validator.py +2 -2
- edc_ltfu/action_items.py +1 -2
- edc_ltfu/forms/ltfu_form_validator_mixin.py +3 -3
- edc_ltfu/modeladmin_mixin.py +1 -1
- edc_ltfu/modelform_mixins.py +2 -2
- edc_metadata/admin/modeladmin_mixins.py +11 -9
- edc_metadata/management/commands/update_metadata.py +1 -1
- edc_metadata/management/commands/update_metadata_schedule_names.py +7 -7
- edc_metadata/management/commands/validate_entry_status.py +1 -1
- edc_metadata/management/commands/validate_rule_groups.py +1 -1
- edc_metadata/metadata/metadata_getter.py +3 -5
- edc_metadata/metadata_handler.py +5 -5
- edc_metadata/metadata_mixins/source_model_metadata_mixin.py +1 -1
- edc_metadata/metadata_refresher.py +1 -1
- edc_metadata/metadata_rules/crf/crf_rule.py +1 -1
- edc_metadata/metadata_rules/logic.py +3 -3
- edc_metadata/metadata_rules/persistant_singleton_mixin.py +2 -4
- edc_metadata/metadata_rules/requisition/requisition_rule_group.py +1 -1
- edc_metadata/metadata_rules/rule.py +4 -3
- edc_metadata/metadata_rules/rule_group.py +2 -2
- edc_metadata/metadata_rules/rule_group_meta_options.py +2 -2
- edc_metadata/metadata_rules/rule_group_metaclass.py +21 -22
- edc_metadata/metadata_rules/site.py +1 -1
- edc_metadata/metadata_updater.py +4 -3
- edc_metadata/model_mixins/creates/creates_metadata_model_mixin.py +3 -5
- edc_metadata/model_mixins/updates/updates_metadata_model_mixin.py +1 -1
- edc_metadata/next_form_getter.py +15 -19
- edc_metadata/offline_models.py +1 -1
- edc_metadata/requisition/requisition_metadata_handler.py +5 -5
- edc_metadata/update_metadata_on_schedule_change.py +2 -4
- edc_metadata/utils.py +1 -1
- edc_model/models/signals.py +7 -2
- edc_model_admin/mixins/model_admin_redirect_on_delete_mixin.py +4 -3
- edc_navbar/apps.py +0 -2
- edc_navbar/navbar.py +1 -1
- edc_navbar/navbar_item.py +29 -16
- edc_navbar/navbars.py +6 -19
- edc_navbar/site_navbars.py +6 -7
- edc_navbar/system_checks.py +3 -10
- edc_navbar/utils.py +14 -0
- edc_navbar/view_mixin.py +6 -9
- edc_pharmacy/navbars.py +1 -1
- edc_pharmacy/views/confirm_stock_from_queryset_view.py +3 -3
- edc_protocol/middleware.py +9 -13
- edc_protocol/navbars.py +1 -1
- edc_refusal/forms.py +1 -3
- edc_reportable/utils/convert_units.py +1 -1
- edc_review_dashboard/middleware.py +6 -3
- edc_review_dashboard/navbars.py +1 -2
- edc_review_dashboard/urls.py +3 -2
- edc_review_dashboard/views/subject_review_listboard_view.py +4 -2
- edc_subject_dashboard/dashboard_templates.py +1 -3
- edc_subject_dashboard/dashboard_urls.py +8 -0
- edc_subject_dashboard/middleware.py +10 -7
- edc_subject_dashboard/templates/edc_subject_dashboard/buttons/refresh_appointments_button.html +1 -1
- edc_subject_dashboard/templates/edc_subject_dashboard/dashboard.html +1 -1
- edc_subject_dashboard/templatetags/edc_subject_dashboard_extras.py +3 -1
- edc_subject_dashboard/urls.py +13 -4
- edc_subject_dashboard/views/base_requisition_view.py +2 -1
- edc_subject_dashboard/views/subject_dashboard_view.py +1 -2
- edc_timepoint/__init__.py +0 -2
- edc_timepoint/model_mixins.py +1 -2
- edc_timepoint/utils.py +1 -1
- edc_timepoint/visit_timepoint_lookup.py +6 -0
- edc_visit_schedule/admin/subject_schedule_history_admin.py +1 -2
- edc_visit_schedule/navbars.py +3 -4
- edc_visit_schedule/visit/visit.py +15 -0
- edc_visit_tracking/model_mixins/visit_model_mixin/visit_model_mixin.py +5 -0
- edc_visit_tracking/models/subject_visit.py +5 -0
- edc_lab_dashboard/model_wrappers/__init__.py +0 -8
- edc_lab_dashboard/model_wrappers/aliquot_model_wrapper.py +0 -31
- edc_lab_dashboard/model_wrappers/base_box_item_model_wrapper.py +0 -21
- edc_lab_dashboard/model_wrappers/box_model_wrapper.py +0 -12
- edc_lab_dashboard/model_wrappers/manage_box_item_model_wrapper.py +0 -6
- edc_lab_dashboard/model_wrappers/manifest_item_model_wrapper.py +0 -21
- edc_lab_dashboard/model_wrappers/manifest_model_wrapper.py +0 -11
- edc_lab_dashboard/model_wrappers/requisition_model_wrapper.py +0 -25
- edc_lab_dashboard/model_wrappers/result_model_wrapper.py +0 -8
- edc_lab_dashboard/model_wrappers/verify_box_model_wrapper.py +0 -10
- edc_navbar/get_default_navbar.py +0 -9
- {clinicedc-2.0.39.dist-info → clinicedc-2.0.41.dist-info}/licenses/LICENSE +0 -0
edc_listboard/middleware.py
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
2
|
|
|
3
|
+
from edc_dashboard.middleware_mixins import EdcTemplateMiddlewareMixin
|
|
4
|
+
|
|
3
5
|
from .dashboard_templates import dashboard_templates
|
|
4
6
|
|
|
5
7
|
|
|
6
|
-
class DashboardMiddleware:
|
|
8
|
+
class DashboardMiddleware(EdcTemplateMiddlewareMixin):
|
|
7
9
|
def __init__(self, get_response):
|
|
8
10
|
self.get_response = get_response
|
|
9
11
|
|
|
10
12
|
def __call__(self, request):
|
|
13
|
+
self.check_for_required_request_attrs(request)
|
|
11
14
|
return self.get_response(request)
|
|
12
15
|
|
|
13
|
-
def process_view(self, request, *args)
|
|
14
|
-
template_data =
|
|
15
|
-
|
|
16
|
-
template_data.update(settings.LISTBOARD_BASE_TEMPLATES)
|
|
17
|
-
except AttributeError:
|
|
18
|
-
pass
|
|
16
|
+
def process_view(self, request, *args):
|
|
17
|
+
template_data = getattr(settings, "LISTBOARD_BASE_TEMPLATES", {})
|
|
18
|
+
template_data.update(**dashboard_templates)
|
|
19
19
|
request.template_data.update(**template_data)
|
|
20
20
|
|
|
21
21
|
def process_template_response(self, request, response):
|
|
22
|
-
if response
|
|
22
|
+
if getattr(response, "context_data", None):
|
|
23
23
|
response.context_data.update(**request.template_data)
|
|
24
|
+
request.template_data.update(**request.template_data)
|
|
24
25
|
return response
|
|
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
|
|
|
12
12
|
|
|
13
13
|
class ListboardFilterViewMixin:
|
|
14
14
|
listboard_view_filters = ListboardViewFilters()
|
|
15
|
-
listboard_filter_url = None
|
|
15
|
+
listboard_filter_url = None # url name
|
|
16
16
|
|
|
17
17
|
def __init__(self, **kwargs):
|
|
18
18
|
self.listboard_view_exclude_filter_applied = False # TODO: ??
|
|
@@ -11,7 +11,7 @@ class SearchFormViewError(Exception):
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class SearchFormViewMixin:
|
|
14
|
-
search_form_url = None
|
|
14
|
+
search_form_url = None # url_name in url_names dict
|
|
15
15
|
|
|
16
16
|
def get_context_data(self, **kwargs) -> dict[str, Any]:
|
|
17
17
|
kwargs.update(search_form_url_reversed=self.search_form_url_reversed)
|
|
@@ -8,6 +8,7 @@ from django.db.models import Q
|
|
|
8
8
|
from django.utils.translation import gettext as _
|
|
9
9
|
from django.views.generic.list import ListView
|
|
10
10
|
|
|
11
|
+
from edc_dashboard.url_names import url_names
|
|
11
12
|
from edc_dashboard.view_mixins import (
|
|
12
13
|
TemplateRequestContextMixin,
|
|
13
14
|
UrlRequestContextMixin,
|
|
@@ -31,8 +32,8 @@ class BaseListboardView(SiteViewMixin, TemplateRequestContextMixin, ListView):
|
|
|
31
32
|
listboard_template: str | None = None # an existing key in request.context_data
|
|
32
33
|
|
|
33
34
|
# if self.listboard_url declared through another mixin.
|
|
34
|
-
listboard_url: str | None = None # an existing key in request.context_data
|
|
35
|
-
listboard_back_url: str | None = None
|
|
35
|
+
listboard_url: str | None = None # an existing key in request.context_data.url_names
|
|
36
|
+
listboard_back_url: str | None = None # see url_names, defaults to listboard_url
|
|
36
37
|
|
|
37
38
|
# styling
|
|
38
39
|
# default, info, success, danger, warning, etc. See Bootstrap.
|
|
@@ -65,32 +66,22 @@ class BaseListboardView(SiteViewMixin, TemplateRequestContextMixin, ListView):
|
|
|
65
66
|
self.listboard_fa_icon = f"fas {self.listboard_fa_icon}"
|
|
66
67
|
kwargs.update(
|
|
67
68
|
empty_queryset_message=self.get_empty_queryset_message(),
|
|
69
|
+
has_listboard_model_perms=self.has_listboard_model_perms,
|
|
70
|
+
has_view_listboard_perms=self.has_view_listboard_perms,
|
|
68
71
|
listboard_fa_icon=self.listboard_fa_icon,
|
|
72
|
+
listboard_instructions=self.listboard_instructions,
|
|
69
73
|
listboard_panel_style=self.listboard_panel_style,
|
|
70
74
|
listboard_panel_title=self.listboard_panel_title,
|
|
71
|
-
listboard_instructions=self.listboard_instructions,
|
|
72
|
-
show_change_form_button=self.show_change_form_button,
|
|
73
|
-
# object_list=self.object_list,
|
|
74
|
-
**self.add_url_to_context(
|
|
75
|
-
new_key="listboard_url", existing_key=self.listboard_url
|
|
76
|
-
),
|
|
77
|
-
)
|
|
78
|
-
if self.listboard_back_url:
|
|
79
|
-
kwargs.update(
|
|
80
|
-
**self.add_url_to_context(
|
|
81
|
-
new_key="listboard_back_url",
|
|
82
|
-
existing_key=self.listboard_back_url,
|
|
83
|
-
)
|
|
84
|
-
)
|
|
85
|
-
kwargs.update(
|
|
86
|
-
has_listboard_model_perms=self.has_listboard_model_perms,
|
|
87
|
-
has_view_listboard_perms=self.has_view_listboard_perms,
|
|
88
75
|
listboard_view_permission_codename=self.listboard_view_permission_codename,
|
|
89
76
|
permissions_warning_message=self.permissions_warning_message,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
77
|
+
show_change_form_button=self.show_change_form_button,
|
|
78
|
+
**{"listboard_url": url_names.get(self.listboard_url)},
|
|
79
|
+
**{"paginator_url": url_names.get(self.paginator_url or self.listboard_url)},
|
|
80
|
+
**{
|
|
81
|
+
"listboard_back_url": url_names.get(
|
|
82
|
+
self.listboard_back_url or self.listboard_url
|
|
83
|
+
)
|
|
84
|
+
},
|
|
94
85
|
)
|
|
95
86
|
return super().get_context_data(**kwargs)
|
|
96
87
|
|
|
@@ -165,14 +156,14 @@ class BaseListboardView(SiteViewMixin, TemplateRequestContextMixin, ListView):
|
|
|
165
156
|
queryset = queryset.order_by(*ordering)
|
|
166
157
|
return queryset
|
|
167
158
|
|
|
168
|
-
def get_queryset_filter_options(self, request, *args, **kwargs) -> tuple[Q, dict]:
|
|
159
|
+
def get_queryset_filter_options(self, request, *args, **kwargs) -> tuple[Q, dict]: # noqa: ARG002
|
|
169
160
|
"""Returns filtering applied to every queryset"""
|
|
170
161
|
options = dict(site_id__in=sites.get_site_ids_for_user(request=self.request))
|
|
171
162
|
if self.has_view_only_my_listboard_perms:
|
|
172
163
|
options.update(user_created=self.request.user.username)
|
|
173
164
|
return Q(), options
|
|
174
165
|
|
|
175
|
-
def get_queryset_exclude_options(self, request, *args, **kwargs) -> tuple[Q, dict]:
|
|
166
|
+
def get_queryset_exclude_options(self, request, *args, **kwargs) -> tuple[Q, dict]: # noqa: ARG002
|
|
176
167
|
"""Returns exclude options applied to every queryset"""
|
|
177
168
|
return Q(), {}
|
|
178
169
|
|
|
@@ -30,13 +30,13 @@ class ScreeningListboardView(
|
|
|
30
30
|
ordering = "-report_datetime"
|
|
31
31
|
paginate_by = 10
|
|
32
32
|
search_form_url = "screening_listboard_url"
|
|
33
|
-
search_fields =
|
|
33
|
+
search_fields = (
|
|
34
34
|
"screening_identifier",
|
|
35
35
|
"initials__exact",
|
|
36
36
|
"subject_identifier",
|
|
37
37
|
"user_created",
|
|
38
38
|
"user_modified",
|
|
39
|
-
|
|
39
|
+
)
|
|
40
40
|
|
|
41
41
|
def get_context_data(self, **kwargs) -> dict:
|
|
42
42
|
kwargs.update(
|
|
@@ -23,7 +23,7 @@ class SubjectListboardView(
|
|
|
23
23
|
navbar_selected_item: str = "consented_subject"
|
|
24
24
|
search_form_url: str = "subject_listboard_url"
|
|
25
25
|
|
|
26
|
-
search_fields
|
|
26
|
+
search_fields = (
|
|
27
27
|
"user_created",
|
|
28
28
|
"user_modified",
|
|
29
29
|
"screening_identifier",
|
|
@@ -31,7 +31,7 @@ class SubjectListboardView(
|
|
|
31
31
|
"initials__exact",
|
|
32
32
|
"identity__exact",
|
|
33
33
|
"first_name__exact",
|
|
34
|
-
|
|
34
|
+
)
|
|
35
35
|
|
|
36
36
|
def get_listboard_model(self) -> str:
|
|
37
37
|
return self.listboard_model
|
|
@@ -49,12 +49,12 @@ class SubjectLocatorFormValidator(FormValidator):
|
|
|
49
49
|
|
|
50
50
|
def validate_may_call_fields(self):
|
|
51
51
|
validations = {}
|
|
52
|
-
number_fields =
|
|
52
|
+
number_fields = ("subject_cell", "subject_phone")
|
|
53
53
|
if self.cleaned_data.get("may_call") == YES:
|
|
54
54
|
if all([self.cleaned_data.get(f) is None for f in number_fields]):
|
|
55
55
|
validations = {k: "This field is required" for k in number_fields}
|
|
56
56
|
elif self.cleaned_data.get("may_call") == NO:
|
|
57
|
-
number_fields
|
|
57
|
+
number_fields = {*number_fields, "subject_cell_alt", "subject_phone_alt"}
|
|
58
58
|
for field in number_fields:
|
|
59
59
|
if self.cleaned_data.get(field):
|
|
60
60
|
validations.update({field: "This field is not required."})
|
edc_ltfu/action_items.py
CHANGED
|
@@ -23,13 +23,13 @@ class LtfuFormValidatorMixin(FormValidator):
|
|
|
23
23
|
|
|
24
24
|
try:
|
|
25
25
|
self.ltfu_model_cls.objects.get(subject_identifier=subject_identifier)
|
|
26
|
-
except ObjectDoesNotExist:
|
|
26
|
+
except ObjectDoesNotExist as e:
|
|
27
27
|
if self.offschedule_reason_field not in self.cleaned_data:
|
|
28
28
|
raise ImproperlyConfigured(
|
|
29
29
|
"Unknown offschedule_reason_field. "
|
|
30
30
|
f"Got '{self.offschedule_reason_field}'. "
|
|
31
31
|
f"See form {self.__class__.__name__}"
|
|
32
|
-
)
|
|
32
|
+
) from e
|
|
33
33
|
if self.cleaned_data.get(self.offschedule_reason_field) == LTFU:
|
|
34
34
|
raise forms.ValidationError(
|
|
35
35
|
{
|
|
@@ -39,4 +39,4 @@ class LtfuFormValidatorMixin(FormValidator):
|
|
|
39
39
|
"form first."
|
|
40
40
|
)
|
|
41
41
|
}
|
|
42
|
-
)
|
|
42
|
+
) from e
|
edc_ltfu/modeladmin_mixin.py
CHANGED
edc_ltfu/modelform_mixins.py
CHANGED
|
@@ -69,7 +69,7 @@ class RequiresLtfuFormValidatorMixin:
|
|
|
69
69
|
ltfu = django_apps.get_model(self.ltfu_model).objects.get(
|
|
70
70
|
subject_identifier=subject_identifier
|
|
71
71
|
)
|
|
72
|
-
except ObjectDoesNotExist:
|
|
72
|
+
except ObjectDoesNotExist as e:
|
|
73
73
|
if (
|
|
74
74
|
self.cleaned_data.get(self.offschedule_reason_field)
|
|
75
75
|
and self.cleaned_data.get(self.offschedule_reason_field).name
|
|
@@ -80,7 +80,7 @@ class RequiresLtfuFormValidatorMixin:
|
|
|
80
80
|
f"`{self.ltfu_model_cls._meta.verbose_name}` "
|
|
81
81
|
"form first."
|
|
82
82
|
)
|
|
83
|
-
raise forms.ValidationError({self.offschedule_reason_field: msg})
|
|
83
|
+
raise forms.ValidationError({self.offschedule_reason_field: msg}) from e
|
|
84
84
|
else:
|
|
85
85
|
if self.cleaned_data.get(self.ltfu_date_field) and (
|
|
86
86
|
ltfu.ltfu_date != self.cleaned_data.get(self.ltfu_date_field)
|
|
@@ -8,12 +8,8 @@ from django.utils.html import format_html
|
|
|
8
8
|
from django.utils.safestring import mark_safe
|
|
9
9
|
from django_audit_fields import ModelAdminAuditFieldsMixin, audit_fieldset_tuple
|
|
10
10
|
from django_revision.modeladmin_mixin import ModelAdminRevisionMixin
|
|
11
|
-
from rangefilter.filters import DateRangeFilterBuilder
|
|
12
|
-
|
|
13
11
|
from edc_appointment.utils import get_appointment_model_cls
|
|
14
12
|
from edc_dashboard.url_names import url_names
|
|
15
|
-
from edc_metadata import KEYED, REQUIRED
|
|
16
|
-
from edc_metadata.admin.list_filters import CreatedListFilter
|
|
17
13
|
from edc_model_admin.mixins import (
|
|
18
14
|
ModelAdminInstitutionMixin,
|
|
19
15
|
ModelAdminNextUrlRedirectMixin,
|
|
@@ -22,6 +18,10 @@ from edc_model_admin.mixins import (
|
|
|
22
18
|
TemplatesModelAdminMixin,
|
|
23
19
|
)
|
|
24
20
|
from edc_sites.admin import SiteModelAdminMixin
|
|
21
|
+
from rangefilter.filters import DateRangeFilterBuilder
|
|
22
|
+
|
|
23
|
+
from edc_metadata import KEYED, REQUIRED
|
|
24
|
+
from edc_metadata.admin.list_filters import CreatedListFilter
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class MetadataModelAdminMixin(
|
|
@@ -49,6 +49,8 @@ class MetadataModelAdminMixin(
|
|
|
49
49
|
|
|
50
50
|
change_search_field_name = "subject_identifier"
|
|
51
51
|
|
|
52
|
+
subject_dashboard_url_name = "subject_dashboard_url" # url_name
|
|
53
|
+
|
|
52
54
|
fieldsets = (
|
|
53
55
|
[
|
|
54
56
|
None,
|
|
@@ -149,8 +151,6 @@ class MetadataModelAdminMixin(
|
|
|
149
151
|
extra_context.update(show_cancel=True)
|
|
150
152
|
return extra_context
|
|
151
153
|
|
|
152
|
-
subject_dashboard_url_name = "subject_dashboard_url"
|
|
153
|
-
|
|
154
154
|
def get_subject_dashboard_url(self, obj=None) -> str | None:
|
|
155
155
|
opts = {}
|
|
156
156
|
if obj:
|
|
@@ -195,9 +195,11 @@ class MetadataModelAdminMixin(
|
|
|
195
195
|
)
|
|
196
196
|
return obj.get_entry_status_display()
|
|
197
197
|
|
|
198
|
-
def get_view_on_site_url(self, obj=None):
|
|
198
|
+
def get_view_on_site_url(self, obj=None) -> None | str:
|
|
199
|
+
url = None
|
|
199
200
|
if obj is None or not self.view_on_site:
|
|
200
|
-
|
|
201
|
+
url = None
|
|
201
202
|
if hasattr(obj, "get_absolute_url"):
|
|
202
203
|
url = reverse(self.changelist_url)
|
|
203
|
-
|
|
204
|
+
url = f"{url}?q={obj.subject_identifier}"
|
|
205
|
+
return url
|
|
@@ -12,7 +12,7 @@ style = color_style()
|
|
|
12
12
|
class Command(BaseCommand):
|
|
13
13
|
help = "Update metadata and re-run metadatarules"
|
|
14
14
|
|
|
15
|
-
def handle(self, *args, **options) -> None:
|
|
15
|
+
def handle(self, *args, **options) -> None: # noqa: ARG002
|
|
16
16
|
metadata_refresher = MetadataRefresher(verbose=True)
|
|
17
17
|
sys.stdout.write("Deleting all CrfMetadata... \r")
|
|
18
18
|
CrfMetadata.objects.all().delete()
|
|
@@ -12,7 +12,7 @@ style = color_style()
|
|
|
12
12
|
class Command(BaseCommand):
|
|
13
13
|
help = "Update metadata for changed visit_schedule/schedule names"
|
|
14
14
|
pattern = "^[0-9a-z_]+$"
|
|
15
|
-
fieldnames =
|
|
15
|
+
fieldnames = ("visit_schedule_name", "schedule_name")
|
|
16
16
|
|
|
17
17
|
def add_arguments(self, parser):
|
|
18
18
|
parser.add_argument(
|
|
@@ -43,15 +43,15 @@ class Command(BaseCommand):
|
|
|
43
43
|
help="Do a dry run. (Default: True)",
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
-
def handle(self, *args, **options):
|
|
47
|
-
dry_run =
|
|
46
|
+
def handle(self, *args, **options): # noqa: ARG002
|
|
47
|
+
dry_run = options.get("dry_run", "") != "False"
|
|
48
48
|
|
|
49
49
|
try:
|
|
50
50
|
UpdateMetadataOnScheduleChange(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
field=options.get("field"),
|
|
52
|
+
new_name=options.get("new_value"),
|
|
53
|
+
old_name=options.get("old_value"),
|
|
54
54
|
dry_run=dry_run,
|
|
55
55
|
)
|
|
56
56
|
except UpdateMetadataError as e:
|
|
57
|
-
raise CommandError(e)
|
|
57
|
+
raise CommandError(e) from e
|
|
@@ -15,7 +15,7 @@ from ...models import CrfMetadata, RequisitionMetadata
|
|
|
15
15
|
class Command(BaseCommand):
|
|
16
16
|
help = "Performs a `get_model` for each target models referenced"
|
|
17
17
|
|
|
18
|
-
def handle(self, *args, **options):
|
|
18
|
+
def handle(self, *args, **options): # noqa: ARG002
|
|
19
19
|
grouping = (
|
|
20
20
|
RequisitionMetadata.objects.distinct()
|
|
21
21
|
.values("model")
|
|
@@ -6,5 +6,5 @@ from ...metadata_rules import site_metadata_rules
|
|
|
6
6
|
class Command(BaseCommand):
|
|
7
7
|
help = "Performs a `get_model` for each target models referenced"
|
|
8
8
|
|
|
9
|
-
def handle(self, *args, **options):
|
|
9
|
+
def handle(self, *args, **options): # noqa: ARG002
|
|
10
10
|
site_metadata_rules.validate()
|
|
@@ -47,7 +47,8 @@ class MetadataValidator:
|
|
|
47
47
|
if not model_cls_registered_with_admin_site(source_model_cls):
|
|
48
48
|
warn(
|
|
49
49
|
"Model class not registered with Admin. "
|
|
50
|
-
f"Deleting related metadata. Got {source_model_cls}."
|
|
50
|
+
f"Deleting related metadata. Got {source_model_cls}.",
|
|
51
|
+
stacklevel=2,
|
|
51
52
|
)
|
|
52
53
|
self.metadata_obj.delete()
|
|
53
54
|
self.metadata_obj = None
|
|
@@ -75,10 +76,7 @@ class MetadataValidator:
|
|
|
75
76
|
@staticmethod
|
|
76
77
|
def model_cls_registered_with_admin_site(model_cls: Any) -> bool:
|
|
77
78
|
"""Returns True if model cls is registered in Admin."""
|
|
78
|
-
for admin_site in all_sites
|
|
79
|
-
if model_cls in admin_site._registry:
|
|
80
|
-
return True
|
|
81
|
-
return False
|
|
79
|
+
return any(model_cls in admin_site._registry for admin_site in all_sites)
|
|
82
80
|
|
|
83
81
|
|
|
84
82
|
class MetadataGetter:
|
edc_metadata/metadata_handler.py
CHANGED
|
@@ -25,7 +25,7 @@ class MetadataHandlerError(Exception):
|
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class MetadataObjectDoesNotExist(Exception):
|
|
28
|
+
class MetadataObjectDoesNotExist(Exception): # noqa: N818
|
|
29
29
|
pass
|
|
30
30
|
|
|
31
31
|
|
|
@@ -73,15 +73,15 @@ class MetadataHandler:
|
|
|
73
73
|
"""Returns a new metadata model instance for this CRF."""
|
|
74
74
|
metadata_obj = None
|
|
75
75
|
try:
|
|
76
|
-
crf =
|
|
76
|
+
crf = next(
|
|
77
77
|
f for f in self.creator.related_visit.visit.all_crfs if f.model == self.model
|
|
78
|
-
|
|
79
|
-
except
|
|
78
|
+
)
|
|
79
|
+
except StopIteration as e:
|
|
80
80
|
if self.related_visit.reason != MISSED_VISIT:
|
|
81
81
|
raise MetadataHandlerError(
|
|
82
82
|
"Create failed. Model not found. Not in visit.all_crfs. "
|
|
83
83
|
f"Model {self.model}. Got {e}"
|
|
84
|
-
)
|
|
84
|
+
) from e
|
|
85
85
|
else:
|
|
86
86
|
metadata_obj = self.creator.create_crf(crf)
|
|
87
87
|
return metadata_obj
|
|
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
|
|
|
22
22
|
class SourceModelMetadataMixin:
|
|
23
23
|
"""Mixin class for Metadata and MetadataUpdater class."""
|
|
24
24
|
|
|
25
|
-
def __init__(self, source_model: str, related_visit: RelatedVisitModel
|
|
25
|
+
def __init__(self, source_model: str, related_visit: RelatedVisitModel):
|
|
26
26
|
self._source_model_obj = None
|
|
27
27
|
self._source_model = source_model
|
|
28
28
|
self.related_visit = related_visit
|
|
@@ -44,7 +44,7 @@ class MetadataRefresher:
|
|
|
44
44
|
def source_models(self) -> list[str]:
|
|
45
45
|
if not self._source_models:
|
|
46
46
|
self._source_models = []
|
|
47
|
-
for
|
|
47
|
+
for rule_groups_list in site_metadata_rules.rule_groups.values():
|
|
48
48
|
for rule_groups in rule_groups_list:
|
|
49
49
|
if (
|
|
50
50
|
rule_groups._meta.source_model
|
|
@@ -19,7 +19,7 @@ class CrfRuleModelConflict(Exception): # noqa: N818
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class CrfRule(Rule):
|
|
22
|
-
def __init__(self, target_models: list[str]
|
|
22
|
+
def __init__(self, target_models: list[str], **kwargs) -> None:
|
|
23
23
|
super().__init__(**kwargs)
|
|
24
24
|
self.metadata_category = CRF
|
|
25
25
|
self.target_models = target_models
|
|
@@ -29,9 +29,9 @@ class Logic:
|
|
|
29
29
|
|
|
30
30
|
def __init__(
|
|
31
31
|
self,
|
|
32
|
-
predicate: P | PF | Callable
|
|
33
|
-
consequence: str
|
|
34
|
-
alternative: str
|
|
32
|
+
predicate: P | PF | Callable,
|
|
33
|
+
consequence: str,
|
|
34
|
+
alternative: str,
|
|
35
35
|
comment: str | None = None,
|
|
36
36
|
) -> None:
|
|
37
37
|
if not callable(predicate):
|
|
@@ -36,11 +36,9 @@ class PersistantSingletonMixin:
|
|
|
36
36
|
)
|
|
37
37
|
except ObjectDoesNotExist:
|
|
38
38
|
obj = None
|
|
39
|
-
required = (
|
|
40
|
-
|
|
41
|
-
if visit == self.get_last_attended_scheduled_visit(visit)
|
|
39
|
+
required = bool(
|
|
40
|
+
visit == self.get_last_attended_scheduled_visit(visit)
|
|
42
41
|
and visit.visit_code not in exclude_visit_codes
|
|
43
|
-
else False
|
|
44
42
|
)
|
|
45
43
|
except MultipleObjectsReturned:
|
|
46
44
|
# necessary if the collection schedule changes and a singleton form
|
|
@@ -33,7 +33,7 @@ class RequisitionRuleGroupMetaOptions(RuleGroupMetaOptions):
|
|
|
33
33
|
super().__init__(group_name, attrs)
|
|
34
34
|
self.requisition_model = self.options.get("requisition_model")
|
|
35
35
|
if self.requisition_model:
|
|
36
|
-
if len(self.requisition_model.split(".")) != 2:
|
|
36
|
+
if len(self.requisition_model.split(".")) != 2: # noqa: PLR2004
|
|
37
37
|
self.requisition_model = f"{self.app_label}.{self.requisition_model}"
|
|
38
38
|
self.options.update(requisition_model=self.requisition_model)
|
|
39
39
|
self.options.update(target_models=[self.requisition_model])
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
from edc_appointment.constants import MISSED_APPT
|
|
@@ -28,9 +29,9 @@ class Rule:
|
|
|
28
29
|
|
|
29
30
|
def __init__(
|
|
30
31
|
self,
|
|
31
|
-
predicate: P | PF |
|
|
32
|
-
consequence: str
|
|
33
|
-
alternative: str
|
|
32
|
+
predicate: P | PF | Callable | str,
|
|
33
|
+
consequence: str,
|
|
34
|
+
alternative: str,
|
|
34
35
|
) -> None:
|
|
35
36
|
self.predicate = predicate
|
|
36
37
|
self.consequence = consequence
|
|
@@ -11,7 +11,7 @@ class RuleGroupError(Exception):
|
|
|
11
11
|
pass
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class TargetModelConflict(Exception):
|
|
14
|
+
class TargetModelConflict(Exception): # noqa: N818
|
|
15
15
|
pass
|
|
16
16
|
|
|
17
17
|
|
|
@@ -61,7 +61,7 @@ class RuleGroup:
|
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
@classmethod
|
|
64
|
-
def _lookup_model(cls, model: str
|
|
64
|
+
def _lookup_model(cls, model: str, category: str) -> Any:
|
|
65
65
|
sys.stdout.write(f" ( ) {model}\r")
|
|
66
66
|
model_cls = None
|
|
67
67
|
try:
|
|
@@ -42,13 +42,13 @@ class RuleGroupMetaOptions:
|
|
|
42
42
|
# source model
|
|
43
43
|
self.source_model = self.options.get("source_model")
|
|
44
44
|
if self.source_model:
|
|
45
|
-
if len(self.source_model.split(".")) != 2:
|
|
45
|
+
if len(self.source_model.split(".")) != 2: # noqa: PLR2004
|
|
46
46
|
self.source_model = f"{self.app_label}.{self.source_model}"
|
|
47
47
|
self.options.update(source_model=self.source_model)
|
|
48
48
|
# related visit model
|
|
49
49
|
self.related_visit_model = self.options.get("related_visit_model")
|
|
50
50
|
if self.related_visit_model:
|
|
51
|
-
if len(self.related_visit_model.split(".")) != 2:
|
|
51
|
+
if len(self.related_visit_model.split(".")) != 2: # noqa: PLR2004
|
|
52
52
|
raise RuleGroupMetaError(
|
|
53
53
|
"Invalid _meta attr. Expected _meta.related_visit_model to be in "
|
|
54
54
|
f"label_lower format. Got '{self.related_visit_model}'. See {group_name}."
|
|
@@ -51,7 +51,7 @@ class RuleGroupMetaclass(type):
|
|
|
51
51
|
return super().__new__(mcs, name, bases, attrs)
|
|
52
52
|
|
|
53
53
|
@classmethod
|
|
54
|
-
def __get_rules(mcs, name: str, attrs: dict, meta: Any) -> tuple:
|
|
54
|
+
def __get_rules(mcs, name: str, attrs: dict, meta: Any) -> tuple: # noqa: N804
|
|
55
55
|
"""Returns a list of rules after updating each rule's attrs
|
|
56
56
|
with values from Meta.
|
|
57
57
|
|
|
@@ -59,28 +59,27 @@ class RuleGroupMetaclass(type):
|
|
|
59
59
|
"""
|
|
60
60
|
rules = []
|
|
61
61
|
for key, value in attrs.items():
|
|
62
|
-
if not key.startswith("_"):
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
rules.append(rule)
|
|
62
|
+
if not key.startswith("_") and isinstance(value, Rule):
|
|
63
|
+
rule = value
|
|
64
|
+
rule.name = key
|
|
65
|
+
rule.group = name
|
|
66
|
+
if isinstance(rule.predicate, (str,)):
|
|
67
|
+
predicates = getattr(meta, "predicates", None)
|
|
68
|
+
if not predicates:
|
|
69
|
+
raise RuleGroupError(
|
|
70
|
+
"RuleGroup Meta attr `predicates` may not be `None` if a "
|
|
71
|
+
"rule.predicate in the RuleGroup is a string. "
|
|
72
|
+
f"See {attrs.get('__qualname__')}."
|
|
73
|
+
)
|
|
74
|
+
rule.predicate = getattr(meta.predicates, rule.predicate)
|
|
75
|
+
for k, v in meta.options.items():
|
|
76
|
+
setattr(rule, k, v)
|
|
77
|
+
rule.target_models = mcs.__get_target_models(rule, meta)
|
|
78
|
+
rules.append(rule)
|
|
80
79
|
return tuple(rules)
|
|
81
80
|
|
|
82
81
|
@classmethod
|
|
83
|
-
def __get_target_models(mcs, rule: Any, meta: Any) -> Any:
|
|
82
|
+
def __get_target_models(mcs, rule: Any, meta: Any) -> Any: # noqa: N804
|
|
84
83
|
"""Returns target models as a list of label_lowers.
|
|
85
84
|
|
|
86
85
|
Target models are the models whose metadata is acted upon.
|
|
@@ -90,7 +89,7 @@ class RuleGroupMetaclass(type):
|
|
|
90
89
|
"""
|
|
91
90
|
target_models = []
|
|
92
91
|
for target_model in rule.target_models:
|
|
93
|
-
if len(target_model.split(".")) != 2:
|
|
94
|
-
target_model = f"{meta.app_label}.{target_model}"
|
|
92
|
+
if len(target_model.split(".")) != 2: # noqa: PLR2004
|
|
93
|
+
target_model = f"{meta.app_label}.{target_model}" # noqa: PLW2901
|
|
95
94
|
target_models.append(target_model)
|
|
96
95
|
return target_models
|
edc_metadata/metadata_updater.py
CHANGED
|
@@ -31,8 +31,9 @@ class MetadataUpdater(SourceModelMetadataMixin):
|
|
|
31
31
|
|
|
32
32
|
def __init__(
|
|
33
33
|
self,
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
*,
|
|
35
|
+
related_visit: RelatedVisitModel,
|
|
36
|
+
source_model: str,
|
|
36
37
|
allow_create: bool | None = None,
|
|
37
38
|
):
|
|
38
39
|
super().__init__(source_model, related_visit)
|
|
@@ -46,7 +47,7 @@ class MetadataUpdater(SourceModelMetadataMixin):
|
|
|
46
47
|
f"source_model={self.source_model})"
|
|
47
48
|
)
|
|
48
49
|
|
|
49
|
-
def get_and_update(self, entry_status: str
|
|
50
|
+
def get_and_update(self, entry_status: str) -> CrfMetadata | RequisitionMetadata:
|
|
50
51
|
metadata_obj = self.metadata_handler.metadata_obj
|
|
51
52
|
if entry_status != KEYED and self.source_model_obj_exists:
|
|
52
53
|
entry_status = KEYED
|