clinicedc 2.0.7__py3-none-any.whl → 2.0.9__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.7.dist-info → clinicedc-2.0.9.dist-info}/METADATA +4 -3
- {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/RECORD +136 -137
- {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/WHEEL +1 -1
- edc_action_item/auths.py +37 -32
- edc_action_item/models/action_model_mixin.py +1 -2
- edc_action_item/models/signals.py +22 -23
- edc_action_item/site_action_items.py +5 -9
- edc_action_item/utils.py +3 -3
- edc_adverse_event/auths.py +55 -51
- edc_adverse_event/model_mixins/ae_tmg/ae_tmg_methods_model_mixin.py +2 -4
- edc_appointment/auths.py +14 -10
- edc_appointment/creators/appointment_creator.py +1 -1
- edc_appointment/creators/appointments_creator.py +1 -1
- edc_appointment/model_mixins/appointment_methods_model_mixin.py +2 -3
- edc_appointment/model_mixins/appointment_model_mixin.py +31 -28
- edc_appointment/models/appointment.py +1 -1
- edc_appointment/utils.py +19 -24
- edc_auth/auth_objects/__init__.py +2 -20
- edc_auth/auth_objects/default_groups.py +13 -11
- edc_auth/auth_objects/default_roles.py +26 -24
- edc_auth/auth_updater/auth_updater.py +13 -2
- edc_auth/auth_updater/group_updater.py +12 -10
- edc_auth/auth_updater/role_updater.py +2 -2
- edc_auth/constants.py +10 -0
- edc_auth/import_users.py +3 -3
- edc_auth/migrations/0036_alter_userprofile_alternate_email_and_more.py +88 -0
- edc_auth/models/user_profile.py +14 -11
- edc_auth/site_auths.py +80 -67
- edc_consent/auths.py +18 -12
- edc_constants/constants.py +1 -0
- edc_crf/auths.py +5 -0
- edc_dashboard/auths.py +10 -6
- edc_dashboard/url_config.py +92 -83
- edc_dashboard/url_names.py +4 -4
- edc_dashboard/view_mixins/url_request_context_mixin.py +6 -5
- edc_data_manager/admin/data_query_admin.py +12 -11
- edc_data_manager/auths.py +37 -34
- edc_data_manager/rule/query_rule_wrapper.py +7 -7
- edc_export/archive_exporter.py +3 -2
- edc_export/auths.py +32 -28
- edc_export/model_exporter/model_exporter.py +4 -1
- edc_facility/auths.py +8 -3
- edc_facility/facility.py +8 -9
- edc_form_label/custom_label_condition.py +11 -8
- edc_form_label/form_label.py +1 -1
- edc_form_runners/auths.py +11 -6
- edc_form_validators/applicable_field_validator.py +7 -6
- edc_form_validators/base_form_validator.py +8 -9
- edc_form_validators/other_specify_field_validator.py +2 -8
- edc_form_validators/required_field_validator.py +19 -16
- edc_identifier/research_identifier.py +11 -10
- edc_identifier/simple_identifier.py +8 -2
- edc_lab/auths.py +26 -23
- edc_lab/lab/aliquot_creator.py +5 -8
- edc_lab/lab/primary_aliquot.py +14 -5
- edc_lab/migrations/0038_alter_aliquot_slug_alter_box_slug_alter_boxitem_slug_and_more.py +112 -0
- edc_lab/model_mixins/requisition/requisition_model_mixin.py +6 -8
- edc_lab/models/aliquot.py +2 -2
- edc_lab/models/manifest/manifest.py +2 -2
- edc_lab/models/manifest/manifest_item.py +1 -1
- edc_lab_dashboard/auths.py +16 -11
- edc_lab_results/calculate_missing.py +8 -8
- edc_lab_results/form_validator_mixins/blood_results_form_validator_mixin.py +2 -2
- edc_lab_results/get_summary.py +26 -25
- edc_lab_results/model_mixins/blood_result_model_mixin.py +2 -0
- edc_label/auths.py +6 -1
- edc_label/label_template.py +8 -8
- edc_list_data/load_model_data.py +3 -3
- edc_list_data/post_migrate_signals.py +1 -1
- edc_list_data/preload_data.py +2 -2
- edc_list_data/row.py +1 -1
- edc_list_data/site_list_data.py +6 -5
- edc_locator/auths.py +18 -13
- edc_metadata/auths.py +11 -7
- edc_metadata/metadata/metadata.py +1 -1
- edc_metadata/metadata_rules/crf/crf_rule.py +1 -1
- edc_metadata/metadata_rules/metadata_rule_evaluator.py +5 -3
- edc_metadata/metadata_rules/rule.py +2 -3
- edc_metadata/metadata_rules/rule_evaluator.py +1 -1
- edc_metadata/model_mixins/updates/updates_crf_metadata_model_mixin.py +7 -4
- edc_metadata/model_mixins/updates/updates_requisition_metadata_model_mixin.py +5 -2
- edc_metadata/models/signals.py +10 -11
- edc_navbar/auths.py +18 -13
- edc_notification/auths.py +9 -4
- edc_notification/notification/graded_event_notification.py +2 -2
- edc_notification/notification/model_notification.py +3 -30
- edc_notification/notification/new_model_notification.py +1 -1
- edc_notification/notification/notification.py +1 -1
- edc_notification/notification/updated_model_notification.py +2 -2
- edc_offstudy/auths.py +12 -7
- edc_pdutils/df_exporters/csv_model_exporter.py +5 -2
- edc_pharmacy/auths.py +19 -15
- edc_pharmacy/models/medication/formulation.py +5 -7
- edc_pharmacy/prescribe/create_prescription.py +3 -3
- edc_pharmacy/utils/confirm_stock.py +1 -1
- edc_pharmacy/utils/confirm_stock_at_site.py +1 -1
- edc_pharmacy/views/confirmation_at_site_view.py +6 -9
- edc_prn/admin_site.py +5 -0
- edc_prn/prn.py +10 -11
- edc_prn/urls.py +11 -0
- edc_protocol_incident/action_items.py +4 -4
- edc_protocol_incident/auths.py +27 -20
- edc_pylabels/auths.py +6 -1
- edc_qareports/auths.py +11 -7
- edc_randomization/admin.py +30 -24
- edc_randomization/auths.py +12 -7
- edc_randomization/randomizer.py +22 -20
- edc_randomization/utils.py +17 -16
- edc_refusal/auths.py +7 -2
- edc_refusal/model_mixins.py +1 -1
- edc_registration/auths.py +28 -23
- edc_registration/model_mixins/updates_or_creates_registered_subject_model_mixin.py +13 -4
- edc_registration/models/registered_subject.py +1 -1
- edc_reportable/utils/get_reference_range_collection.py +2 -3
- edc_reportable/utils/load_data.py +1 -1
- edc_review_dashboard/auths.py +23 -18
- edc_screening/age_evaluator.py +3 -3
- edc_screening/auths.py +35 -30
- edc_screening/eligibility.py +1 -1
- edc_screening/gender_evaluator.py +1 -1
- edc_screening/model_mixins/eligibility_model_mixin.py +0 -2
- edc_screening/model_mixins/screening_methods_model_mixin.py +1 -1
- edc_screening/screening_eligibility.py +2 -4
- edc_screening/utils.py +9 -9
- edc_search/generate_slug.py +26 -0
- edc_search/model_mixins.py +10 -21
- edc_sites/auths.py +8 -3
- edc_subject_dashboard/auths.py +27 -22
- edc_timepoint/apps.py +0 -21
- edc_unblinding/auths.py +9 -4
- edc_utils/__init__.py +3 -1
- edc_utils/show_urls.py +29 -2
- edc_visit_schedule/auths.py +6 -1
- edc_visit_schedule/site_visit_schedules.py +2 -2
- edc_visit_tracking/models/signals.py +2 -2
- edc_form_label/models.py +0 -0
- edc_search/constants.py +0 -1
- edc_search/models.py +0 -0
- edc_search/search_slug.py +0 -51
- edc_search/updater.py +0 -30
- edc_search/wsgi.py +0 -7
- {clinicedc-2.0.7.dist-info → clinicedc-2.0.9.dist-info}/licenses/LICENSE +0 -0
edc_auth/site_auths.py
CHANGED
|
@@ -9,30 +9,40 @@ from django.apps import apps as django_apps
|
|
|
9
9
|
from django.conf import settings
|
|
10
10
|
from django.utils.module_loading import import_module, module_has_submodule
|
|
11
11
|
|
|
12
|
-
from .auth_objects import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
from .auth_objects import default_pii_models, get_default_groups, get_default_roles
|
|
13
|
+
from .constants import (
|
|
14
|
+
CUSTOM_PERMISSIONS_TUPLES_KEY,
|
|
15
|
+
GROUPS_KEY,
|
|
16
|
+
PII_MODELS_KEY,
|
|
17
|
+
POST_UPDATE_FUNCS_KEY,
|
|
18
|
+
PRE_UPDATE_FUNCS_KEY,
|
|
19
|
+
ROLES_KEY,
|
|
20
|
+
UPDATE_GROUPS_KEY,
|
|
21
|
+
UPDATE_ROLES_KEY,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AlreadyRegistered(Exception): # noqa: N818
|
|
16
26
|
pass
|
|
17
27
|
|
|
18
28
|
|
|
19
|
-
class InvalidGroup(Exception):
|
|
29
|
+
class InvalidGroup(Exception): # noqa: N818
|
|
20
30
|
pass
|
|
21
31
|
|
|
22
32
|
|
|
23
|
-
class InvalidRole(Exception):
|
|
33
|
+
class InvalidRole(Exception): # noqa: N818
|
|
24
34
|
pass
|
|
25
35
|
|
|
26
36
|
|
|
27
|
-
class RoleAlreadyExists(Exception):
|
|
37
|
+
class RoleAlreadyExists(Exception): # noqa: N818
|
|
28
38
|
pass
|
|
29
39
|
|
|
30
40
|
|
|
31
|
-
class GroupAlreadyExists(Exception):
|
|
41
|
+
class GroupAlreadyExists(Exception): # noqa: N818
|
|
32
42
|
pass
|
|
33
43
|
|
|
34
44
|
|
|
35
|
-
class PiiModelAlreadyExists(Exception):
|
|
45
|
+
class PiiModelAlreadyExists(Exception): # noqa: N818
|
|
36
46
|
pass
|
|
37
47
|
|
|
38
48
|
|
|
@@ -96,39 +106,39 @@ class SiteAuths:
|
|
|
96
106
|
|
|
97
107
|
def initialize(self):
|
|
98
108
|
self.registry = {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
GROUPS_KEY: get_default_groups(),
|
|
110
|
+
ROLES_KEY: get_default_roles(),
|
|
111
|
+
UPDATE_GROUPS_KEY: {},
|
|
112
|
+
UPDATE_ROLES_KEY: {},
|
|
113
|
+
CUSTOM_PERMISSIONS_TUPLES_KEY: {},
|
|
114
|
+
PRE_UPDATE_FUNCS_KEY: [],
|
|
115
|
+
POST_UPDATE_FUNCS_KEY: [],
|
|
116
|
+
PII_MODELS_KEY: default_pii_models,
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
def clear(self):
|
|
110
120
|
self.registry = {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
GROUPS_KEY: {},
|
|
122
|
+
ROLES_KEY: {},
|
|
123
|
+
UPDATE_GROUPS_KEY: {},
|
|
124
|
+
UPDATE_ROLES_KEY: {},
|
|
125
|
+
CUSTOM_PERMISSIONS_TUPLES_KEY: {},
|
|
126
|
+
PRE_UPDATE_FUNCS_KEY: [],
|
|
127
|
+
POST_UPDATE_FUNCS_KEY: [],
|
|
128
|
+
PII_MODELS_KEY: [],
|
|
119
129
|
}
|
|
120
130
|
|
|
121
131
|
def clear_values(self):
|
|
122
132
|
registry = deepcopy(self.registry)
|
|
123
133
|
self.registry = {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
GROUPS_KEY: {k: [] for k in registry.get(GROUPS_KEY)},
|
|
135
|
+
ROLES_KEY: {k: [] for k in self.registry.get(ROLES_KEY)},
|
|
136
|
+
UPDATE_GROUPS_KEY: {},
|
|
137
|
+
UPDATE_ROLES_KEY: {},
|
|
138
|
+
CUSTOM_PERMISSIONS_TUPLES_KEY: {},
|
|
139
|
+
PRE_UPDATE_FUNCS_KEY: [],
|
|
140
|
+
POST_UPDATE_FUNCS_KEY: [],
|
|
141
|
+
PII_MODELS_KEY: [],
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
@property
|
|
@@ -136,15 +146,15 @@ class SiteAuths:
|
|
|
136
146
|
return getattr(settings, "EDC_AUTH_SKIP_SITE_AUTHS", False)
|
|
137
147
|
|
|
138
148
|
def add_pre_update_func(self, func):
|
|
139
|
-
self.registry[
|
|
149
|
+
self.registry[PRE_UPDATE_FUNCS_KEY].append(func)
|
|
140
150
|
|
|
141
151
|
def add_post_update_func(self, app_label: str, func: Callable):
|
|
142
|
-
self.registry[
|
|
152
|
+
self.registry[POST_UPDATE_FUNCS_KEY].append((app_label, func))
|
|
143
153
|
|
|
144
154
|
def add_pii_model(self, model_name):
|
|
145
|
-
if model_name in self.registry[
|
|
155
|
+
if model_name in self.registry[PII_MODELS_KEY]:
|
|
146
156
|
raise PiiModelAlreadyExists(f"PII model already exists. Got {model_name}")
|
|
147
|
-
self.registry[
|
|
157
|
+
self.registry[PII_MODELS_KEY].append(model_name)
|
|
148
158
|
|
|
149
159
|
def add_groups(self, data: dict):
|
|
150
160
|
for name, codenames in data.items():
|
|
@@ -162,7 +172,7 @@ class SiteAuths:
|
|
|
162
172
|
convert_to_export=None,
|
|
163
173
|
no_delete=None,
|
|
164
174
|
):
|
|
165
|
-
if name in self.registry[
|
|
175
|
+
if name in self.registry[GROUPS_KEY]:
|
|
166
176
|
raise GroupAlreadyExists(f"Group name already exists. Got {name}.")
|
|
167
177
|
if no_delete:
|
|
168
178
|
codenames_or_func = self.remove_delete_codenames(codenames_or_func)
|
|
@@ -170,18 +180,18 @@ class SiteAuths:
|
|
|
170
180
|
codenames_or_func = self.get_view_only_codenames(codenames_or_func)
|
|
171
181
|
if convert_to_export:
|
|
172
182
|
codenames_or_func = self.convert_to_export_codenames(codenames_or_func)
|
|
173
|
-
self.registry[
|
|
183
|
+
self.registry[GROUPS_KEY].update({name: codenames_or_func})
|
|
174
184
|
|
|
175
185
|
def add_role(self, *group_names, name=None):
|
|
176
|
-
if name in self.registry[
|
|
186
|
+
if name in self.registry[ROLES_KEY]:
|
|
177
187
|
raise RoleAlreadyExists(f"Role name already exists. Got {name}.")
|
|
178
188
|
group_names = list(set(group_names))
|
|
179
|
-
self.registry[
|
|
189
|
+
self.registry[ROLES_KEY].update({name: group_names})
|
|
180
190
|
|
|
181
191
|
def update_group(
|
|
182
192
|
self, *codenames_or_func, name=None, key=None, view_only=None, no_delete=None
|
|
183
193
|
) -> None:
|
|
184
|
-
key = key or
|
|
194
|
+
key = key or UPDATE_GROUPS_KEY
|
|
185
195
|
if no_delete:
|
|
186
196
|
codenames_or_func = self.remove_delete_codenames(codenames_or_func)
|
|
187
197
|
if view_only:
|
|
@@ -191,30 +201,30 @@ class SiteAuths:
|
|
|
191
201
|
try:
|
|
192
202
|
existing_codenames = list(set(existing_codenames))
|
|
193
203
|
except TypeError as e:
|
|
194
|
-
raise TypeError(f"{e}. Got {name}")
|
|
204
|
+
raise TypeError(f"{e}. Got {name}") from e
|
|
195
205
|
existing_codenames.extend(codenames_or_func)
|
|
196
206
|
existing_codenames = list(set(existing_codenames))
|
|
197
207
|
self.registry[key].update({name: existing_codenames})
|
|
198
208
|
|
|
199
209
|
def update_role(self, *group_names, name=None, key=None) -> None:
|
|
200
|
-
key = key or
|
|
210
|
+
key = key or UPDATE_ROLES_KEY
|
|
201
211
|
group_names = list(set(group_names))
|
|
202
|
-
existing_group_names =
|
|
203
|
-
|
|
212
|
+
existing_group_names = [
|
|
213
|
+
name for name in self.registry[key].get(name) or [] if name not in group_names
|
|
214
|
+
]
|
|
204
215
|
existing_group_names.extend(group_names)
|
|
205
|
-
existing_group_names = list(set(existing_group_names))
|
|
206
216
|
self.registry[key].update({name: existing_group_names})
|
|
207
217
|
|
|
208
218
|
def add_custom_permissions_tuples(
|
|
209
219
|
self, model: str, codename_tuples: tuple[tuple[str, str], ...]
|
|
210
220
|
):
|
|
211
221
|
try:
|
|
212
|
-
self.registry[
|
|
222
|
+
self.registry[CUSTOM_PERMISSIONS_TUPLES_KEY][model]
|
|
213
223
|
except KeyError:
|
|
214
|
-
self.registry[
|
|
224
|
+
self.registry[CUSTOM_PERMISSIONS_TUPLES_KEY].update({model: []})
|
|
215
225
|
for codename_tuple in codename_tuples:
|
|
216
|
-
if codename_tuple not in self.registry[
|
|
217
|
-
self.registry[
|
|
226
|
+
if codename_tuple not in self.registry[CUSTOM_PERMISSIONS_TUPLES_KEY][model]:
|
|
227
|
+
self.registry[CUSTOM_PERMISSIONS_TUPLES_KEY][model].append(codename_tuple)
|
|
218
228
|
|
|
219
229
|
@staticmethod
|
|
220
230
|
def get_view_only_codenames(codenames):
|
|
@@ -225,7 +235,8 @@ class SiteAuths:
|
|
|
225
235
|
Does not remove `edc_navbar`, 'nav_' or `edc_dashboard`
|
|
226
236
|
codenames.
|
|
227
237
|
"""
|
|
228
|
-
callables = [lambda: view_only_wrapper(c) for c in codenames if callable(c)]
|
|
238
|
+
# callables = [lambda: view_only_wrapper(c) for c in codenames if callable(c)]
|
|
239
|
+
callables = [lambda c=c: view_only_wrapper(c) for c in codenames if callable(c)]
|
|
229
240
|
view_only_codenames = [
|
|
230
241
|
codename
|
|
231
242
|
for codename in codenames
|
|
@@ -263,27 +274,27 @@ class SiteAuths:
|
|
|
263
274
|
|
|
264
275
|
@property
|
|
265
276
|
def roles(self):
|
|
266
|
-
return self.registry[
|
|
277
|
+
return self.registry[ROLES_KEY]
|
|
267
278
|
|
|
268
279
|
@property
|
|
269
280
|
def groups(self):
|
|
270
|
-
return self.registry[
|
|
281
|
+
return self.registry[GROUPS_KEY]
|
|
271
282
|
|
|
272
283
|
@property
|
|
273
284
|
def pii_models(self):
|
|
274
|
-
return self.registry[
|
|
285
|
+
return self.registry[PII_MODELS_KEY]
|
|
275
286
|
|
|
276
287
|
@property
|
|
277
288
|
def pre_update_funcs(self):
|
|
278
|
-
return self.registry[
|
|
289
|
+
return self.registry[PRE_UPDATE_FUNCS_KEY]
|
|
279
290
|
|
|
280
291
|
@property
|
|
281
292
|
def post_update_funcs(self) -> tuple[str, Callable]:
|
|
282
|
-
return self.registry[
|
|
293
|
+
return self.registry[POST_UPDATE_FUNCS_KEY]
|
|
283
294
|
|
|
284
295
|
@property
|
|
285
296
|
def custom_permissions_tuples(self):
|
|
286
|
-
return self.registry[
|
|
297
|
+
return self.registry[CUSTOM_PERMISSIONS_TUPLES_KEY]
|
|
287
298
|
|
|
288
299
|
def verify_and_populate(
|
|
289
300
|
self, app_name: str | None = None, warn_only: bool | None = None
|
|
@@ -294,28 +305,30 @@ class SiteAuths:
|
|
|
294
305
|
* Updates data from `update_groups` -> `groups`
|
|
295
306
|
* Updates data from `update_roles` -> `roles`
|
|
296
307
|
"""
|
|
297
|
-
for name, codenames in self.registry[
|
|
298
|
-
if name not in self.registry[
|
|
308
|
+
for name, codenames in self.registry[UPDATE_GROUPS_KEY].items():
|
|
309
|
+
if name not in self.registry[GROUPS_KEY]:
|
|
299
310
|
msg = (
|
|
300
311
|
f"Cannot update group. Group name does not exist. See app={app_name}"
|
|
301
312
|
f"update_groups['groups']={codenames}. Got {name}"
|
|
302
313
|
)
|
|
303
314
|
if warn_only:
|
|
304
|
-
warn(msg)
|
|
315
|
+
warn(msg, stacklevel=2)
|
|
305
316
|
else:
|
|
306
317
|
raise InvalidGroup(msg)
|
|
307
|
-
self.update_group(*codenames, name=name, key=
|
|
308
|
-
|
|
309
|
-
|
|
318
|
+
self.update_group(*codenames, name=name, key=GROUPS_KEY)
|
|
319
|
+
self.registry[UPDATE_GROUPS_KEY] = {}
|
|
320
|
+
for name, group_names in self.registry[UPDATE_ROLES_KEY].items():
|
|
321
|
+
if name not in self.registry[ROLES_KEY]:
|
|
310
322
|
msg = (
|
|
311
323
|
f"Cannot update role. Role name does not exist. See app={app_name}. "
|
|
312
324
|
f"update_roles['groups']={group_names}. Got {name}"
|
|
313
325
|
)
|
|
314
326
|
if warn_only:
|
|
315
|
-
warn(msg)
|
|
327
|
+
warn(msg, stacklevel=2)
|
|
316
328
|
else:
|
|
317
329
|
raise InvalidRole(msg)
|
|
318
|
-
self.update_role(*group_names, name=name, key=
|
|
330
|
+
self.update_role(*group_names, name=name, key=ROLES_KEY)
|
|
331
|
+
self.registry[UPDATE_ROLES_KEY] = {}
|
|
319
332
|
|
|
320
333
|
def autodiscover(self, module_name=None, verbose=True):
|
|
321
334
|
"""Autodiscovers in the auths.py file of any INSTALLED_APP."""
|
|
@@ -335,7 +348,7 @@ class SiteAuths:
|
|
|
335
348
|
except ImportError as e:
|
|
336
349
|
site_auths.registry = before_import_registry
|
|
337
350
|
if module_has_submodule(mod, module_name):
|
|
338
|
-
raise SiteAuthError(str(e))
|
|
351
|
+
raise SiteAuthError(str(e)) from e
|
|
339
352
|
except ImportError:
|
|
340
353
|
pass
|
|
341
354
|
self.verify_and_populate(app_name=app_name)
|
edc_consent/auths.py
CHANGED
|
@@ -6,15 +6,21 @@ from edc_auth.utils import remove_default_model_permissions_from_edc_permissions
|
|
|
6
6
|
|
|
7
7
|
from .auth_objects import consent_codenames, navbar_tuples
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
|
|
10
|
+
def update_site_auths():
|
|
11
|
+
|
|
12
|
+
site_auths.add_post_update_func(
|
|
13
|
+
"edc_consent",
|
|
14
|
+
remove_default_model_permissions_from_edc_permissions,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
site_auths.add_custom_permissions_tuples(
|
|
18
|
+
model="edc_consent.edcpermissions", codename_tuples=navbar_tuples
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
site_auths.update_group(*consent_codenames, name=PII, no_delete=True)
|
|
22
|
+
site_auths.update_group(*consent_codenames, name=PII_VIEW, view_only=True)
|
|
23
|
+
site_auths.add_pii_model(settings.SUBJECT_CONSENT_MODEL)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
update_site_auths()
|
edc_constants/constants.py
CHANGED
edc_crf/auths.py
CHANGED
edc_dashboard/auths.py
CHANGED
|
@@ -3,11 +3,15 @@ from edc_auth.utils import remove_default_model_permissions_from_edc_permissions
|
|
|
3
3
|
|
|
4
4
|
from .auth_objects import dashboard_tuples
|
|
5
5
|
|
|
6
|
-
site_auths.add_post_update_func(
|
|
7
|
-
"edc_dashboard", remove_default_model_permissions_from_edc_permissions
|
|
8
|
-
)
|
|
9
6
|
|
|
7
|
+
def update_site_auths():
|
|
8
|
+
site_auths.add_post_update_func(
|
|
9
|
+
"edc_dashboard", remove_default_model_permissions_from_edc_permissions
|
|
10
|
+
)
|
|
10
11
|
|
|
11
|
-
site_auths.add_custom_permissions_tuples(
|
|
12
|
-
|
|
13
|
-
)
|
|
12
|
+
site_auths.add_custom_permissions_tuples(
|
|
13
|
+
model="edc_dashboard.edcpermissions", codename_tuples=dashboard_tuples
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
update_site_auths()
|
edc_dashboard/url_config.py
CHANGED
|
@@ -20,12 +20,13 @@ if TYPE_CHECKING:
|
|
|
20
20
|
class UrlConfig:
|
|
21
21
|
def __init__(
|
|
22
22
|
self,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
*,
|
|
24
|
+
url_name: str,
|
|
25
|
+
namespace: str,
|
|
26
|
+
view_class: type[View | UrlRequestContextMixin],
|
|
27
|
+
label: str,
|
|
28
|
+
identifier_label: str,
|
|
29
|
+
identifier_pattern: str,
|
|
29
30
|
):
|
|
30
31
|
self.identifier_label = identifier_label
|
|
31
32
|
self.identifier_pattern = identifier_pattern
|
|
@@ -39,102 +40,108 @@ class UrlConfig:
|
|
|
39
40
|
@property
|
|
40
41
|
def dashboard_urls(self) -> list[URLPattern]:
|
|
41
42
|
"""Returns url patterns."""
|
|
42
|
-
|
|
43
|
+
return [
|
|
43
44
|
re_path(
|
|
44
|
-
"
|
|
45
|
-
"(?P
|
|
45
|
+
"{label}/"
|
|
46
|
+
"(?P<{identifier_label}>{identifier_pattern})/"
|
|
46
47
|
r"(?P<visit_schedule_name>\w+)/"
|
|
47
48
|
r"(?P<schedule_name>\w+)/"
|
|
48
49
|
r"(?P<visit_code>\w+)/"
|
|
49
|
-
r"(?P<unscheduled>\w+)/"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
r"(?P<unscheduled>\w+)/".format(
|
|
51
|
+
**dict(
|
|
52
|
+
label=self.label,
|
|
53
|
+
identifier_label=self.identifier_label,
|
|
54
|
+
identifier_pattern=self.identifier_pattern,
|
|
55
|
+
)
|
|
56
|
+
),
|
|
55
57
|
self.view_class.as_view(),
|
|
56
58
|
name=self.url_name,
|
|
57
59
|
),
|
|
58
60
|
re_path(
|
|
59
|
-
"
|
|
60
|
-
"(?P
|
|
61
|
+
"{label}/"
|
|
62
|
+
"(?P<{identifier_label}>{identifier_pattern})/"
|
|
61
63
|
r"(?P<visit_schedule_name>\w+)/"
|
|
62
64
|
r"(?P<schedule_name>\w+)/"
|
|
63
|
-
r"(?P<visit_code>\w+)/"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
r"(?P<visit_code>\w+)/".format(
|
|
66
|
+
**dict(
|
|
67
|
+
label=self.label,
|
|
68
|
+
identifier_label=self.identifier_label,
|
|
69
|
+
identifier_pattern=self.identifier_pattern,
|
|
70
|
+
)
|
|
68
71
|
),
|
|
69
72
|
self.view_class.as_view(),
|
|
70
73
|
name=self.url_name,
|
|
71
74
|
),
|
|
72
75
|
re_path(
|
|
73
|
-
"
|
|
74
|
-
"(?P
|
|
75
|
-
"(?P<appointment
|
|
76
|
+
"{label}/"
|
|
77
|
+
"(?P<{identifier_label}>{identifier_pattern})/"
|
|
78
|
+
"(?P<appointment>{uuid_pattern})/"
|
|
76
79
|
r"(?P<scanning>\d)/"
|
|
77
|
-
r"(?P<error>\d)/"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
r"(?P<error>\d)/".format(
|
|
81
|
+
**dict(
|
|
82
|
+
label=self.label,
|
|
83
|
+
identifier_label=self.identifier_label,
|
|
84
|
+
identifier_pattern=self.identifier_pattern,
|
|
85
|
+
uuid_pattern=UUID_PATTERN.pattern,
|
|
86
|
+
)
|
|
83
87
|
),
|
|
84
88
|
self.view_class.as_view(),
|
|
85
89
|
name=self.url_name,
|
|
86
90
|
),
|
|
87
91
|
re_path(
|
|
88
|
-
"
|
|
89
|
-
"(?P
|
|
90
|
-
"(?P<appointment
|
|
91
|
-
r"(?P<reason>\w+)/"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
"{label}/"
|
|
93
|
+
"(?P<{identifier_label}>{identifier_pattern})/"
|
|
94
|
+
"(?P<appointment>{uuid_pattern})/"
|
|
95
|
+
r"(?P<reason>\w+)/".format(
|
|
96
|
+
**dict(
|
|
97
|
+
label=self.label,
|
|
98
|
+
identifier_label=self.identifier_label,
|
|
99
|
+
identifier_pattern=self.identifier_pattern,
|
|
100
|
+
uuid_pattern=UUID_PATTERN.pattern,
|
|
101
|
+
)
|
|
97
102
|
),
|
|
98
103
|
self.view_class.as_view(),
|
|
99
104
|
name=self.url_name,
|
|
100
105
|
),
|
|
101
106
|
re_path(
|
|
102
|
-
"
|
|
103
|
-
"(?P
|
|
104
|
-
"(?P<appointment
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
"{label}/"
|
|
108
|
+
"(?P<{identifier_label}>{identifier_pattern})/"
|
|
109
|
+
"(?P<appointment>{uuid_pattern})/".format(
|
|
110
|
+
**dict(
|
|
111
|
+
label=self.label,
|
|
112
|
+
identifier_label=self.identifier_label,
|
|
113
|
+
identifier_pattern=self.identifier_pattern,
|
|
114
|
+
uuid_pattern=UUID_PATTERN.pattern,
|
|
115
|
+
)
|
|
110
116
|
),
|
|
111
117
|
self.view_class.as_view(),
|
|
112
118
|
name=self.url_name,
|
|
113
119
|
),
|
|
114
120
|
re_path(
|
|
115
|
-
"
|
|
116
|
-
"(?P
|
|
117
|
-
r"(?P<schedule_name>\w+)/"
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
"{label}/"
|
|
122
|
+
"(?P<{identifier_label}>{identifier_pattern})/"
|
|
123
|
+
r"(?P<schedule_name>\w+)/".format(
|
|
124
|
+
**dict(
|
|
125
|
+
label=self.label,
|
|
126
|
+
identifier_label=self.identifier_label,
|
|
127
|
+
identifier_pattern=self.identifier_pattern,
|
|
128
|
+
)
|
|
122
129
|
),
|
|
123
130
|
self.view_class.as_view(),
|
|
124
131
|
name=self.url_name,
|
|
125
132
|
),
|
|
126
133
|
re_path(
|
|
127
|
-
"
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
"{label}/(?P<{identifier_label}>{identifier_pattern})/".format(
|
|
135
|
+
**dict(
|
|
136
|
+
label=self.label,
|
|
137
|
+
identifier_label=self.identifier_label,
|
|
138
|
+
identifier_pattern=self.identifier_pattern,
|
|
139
|
+
)
|
|
132
140
|
),
|
|
133
141
|
self.view_class.as_view(),
|
|
134
142
|
name=self.url_name,
|
|
135
143
|
),
|
|
136
144
|
]
|
|
137
|
-
return urlpatterns
|
|
138
145
|
|
|
139
146
|
@property
|
|
140
147
|
def listboard_urls(self) -> list[URLPattern]:
|
|
@@ -142,52 +149,54 @@ class UrlConfig:
|
|
|
142
149
|
|
|
143
150
|
configs = [(listboard_url, listboard_view_class, label), (), ...]
|
|
144
151
|
"""
|
|
145
|
-
|
|
152
|
+
return [
|
|
146
153
|
re_path(
|
|
147
|
-
"
|
|
148
|
-
r"(?P<page>\d+)/"
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
"{label}/(?P<{identifier_label}>{identifier_pattern})/"
|
|
155
|
+
r"(?P<page>\d+)/".format(
|
|
156
|
+
**dict(
|
|
157
|
+
label=self.label,
|
|
158
|
+
identifier_label=self.identifier_label,
|
|
159
|
+
identifier_pattern=self.identifier_pattern,
|
|
160
|
+
)
|
|
153
161
|
),
|
|
154
162
|
self.view_class.as_view(),
|
|
155
163
|
name=self.url_name,
|
|
156
164
|
),
|
|
157
165
|
re_path(
|
|
158
|
-
"
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
166
|
+
"{label}/(?P<{identifier_label}>{identifier_pattern})/".format(
|
|
167
|
+
**dict(
|
|
168
|
+
label=self.label,
|
|
169
|
+
identifier_label=self.identifier_label,
|
|
170
|
+
identifier_pattern=self.identifier_pattern,
|
|
171
|
+
)
|
|
163
172
|
),
|
|
164
173
|
self.view_class.as_view(),
|
|
165
174
|
name=self.url_name,
|
|
166
175
|
),
|
|
167
176
|
re_path(
|
|
168
|
-
r"
|
|
177
|
+
r"{label}/(?P<page>\d+)/".format(**dict(label=self.label)),
|
|
169
178
|
self.view_class.as_view(),
|
|
170
179
|
name=self.url_name,
|
|
171
180
|
),
|
|
172
181
|
re_path(
|
|
173
|
-
r"
|
|
182
|
+
r"{label}/".format(**dict(label=self.label)),
|
|
174
183
|
self.view_class.as_view(),
|
|
175
184
|
name=self.url_name,
|
|
176
185
|
),
|
|
177
186
|
]
|
|
178
|
-
return urlpatterns
|
|
179
187
|
|
|
180
188
|
@property
|
|
181
189
|
def review_listboard_urls(self) -> list[URLPattern]:
|
|
182
190
|
url_patterns = [
|
|
183
191
|
re_path(
|
|
184
|
-
"
|
|
185
|
-
"(?P<appointment
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
"{label}/(?P<{identifier_label}>{identifier_pattern})/"
|
|
193
|
+
"(?P<appointment>{uuid_pattern})/".format(
|
|
194
|
+
**dict(
|
|
195
|
+
label=self.label,
|
|
196
|
+
identifier_label=self.identifier_label,
|
|
197
|
+
identifier_pattern=self.identifier_pattern,
|
|
198
|
+
uuid_pattern=UUID_PATTERN.pattern,
|
|
199
|
+
)
|
|
191
200
|
),
|
|
192
201
|
self.view_class.as_view(),
|
|
193
202
|
name=self.url_name,
|
edc_dashboard/url_names.py
CHANGED
|
@@ -3,11 +3,11 @@ from __future__ import annotations
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class AlreadyRegistered(Exception):
|
|
6
|
+
class AlreadyRegistered(Exception): # noqa: N818
|
|
7
7
|
pass
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class InvalidDashboardUrlName(Exception):
|
|
10
|
+
class InvalidDashboardUrlName(Exception): # noqa: N818
|
|
11
11
|
pass
|
|
12
12
|
|
|
13
13
|
|
|
@@ -16,12 +16,12 @@ class UrlNames:
|
|
|
16
16
|
registry: dict[str, str] = field(default_factory=dict)
|
|
17
17
|
|
|
18
18
|
def register(
|
|
19
|
-
self, name: str = None, url: str = None, namespace: str | None = None
|
|
19
|
+
self, name: str | None = None, url: str | None = None, namespace: str | None = None
|
|
20
20
|
) -> None:
|
|
21
21
|
name = name or url
|
|
22
22
|
complete_url = f"{namespace}:{url}" if namespace else url
|
|
23
23
|
if name in self.registry:
|
|
24
|
-
raise AlreadyRegistered(f"Url already registered. Got {
|
|
24
|
+
raise AlreadyRegistered(f"Url already registered. Got {complete_url}.")
|
|
25
25
|
self.registry.update({name: complete_url})
|
|
26
26
|
|
|
27
27
|
def register_from_dict(self, **urldata: str) -> None:
|