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_dashboard/url_config.py
CHANGED
|
@@ -17,25 +17,44 @@ if TYPE_CHECKING:
|
|
|
17
17
|
class View(UrlRequestContextMixin, BaseView): ...
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
class UrlConfigError(Exception):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
20
24
|
class UrlConfig:
|
|
25
|
+
"""A class to generate url_patterns for edc DashboardViews,
|
|
26
|
+
ListBoardViews and SubjectReviewDashboardView.
|
|
27
|
+
|
|
28
|
+
* registers the url_with_namespace to `url_names`
|
|
29
|
+
* The pretty url uses the `url_names_key` less the '_url' suffix
|
|
30
|
+
* the url pattern name is the same as the given `url_names_key`
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
|
|
21
34
|
def __init__(
|
|
22
35
|
self,
|
|
23
36
|
*,
|
|
24
|
-
|
|
37
|
+
url_names_key: str,
|
|
25
38
|
namespace: str,
|
|
26
39
|
view_class: type[View | UrlRequestContextMixin],
|
|
27
|
-
label: str,
|
|
28
40
|
identifier_label: str,
|
|
29
41
|
identifier_pattern: str,
|
|
30
42
|
):
|
|
43
|
+
if not url_names_key.endswith("_url"):
|
|
44
|
+
raise UrlConfigError(
|
|
45
|
+
f"Invalid `url_names_key`. Must end with '_url'. Got {url_names_key}."
|
|
46
|
+
)
|
|
47
|
+
self.url_pattern_name = url_names_key
|
|
48
|
+
self.url_pretty_label = url_names_key.replace("_url", "")
|
|
49
|
+
self.view_class = view_class
|
|
31
50
|
self.identifier_label = identifier_label
|
|
32
51
|
self.identifier_pattern = identifier_pattern
|
|
33
|
-
self.label = label
|
|
34
|
-
self.url_name = url_name
|
|
35
|
-
self.view_class = view_class
|
|
36
52
|
|
|
37
|
-
# register
|
|
38
|
-
url_names.register(
|
|
53
|
+
# register with url_names dictionary / registry
|
|
54
|
+
url_names.register(
|
|
55
|
+
key=url_names_key,
|
|
56
|
+
url_with_namespace=f"{namespace}:{self.url_pattern_name}",
|
|
57
|
+
)
|
|
39
58
|
|
|
40
59
|
@property
|
|
41
60
|
def dashboard_urls(self) -> list[URLPattern]:
|
|
@@ -49,13 +68,13 @@ class UrlConfig:
|
|
|
49
68
|
r"(?P<visit_code>\w+)/"
|
|
50
69
|
r"(?P<unscheduled>\w+)/".format(
|
|
51
70
|
**dict(
|
|
52
|
-
label=self.
|
|
71
|
+
label=self.url_pretty_label,
|
|
53
72
|
identifier_label=self.identifier_label,
|
|
54
73
|
identifier_pattern=self.identifier_pattern,
|
|
55
74
|
)
|
|
56
75
|
),
|
|
57
76
|
self.view_class.as_view(),
|
|
58
|
-
name=self.
|
|
77
|
+
name=self.url_pattern_name,
|
|
59
78
|
),
|
|
60
79
|
re_path(
|
|
61
80
|
"{label}/"
|
|
@@ -64,13 +83,13 @@ class UrlConfig:
|
|
|
64
83
|
r"(?P<schedule_name>\w+)/"
|
|
65
84
|
r"(?P<visit_code>\w+)/".format(
|
|
66
85
|
**dict(
|
|
67
|
-
label=self.
|
|
86
|
+
label=self.url_pretty_label,
|
|
68
87
|
identifier_label=self.identifier_label,
|
|
69
88
|
identifier_pattern=self.identifier_pattern,
|
|
70
89
|
)
|
|
71
90
|
),
|
|
72
91
|
self.view_class.as_view(),
|
|
73
|
-
name=self.
|
|
92
|
+
name=self.url_pattern_name,
|
|
74
93
|
),
|
|
75
94
|
re_path(
|
|
76
95
|
"{label}/"
|
|
@@ -79,14 +98,14 @@ class UrlConfig:
|
|
|
79
98
|
r"(?P<scanning>\d)/"
|
|
80
99
|
r"(?P<error>\d)/".format(
|
|
81
100
|
**dict(
|
|
82
|
-
label=self.
|
|
101
|
+
label=self.url_pretty_label,
|
|
83
102
|
identifier_label=self.identifier_label,
|
|
84
103
|
identifier_pattern=self.identifier_pattern,
|
|
85
104
|
uuid_pattern=UUID_PATTERN.pattern,
|
|
86
105
|
)
|
|
87
106
|
),
|
|
88
107
|
self.view_class.as_view(),
|
|
89
|
-
name=self.
|
|
108
|
+
name=self.url_pattern_name,
|
|
90
109
|
),
|
|
91
110
|
re_path(
|
|
92
111
|
"{label}/"
|
|
@@ -94,52 +113,52 @@ class UrlConfig:
|
|
|
94
113
|
"(?P<appointment>{uuid_pattern})/"
|
|
95
114
|
r"(?P<reason>\w+)/".format(
|
|
96
115
|
**dict(
|
|
97
|
-
label=self.
|
|
116
|
+
label=self.url_pretty_label,
|
|
98
117
|
identifier_label=self.identifier_label,
|
|
99
118
|
identifier_pattern=self.identifier_pattern,
|
|
100
119
|
uuid_pattern=UUID_PATTERN.pattern,
|
|
101
120
|
)
|
|
102
121
|
),
|
|
103
122
|
self.view_class.as_view(),
|
|
104
|
-
name=self.
|
|
123
|
+
name=self.url_pattern_name,
|
|
105
124
|
),
|
|
106
125
|
re_path(
|
|
107
126
|
"{label}/"
|
|
108
127
|
"(?P<{identifier_label}>{identifier_pattern})/"
|
|
109
128
|
"(?P<appointment>{uuid_pattern})/".format(
|
|
110
129
|
**dict(
|
|
111
|
-
label=self.
|
|
130
|
+
label=self.url_pretty_label,
|
|
112
131
|
identifier_label=self.identifier_label,
|
|
113
132
|
identifier_pattern=self.identifier_pattern,
|
|
114
133
|
uuid_pattern=UUID_PATTERN.pattern,
|
|
115
134
|
)
|
|
116
135
|
),
|
|
117
136
|
self.view_class.as_view(),
|
|
118
|
-
name=self.
|
|
137
|
+
name=self.url_pattern_name,
|
|
119
138
|
),
|
|
120
139
|
re_path(
|
|
121
140
|
"{label}/"
|
|
122
141
|
"(?P<{identifier_label}>{identifier_pattern})/"
|
|
123
142
|
r"(?P<schedule_name>\w+)/".format(
|
|
124
143
|
**dict(
|
|
125
|
-
label=self.
|
|
144
|
+
label=self.url_pretty_label,
|
|
126
145
|
identifier_label=self.identifier_label,
|
|
127
146
|
identifier_pattern=self.identifier_pattern,
|
|
128
147
|
)
|
|
129
148
|
),
|
|
130
149
|
self.view_class.as_view(),
|
|
131
|
-
name=self.
|
|
150
|
+
name=self.url_pattern_name,
|
|
132
151
|
),
|
|
133
152
|
re_path(
|
|
134
153
|
"{label}/(?P<{identifier_label}>{identifier_pattern})/".format(
|
|
135
154
|
**dict(
|
|
136
|
-
label=self.
|
|
155
|
+
label=self.url_pretty_label,
|
|
137
156
|
identifier_label=self.identifier_label,
|
|
138
157
|
identifier_pattern=self.identifier_pattern,
|
|
139
158
|
)
|
|
140
159
|
),
|
|
141
160
|
self.view_class.as_view(),
|
|
142
|
-
name=self.
|
|
161
|
+
name=self.url_pattern_name,
|
|
143
162
|
),
|
|
144
163
|
]
|
|
145
164
|
|
|
@@ -154,34 +173,34 @@ class UrlConfig:
|
|
|
154
173
|
"{label}/(?P<{identifier_label}>{identifier_pattern})/"
|
|
155
174
|
r"(?P<page>\d+)/".format(
|
|
156
175
|
**dict(
|
|
157
|
-
label=self.
|
|
176
|
+
label=self.url_pretty_label,
|
|
158
177
|
identifier_label=self.identifier_label,
|
|
159
178
|
identifier_pattern=self.identifier_pattern,
|
|
160
179
|
)
|
|
161
180
|
),
|
|
162
181
|
self.view_class.as_view(),
|
|
163
|
-
name=self.
|
|
182
|
+
name=self.url_pattern_name,
|
|
164
183
|
),
|
|
165
184
|
re_path(
|
|
166
185
|
"{label}/(?P<{identifier_label}>{identifier_pattern})/".format(
|
|
167
186
|
**dict(
|
|
168
|
-
label=self.
|
|
187
|
+
label=self.url_pretty_label,
|
|
169
188
|
identifier_label=self.identifier_label,
|
|
170
189
|
identifier_pattern=self.identifier_pattern,
|
|
171
190
|
)
|
|
172
191
|
),
|
|
173
192
|
self.view_class.as_view(),
|
|
174
|
-
name=self.
|
|
193
|
+
name=self.url_pattern_name,
|
|
175
194
|
),
|
|
176
195
|
re_path(
|
|
177
|
-
r"{label}/(?P<page>\d+)/".format(**dict(label=self.
|
|
196
|
+
r"{label}/(?P<page>\d+)/".format(**dict(label=self.url_pretty_label)),
|
|
178
197
|
self.view_class.as_view(),
|
|
179
|
-
name=self.
|
|
198
|
+
name=self.url_pattern_name,
|
|
180
199
|
),
|
|
181
200
|
re_path(
|
|
182
|
-
r"{label}/".format(**dict(label=self.
|
|
201
|
+
r"{label}/".format(**dict(label=self.url_pretty_label)),
|
|
183
202
|
self.view_class.as_view(),
|
|
184
|
-
name=self.
|
|
203
|
+
name=self.url_pattern_name,
|
|
185
204
|
),
|
|
186
205
|
]
|
|
187
206
|
|
|
@@ -192,14 +211,14 @@ class UrlConfig:
|
|
|
192
211
|
"{label}/(?P<{identifier_label}>{identifier_pattern})/"
|
|
193
212
|
"(?P<appointment>{uuid_pattern})/".format(
|
|
194
213
|
**dict(
|
|
195
|
-
label=self.
|
|
214
|
+
label=self.url_pretty_label,
|
|
196
215
|
identifier_label=self.identifier_label,
|
|
197
216
|
identifier_pattern=self.identifier_pattern,
|
|
198
217
|
uuid_pattern=UUID_PATTERN.pattern,
|
|
199
218
|
)
|
|
200
219
|
),
|
|
201
220
|
self.view_class.as_view(),
|
|
202
|
-
name=self.
|
|
221
|
+
name=self.url_pattern_name,
|
|
203
222
|
)
|
|
204
223
|
]
|
|
205
224
|
url_patterns.extend(self.listboard_urls)
|
edc_dashboard/url_names.py
CHANGED
|
@@ -16,35 +16,41 @@ class UrlNames:
|
|
|
16
16
|
registry: dict[str, str] = field(default_factory=dict)
|
|
17
17
|
|
|
18
18
|
def register(
|
|
19
|
-
self,
|
|
19
|
+
self,
|
|
20
|
+
key: str,
|
|
21
|
+
namespace: str | None = None,
|
|
22
|
+
url: str | None = None,
|
|
23
|
+
url_with_namespace: str | None = None,
|
|
20
24
|
) -> None:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
url_with_namespace = url_with_namespace or f"{namespace}:{url}"
|
|
26
|
+
if key in self.registry:
|
|
27
|
+
raise AlreadyRegistered(
|
|
28
|
+
"Url already registered with url_names. "
|
|
29
|
+
f"See {key}:{self.registry[key]}. Got {url_with_namespace}."
|
|
30
|
+
)
|
|
31
|
+
self.registry.update({key: url_with_namespace})
|
|
26
32
|
|
|
27
33
|
def register_from_dict(self, **urldata: str) -> None:
|
|
28
|
-
for
|
|
34
|
+
for key, url_with_namespace in urldata.items():
|
|
29
35
|
try:
|
|
30
|
-
namespace, url =
|
|
36
|
+
namespace, url = url_with_namespace.split(":")
|
|
31
37
|
except ValueError:
|
|
32
|
-
namespace, url =
|
|
33
|
-
self.register(
|
|
38
|
+
namespace, url = url_with_namespace, None
|
|
39
|
+
self.register(key, namespace, url=url)
|
|
34
40
|
|
|
35
41
|
def all(self) -> dict[str, str]:
|
|
36
42
|
return self.registry
|
|
37
43
|
|
|
38
|
-
def get(self,
|
|
39
|
-
if
|
|
44
|
+
def get(self, key: str) -> str:
|
|
45
|
+
if key not in self.registry:
|
|
40
46
|
raise InvalidDashboardUrlName(
|
|
41
|
-
f"Invalid
|
|
42
|
-
f"Got '{
|
|
47
|
+
f"Invalid key for url_names. Expected one of {self.registry.keys()}. "
|
|
48
|
+
f"Got '{key}'."
|
|
43
49
|
)
|
|
44
|
-
return self.registry.get(
|
|
50
|
+
return self.registry.get(key)
|
|
45
51
|
|
|
46
|
-
def get_or_raise(self,
|
|
47
|
-
return self.get(
|
|
52
|
+
def get_or_raise(self, key: str) -> str:
|
|
53
|
+
return self.get(key)
|
|
48
54
|
|
|
49
55
|
|
|
50
56
|
url_names = UrlNames()
|
edc_dashboard/utils.py
CHANGED
|
@@ -5,14 +5,14 @@ from django.conf import settings
|
|
|
5
5
|
from django.template.loader import select_template
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class EdcTemplateDoesNotExist(Exception):
|
|
8
|
+
class EdcTemplateDoesNotExist(Exception): # noqa: N818
|
|
9
9
|
pass
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def get_index_page() -> int:
|
|
13
13
|
index_page = getattr(settings, "INDEX_PAGE", None)
|
|
14
14
|
if not index_page:
|
|
15
|
-
warn("Settings attribute not set. See settings.INDEX_PAGE")
|
|
15
|
+
warn("Settings attribute not set. See settings.INDEX_PAGE", stacklevel=2)
|
|
16
16
|
return getattr(settings, "INDEX_PAGE", None)
|
|
17
17
|
|
|
18
18
|
|
|
@@ -44,8 +44,8 @@ def select_edc_template(relative_path, default_app_label):
|
|
|
44
44
|
default_path = default_app_label
|
|
45
45
|
return select_template(
|
|
46
46
|
[
|
|
47
|
-
os.path.join(local_path, relative_path),
|
|
48
|
-
os.path.join(default_path, relative_path),
|
|
47
|
+
str(os.path.join(local_path, relative_path)), # noqa: PTH118
|
|
48
|
+
str(os.path.join(default_path, relative_path)), # noqa: PTH118
|
|
49
49
|
]
|
|
50
50
|
)
|
|
51
51
|
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
|
|
4
1
|
class TemplateRequestContextError(Exception):
|
|
5
2
|
pass
|
|
6
3
|
|
|
@@ -13,10 +10,10 @@ class TemplateRequestContextMixin:
|
|
|
13
10
|
return [self.get_template_from_context(self.listboard_template)]
|
|
14
11
|
"""
|
|
15
12
|
|
|
16
|
-
def get_context_data(self, **kwargs) -> dict[str, Any]:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
# def get_context_data(self, **kwargs) -> dict[str, Any]:
|
|
14
|
+
# """Adds template data to context."""
|
|
15
|
+
# kwargs.update(self.request.template_data)
|
|
16
|
+
# return super().get_context_data(**kwargs)
|
|
20
17
|
|
|
21
18
|
def get_template_from_context(self, key=None):
|
|
22
19
|
"""Returns a template_name from request.context_data."""
|
|
@@ -26,5 +23,5 @@ class TemplateRequestContextMixin:
|
|
|
26
23
|
raise TemplateRequestContextError(
|
|
27
24
|
f"Template name not defined in request context data. "
|
|
28
25
|
f"Expected one of {list(self.request.template_data.keys())}. Got {e}. "
|
|
29
|
-
)
|
|
26
|
+
) from e
|
|
30
27
|
return template_name
|
|
@@ -6,7 +6,6 @@ from edc_protocol.research_protocol_config import ResearchProtocolConfig
|
|
|
6
6
|
from edc_utils.text import convert_from_camel
|
|
7
7
|
|
|
8
8
|
from ..url_config import UrlConfig
|
|
9
|
-
from ..url_names import InvalidDashboardUrlName, url_names
|
|
10
9
|
|
|
11
10
|
if TYPE_CHECKING:
|
|
12
11
|
from django.urls import URLPattern
|
|
@@ -23,45 +22,58 @@ class UrlRequestContextMixin:
|
|
|
23
22
|
urlconfig_label = None
|
|
24
23
|
url_name = None
|
|
25
24
|
|
|
26
|
-
@classmethod
|
|
27
|
-
def get_urlname(cls):
|
|
28
|
-
|
|
25
|
+
# @classmethod
|
|
26
|
+
# def get_urlname(cls):
|
|
27
|
+
# return cls.url_name
|
|
29
28
|
|
|
30
29
|
@classmethod
|
|
31
30
|
def urls(
|
|
32
31
|
cls,
|
|
33
32
|
*,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
namespace: str | None = None,
|
|
33
|
+
namespace: str,
|
|
34
|
+
url_names_key: str,
|
|
37
35
|
identifier_label: str | None = None,
|
|
36
|
+
identifier_pattern: str | None = None,
|
|
38
37
|
) -> list[URLPattern]:
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
"""Returns a list of url patterns generated by UrlConfig.
|
|
39
|
+
|
|
40
|
+
Each url pattern has the same name; `cls.url_name`.
|
|
41
|
+
"""
|
|
42
|
+
url_names_key = (
|
|
43
|
+
url_names_key
|
|
41
44
|
or cls.urlconfig_label
|
|
42
45
|
or convert_from_camel(cls.__name__.replace("view", "")).lower()
|
|
43
46
|
)
|
|
44
47
|
urlconfig = UrlConfig(
|
|
45
|
-
url_name=cls.get_urlname(),
|
|
46
|
-
namespace=namespace,
|
|
47
48
|
view_class=cls,
|
|
48
|
-
|
|
49
|
+
namespace=namespace,
|
|
50
|
+
url_names_key=url_names_key,
|
|
49
51
|
identifier_label=identifier_label or cls.urlconfig_identifier_label,
|
|
50
52
|
identifier_pattern=identifier_pattern or cls.urlconfig_identifier_pattern,
|
|
51
53
|
)
|
|
54
|
+
if cls.urlconfig_getattr not in [
|
|
55
|
+
"dashboard_urls",
|
|
56
|
+
"listboard_urls",
|
|
57
|
+
"review_listboard_urls",
|
|
58
|
+
]:
|
|
59
|
+
raise UrlRequestContextError(
|
|
60
|
+
f"Invalid urlconfig attr. Got {cls.urlconfig_getattr}."
|
|
61
|
+
)
|
|
52
62
|
return getattr(urlconfig, cls.urlconfig_getattr)
|
|
53
63
|
|
|
54
|
-
@staticmethod
|
|
55
|
-
def add_url_to_context(new_key=None, existing_key=None) -> dict[str, str]:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
# @staticmethod
|
|
65
|
+
# def add_url_to_context(new_key=None, existing_key=None) -> dict[str, str]:
|
|
66
|
+
# """Add url as new_key to the context using the value
|
|
67
|
+
# of the existing_key from request.context_data.
|
|
68
|
+
# """
|
|
69
|
+
# if new_key != existing_key:
|
|
70
|
+
# try:
|
|
71
|
+
# url_data = {new_key: url_names.get(existing_key)}
|
|
72
|
+
# except InvalidDashboardUrlName as e:
|
|
73
|
+
# raise UrlRequestContextError(
|
|
74
|
+
# f"Url name not defined in url_names. "
|
|
75
|
+
# f"Expected one of {url_names.registry}. Got {e}. "
|
|
76
|
+
# f"Hint: check if dashboard middleware is loaded."
|
|
77
|
+
# ) from e
|
|
78
|
+
# return url_data
|
|
79
|
+
# return {existing_key: url_names.get(existing_key)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
from django.conf import settings
|
|
1
2
|
from django.views.generic import TemplateView
|
|
2
|
-
|
|
3
3
|
from edc_navbar import NavbarViewMixin
|
|
4
4
|
|
|
5
5
|
from ..view_mixins import AdministrationViewMixin, EdcViewMixin
|
|
@@ -7,4 +7,4 @@ from ..view_mixins import AdministrationViewMixin, EdcViewMixin
|
|
|
7
7
|
|
|
8
8
|
class AdministrationView(EdcViewMixin, NavbarViewMixin, AdministrationViewMixin, TemplateView):
|
|
9
9
|
navbar_selected_item = "administration"
|
|
10
|
-
navbar_name = "default"
|
|
10
|
+
navbar_name = getattr(settings, "APP_NAME", "default")
|
|
@@ -8,7 +8,7 @@ from ..view_mixins import TemplateRequestContextMixin, UrlRequestContextMixin
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class DashboardView(UrlRequestContextMixin, TemplateRequestContextMixin, TemplateView):
|
|
11
|
-
dashboard_url_name = None
|
|
11
|
+
dashboard_url_name = None # see url_names dictionary
|
|
12
12
|
dashboard_template = None # may be None if `dashboard_template_name` is defined
|
|
13
13
|
dashboard_template_name = None # may be None if `dashboard_template` is defined
|
|
14
14
|
|
|
@@ -31,15 +31,10 @@ class DashboardView(UrlRequestContextMixin, TemplateRequestContextMixin, Templat
|
|
|
31
31
|
return url_names.get(self.dashboard_url_name)
|
|
32
32
|
|
|
33
33
|
def get_template_names(self):
|
|
34
|
-
if self.
|
|
35
|
-
return [self.
|
|
36
|
-
return [self.get_template_from_context(self.
|
|
34
|
+
if self.dashboard_template:
|
|
35
|
+
return [self.dashboard_template]
|
|
36
|
+
return [self.get_template_from_context(self.dashboard_template_name)]
|
|
37
37
|
|
|
38
38
|
def get_context_data(self, **kwargs) -> dict[str, Any]:
|
|
39
|
-
kwargs.update(
|
|
40
|
-
**self.add_url_to_context(
|
|
41
|
-
new_key="dashboard_url_name",
|
|
42
|
-
existing_key=self.dashboard_url_name,
|
|
43
|
-
)
|
|
44
|
-
)
|
|
39
|
+
kwargs.update(**{self.dashboard_url_name: url_names.get(self.dashboard_url_name)})
|
|
45
40
|
return super().get_context_data(**kwargs)
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
1
6
|
import arrow
|
|
2
7
|
from django.conf import settings
|
|
3
8
|
from django.core.exceptions import ObjectDoesNotExist
|
|
@@ -15,6 +20,12 @@ from edc_visit_tracking.utils import (
|
|
|
15
20
|
from ..constants import AUTO_RESOLVED
|
|
16
21
|
from ..models import DataQuery
|
|
17
22
|
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from edc_registration.models import RegisteredSubject
|
|
25
|
+
from edc_visit_schedule.visit_schedule import VisitSchedule
|
|
26
|
+
|
|
27
|
+
from ..models import QueryRule
|
|
28
|
+
|
|
18
29
|
|
|
19
30
|
class QueryRuleHandlerError(Exception):
|
|
20
31
|
pass
|
|
@@ -57,11 +68,12 @@ class QueryRuleHandler:
|
|
|
57
68
|
|
|
58
69
|
def __init__(
|
|
59
70
|
self,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
now
|
|
71
|
+
*,
|
|
72
|
+
query_rule_obj: QueryRule,
|
|
73
|
+
registered_subject: RegisteredSubject,
|
|
74
|
+
visit_schedule_obj: VisitSchedule,
|
|
75
|
+
now: datetime,
|
|
76
|
+
visit_code_sequence: int | None = None,
|
|
65
77
|
):
|
|
66
78
|
self._field_values = {}
|
|
67
79
|
self._model_obj = None
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Generated by Django 5.2.7 on 2025-10-24 04:17
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("edc_data_manager", "0042_alter_datadictionary_revision_and_more"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name="historicalqueryrule",
|
|
15
|
+
name="comment",
|
|
16
|
+
field=models.TextField(blank=True, default=""),
|
|
17
|
+
),
|
|
18
|
+
migrations.AlterField(
|
|
19
|
+
model_name="historicalqueryrule",
|
|
20
|
+
name="query_text",
|
|
21
|
+
field=models.TextField(
|
|
22
|
+
blank=True,
|
|
23
|
+
default="",
|
|
24
|
+
help_text="Generic query text for auto-generated queries.",
|
|
25
|
+
),
|
|
26
|
+
),
|
|
27
|
+
migrations.AlterField(
|
|
28
|
+
model_name="historicalqueryrule",
|
|
29
|
+
name="reference_model",
|
|
30
|
+
field=models.CharField(default="", editable=False, max_length=150),
|
|
31
|
+
),
|
|
32
|
+
migrations.AlterField(
|
|
33
|
+
model_name="queryrule",
|
|
34
|
+
name="comment",
|
|
35
|
+
field=models.TextField(blank=True, default=""),
|
|
36
|
+
),
|
|
37
|
+
migrations.AlterField(
|
|
38
|
+
model_name="queryrule",
|
|
39
|
+
name="query_text",
|
|
40
|
+
field=models.TextField(
|
|
41
|
+
blank=True,
|
|
42
|
+
default="",
|
|
43
|
+
help_text="Generic query text for auto-generated queries.",
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
migrations.AlterField(
|
|
47
|
+
model_name="queryrule",
|
|
48
|
+
name="reference_model",
|
|
49
|
+
field=models.CharField(default="", editable=False, max_length=150),
|
|
50
|
+
),
|
|
51
|
+
]
|
|
@@ -7,7 +7,7 @@ from django.db import models
|
|
|
7
7
|
from django.db.models.deletion import PROTECT
|
|
8
8
|
from django.template.loader import render_to_string
|
|
9
9
|
|
|
10
|
-
from edc_constants.constants import NORMAL
|
|
10
|
+
from edc_constants.constants import NORMAL, NULL_STRING
|
|
11
11
|
from edc_model.models import BaseUuidModel, HistoricalRecords
|
|
12
12
|
from edc_visit_schedule.constants import DAYS, HOURS, MONTHS, WEEKS
|
|
13
13
|
|
|
@@ -31,7 +31,7 @@ class QueryRuleManager(models.Manager):
|
|
|
31
31
|
def get_rule_handler_choices(model_name=None):
|
|
32
32
|
choices = []
|
|
33
33
|
for rule_handler in site_data_manager.get_rule_handlers(model_name=model_name):
|
|
34
|
-
choices.append((rule_handler.name, rule_handler.display_name))
|
|
34
|
+
choices.append((rule_handler.name, rule_handler.display_name)) # noqa: PERF401
|
|
35
35
|
return tuple(choices) or (
|
|
36
36
|
(DEFAULT_RULE_HANDLER, DEFAULT_RULE_HANDLER.replace("_", " ").title()),
|
|
37
37
|
)
|
|
@@ -105,7 +105,7 @@ class QueryRule(BaseUuidModel):
|
|
|
105
105
|
|
|
106
106
|
title = models.CharField(max_length=150, unique=True)
|
|
107
107
|
|
|
108
|
-
reference_model = models.CharField(max_length=150,
|
|
108
|
+
reference_model = models.CharField(max_length=150, default=NULL_STRING, editable=False)
|
|
109
109
|
|
|
110
110
|
sender = models.ForeignKey(
|
|
111
111
|
DataManagerUser,
|
|
@@ -152,7 +152,7 @@ class QueryRule(BaseUuidModel):
|
|
|
152
152
|
|
|
153
153
|
query_text = models.TextField(
|
|
154
154
|
help_text="Generic query text for auto-generated queries.",
|
|
155
|
-
|
|
155
|
+
default=NULL_STRING,
|
|
156
156
|
blank=True,
|
|
157
157
|
)
|
|
158
158
|
|
|
@@ -192,7 +192,7 @@ class QueryRule(BaseUuidModel):
|
|
|
192
192
|
|
|
193
193
|
reference = models.CharField(max_length=36, default=uuid4, unique=True)
|
|
194
194
|
|
|
195
|
-
comment = models.TextField(
|
|
195
|
+
comment = models.TextField(default=NULL_STRING, blank=True)
|
|
196
196
|
|
|
197
197
|
objects = QueryRuleManager()
|
|
198
198
|
|
|
@@ -209,13 +209,13 @@ class QueryRule(BaseUuidModel):
|
|
|
209
209
|
def natural_key(self):
|
|
210
210
|
return (self.title,)
|
|
211
211
|
|
|
212
|
-
natural_key.dependencies =
|
|
212
|
+
natural_key.dependencies = (
|
|
213
213
|
"edc_data_manager.CrfDataDictionary",
|
|
214
214
|
"edc_data_manager.DataManagerUser",
|
|
215
215
|
"edc_data_manager.QueryUser",
|
|
216
216
|
"edc_data_manager.queryvisitschedule",
|
|
217
217
|
"edc_data_manager.RequisitionPanel",
|
|
218
|
-
|
|
218
|
+
)
|
|
219
219
|
|
|
220
220
|
@property
|
|
221
221
|
def rendered_query_text(self) -> str:
|
edc_data_manager/navbar_item.py
CHANGED
|
@@ -21,7 +21,7 @@ class RuleRunner:
|
|
|
21
21
|
def __init__(
|
|
22
22
|
self,
|
|
23
23
|
query_rule_obj: QueryRule = None,
|
|
24
|
-
now: datetime = None,
|
|
24
|
+
now: datetime | None = None,
|
|
25
25
|
verbose: bool | None = None,
|
|
26
26
|
):
|
|
27
27
|
self.query_rule_obj = query_rule_obj # query rule model instance
|
|
@@ -55,11 +55,11 @@ class RuleRunner:
|
|
|
55
55
|
|
|
56
56
|
def run_one(
|
|
57
57
|
self,
|
|
58
|
-
subject_identifier: str
|
|
59
|
-
visit_schedule_name: str
|
|
60
|
-
schedule_name: str
|
|
61
|
-
visit_code: str
|
|
62
|
-
timepoint: Decimal
|
|
58
|
+
subject_identifier: str,
|
|
59
|
+
visit_schedule_name: str,
|
|
60
|
+
schedule_name: str,
|
|
61
|
+
visit_code: str,
|
|
62
|
+
timepoint: Decimal,
|
|
63
63
|
):
|
|
64
64
|
visit_schedule_obj = QueryVisitSchedule.objects.get(
|
|
65
65
|
visit_schedule_name=visit_schedule_name,
|