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
|
@@ -30,10 +30,11 @@ class UrlRequestContextMixin:
|
|
|
30
30
|
@classmethod
|
|
31
31
|
def urls(
|
|
32
32
|
cls,
|
|
33
|
-
|
|
34
|
-
label: str
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
*,
|
|
34
|
+
label: str,
|
|
35
|
+
identifier_pattern: str,
|
|
36
|
+
namespace: str | None = None,
|
|
37
|
+
identifier_label: str | None = None,
|
|
37
38
|
) -> list[URLPattern]:
|
|
38
39
|
label = (
|
|
39
40
|
label
|
|
@@ -62,5 +63,5 @@ class UrlRequestContextMixin:
|
|
|
62
63
|
f"Url name not defined in url_names. "
|
|
63
64
|
f"Expected one of {url_names.registry}. Got {e}. "
|
|
64
65
|
f"Hint: check if dashboard middleware is loaded."
|
|
65
|
-
)
|
|
66
|
+
) from e
|
|
66
67
|
return url_data
|
|
@@ -45,7 +45,7 @@ class DataQueryAdmin(SiteModelAdminMixin, ModelAdminSubjectDashboardMixin, Simpl
|
|
|
45
45
|
|
|
46
46
|
locked_column_template_name = "edc_data_manager/columns/locked.html"
|
|
47
47
|
|
|
48
|
-
status_column_context = {
|
|
48
|
+
status_column_context = { # noqa: RUF012
|
|
49
49
|
"NEW": NEW,
|
|
50
50
|
"OPEN": OPEN,
|
|
51
51
|
"FEEDBACK": FEEDBACK,
|
|
@@ -61,13 +61,13 @@ class DataQueryAdmin(SiteModelAdminMixin, ModelAdminSubjectDashboardMixin, Simpl
|
|
|
61
61
|
|
|
62
62
|
actions = (toggle_dm_status,)
|
|
63
63
|
|
|
64
|
-
radio_fields = {
|
|
64
|
+
radio_fields = { # noqa: RUF012
|
|
65
65
|
"status": admin.VERTICAL,
|
|
66
66
|
"site_response_status": admin.VERTICAL,
|
|
67
67
|
"query_priority": admin.VERTICAL,
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
autocomplete_fields =
|
|
70
|
+
autocomplete_fields = (
|
|
71
71
|
"sender",
|
|
72
72
|
"recipients",
|
|
73
73
|
"data_dictionaries",
|
|
@@ -75,7 +75,7 @@ class DataQueryAdmin(SiteModelAdminMixin, ModelAdminSubjectDashboardMixin, Simpl
|
|
|
75
75
|
"visit_schedule",
|
|
76
76
|
"registered_subject",
|
|
77
77
|
"dm_user",
|
|
78
|
-
|
|
78
|
+
)
|
|
79
79
|
|
|
80
80
|
list_display = (
|
|
81
81
|
"wrapped_title",
|
|
@@ -201,11 +201,10 @@ class DataQueryAdmin(SiteModelAdminMixin, ModelAdminSubjectDashboardMixin, Simpl
|
|
|
201
201
|
wrapped_title.short_description = "Title"
|
|
202
202
|
|
|
203
203
|
def formfield_for_manytomany(self, db_field, request, **kwargs):
|
|
204
|
-
if db_field.name == "data_dictionaries":
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
)
|
|
204
|
+
if db_field.name == "data_dictionaries" and request.GET.get("title"):
|
|
205
|
+
kwargs["queryset"] = DataDictionary.objects.filter(
|
|
206
|
+
model_verbose_name=request.GET.get("title")
|
|
207
|
+
)
|
|
209
208
|
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
|
210
209
|
|
|
211
210
|
def query_dates(self, obj):
|
|
@@ -332,7 +331,8 @@ class DataQueryAdmin(SiteModelAdminMixin, ModelAdminSubjectDashboardMixin, Simpl
|
|
|
332
331
|
"missed_visit",
|
|
333
332
|
"auto_resolved",
|
|
334
333
|
"registered_subject",
|
|
335
|
-
|
|
334
|
+
*action_fields,
|
|
335
|
+
)
|
|
336
336
|
if not request.user.groups.filter(name=DATA_MANAGER):
|
|
337
337
|
extra_fields = (
|
|
338
338
|
"data_dictionaries",
|
|
@@ -350,10 +350,11 @@ class DataQueryAdmin(SiteModelAdminMixin, ModelAdminSubjectDashboardMixin, Simpl
|
|
|
350
350
|
"title",
|
|
351
351
|
"visit_code_sequence",
|
|
352
352
|
"visit_schedule",
|
|
353
|
+
*extra_fields,
|
|
353
354
|
)
|
|
354
355
|
if not obj:
|
|
355
356
|
extra_fields = tuple(f for f in extra_fields if f != "registered_subject")
|
|
356
|
-
return fields
|
|
357
|
+
return *fields, *extra_fields
|
|
357
358
|
|
|
358
359
|
def get_subject_dashboard_url_kwargs(self, obj):
|
|
359
360
|
def get_opts():
|
edc_data_manager/auths.py
CHANGED
|
@@ -18,45 +18,48 @@ from .auth_objects import (
|
|
|
18
18
|
data_manager,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
-
site_auths.add_custom_permissions_tuples(
|
|
22
|
-
model="edc_data_manager.edcpermissions", codename_tuples=custom_codename_tuples
|
|
23
|
-
)
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
22
|
+
def update_site_auths():
|
|
23
|
+
site_auths.add_custom_permissions_tuples(
|
|
24
|
+
model="edc_data_manager.edcpermissions", codename_tuples=custom_codename_tuples
|
|
25
|
+
)
|
|
29
26
|
|
|
27
|
+
site_auths.add_custom_permissions_tuples(
|
|
28
|
+
model="edc_data_manager.edcpermissions",
|
|
29
|
+
codename_tuples=[("edc_data_manager.special_bypassmodelform", "Can bypass modelform")],
|
|
30
|
+
)
|
|
30
31
|
|
|
31
|
-
# groups
|
|
32
|
-
site_auths.add_group(*data_manager, name=DATA_MANAGER)
|
|
33
|
-
site_auths.add_group(*data_manager, name=DATA_QUERY_VIEW, view_only=True)
|
|
32
|
+
# groups
|
|
33
|
+
site_auths.add_group(*data_manager, name=DATA_MANAGER)
|
|
34
|
+
site_auths.add_group(*data_manager, name=DATA_QUERY_VIEW, view_only=True)
|
|
34
35
|
|
|
35
|
-
site_auths.add_group(*data_manager, name=DATA_QUERY, view_only=True)
|
|
36
|
-
site_auths.update_group("edc_data_manager.change_dataquery", name=DATA_QUERY)
|
|
36
|
+
site_auths.add_group(*data_manager, name=DATA_QUERY, view_only=True)
|
|
37
|
+
site_auths.update_group("edc_data_manager.change_dataquery", name=DATA_QUERY)
|
|
37
38
|
|
|
38
|
-
site_auths.add_group(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
39
|
+
site_auths.add_group(
|
|
40
|
+
"edc_data_manager.export_datadictionary",
|
|
41
|
+
"edc_data_manager.export_dataquery",
|
|
42
|
+
"edc_data_manager.export_queryrule",
|
|
43
|
+
name=DATA_MANAGER_EXPORT,
|
|
44
|
+
)
|
|
44
45
|
|
|
45
|
-
site_auths.add_group(*data_manager, name=DATA_MANAGER_SUPER)
|
|
46
|
-
site_auths.update_group("edc_data_manager.change_dataquery", name=DATA_MANAGER_SUPER)
|
|
47
|
-
site_auths.update_group(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
)
|
|
46
|
+
site_auths.add_group(*data_manager, name=DATA_MANAGER_SUPER)
|
|
47
|
+
site_auths.update_group("edc_data_manager.change_dataquery", name=DATA_MANAGER_SUPER)
|
|
48
|
+
site_auths.update_group(
|
|
49
|
+
"edc_data_manager.export_datadictionary",
|
|
50
|
+
"edc_data_manager.export_dataquery",
|
|
51
|
+
"edc_data_manager.export_queryrule",
|
|
52
|
+
"edc_data_manager.special_bypassmodelform",
|
|
53
|
+
"edc_data_manager.change_dataquery",
|
|
54
|
+
name=DATA_MANAGER_SUPER,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# roles
|
|
58
|
+
site_auths.add_role(CELERY_MANAGER, DATA_MANAGER, name=DATA_MANAGER_ROLE)
|
|
59
|
+
site_auths.add_role(DATA_QUERY, name=SITE_DATA_MANAGER_ROLE)
|
|
60
|
+
site_auths.update_role(DATA_QUERY, name=CLINICIAN_ROLE)
|
|
61
|
+
site_auths.update_role(DATA_QUERY, name=NURSE_ROLE)
|
|
62
|
+
site_auths.update_role(DATA_QUERY, name=CLINICIAN_SUPER_ROLE)
|
|
55
63
|
|
|
56
64
|
|
|
57
|
-
|
|
58
|
-
site_auths.add_role(CELERY_MANAGER, DATA_MANAGER, name=DATA_MANAGER_ROLE)
|
|
59
|
-
site_auths.add_role(DATA_QUERY, name=SITE_DATA_MANAGER_ROLE)
|
|
60
|
-
site_auths.update_role(DATA_QUERY, name=CLINICIAN_ROLE)
|
|
61
|
-
site_auths.update_role(DATA_QUERY, name=NURSE_ROLE)
|
|
62
|
-
site_auths.update_role(DATA_QUERY, name=CLINICIAN_SUPER_ROLE)
|
|
65
|
+
update_site_auths()
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from decimal import Decimal
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from tqdm import tqdm
|
|
8
8
|
|
|
@@ -17,12 +17,12 @@ if TYPE_CHECKING:
|
|
|
17
17
|
class QueryRuleWrapper:
|
|
18
18
|
def __init__(
|
|
19
19
|
self,
|
|
20
|
-
query_rule_obj: QueryRule = None,
|
|
21
|
-
subject_identifiers: list[
|
|
22
|
-
visit_schedule_obj: QueryVisitSchedule = None,
|
|
23
|
-
timepoint: Decimal = None,
|
|
24
|
-
entry_status: str = None,
|
|
25
|
-
now: datetime = None,
|
|
20
|
+
query_rule_obj: QueryRule | None = None,
|
|
21
|
+
subject_identifiers: list[str] | None = None,
|
|
22
|
+
visit_schedule_obj: QueryVisitSchedule | None = None,
|
|
23
|
+
timepoint: Decimal | None = None,
|
|
24
|
+
entry_status: str | None = None,
|
|
25
|
+
now: datetime | None = None,
|
|
26
26
|
verbose: bool | None = None,
|
|
27
27
|
):
|
|
28
28
|
self.now = now
|
edc_export/archive_exporter.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from tempfile import mkdtemp
|
|
4
5
|
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
@@ -17,7 +18,7 @@ if TYPE_CHECKING:
|
|
|
17
18
|
from django.contrib.auth.models import User
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
class ArchiveExporterNothingExported(Exception):
|
|
21
|
+
class ArchiveExporterNothingExported(Exception): # noqa: N818
|
|
21
22
|
pass
|
|
22
23
|
|
|
23
24
|
|
|
@@ -56,7 +57,7 @@ class ArchiveExporter:
|
|
|
56
57
|
for model in models:
|
|
57
58
|
csv_exporter = self.csv_exporter_cls(
|
|
58
59
|
model=model,
|
|
59
|
-
export_folder=tmp_folder,
|
|
60
|
+
export_folder=Path(tmp_folder),
|
|
60
61
|
decrypt=decrypt,
|
|
61
62
|
site_ids=sites.get_site_ids_for_user(
|
|
62
63
|
user=user, site_id=sites.get_current_site().site_id
|
edc_export/auths.py
CHANGED
|
@@ -4,34 +4,38 @@ from edc_auth.utils import remove_default_model_permissions_from_edc_permissions
|
|
|
4
4
|
from .auth_objects import export_codenames
|
|
5
5
|
from .constants import DATA_EXPORTER_ROLE, EXPORT, EXPORT_PII
|
|
6
6
|
|
|
7
|
-
site_auths.add_post_update_func(
|
|
8
|
-
"edc_export",
|
|
9
|
-
remove_default_model_permissions_from_edc_permissions,
|
|
10
|
-
)
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"Can export the visit schedule",
|
|
18
|
-
),
|
|
19
|
-
(
|
|
20
|
-
"edc_export.view_export_dashboard",
|
|
21
|
-
"Can view export dashboard",
|
|
22
|
-
),
|
|
23
|
-
(
|
|
24
|
-
"edc_export.export_subjectschedulehistory",
|
|
25
|
-
"Can export subject schedule history",
|
|
26
|
-
),
|
|
27
|
-
(
|
|
28
|
-
"edc_export.export_pii",
|
|
29
|
-
"Can export PII",
|
|
30
|
-
),
|
|
31
|
-
],
|
|
32
|
-
)
|
|
8
|
+
def update_site_auths():
|
|
9
|
+
site_auths.add_post_update_func(
|
|
10
|
+
"edc_export",
|
|
11
|
+
remove_default_model_permissions_from_edc_permissions,
|
|
12
|
+
)
|
|
33
13
|
|
|
14
|
+
site_auths.add_custom_permissions_tuples(
|
|
15
|
+
model="edc_export.edcpermissions",
|
|
16
|
+
codename_tuples=[
|
|
17
|
+
(
|
|
18
|
+
"edc_export.export_visitschedule",
|
|
19
|
+
"Can export the visit schedule",
|
|
20
|
+
),
|
|
21
|
+
(
|
|
22
|
+
"edc_export.view_export_dashboard",
|
|
23
|
+
"Can view export dashboard",
|
|
24
|
+
),
|
|
25
|
+
(
|
|
26
|
+
"edc_export.export_subjectschedulehistory",
|
|
27
|
+
"Can export subject schedule history",
|
|
28
|
+
),
|
|
29
|
+
(
|
|
30
|
+
"edc_export.export_pii",
|
|
31
|
+
"Can export PII",
|
|
32
|
+
),
|
|
33
|
+
],
|
|
34
|
+
)
|
|
34
35
|
|
|
35
|
-
site_auths.add_group(*export_codenames, name=EXPORT)
|
|
36
|
-
site_auths.add_role(EXPORT, name=DATA_EXPORTER_ROLE)
|
|
37
|
-
site_auths.add_group("edc_export.export_pii", name=EXPORT_PII)
|
|
36
|
+
site_auths.add_group(*export_codenames, name=EXPORT)
|
|
37
|
+
site_auths.add_role(EXPORT, name=DATA_EXPORTER_ROLE)
|
|
38
|
+
site_auths.add_group("edc_export.export_pii", name=EXPORT_PII)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
update_site_auths()
|
|
@@ -102,7 +102,10 @@ class ModelExporter:
|
|
|
102
102
|
if f in self.export_fields or f in self.audit_fields or f in self.required_fields:
|
|
103
103
|
self._field_names.pop(self._field_names.index(f))
|
|
104
104
|
self._field_names = (
|
|
105
|
-
self.export_fields
|
|
105
|
+
*self.export_fields,
|
|
106
|
+
*self.required_fields,
|
|
107
|
+
*self.field_names,
|
|
108
|
+
*self.audit_fields,
|
|
106
109
|
)
|
|
107
110
|
|
|
108
111
|
@property
|
edc_facility/auths.py
CHANGED
|
@@ -2,6 +2,11 @@ from edc_auth.site_auths import site_auths
|
|
|
2
2
|
|
|
3
3
|
from .auth_objects import EDC_FACILITY, EDC_FACILITY_SUPER, EDC_FACILITY_VIEW, codenames
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
site_auths.add_group(*codenames, name=
|
|
5
|
+
|
|
6
|
+
def update_site_auths():
|
|
7
|
+
site_auths.add_group(*codenames, name=EDC_FACILITY_VIEW, view_only=True)
|
|
8
|
+
site_auths.add_group(*codenames, name=EDC_FACILITY, no_delete=True)
|
|
9
|
+
site_auths.add_group(*codenames, name=EDC_FACILITY_SUPER)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
update_site_auths()
|
edc_facility/facility.py
CHANGED
|
@@ -7,8 +7,7 @@ from zoneinfo import ZoneInfo
|
|
|
7
7
|
|
|
8
8
|
import arrow
|
|
9
9
|
from arrow import Arrow
|
|
10
|
-
from dateutil.
|
|
11
|
-
from dateutil.relativedelta import relativedelta
|
|
10
|
+
from dateutil.relativedelta import relativedelta, weekday
|
|
12
11
|
from django.conf import settings
|
|
13
12
|
from django.utils import timezone
|
|
14
13
|
|
|
@@ -36,19 +35,18 @@ class Facility:
|
|
|
36
35
|
def __init__(
|
|
37
36
|
self,
|
|
38
37
|
name: str | None = None,
|
|
39
|
-
days: list | None = None,
|
|
38
|
+
days: list[weekday] | None = None,
|
|
40
39
|
slots: list[int] | None = None,
|
|
41
40
|
best_effort_available_datetime: datetime | None = None,
|
|
42
41
|
):
|
|
43
|
-
self.days =
|
|
42
|
+
self.days = days
|
|
44
43
|
self.name = name
|
|
45
44
|
if not name:
|
|
46
45
|
raise FacilityError(f"Name cannot be None. See {self!r}")
|
|
47
46
|
self.best_effort_available_datetime = (
|
|
48
47
|
True if best_effort_available_datetime is None else best_effort_available_datetime
|
|
49
48
|
)
|
|
50
|
-
for
|
|
51
|
-
self.days.append(getattr(day, "weekday", weekday(day)))
|
|
49
|
+
self.weekdays = [d.weekday for d in self.days]
|
|
52
50
|
self.slots = slots or [99999 for _ in self.days]
|
|
53
51
|
self.config = dict(zip([str(d) for d in self.days], self.slots, strict=False))
|
|
54
52
|
self.holidays = self.holiday_cls()
|
|
@@ -69,9 +67,9 @@ class Facility:
|
|
|
69
67
|
slots_per_day = 0
|
|
70
68
|
return slots_per_day
|
|
71
69
|
|
|
72
|
-
@property
|
|
73
|
-
def weekdays(self) -> list[int]:
|
|
74
|
-
|
|
70
|
+
# @property
|
|
71
|
+
# def weekdays(self) -> list[int]:
|
|
72
|
+
# return [d.weekday for d in self.days]
|
|
75
73
|
|
|
76
74
|
@staticmethod
|
|
77
75
|
def open_slot_on(arr) -> Arrow:
|
|
@@ -132,6 +130,7 @@ class Facility:
|
|
|
132
130
|
reverse_delta=None,
|
|
133
131
|
taken_datetimes=None,
|
|
134
132
|
schedule_on_holidays=None,
|
|
133
|
+
**kwargs, # noqa: ARG002
|
|
135
134
|
):
|
|
136
135
|
"""Returns an arrow object for a datetime equal to or
|
|
137
136
|
close to the suggested datetime.
|
|
@@ -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
|
|
@@ -28,20 +29,24 @@ class CustomLabelCondition:
|
|
|
28
29
|
"""
|
|
29
30
|
return
|
|
30
31
|
|
|
31
|
-
def get_additional_options(self, request=None, obj=None, model=None):
|
|
32
|
+
def get_additional_options(self, request=None, obj=None, model=None): # noqa: ARG002
|
|
32
33
|
return {}
|
|
33
34
|
|
|
34
35
|
@property
|
|
35
36
|
def appointment(self) -> Appointment | None:
|
|
36
37
|
"""Returns the appointment instance for this request or None."""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
with contextlib.suppress(ObjectDoesNotExist):
|
|
39
|
+
return django_apps.get_model(self.appointment_model).objects.get(
|
|
40
|
+
pk=self.request.GET.get("appointment")
|
|
41
|
+
)
|
|
42
|
+
return None
|
|
40
43
|
|
|
41
44
|
@property
|
|
42
45
|
def previous_appointment(self) -> Appointment | None:
|
|
43
46
|
"""Returns the previous appointment for this request or None."""
|
|
44
|
-
|
|
47
|
+
with contextlib.suppress(ObjectDoesNotExist):
|
|
48
|
+
return self.appointment.previous_by_timepoint
|
|
49
|
+
return None
|
|
45
50
|
|
|
46
51
|
@property
|
|
47
52
|
def previous_visit(self):
|
|
@@ -73,10 +78,8 @@ class CustomLabelCondition:
|
|
|
73
78
|
"""
|
|
74
79
|
previous_obj = None
|
|
75
80
|
if self.previous_visit:
|
|
76
|
-
|
|
81
|
+
with contextlib.suppress(ObjectDoesNotExist):
|
|
77
82
|
previous_obj = self.model.objects.get(
|
|
78
83
|
**{f"{self.model.related_visit_model_attr()}": self.previous_visit}
|
|
79
84
|
)
|
|
80
|
-
except ObjectDoesNotExist:
|
|
81
|
-
pass
|
|
82
85
|
return previous_obj
|
edc_form_label/form_label.py
CHANGED
edc_form_runners/auths.py
CHANGED
|
@@ -9,10 +9,15 @@ from .auth_objects import (
|
|
|
9
9
|
codenames,
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
-
site_auths.add_group(*codenames, name=EDC_FORM_RUNNERS_VIEW, view_only=True)
|
|
13
|
-
site_auths.add_group(*codenames, name=EDC_FORM_RUNNERS, no_delete=True)
|
|
14
|
-
site_auths.add_group(*codenames, name=EDC_FORM_RUNNERS_SUPER)
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
site_auths.
|
|
18
|
-
site_auths.
|
|
13
|
+
def update_site_auths():
|
|
14
|
+
site_auths.add_group(*codenames, name=EDC_FORM_RUNNERS_VIEW, view_only=True)
|
|
15
|
+
site_auths.add_group(*codenames, name=EDC_FORM_RUNNERS, no_delete=True)
|
|
16
|
+
site_auths.add_group(*codenames, name=EDC_FORM_RUNNERS_SUPER)
|
|
17
|
+
|
|
18
|
+
site_auths.update_role(EDC_FORM_RUNNERS_VIEW, name=AUDITOR_ROLE)
|
|
19
|
+
site_auths.update_role(EDC_FORM_RUNNERS, name=CLINICIAN_ROLE)
|
|
20
|
+
site_auths.update_role(EDC_FORM_RUNNERS_SUPER, name=DATA_MANAGER_ROLE)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
update_site_auths()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
from edc_constants.constants import NOT_APPLICABLE
|
|
3
|
+
from edc_constants.constants import NOT_APPLICABLE, NULL_STRING
|
|
4
4
|
|
|
5
5
|
from .base_form_validator import (
|
|
6
6
|
APPLICABLE_ERROR,
|
|
@@ -33,8 +33,8 @@ class ApplicableFieldValidator(BaseFormValidator):
|
|
|
33
33
|
def applicable_if(
|
|
34
34
|
self,
|
|
35
35
|
*responses: Any,
|
|
36
|
-
field: str
|
|
37
|
-
field_applicable: str
|
|
36
|
+
field: str,
|
|
37
|
+
field_applicable: str,
|
|
38
38
|
inverse: bool | None = None,
|
|
39
39
|
is_instance_field: bool | None = None,
|
|
40
40
|
msg: str | None = None,
|
|
@@ -95,8 +95,8 @@ class ApplicableFieldValidator(BaseFormValidator):
|
|
|
95
95
|
def applicable(
|
|
96
96
|
self,
|
|
97
97
|
*responses: Any,
|
|
98
|
-
field: str
|
|
99
|
-
field_applicable: str
|
|
98
|
+
field: str,
|
|
99
|
+
field_applicable: str,
|
|
100
100
|
inverse: bool | None = None,
|
|
101
101
|
is_instance_field: bool | None = None,
|
|
102
102
|
msg: str | None = None,
|
|
@@ -117,7 +117,8 @@ class ApplicableFieldValidator(BaseFormValidator):
|
|
|
117
117
|
field_applicable_value = self.get(field_applicable)
|
|
118
118
|
|
|
119
119
|
if field_value in responses and (
|
|
120
|
-
field_applicable_value
|
|
120
|
+
field_applicable_value in (NULL_STRING, not_applicable)
|
|
121
|
+
or field_applicable_value is None
|
|
121
122
|
):
|
|
122
123
|
self.raise_applicable(field_applicable, msg=msg, applicable_msg=applicable_msg)
|
|
123
124
|
elif (
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
from copy import copy
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
6
7
|
from django.core.exceptions import NON_FIELD_ERRORS
|
|
7
|
-
from django.forms import ValidationError
|
|
8
|
+
from django.forms import ValidationError
|
|
8
9
|
|
|
9
10
|
APPLICABLE_ERROR = "applicable"
|
|
10
11
|
INVALID_ERROR = "invalid"
|
|
@@ -14,7 +15,7 @@ REQUIRED_ERROR = "required"
|
|
|
14
15
|
OUT_OF_RANGE_ERROR = "out_of_range"
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
class InvalidModelFormFieldValidator(Exception):
|
|
18
|
+
class InvalidModelFormFieldValidator(Exception): # noqa: N818
|
|
18
19
|
def __init__(self, message, code=None):
|
|
19
20
|
message = f"Invalid field validator. Got '{message}'"
|
|
20
21
|
super().__init__(message)
|
|
@@ -119,13 +120,13 @@ class BaseFormValidator:
|
|
|
119
120
|
"""
|
|
120
121
|
try:
|
|
121
122
|
self._clean()
|
|
122
|
-
except
|
|
123
|
+
except ValidationError as e:
|
|
123
124
|
self.capture_error_message(e)
|
|
124
125
|
self.capture_error_code(e)
|
|
125
|
-
raise
|
|
126
|
+
raise ValidationError(e) from e
|
|
126
127
|
return self.cleaned_data
|
|
127
128
|
|
|
128
|
-
def capture_error_message(self, e:
|
|
129
|
+
def capture_error_message(self, e: ValidationError) -> None:
|
|
129
130
|
try:
|
|
130
131
|
self._errors.update(**e.error_dict)
|
|
131
132
|
except AttributeError:
|
|
@@ -134,11 +135,9 @@ class BaseFormValidator:
|
|
|
134
135
|
except AttributeError:
|
|
135
136
|
self._errors.update({NON_FIELD_ERRORS: str(e)})
|
|
136
137
|
|
|
137
|
-
def capture_error_code(self, e:
|
|
138
|
-
|
|
138
|
+
def capture_error_code(self, e: ValidationError) -> None:
|
|
139
|
+
with contextlib.suppress(AttributeError):
|
|
139
140
|
self._error_codes.append(e.code)
|
|
140
|
-
except AttributeError:
|
|
141
|
-
pass
|
|
142
141
|
|
|
143
142
|
def get_inline_field_value(self, field=None, inline_set=None):
|
|
144
143
|
"""Returns the value of the first inline field that has a value."""
|
|
@@ -41,20 +41,14 @@ class OtherSpecifyFieldValidator(BaseFormValidator):
|
|
|
41
41
|
cleaned_data.get(field), fk_stored_field_name, cleaned_data.get(field)
|
|
42
42
|
)
|
|
43
43
|
|
|
44
|
-
if (
|
|
45
|
-
field_value is not None
|
|
46
|
-
and field_value == other
|
|
47
|
-
and not cleaned_data.get(other_specify_field)
|
|
48
|
-
):
|
|
44
|
+
if field_value and field_value == other and not cleaned_data.get(other_specify_field):
|
|
49
45
|
ref = "" if not ref else f" ref: {ref}"
|
|
50
46
|
message = {other_specify_field: required_msg or f"This field is required.{ref}"}
|
|
51
47
|
self._errors.update(message)
|
|
52
48
|
self._error_codes.append(REQUIRED_ERROR)
|
|
53
49
|
raise ValidationError(message, code=REQUIRED_ERROR)
|
|
54
50
|
if (
|
|
55
|
-
field_value
|
|
56
|
-
and field_value != other
|
|
57
|
-
and cleaned_data.get(other_specify_field)
|
|
51
|
+
field_value and field_value != other and cleaned_data.get(other_specify_field)
|
|
58
52
|
) or (field_value is None and cleaned_data.get(other_specify_field)):
|
|
59
53
|
ref = "" if not ref else f" ref: {ref}"
|
|
60
54
|
message = {
|