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
|
@@ -43,10 +43,9 @@ class CreatesMetadataModelMixin(RelatedVisitProtocol, models.Model):
|
|
|
43
43
|
|
|
44
44
|
Also called by post_save signal after metadata is updated.
|
|
45
45
|
"""
|
|
46
|
-
|
|
46
|
+
return self.metadata_rule_evaluator_cls(
|
|
47
47
|
related_visit=self, allow_create=allow_create
|
|
48
|
-
)
|
|
49
|
-
metadata_rule_evaluator.evaluate_rules()
|
|
48
|
+
).evaluate_rules()
|
|
50
49
|
|
|
51
50
|
@property
|
|
52
51
|
def metadata_query_options(self) -> dict[str, Any]:
|
|
@@ -54,14 +53,13 @@ class CreatesMetadataModelMixin(RelatedVisitProtocol, models.Model):
|
|
|
54
53
|
the related_visit.
|
|
55
54
|
"""
|
|
56
55
|
visit: Visit = self.visits.get(self.appointment.visit_code)
|
|
57
|
-
|
|
56
|
+
return dict(
|
|
58
57
|
visit_schedule_name=self.appointment.visit_schedule_name,
|
|
59
58
|
schedule_name=self.appointment.schedule_name,
|
|
60
59
|
visit_code=visit.code,
|
|
61
60
|
visit_code_sequence=self.appointment.visit_code_sequence,
|
|
62
61
|
timepoint=self.appointment.timepoint,
|
|
63
62
|
)
|
|
64
|
-
return options
|
|
65
63
|
|
|
66
64
|
@property
|
|
67
65
|
def crf_metadata(self):
|
|
@@ -42,7 +42,7 @@ class UpdatesMetadataModelMixin(models.Model):
|
|
|
42
42
|
metadata_updater_cls = MetadataUpdater
|
|
43
43
|
metadata_category: str = CRF
|
|
44
44
|
|
|
45
|
-
def metadata_update(self: CrfModel | RequisitionModel, entry_status: str
|
|
45
|
+
def metadata_update(self: CrfModel | RequisitionModel, entry_status: str) -> None:
|
|
46
46
|
"""Updates metatadata."""
|
|
47
47
|
self.metadata_updater.get_and_update(entry_status=entry_status)
|
|
48
48
|
|
edc_metadata/next_form_getter.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
from django.apps import apps as django_apps
|
|
@@ -10,6 +11,7 @@ from .metadata import CrfMetadataGetter, RequisitionMetadataGetter
|
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
12
13
|
from edc_appointment.models import Appointment
|
|
14
|
+
from edc_model.utils import CrfLikeModel
|
|
13
15
|
from edc_visit_schedule.visit import Crf, Requisition, Visit
|
|
14
16
|
|
|
15
17
|
from .metadata import MetadataGetter
|
|
@@ -22,10 +24,10 @@ class NextFormGetter:
|
|
|
22
24
|
|
|
23
25
|
def __init__(
|
|
24
26
|
self,
|
|
25
|
-
model_obj=None,
|
|
26
|
-
appointment: Appointment = None,
|
|
27
|
-
model: str = None,
|
|
28
|
-
panel_name: str = None,
|
|
27
|
+
model_obj: CrfLikeModel | None = None,
|
|
28
|
+
appointment: Appointment | None = None,
|
|
29
|
+
model: str | None = None,
|
|
30
|
+
panel_name: str | None = None,
|
|
29
31
|
):
|
|
30
32
|
self._getter = None
|
|
31
33
|
self._next_metadata_obj = None
|
|
@@ -58,7 +60,7 @@ class NextFormGetter:
|
|
|
58
60
|
"""
|
|
59
61
|
if not self._model_obj:
|
|
60
62
|
model_cls = django_apps.get_model(self.model)
|
|
61
|
-
|
|
63
|
+
with contextlib.suppress(ObjectDoesNotExist):
|
|
62
64
|
self._model_obj = model_cls.objects.get(
|
|
63
65
|
**{
|
|
64
66
|
f"{model_cls.related_visit_model_attr()}__appointment": (
|
|
@@ -66,8 +68,6 @@ class NextFormGetter:
|
|
|
66
68
|
)
|
|
67
69
|
}
|
|
68
70
|
)
|
|
69
|
-
except ObjectDoesNotExist:
|
|
70
|
-
pass
|
|
71
71
|
return self._model_obj
|
|
72
72
|
|
|
73
73
|
@property
|
|
@@ -94,23 +94,19 @@ class NextFormGetter:
|
|
|
94
94
|
|
|
95
95
|
@property
|
|
96
96
|
def next_panel(self) -> str | None:
|
|
97
|
-
if not self._next_panel:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
self._next_panel = self.next_metadata_obj.panel_name
|
|
101
|
-
except AttributeError:
|
|
102
|
-
pass
|
|
97
|
+
if not self._next_panel and self.next_metadata_obj:
|
|
98
|
+
with contextlib.suppress(AttributeError):
|
|
99
|
+
self._next_panel = self.next_metadata_obj.panel_name
|
|
103
100
|
return self._next_panel
|
|
104
101
|
|
|
105
102
|
@property
|
|
106
103
|
def panel_name(self) -> str | None:
|
|
107
104
|
"""Returns a panel_name or None."""
|
|
108
|
-
if not self._panel_name:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
self._panel_name = None
|
|
105
|
+
if not self._panel_name and self.model_obj:
|
|
106
|
+
try:
|
|
107
|
+
self._panel_name = self.model_obj.panel.name
|
|
108
|
+
except AttributeError:
|
|
109
|
+
self._panel_name = None
|
|
114
110
|
return self._panel_name
|
|
115
111
|
|
|
116
112
|
@property
|
edc_metadata/offline_models.py
CHANGED
|
@@ -8,6 +8,6 @@ offline_models = []
|
|
|
8
8
|
app_config = django_apps.get_app_config("edc_metadata")
|
|
9
9
|
for model in app_config.get_models():
|
|
10
10
|
if not issubclass(model, ListModelMixin):
|
|
11
|
-
offline_models.append(model._meta.label_lower)
|
|
11
|
+
offline_models.append(model._meta.label_lower) # noqa: PERF401
|
|
12
12
|
|
|
13
13
|
site_offline_models.register(offline_models, OfflineModel)
|
|
@@ -21,24 +21,24 @@ class RequisitionMetadataHandler(MetadataHandler):
|
|
|
21
21
|
super().__init__(**kwargs)
|
|
22
22
|
self.panel = panel
|
|
23
23
|
|
|
24
|
-
def _create(self
|
|
24
|
+
def _create(self) -> RequisitionMetadata:
|
|
25
25
|
"""Returns a created RequisitionMetadata model instance for this
|
|
26
26
|
requisition.
|
|
27
27
|
"""
|
|
28
28
|
metadata_obj = None
|
|
29
29
|
try:
|
|
30
|
-
requisition_object =
|
|
30
|
+
requisition_object = next(
|
|
31
31
|
requisition
|
|
32
32
|
for requisition in self.creator.related_visit.visit.all_requisitions
|
|
33
33
|
if requisition.panel.name == self.panel.name
|
|
34
|
-
|
|
35
|
-
except
|
|
34
|
+
)
|
|
35
|
+
except StopIteration as e:
|
|
36
36
|
if self.related_visit.reason != MISSED_VISIT:
|
|
37
37
|
raise MetadataHandlerError(
|
|
38
38
|
"Panel not found. Not in visit.all_requisitions. "
|
|
39
39
|
f"Panel `{self.panel}` at `{self.creator.related_visit.visit}`. "
|
|
40
40
|
f"Got {e}. Check your visit schedule."
|
|
41
|
-
)
|
|
41
|
+
) from e
|
|
42
42
|
else:
|
|
43
43
|
metadata_obj = self.creator.create_requisition(requisition_object)
|
|
44
44
|
return metadata_obj
|
|
@@ -33,13 +33,11 @@ class UpdateMetadataOnScheduleChange:
|
|
|
33
33
|
f"value for field '{self.fieldname}'.\n"
|
|
34
34
|
f"Old value='{self.old_value}', New value='{self.new_value}'.\n"
|
|
35
35
|
)
|
|
36
|
-
for
|
|
36
|
+
for model_cls in self.models.values():
|
|
37
37
|
count = model_cls.objects.filter(**{self.fieldname: self.old_value}).count()
|
|
38
38
|
sys.stdout.write(f"{model_cls._meta.label_lower}. {count} records found.\n")
|
|
39
39
|
sys.stdout.write(
|
|
40
|
-
style.ERROR(
|
|
41
|
-
"No records have been updated. \n" "Set --dry-run=False to update.\n"
|
|
42
|
-
)
|
|
40
|
+
style.ERROR("No records have been updated. \nSet --dry-run=False to update.\n")
|
|
43
41
|
)
|
|
44
42
|
else:
|
|
45
43
|
sys.stdout.write(style.SUCCESS("Updating... \n"))
|
edc_metadata/utils.py
CHANGED
edc_model/models/signals.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
|
|
1
3
|
from django import dispatch
|
|
4
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
2
5
|
from django.db.models import expressions
|
|
3
6
|
from simple_history import signals
|
|
4
7
|
|
|
@@ -17,12 +20,14 @@ def remove_f_expressions(sender, instance, history_instance, **kwargs) -> None:
|
|
|
17
20
|
"""
|
|
18
21
|
f_expression_fields = []
|
|
19
22
|
for field in history_instance._meta.fields:
|
|
20
|
-
|
|
23
|
+
with contextlib.suppress(ObjectDoesNotExist):
|
|
24
|
+
field_value = getattr(history_instance, field.name)
|
|
21
25
|
if isinstance(field_value, expressions.BaseExpression):
|
|
22
26
|
f_expression_fields.append(field.name)
|
|
23
27
|
|
|
24
28
|
if f_expression_fields:
|
|
25
29
|
instance.refresh_from_db()
|
|
26
30
|
for field_name in f_expression_fields:
|
|
27
|
-
|
|
31
|
+
with contextlib.suppress(ObjectDoesNotExist):
|
|
32
|
+
field_value = getattr(instance, field_name)
|
|
28
33
|
setattr(history_instance, field_name, field_value)
|
|
@@ -6,7 +6,6 @@ from django.contrib import messages
|
|
|
6
6
|
from django.http.response import HttpResponseRedirect
|
|
7
7
|
from django.urls import reverse
|
|
8
8
|
from django.utils.encoding import force_str
|
|
9
|
-
|
|
10
9
|
from edc_dashboard.url_names import InvalidDashboardUrlName, url_names
|
|
11
10
|
|
|
12
11
|
|
|
@@ -75,8 +74,10 @@ class ModelAdminRedirectOnDeleteMixin:
|
|
|
75
74
|
"""Overridden to redirect to `post_url_on_delete`, if not None."""
|
|
76
75
|
if self.post_url_on_delete:
|
|
77
76
|
opts = self.model._meta
|
|
78
|
-
msg = (
|
|
79
|
-
|
|
77
|
+
msg = (
|
|
78
|
+
f'The {force_str(opts.verbose_name)} "{force_str(obj_display)}" '
|
|
79
|
+
"was deleted successfully."
|
|
80
|
+
)
|
|
80
81
|
messages.add_message(request, messages.SUCCESS, msg)
|
|
81
82
|
return HttpResponseRedirect(self.post_url_on_delete)
|
|
82
83
|
return super().response_delete(request, obj_display, obj_id)
|
edc_navbar/apps.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from django.apps import AppConfig as DjangoAppConfig
|
|
2
|
-
from django.conf import settings
|
|
3
2
|
from django.core.management.color import color_style
|
|
4
3
|
|
|
5
4
|
style = color_style()
|
|
@@ -9,4 +8,3 @@ class AppConfig(DjangoAppConfig):
|
|
|
9
8
|
name = "edc_navbar"
|
|
10
9
|
verbose_name = "Edc Navbar"
|
|
11
10
|
register_default_navbar = True
|
|
12
|
-
default_navbar_name = getattr(settings, "DEFAULT_NAVBAR_NAME", "default")
|
edc_navbar/navbar.py
CHANGED
edc_navbar/navbar_item.py
CHANGED
|
@@ -6,7 +6,6 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
from django.core.management.color import color_style
|
|
7
7
|
from django.urls import NoReverseMatch
|
|
8
8
|
from django.urls.base import reverse
|
|
9
|
-
|
|
10
9
|
from edc_dashboard.url_names import InvalidDashboardUrlName, url_names
|
|
11
10
|
|
|
12
11
|
if TYPE_CHECKING:
|
|
@@ -23,10 +22,13 @@ class NavbarItem:
|
|
|
23
22
|
title: str = field(default=None)
|
|
24
23
|
label: str | None = field(default=None)
|
|
25
24
|
codename: str = field(default=None)
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
url_names_key: str | None = field(
|
|
26
|
+
default=None
|
|
27
|
+
) # must be valid key in url_names dictionary
|
|
28
|
+
url_with_namespace: str | None = field(default=None)
|
|
29
|
+
url_without_namespace: str | None = field(default=None)
|
|
28
30
|
fa_icon: str | None = field(default=None)
|
|
29
|
-
disabled: str = field(default="disabled")
|
|
31
|
+
disabled: str | None = field(default="disabled")
|
|
30
32
|
|
|
31
33
|
active: bool = field(default=None)
|
|
32
34
|
|
|
@@ -37,24 +39,35 @@ class NavbarItem:
|
|
|
37
39
|
|
|
38
40
|
def __post_init__(self):
|
|
39
41
|
self.title = self.title or self.label or self.name.title() # the anchor title
|
|
42
|
+
if self.url_with_namespace and ":" not in self.url_with_namespace:
|
|
43
|
+
raise InvalidDashboardUrlName(
|
|
44
|
+
f"Invalid url_with_namespace. Got {self.url_with_namespace}"
|
|
45
|
+
)
|
|
40
46
|
|
|
41
47
|
def get_url(self, raise_exception: bool | None = None) -> str | None:
|
|
48
|
+
url = (
|
|
49
|
+
self.url_without_namespace
|
|
50
|
+
or self.url_with_namespace
|
|
51
|
+
or url_names.get(self.url_names_key)
|
|
52
|
+
)
|
|
42
53
|
try:
|
|
43
|
-
url = reverse(
|
|
44
|
-
except NoReverseMatch:
|
|
45
|
-
url = None
|
|
54
|
+
url = reverse(url)
|
|
55
|
+
except NoReverseMatch as e:
|
|
46
56
|
if raise_exception:
|
|
47
|
-
|
|
57
|
+
errmsg = (
|
|
58
|
+
f"Reverse for Navbar url not found. Tried {url}. "
|
|
59
|
+
f"See NavbarItem(name={self.name}"
|
|
60
|
+
)
|
|
61
|
+
if self.url_without_namespace == url:
|
|
62
|
+
errmsg = f"{errmsg}, url_without_namespace={self.url_without_namespace})."
|
|
63
|
+
elif self.url_with_namespace == url:
|
|
64
|
+
errmsg = f"{errmsg}, url_with_namespace={self.url_with_namespace})."
|
|
65
|
+
elif url_names.get(self.url_names_key) == url:
|
|
66
|
+
errmsg = f"{errmsg}, url_names_key={self.url_names_key})."
|
|
67
|
+
raise NoReverseMatch(errmsg) from e
|
|
68
|
+
url = None
|
|
48
69
|
return url
|
|
49
70
|
|
|
50
|
-
@property
|
|
51
|
-
def real_url_name(self) -> str:
|
|
52
|
-
try:
|
|
53
|
-
url_name = url_names.get(self.url_name)
|
|
54
|
-
except InvalidDashboardUrlName:
|
|
55
|
-
url_name = self.url_name.split(":")[1] if self.no_url_namespace else self.url_name
|
|
56
|
-
return url_name
|
|
57
|
-
|
|
58
71
|
def set_disabled(self, user: User | None = None):
|
|
59
72
|
if user and user.has_perm(self.codename):
|
|
60
73
|
self.disabled = ""
|
edc_navbar/navbars.py
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
from django.apps import apps as django_apps
|
|
2
|
-
|
|
3
1
|
from .navbar import Navbar
|
|
4
2
|
from .navbar_item import NavbarItem
|
|
5
3
|
from .site_navbars import site_navbars
|
|
4
|
+
from .utils import get_default_navbar_name, get_register_default_navbar
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if app_config.register_default_navbar:
|
|
10
|
-
default_navbar = Navbar(name=app_config.default_navbar_name)
|
|
6
|
+
if get_register_default_navbar():
|
|
7
|
+
default_navbar = Navbar(name=get_default_navbar_name())
|
|
11
8
|
|
|
12
9
|
default_navbar.register(
|
|
13
10
|
NavbarItem(
|
|
14
11
|
name="home",
|
|
15
12
|
title="Home",
|
|
16
13
|
fa_icon="fa-home",
|
|
17
|
-
|
|
14
|
+
url_without_namespace="home_url",
|
|
18
15
|
codename="edc_navbar.nav_home",
|
|
19
16
|
)
|
|
20
17
|
)
|
|
@@ -25,19 +22,9 @@ if app_config.register_default_navbar:
|
|
|
25
22
|
title="Administration",
|
|
26
23
|
fa_icon="fa-cog",
|
|
27
24
|
codename="edc_navbar.nav_administration",
|
|
28
|
-
|
|
25
|
+
# url_names_key="administration",
|
|
26
|
+
url_without_namespace="administration_url",
|
|
29
27
|
)
|
|
30
28
|
)
|
|
31
29
|
|
|
32
|
-
# remove to use a form. See edc_navbar.html
|
|
33
|
-
# default_navbar.register(
|
|
34
|
-
# NavbarItem(
|
|
35
|
-
# name="logout",
|
|
36
|
-
# title="Logout",
|
|
37
|
-
# fa_icon="fa-sign-out-alt",
|
|
38
|
-
# url_name="edc_auth_admin:logout",
|
|
39
|
-
# codename="edc_navbar.nav_logout",
|
|
40
|
-
# )
|
|
41
|
-
# )
|
|
42
|
-
|
|
43
30
|
site_navbars.register(default_navbar)
|
edc_navbar/site_navbars.py
CHANGED
|
@@ -44,16 +44,16 @@ class NavbarCollection:
|
|
|
44
44
|
navbar_name=name,
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
-
def get_navbar(self, name: str
|
|
47
|
+
def get_navbar(self, name: str, selected_item: str | None = None) -> Navbar:
|
|
48
48
|
"""Returns a selected navbar in the collection."""
|
|
49
49
|
# does the navbar exist?
|
|
50
50
|
try:
|
|
51
51
|
navbar: Navbar = self.registry[name]
|
|
52
|
-
except KeyError:
|
|
52
|
+
except KeyError as e:
|
|
53
53
|
raise NavbarError(
|
|
54
54
|
f"Navbar '{name}' does not exist. Expected one of "
|
|
55
55
|
f"{list(self.registry.keys())}. See {self!r}."
|
|
56
|
-
)
|
|
56
|
+
) from e
|
|
57
57
|
else:
|
|
58
58
|
# does the navbar have items?
|
|
59
59
|
if not navbar.navbar_items:
|
|
@@ -66,10 +66,9 @@ class NavbarCollection:
|
|
|
66
66
|
navbar.set_active(selected_item)
|
|
67
67
|
return navbar
|
|
68
68
|
|
|
69
|
-
def show_user_permissions(self, username: str
|
|
69
|
+
def show_user_permissions(self, username: str, navbar_name: str):
|
|
70
70
|
user = django_apps.get_model("auth.user").objects.get(username=username)
|
|
71
|
-
|
|
72
|
-
return navbar.show_user_permissions(user=user)
|
|
71
|
+
return self.registry.get(navbar_name).show_user_permissions(user=user)
|
|
73
72
|
|
|
74
73
|
def show_user_codenames(self, username=None, navbar_name=None):
|
|
75
74
|
user_permissions = self.show_user_permissions(username, navbar_name)
|
|
@@ -101,7 +100,7 @@ class NavbarCollection:
|
|
|
101
100
|
except ImportError as e:
|
|
102
101
|
site_navbars.registry = before_import_registry
|
|
103
102
|
if module_has_submodule(mod, module_name):
|
|
104
|
-
raise NavbarError(e)
|
|
103
|
+
raise NavbarError(e) from e
|
|
105
104
|
except ImportError:
|
|
106
105
|
pass
|
|
107
106
|
|
edc_navbar/system_checks.py
CHANGED
|
@@ -8,7 +8,7 @@ from edc_navbar import site_navbars
|
|
|
8
8
|
def edc_navbar_checks(app_configs, **kwargs) -> list[CheckMessage]:
|
|
9
9
|
errors = []
|
|
10
10
|
|
|
11
|
-
for
|
|
11
|
+
for navbar in site_navbars.registry.values():
|
|
12
12
|
for navbar_item in navbar.navbar_items:
|
|
13
13
|
try:
|
|
14
14
|
app_label, codename = navbar_item.codename.split(".")
|
|
@@ -30,13 +30,6 @@ def edc_navbar_checks(app_configs, **kwargs) -> list[CheckMessage]:
|
|
|
30
30
|
errors.append(Error(msg, id="edc_navbar.E002"))
|
|
31
31
|
try:
|
|
32
32
|
navbar_item.get_url(raise_exception=True)
|
|
33
|
-
except NoReverseMatch:
|
|
34
|
-
errors.append(
|
|
35
|
-
Error(
|
|
36
|
-
f"NoReverseMatch for url. Got '{navbar_item.real_url_name}'. "
|
|
37
|
-
f"See {navbar_item.name}.",
|
|
38
|
-
id="edc_navbar.E003",
|
|
39
|
-
)
|
|
40
|
-
)
|
|
41
|
-
|
|
33
|
+
except NoReverseMatch as e:
|
|
34
|
+
errors.append(Error(str(e), id="edc_navbar.E003"))
|
|
42
35
|
return errors
|
edc_navbar/utils.py
CHANGED
|
@@ -7,3 +7,17 @@ def get_autodiscover():
|
|
|
7
7
|
|
|
8
8
|
def get_verify_on_load():
|
|
9
9
|
return getattr(settings, "EDC_NAVBAR_VERIFY_ON_LOAD", "")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_register_default_navbar():
|
|
13
|
+
return getattr(settings, "EDC_NAVBAR_REGISTER_DEFAULT_NAVBAR", True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_default_navbar_name():
|
|
17
|
+
"""Returns the default navbar name.
|
|
18
|
+
|
|
19
|
+
For example: inte_dashboard for project INTE.
|
|
20
|
+
"""
|
|
21
|
+
return getattr(
|
|
22
|
+
settings, "EDC_NAVBAR_DEFAULT_NAVBAR_NAME", f"{settings.APP_NAME}_dashboard".lower()
|
|
23
|
+
)
|
edc_navbar/view_mixin.py
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
from django.apps import apps as django_apps
|
|
4
|
-
|
|
5
|
-
from .get_default_navbar import get_default_navbar
|
|
6
3
|
from .site_navbars import site_navbars
|
|
4
|
+
from .utils import get_default_navbar_name
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
class NavbarViewMixin:
|
|
10
8
|
navbar_selected_item = None
|
|
11
|
-
navbar_name =
|
|
9
|
+
navbar_name = get_default_navbar_name()
|
|
12
10
|
|
|
13
11
|
def get_context_data(self, **kwargs) -> dict[str, Any]:
|
|
14
12
|
"""Add rendered navbar <navbar_name> to the context for
|
|
@@ -16,18 +14,17 @@ class NavbarViewMixin:
|
|
|
16
14
|
|
|
17
15
|
Also adds the "default" navbar.
|
|
18
16
|
"""
|
|
19
|
-
kwargs = self.
|
|
17
|
+
kwargs = self.get_context_data_for_navbars(kwargs)
|
|
20
18
|
return super().get_context_data(**kwargs)
|
|
21
19
|
|
|
22
20
|
def get_navbar_name(self):
|
|
23
21
|
return self.navbar_name
|
|
24
22
|
|
|
25
|
-
def
|
|
23
|
+
def get_context_data_for_navbars(self, context) -> dict:
|
|
26
24
|
navbar = site_navbars.get_navbar(name=self.get_navbar_name())
|
|
27
25
|
navbar.set_active(self.get_navbar_selected(**context))
|
|
28
26
|
context.update(navbar=navbar)
|
|
29
|
-
|
|
30
|
-
default_navbar_name = app_config.default_navbar_name
|
|
27
|
+
default_navbar_name = get_default_navbar_name()
|
|
31
28
|
if default_navbar_name and self.get_navbar_name() != default_navbar_name:
|
|
32
29
|
default_navbar = site_navbars.get_navbar(name=default_navbar_name)
|
|
33
30
|
default_navbar.set_active(self.navbar_selected_item)
|
|
@@ -36,5 +33,5 @@ class NavbarViewMixin:
|
|
|
36
33
|
)
|
|
37
34
|
return context
|
|
38
35
|
|
|
39
|
-
def get_navbar_selected(self, **kwargs) -> str:
|
|
36
|
+
def get_navbar_selected(self, **kwargs) -> str: # noqa: ARG002
|
|
40
37
|
return self.navbar_selected_item
|
edc_pharmacy/navbars.py
CHANGED
|
@@ -9,7 +9,6 @@ from django.urls import reverse
|
|
|
9
9
|
from django.utils.decorators import method_decorator
|
|
10
10
|
from django.utils.translation import gettext as _
|
|
11
11
|
from django.views.generic.base import TemplateView
|
|
12
|
-
|
|
13
12
|
from edc_constants.constants import CONFIRMED
|
|
14
13
|
from edc_dashboard.view_mixins import EdcViewMixin
|
|
15
14
|
from edc_navbar import NavbarViewMixin
|
|
@@ -29,8 +28,8 @@ class ConfirmStockFromQuerySetView(
|
|
|
29
28
|
navbar_selected_item = "pharmacy"
|
|
30
29
|
codes_per_page = 12
|
|
31
30
|
|
|
32
|
-
def get_context_data(self, **kwargs):
|
|
33
|
-
|
|
31
|
+
def get_context_data(self, **kwargs):
|
|
32
|
+
kwargs.update(
|
|
34
33
|
CONFIRMED=CONFIRMED,
|
|
35
34
|
ALREADY_CONFIRMED=ALREADY_CONFIRMED,
|
|
36
35
|
INVALID=INVALID,
|
|
@@ -40,6 +39,7 @@ class ConfirmStockFromQuerySetView(
|
|
|
40
39
|
source_changelist_url=self.source_changelist_url,
|
|
41
40
|
**self.session_data,
|
|
42
41
|
)
|
|
42
|
+
return super().get_context_data(**kwargs)
|
|
43
43
|
|
|
44
44
|
@property
|
|
45
45
|
def session_data(self):
|
edc_protocol/middleware.py
CHANGED
|
@@ -8,18 +8,14 @@ class ResearchProtocolConfigMiddleware:
|
|
|
8
8
|
def __call__(self, request):
|
|
9
9
|
return self.get_response(request)
|
|
10
10
|
|
|
11
|
-
def process_view(self, request, *args):
|
|
12
|
-
pass
|
|
13
|
-
|
|
14
11
|
def process_template_response(self, request, response): # noqa: ARG002
|
|
15
|
-
if
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
)
|
|
12
|
+
if getattr(response, "context_data", None):
|
|
13
|
+
protocol_config = ResearchProtocolConfig()
|
|
14
|
+
response.context_data.update(
|
|
15
|
+
copyright=protocol_config.copyright,
|
|
16
|
+
disclaimer=protocol_config.disclaimer,
|
|
17
|
+
institution=protocol_config.institution,
|
|
18
|
+
license=protocol_config.license,
|
|
19
|
+
project_name=protocol_config.project_name,
|
|
20
|
+
)
|
|
25
21
|
return response
|
edc_protocol/navbars.py
CHANGED
edc_refusal/forms.py
CHANGED
|
@@ -2,7 +2,6 @@ from django import forms
|
|
|
2
2
|
from django.core.exceptions import ObjectDoesNotExist
|
|
3
3
|
from django.urls.base import reverse
|
|
4
4
|
from django.utils.html import format_html
|
|
5
|
-
|
|
6
5
|
from edc_constants.constants import OTHER
|
|
7
6
|
from edc_dashboard.url_names import url_names
|
|
8
7
|
from edc_form_validators import FormValidator, FormValidatorMixin
|
|
@@ -47,8 +46,7 @@ class AlreadyConsentedFormMixin:
|
|
|
47
46
|
kwargs={"subject_identifier": obj.subject_identifier},
|
|
48
47
|
)
|
|
49
48
|
msg = format_html(
|
|
50
|
-
|
|
51
|
-
'See subject <A href="{}">{}</A>',
|
|
49
|
+
'Not allowed. Subject has already consented. See subject <A href="{}">{}</A>',
|
|
52
50
|
url,
|
|
53
51
|
obj.subject_identifier,
|
|
54
52
|
)
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
|
|
3
3
|
from django.conf import settings
|
|
4
|
+
from edc_dashboard.middleware_mixins import EdcTemplateMiddlewareMixin
|
|
4
5
|
|
|
5
6
|
from .dashboard_templates import dashboard_templates
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
class DashboardMiddleware:
|
|
9
|
+
class DashboardMiddleware(EdcTemplateMiddlewareMixin):
|
|
9
10
|
def __init__(self, get_response):
|
|
10
11
|
self.get_response = get_response
|
|
11
12
|
|
|
12
13
|
def __call__(self, request):
|
|
14
|
+
self.check_for_required_request_attrs(request)
|
|
13
15
|
return self.get_response(request)
|
|
14
16
|
|
|
15
|
-
def process_view(self, request, *args) -> None:
|
|
17
|
+
def process_view(self, request, *args) -> None:
|
|
16
18
|
template_data = dashboard_templates
|
|
17
19
|
with contextlib.suppress(AttributeError):
|
|
18
20
|
template_data.update(settings.REVIEW_DASHBOARD_BASE_TEMPLATES)
|
|
19
21
|
request.template_data.update(**template_data)
|
|
20
22
|
|
|
21
23
|
def process_template_response(self, request, response):
|
|
22
|
-
if response
|
|
24
|
+
if getattr(response, "context_data", None):
|
|
23
25
|
response.context_data.update(**request.template_data)
|
|
26
|
+
request.template_data.update(**request.template_data)
|
|
24
27
|
return response
|
edc_review_dashboard/navbars.py
CHANGED
|
@@ -7,10 +7,9 @@ navbar_item = NavbarItem(
|
|
|
7
7
|
label="Review",
|
|
8
8
|
title="Subject Review",
|
|
9
9
|
codename="edc_review_dashboard.view_subject_review_listboard",
|
|
10
|
-
|
|
10
|
+
url_names_key="subject_review_listboard_url",
|
|
11
11
|
)
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
navbar.register(navbar_item)
|
|
15
14
|
|
|
16
15
|
site_navbars.register(navbar)
|