clinicedc 2.0.7__py3-none-any.whl → 2.0.8__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.8.dist-info}/METADATA +1 -1
- {clinicedc-2.0.7.dist-info → clinicedc-2.0.8.dist-info}/RECORD +134 -137
- {clinicedc-2.0.7.dist-info → clinicedc-2.0.8.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/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/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.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
from ..constants import (
|
|
2
|
-
ACCOUNT_MANAGER,
|
|
3
|
-
ACCOUNT_MANAGER_ROLE,
|
|
4
|
-
ADMINISTRATION,
|
|
5
|
-
AUDITOR,
|
|
6
|
-
AUDITOR_ROLE,
|
|
7
|
-
CELERY_MANAGER,
|
|
8
|
-
CLINIC,
|
|
9
|
-
CLINIC_SUPER,
|
|
10
|
-
CLINICIAN_ROLE,
|
|
11
|
-
CLINICIAN_SUPER_ROLE,
|
|
12
|
-
CUSTOM_ROLE,
|
|
13
|
-
EVERYONE,
|
|
14
|
-
NURSE_ROLE,
|
|
15
|
-
PII,
|
|
16
|
-
PII_VIEW,
|
|
17
|
-
STAFF_ROLE,
|
|
18
|
-
)
|
|
19
1
|
from .codenames import (
|
|
20
2
|
account_manager,
|
|
21
3
|
administration,
|
|
@@ -25,6 +7,6 @@ from .codenames import (
|
|
|
25
7
|
clinic_super,
|
|
26
8
|
everyone,
|
|
27
9
|
)
|
|
28
|
-
from .default_groups import
|
|
10
|
+
from .default_groups import get_default_groups
|
|
29
11
|
from .default_pii_models import default_pii_models
|
|
30
|
-
from .default_roles import
|
|
12
|
+
from .default_roles import get_default_roles
|
|
@@ -11,14 +11,16 @@ from ..constants import (
|
|
|
11
11
|
)
|
|
12
12
|
from .codenames import account_manager, administration, celery_manager, everyone
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
|
|
15
|
+
def get_default_groups():
|
|
16
|
+
return {
|
|
17
|
+
ACCOUNT_MANAGER: account_manager,
|
|
18
|
+
ADMINISTRATION: administration,
|
|
19
|
+
AUDITOR: [],
|
|
20
|
+
CELERY_MANAGER: celery_manager,
|
|
21
|
+
CLINIC: [],
|
|
22
|
+
CLINIC_SUPER: [],
|
|
23
|
+
EVERYONE: everyone,
|
|
24
|
+
PII: [],
|
|
25
|
+
PII_VIEW: [],
|
|
26
|
+
}
|
|
@@ -17,27 +17,29 @@ from ..constants import (
|
|
|
17
17
|
STATISTICIAN,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
20
|
+
|
|
21
|
+
def get_default_roles() -> dict[str, list[str]]:
|
|
22
|
+
"""Format {ROLE_NAME: [GROUP_NAME, GROUP_NAME, ...]"""
|
|
23
|
+
return {
|
|
24
|
+
ACCOUNT_MANAGER_ROLE: [ACCOUNT_MANAGER],
|
|
25
|
+
AUDITOR_ROLE: [
|
|
26
|
+
AUDITOR,
|
|
27
|
+
PII_VIEW,
|
|
28
|
+
],
|
|
29
|
+
CLINICIAN_ROLE: [
|
|
30
|
+
CLINIC,
|
|
31
|
+
PII,
|
|
32
|
+
],
|
|
33
|
+
CLINICIAN_SUPER_ROLE: [
|
|
34
|
+
CLINIC_SUPER,
|
|
35
|
+
CLINIC,
|
|
36
|
+
PII,
|
|
37
|
+
],
|
|
38
|
+
CUSTOM_ROLE: [],
|
|
39
|
+
NURSE_ROLE: [
|
|
40
|
+
CLINIC,
|
|
41
|
+
PII,
|
|
42
|
+
],
|
|
43
|
+
STAFF_ROLE: [ADMINISTRATION, EVERYONE],
|
|
44
|
+
STATISTICIAN: [AUDITOR],
|
|
45
|
+
}
|
|
@@ -9,6 +9,7 @@ from django.contrib.auth import get_user_model
|
|
|
9
9
|
from django.core.exceptions import ObjectDoesNotExist
|
|
10
10
|
from django.core.management.color import color_style
|
|
11
11
|
|
|
12
|
+
from ..constants import POST_UPDATE_FUNCS_KEY, PRE_UPDATE_FUNCS_KEY
|
|
12
13
|
from ..site_auths import site_auths
|
|
13
14
|
from .group_updater import GroupUpdater
|
|
14
15
|
from .role_updater import RoleUpdater
|
|
@@ -17,6 +18,14 @@ style = color_style()
|
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class AuthUpdater:
|
|
21
|
+
"""A class to update auth.Group, edc_auth.Role, auth.Permissions
|
|
22
|
+
models using the site_auth registry.
|
|
23
|
+
|
|
24
|
+
Called once on application startup. For example::
|
|
25
|
+
|
|
26
|
+
AuthUpdater(verbose=False, warn_only=True)
|
|
27
|
+
"""
|
|
28
|
+
|
|
20
29
|
group_updater_cls = GroupUpdater
|
|
21
30
|
role_updater_cls = RoleUpdater
|
|
22
31
|
|
|
@@ -38,8 +47,10 @@ class AuthUpdater:
|
|
|
38
47
|
)
|
|
39
48
|
groups = groups or site_auths.groups
|
|
40
49
|
pii_models = pii_models or site_auths.pii_models
|
|
41
|
-
post_update_funcs = post_update_funcs or site_auths.post_update_funcs
|
|
42
|
-
|
|
50
|
+
post_update_funcs = post_update_funcs or [f for f in site_auths.post_update_funcs]
|
|
51
|
+
site_auths.registry[POST_UPDATE_FUNCS_KEY] = []
|
|
52
|
+
pre_update_funcs = pre_update_funcs or [f for f in site_auths.pre_update_funcs]
|
|
53
|
+
site_auths.registry[PRE_UPDATE_FUNCS_KEY] = []
|
|
43
54
|
roles = roles or site_auths.roles
|
|
44
55
|
self.apps = apps
|
|
45
56
|
if not self.edc_auth_skip_auth_updater:
|
|
@@ -11,7 +11,7 @@ from django.core.exceptions import (
|
|
|
11
11
|
)
|
|
12
12
|
from django.core.management.color import color_style
|
|
13
13
|
|
|
14
|
-
from ..
|
|
14
|
+
from ..constants import PII, PII_VIEW
|
|
15
15
|
from ..utils import make_view_only_group_permissions
|
|
16
16
|
|
|
17
17
|
style = color_style()
|
|
@@ -27,7 +27,7 @@ class PermissionsCreatorError(ValidationError):
|
|
|
27
27
|
pass
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class CodenameDoesNotExist(Exception):
|
|
30
|
+
class CodenameDoesNotExist(Exception): # noqa: N818
|
|
31
31
|
pass
|
|
32
32
|
|
|
33
33
|
|
|
@@ -87,7 +87,7 @@ class GroupUpdater:
|
|
|
87
87
|
group = self.group_model_cls.objects.get(name=group_name)
|
|
88
88
|
except ObjectDoesNotExist as e:
|
|
89
89
|
if not create_group:
|
|
90
|
-
raise ObjectDoesNotExist(f"{e} Got {group_name}")
|
|
90
|
+
raise ObjectDoesNotExist(f"{e} Got {group_name}") from e
|
|
91
91
|
group = self.group_model_cls.objects.create(name=group_name)
|
|
92
92
|
else:
|
|
93
93
|
group.permissions.clear()
|
|
@@ -122,7 +122,7 @@ class GroupUpdater:
|
|
|
122
122
|
try:
|
|
123
123
|
app_label, codename = self.get_from_dotted_codename(dotted_codename)
|
|
124
124
|
except PermissionsCodenameError as e:
|
|
125
|
-
warn(str(e))
|
|
125
|
+
warn(str(e), stacklevel=2)
|
|
126
126
|
else:
|
|
127
127
|
# if you add extra codenames you must also add custom
|
|
128
128
|
# codename tuples to the Permissions model before you
|
|
@@ -138,7 +138,7 @@ class GroupUpdater:
|
|
|
138
138
|
except ObjectDoesNotExist as e:
|
|
139
139
|
errmsg = f"{e} Got codename={codename},app_label={app_label}"
|
|
140
140
|
if not self.warn_only:
|
|
141
|
-
raise CodenameDoesNotExist(errmsg)
|
|
141
|
+
raise CodenameDoesNotExist(errmsg) from e
|
|
142
142
|
warn(style.ERROR(errmsg))
|
|
143
143
|
except MultipleObjectsReturned as e:
|
|
144
144
|
if not allow_multiple_objects:
|
|
@@ -166,15 +166,17 @@ class GroupUpdater:
|
|
|
166
166
|
try:
|
|
167
167
|
app_label, _codename = codename.split(".")
|
|
168
168
|
except ValueError as e:
|
|
169
|
-
raise PermissionsCodenameError(
|
|
169
|
+
raise PermissionsCodenameError(
|
|
170
|
+
f"Invalid dotted codename. {e} Got {codename}."
|
|
171
|
+
) from e
|
|
170
172
|
else:
|
|
171
173
|
try:
|
|
172
174
|
self.apps.get_app_config(app_label)
|
|
173
|
-
except LookupError:
|
|
175
|
+
except LookupError as e:
|
|
174
176
|
raise PermissionsCodenameError(
|
|
175
177
|
"Invalid app_label in codename. Expected format "
|
|
176
178
|
f"'<app_label>.<some_codename>'. Got {codename}."
|
|
177
|
-
)
|
|
179
|
+
) from e
|
|
178
180
|
prefix = _codename.split("_")[0]
|
|
179
181
|
if prefix not in self.codename_prefixes:
|
|
180
182
|
raise PermissionsCodenameError(
|
|
@@ -244,7 +246,7 @@ class GroupUpdater:
|
|
|
244
246
|
except ObjectDoesNotExist as e:
|
|
245
247
|
raise CodenameDoesNotExist(
|
|
246
248
|
f"Unable to verify codename. {e} Got '{app_label}.{codename}'"
|
|
247
|
-
)
|
|
249
|
+
) from e
|
|
248
250
|
except MultipleObjectsReturned as e:
|
|
249
251
|
self.delete_and_raise_on_duplicate_codenames(codename, app_label, exception=e)
|
|
250
252
|
return permission
|
|
@@ -267,7 +269,7 @@ class GroupUpdater:
|
|
|
267
269
|
try:
|
|
268
270
|
value, name = codename_tpl
|
|
269
271
|
except ValueError as e:
|
|
270
|
-
raise ValueError(f"{e} Got {codename_tpl}")
|
|
272
|
+
raise ValueError(f"{e} Got {codename_tpl}") from e
|
|
271
273
|
_app_label, codename = value.split(".")
|
|
272
274
|
if app_label and _app_label != app_label:
|
|
273
275
|
raise PermissionsCreatorError(
|
|
@@ -56,11 +56,11 @@ class RoleUpdater:
|
|
|
56
56
|
for group_name in groups:
|
|
57
57
|
try:
|
|
58
58
|
group = self.group_model_cls.objects.get(name=group_name)
|
|
59
|
-
except ObjectDoesNotExist:
|
|
59
|
+
except ObjectDoesNotExist as e:
|
|
60
60
|
raise RoleUpdaterError(
|
|
61
61
|
"Invalid group specified for role. "
|
|
62
62
|
f"`{group_name}` is not a group. See role `{role}`."
|
|
63
|
-
)
|
|
63
|
+
) from e
|
|
64
64
|
# group = self.group_model_cls.objects.create(name=group_name)
|
|
65
65
|
role.groups.add(group)
|
|
66
66
|
index += 1
|
edc_auth/constants.py
CHANGED
|
@@ -19,3 +19,13 @@ CUSTOM_ROLE = "custom"
|
|
|
19
19
|
NURSE_ROLE = "research_nurse"
|
|
20
20
|
STAFF_ROLE = "staff"
|
|
21
21
|
STATISTICIAN = "statistician"
|
|
22
|
+
|
|
23
|
+
# keys for the site_auths registry
|
|
24
|
+
UPDATE_ROLES_KEY = "update_roles"
|
|
25
|
+
UPDATE_GROUPS_KEY = "update_groups"
|
|
26
|
+
PRE_UPDATE_FUNCS_KEY = "pre_update_funcs"
|
|
27
|
+
POST_UPDATE_FUNCS_KEY = "post_update_funcs"
|
|
28
|
+
GROUPS_KEY = "groups"
|
|
29
|
+
ROLES_KEY = "roles"
|
|
30
|
+
PII_MODELS_KEY = "pii_models"
|
|
31
|
+
CUSTOM_PERMISSIONS_TUPLES_KEY = "custom_permissions_tuples"
|
edc_auth/import_users.py
CHANGED
|
@@ -183,9 +183,9 @@ class UserImporter:
|
|
|
183
183
|
self.update_user_sites(site_names or [])
|
|
184
184
|
self.update_user_roles(role_names or [STAFF_ROLE])
|
|
185
185
|
self.user.save()
|
|
186
|
-
self.user.userprofile.job_title = self.job_title
|
|
187
|
-
self.user.userprofile.mobile = self.mobile
|
|
188
|
-
self.user.userprofile.alternate_email = self.alternate_email
|
|
186
|
+
self.user.userprofile.job_title = self.job_title or ""
|
|
187
|
+
self.user.userprofile.mobile = self.mobile or ""
|
|
188
|
+
self.user.userprofile.alternate_email = self.alternate_email or ""
|
|
189
189
|
self.user.userprofile.save()
|
|
190
190
|
|
|
191
191
|
self.site_names = (
|
edc_auth/models/user_profile.py
CHANGED
|
@@ -11,7 +11,7 @@ from edc_export.choices import EXPORT_FORMATS
|
|
|
11
11
|
from edc_export.constants import CSV
|
|
12
12
|
from edc_notification.model_mixins import NotificationUserProfileModelMixin
|
|
13
13
|
|
|
14
|
-
from ..
|
|
14
|
+
from ..constants import CUSTOM_ROLE, STAFF_ROLE
|
|
15
15
|
from .role import Role
|
|
16
16
|
|
|
17
17
|
|
|
@@ -32,21 +32,21 @@ class UserProfile(NotificationUserProfileModelMixin, models.Model):
|
|
|
32
32
|
|
|
33
33
|
sites = models.ManyToManyField(Site, blank=True)
|
|
34
34
|
|
|
35
|
-
job_title = models.CharField(max_length=100,
|
|
35
|
+
job_title = models.CharField(max_length=100, default="", blank=True)
|
|
36
36
|
|
|
37
|
-
alternate_email = models.EmailField(_("Alternate email address"), blank=True,
|
|
37
|
+
alternate_email = models.EmailField(_("Alternate email address"), blank=True, default="")
|
|
38
38
|
|
|
39
39
|
mobile = models.CharField(
|
|
40
40
|
max_length=25,
|
|
41
41
|
validators=[RegexValidator(regex=r"^\+\d+")],
|
|
42
|
-
|
|
42
|
+
default="",
|
|
43
43
|
blank=True,
|
|
44
44
|
help_text="e.g. +1234567890",
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
clinic_label_printer = models.CharField(
|
|
48
48
|
max_length=100,
|
|
49
|
-
|
|
49
|
+
default="",
|
|
50
50
|
blank=True,
|
|
51
51
|
help_text=format_html(
|
|
52
52
|
'Change in <a href="{href}">{label}</a>',
|
|
@@ -57,7 +57,7 @@ class UserProfile(NotificationUserProfileModelMixin, models.Model):
|
|
|
57
57
|
|
|
58
58
|
lab_label_printer = models.CharField(
|
|
59
59
|
max_length=100,
|
|
60
|
-
|
|
60
|
+
default="",
|
|
61
61
|
blank=True,
|
|
62
62
|
help_text=format_html(
|
|
63
63
|
'Change in <a href="{href}">{label}</a>',
|
|
@@ -68,7 +68,7 @@ class UserProfile(NotificationUserProfileModelMixin, models.Model):
|
|
|
68
68
|
|
|
69
69
|
print_server = models.CharField(
|
|
70
70
|
max_length=100,
|
|
71
|
-
|
|
71
|
+
default="",
|
|
72
72
|
blank=True,
|
|
73
73
|
help_text=format_html(
|
|
74
74
|
'Change in <a href="{href}">{label}</a>',
|
|
@@ -82,7 +82,6 @@ class UserProfile(NotificationUserProfileModelMixin, models.Model):
|
|
|
82
82
|
max_length=25,
|
|
83
83
|
choices=EXPORT_FORMATS,
|
|
84
84
|
default=CSV,
|
|
85
|
-
null=True,
|
|
86
85
|
blank=True,
|
|
87
86
|
help_text="Note: requires export permissions",
|
|
88
87
|
)
|
|
@@ -101,9 +100,13 @@ class UserProfile(NotificationUserProfileModelMixin, models.Model):
|
|
|
101
100
|
group_names = [group.name for group in self.user.groups.all()]
|
|
102
101
|
add_group_names = []
|
|
103
102
|
for role in self.roles.all():
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
add_group_names.extend(
|
|
104
|
+
[
|
|
105
|
+
group.name
|
|
106
|
+
for group in role.groups.all()
|
|
107
|
+
if group.name not in group_names
|
|
108
|
+
]
|
|
109
|
+
)
|
|
107
110
|
add_group_names = list(set(add_group_names))
|
|
108
111
|
for name in add_group_names:
|
|
109
112
|
self.user.groups.add(Group.objects.get(name=name))
|
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)
|