clinicedc 2.0.20__py3-none-any.whl → 2.0.22__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.20.dist-info → clinicedc-2.0.22.dist-info}/METADATA +1 -1
- {clinicedc-2.0.20.dist-info → clinicedc-2.0.22.dist-info}/RECORD +64 -64
- edc_appointment/utils.py +4 -5
- edc_pdf_reports/report.py +2 -2
- edc_reportable/reference_range_evaluator.py +1 -2
- edc_subject_dashboard/middleware.py +2 -3
- edc_subject_dashboard/requisition_labels.py +2 -2
- edc_subject_dashboard/requisition_report.py +10 -6
- edc_subject_dashboard/templatetags/edc_subject_dashboard_extras.py +33 -33
- edc_subject_dashboard/view_mixins/subject_visit_view_mixin.py +1 -1
- edc_subject_dashboard/view_utils/crf_button.py +5 -4
- edc_subject_dashboard/view_utils/go_to_forms_button.py +1 -2
- edc_subject_dashboard/view_utils/subject_screening_button.py +2 -6
- edc_subject_dashboard/views/base_requisition_view.py +2 -3
- edc_subject_dashboard/views/refresh_appointments_view.py +6 -6
- edc_subject_dashboard/views/requisition_print_actions_view.py +5 -5
- edc_subject_dashboard/views/requisition_verify_actions_view.py +1 -1
- edc_timepoint/form_mixin.py +3 -3
- edc_timepoint/timepoint.py +1 -1
- edc_timepoint/timepoint_collection.py +3 -3
- edc_transfer/action_items.py +1 -2
- edc_transfer/form_validators.py +2 -2
- edc_unblinding/action_items.py +2 -4
- edc_unblinding/admin/autocomplete_admin.py +2 -2
- edc_unblinding/admin/unblinding_request_admin.py +2 -2
- edc_unblinding/admin/unblinding_review_admin.py +2 -2
- edc_unblinding/auths.py +11 -5
- edc_unblinding/models/unblinding_review.py +2 -2
- edc_utils/age.py +7 -3
- edc_utils/celery.py +3 -3
- edc_utils/context_processors_check.py +1 -1
- edc_utils/get_datetime_from_env.py +1 -1
- edc_utils/get_static_file.py +24 -14
- edc_utils/logging_filters/ignore_specific_ip_disallowed_host.py +1 -1
- edc_utils/message_in_queue.py +1 -4
- edc_utils/show_urls.py +1 -1
- edc_utils/text.py +1 -1
- edc_view_utils/model_button.py +2 -3
- edc_visit_schedule/exceptions.py +3 -3
- edc_visit_schedule/management/commands/find_invalid_onschedules.py +6 -4
- edc_visit_schedule/modelform_mixins/crf/visit_schedule_crf_modelform_mixin.py +1 -1
- edc_visit_schedule/modelform_mixins/off_schedule_modelform_mixin.py +3 -3
- edc_visit_schedule/modelform_mixins/visit_schedule_non_crf_modelform_mixin.py +1 -1
- edc_visit_schedule/models/signals.py +3 -8
- edc_visit_schedule/ordered_collection.py +4 -8
- edc_visit_schedule/post_migrate_signals.py +2 -2
- edc_visit_schedule/schedule/schedule.py +3 -7
- edc_visit_schedule/schedule/window.py +1 -1
- edc_visit_schedule/simple_model_validator.py +4 -4
- edc_visit_schedule/site_visit_schedules.py +12 -16
- edc_visit_schedule/subject_schedule.py +8 -8
- edc_visit_schedule/system_checks.py +3 -3
- edc_visit_schedule/templatetags/edc_visit_schedule_extras.py +2 -2
- edc_visit_schedule/utils.py +13 -14
- edc_visit_schedule/view_mixins.py +1 -1
- edc_visit_schedule/visit/crf.py +7 -6
- edc_visit_schedule/visit/forms_collection.py +2 -2
- edc_visit_schedule/visit/requisition.py +3 -3
- edc_visit_schedule/visit/visit.py +30 -30
- edc_visit_schedule/visit/window_period.py +3 -2
- edc_visit_schedule/visit_schedule/schedules_collection.py +5 -3
- edc_visit_schedule/visit_schedule/visit_schedule.py +5 -5
- {clinicedc-2.0.20.dist-info → clinicedc-2.0.22.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.20.dist-info → clinicedc-2.0.22.dist-info}/licenses/LICENSE +0 -0
|
@@ -48,14 +48,10 @@ class SubjectScreeningButton(DashboardModelButton):
|
|
|
48
48
|
|
|
49
49
|
@property
|
|
50
50
|
def reverse_kwargs(self) -> dict[str, str | UUID]:
|
|
51
|
-
|
|
52
|
-
return kwargs
|
|
51
|
+
return dict(screening_identifier=self.model_obj.screening_identifier)
|
|
53
52
|
|
|
54
53
|
@property
|
|
55
54
|
def title(self) -> str:
|
|
56
|
-
if self.perms.view_only or self.model_obj.consented
|
|
57
|
-
title = _("View")
|
|
58
|
-
else:
|
|
59
|
-
title = _("Edit")
|
|
55
|
+
title = _("View") if self.perms.view_only or self.model_obj.consented else _("Edit")
|
|
60
56
|
verbose_name = self.model_cls._meta.verbose_name.lower()
|
|
61
57
|
return f"{title} {verbose_name}"
|
|
@@ -11,10 +11,9 @@ class BaseRequisitionView(LoginRequiredMixin, PrintersMixin, View):
|
|
|
11
11
|
success_url_name = "subject_dashboard_url"
|
|
12
12
|
|
|
13
13
|
def get_success_url(self):
|
|
14
|
-
|
|
15
|
-
return success_url
|
|
14
|
+
return url_names.get(self.success_url_name)
|
|
16
15
|
|
|
17
|
-
def get(self, request, *args, **kwargs):
|
|
16
|
+
def get(self, request, *args, **kwargs): # noqa: ARG002
|
|
18
17
|
url = reverse("edc_lab_dashboard:home_url")
|
|
19
18
|
return HttpResponseRedirect(url)
|
|
20
19
|
|
|
@@ -14,10 +14,10 @@ class RefreshAppointmentsView(LoginRequiredMixin, View):
|
|
|
14
14
|
|
|
15
15
|
def refresh_appointments(
|
|
16
16
|
self,
|
|
17
|
-
subject_identifier: str
|
|
18
|
-
visit_schedule_name: str
|
|
19
|
-
schedule_name: str
|
|
20
|
-
**kwargs,
|
|
17
|
+
subject_identifier: str,
|
|
18
|
+
visit_schedule_name: str,
|
|
19
|
+
schedule_name: str,
|
|
20
|
+
**kwargs, # noqa: ARG002
|
|
21
21
|
) -> tuple[str, str]:
|
|
22
22
|
return refresh_appointments(
|
|
23
23
|
subject_identifier=subject_identifier,
|
|
@@ -26,8 +26,8 @@ class RefreshAppointmentsView(LoginRequiredMixin, View):
|
|
|
26
26
|
request=self.request,
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
-
def get(self, request, *args, **kwargs):
|
|
30
|
-
subject_identifier,
|
|
29
|
+
def get(self, request, *args, **kwargs): # noqa: ARG002
|
|
30
|
+
subject_identifier, _ = self.refresh_appointments(**kwargs)
|
|
31
31
|
url_name = url_names.get("subject_dashboard_url")
|
|
32
32
|
args = (subject_identifier,)
|
|
33
33
|
url = reverse(url_name, args=args)
|
|
@@ -26,14 +26,14 @@ class RequisitionPrintActionsView(BaseRequisitionView):
|
|
|
26
26
|
checkbox_name = "selected_panel_names"
|
|
27
27
|
|
|
28
28
|
def __init__(self, **kwargs):
|
|
29
|
-
self._appointment = None
|
|
30
|
-
self._selected_panel_names = []
|
|
31
|
-
self._requisition_metadata = None
|
|
29
|
+
self._appointment: Appointment | None = None
|
|
30
|
+
self._selected_panel_names: list[str] | None = []
|
|
31
|
+
self._requisition_metadata: RequisitionMetadata | None = None
|
|
32
32
|
self._requisition_model_cls = None
|
|
33
|
-
self.consignee = None
|
|
33
|
+
self.consignee: Consignee | None = None
|
|
34
34
|
super().__init__(**kwargs)
|
|
35
35
|
|
|
36
|
-
def post(self, request, *args, **kwargs):
|
|
36
|
+
def post(self, request, *args, **kwargs): # noqa: ARG002
|
|
37
37
|
response = None
|
|
38
38
|
if self.selected_panel_names:
|
|
39
39
|
if self.request.POST.get("submit") in [
|
|
@@ -8,7 +8,7 @@ from .base_requisition_view import BaseRequisitionView
|
|
|
8
8
|
class RequisitionVerifyActionsView(BaseRequisitionView):
|
|
9
9
|
requisition_verifier_cls = RequisitionVerifier
|
|
10
10
|
|
|
11
|
-
def post(self, request, *args, **kwargs):
|
|
11
|
+
def post(self, request, *args, **kwargs): # noqa: ARG002
|
|
12
12
|
alert = 1
|
|
13
13
|
error = 1
|
|
14
14
|
subject_identifier = request.POST.get("subject_identifier")
|
edc_timepoint/form_mixin.py
CHANGED
|
@@ -7,15 +7,15 @@ from .constants import CLOSED_TIMEPOINT
|
|
|
7
7
|
|
|
8
8
|
class TimepointFormMixin:
|
|
9
9
|
def clean(self):
|
|
10
|
-
cleaned_data = super(
|
|
10
|
+
cleaned_data = super().clean()
|
|
11
11
|
app_config = django_apps.get_app_config("edc_timepoint")
|
|
12
12
|
try:
|
|
13
13
|
app_config.timepoints[self._meta.model._meta.label_lower]
|
|
14
|
-
except KeyError:
|
|
14
|
+
except KeyError as e:
|
|
15
15
|
raise ImproperlyConfigured(
|
|
16
16
|
"ModelForm uses a model that is not a timepoint. "
|
|
17
17
|
f"Got {self._meta.model._meta.label_lower}."
|
|
18
|
-
)
|
|
18
|
+
) from e
|
|
19
19
|
timepoint_status = cleaned_data.get("timepoint_status")
|
|
20
20
|
if timepoint_status == CLOSED_TIMEPOINT:
|
|
21
21
|
raise forms.ValidationError(
|
edc_timepoint/timepoint.py
CHANGED
|
@@ -3,7 +3,7 @@ from django.apps import apps as django_apps
|
|
|
3
3
|
from .timepoint import Timepoint
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class TimepointDoesNotExist(Exception):
|
|
6
|
+
class TimepointDoesNotExist(Exception): # noqa: N818
|
|
7
7
|
pass
|
|
8
8
|
|
|
9
9
|
|
|
@@ -34,10 +34,10 @@ class TimepointCollection:
|
|
|
34
34
|
"""Returns the timepoint class for this model."""
|
|
35
35
|
try:
|
|
36
36
|
timepoint = self._timepoints[model]
|
|
37
|
-
except KeyError:
|
|
37
|
+
except KeyError as e:
|
|
38
38
|
raise TimepointDoesNotExist(
|
|
39
39
|
f"No timepoint has been configured with {model}. "
|
|
40
40
|
"See AppConfig for edc_timepoint. Hint: Perhaps you are using a custom "
|
|
41
41
|
"`Appointment` model?"
|
|
42
|
-
)
|
|
42
|
+
) from e
|
|
43
43
|
return timepoint
|
edc_transfer/action_items.py
CHANGED
edc_transfer/form_validators.py
CHANGED
|
@@ -48,7 +48,7 @@ class SubjectTransferFormValidatorMixin:
|
|
|
48
48
|
subject_transfer_obj = django_apps.get_model(
|
|
49
49
|
self.subject_transfer_model
|
|
50
50
|
).objects.get(subject_identifier=self.subject_identifier)
|
|
51
|
-
except ObjectDoesNotExist:
|
|
51
|
+
except ObjectDoesNotExist as e:
|
|
52
52
|
if (
|
|
53
53
|
self.cleaned_data.get(self.offschedule_reason_field)
|
|
54
54
|
and self.cleaned_data.get(self.offschedule_reason_field).name
|
|
@@ -59,7 +59,7 @@ class SubjectTransferFormValidatorMixin:
|
|
|
59
59
|
f"`{self.subject_transfer_model_cls._meta.verbose_name}` "
|
|
60
60
|
"form first."
|
|
61
61
|
)
|
|
62
|
-
raise forms.ValidationError({self.offschedule_reason_field: msg})
|
|
62
|
+
raise forms.ValidationError({self.offschedule_reason_field: msg}) from e
|
|
63
63
|
else:
|
|
64
64
|
if self.cleaned_data.get(self.subject_transfer_date_field) and (
|
|
65
65
|
subject_transfer_obj.transfer_date
|
edc_unblinding/action_items.py
CHANGED
|
@@ -19,12 +19,11 @@ class UnblindingRequestAction(ActionWithNotification):
|
|
|
19
19
|
|
|
20
20
|
def get_next_actions(self):
|
|
21
21
|
next_actions = []
|
|
22
|
-
|
|
22
|
+
return self.append_to_next_if_required(
|
|
23
23
|
next_actions=next_actions,
|
|
24
24
|
action_name=UNBLINDING_REVIEW_ACTION,
|
|
25
25
|
required=self.reference_obj.approved == TBD,
|
|
26
26
|
)
|
|
27
|
-
return next_actions
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
class UnblindingReviewAction(ActionWithNotification):
|
|
@@ -43,9 +42,8 @@ class UnblindingReviewAction(ActionWithNotification):
|
|
|
43
42
|
|
|
44
43
|
def get_next_actions(self):
|
|
45
44
|
next_actions = []
|
|
46
|
-
|
|
45
|
+
return self.append_to_next_if_required(
|
|
47
46
|
next_actions=next_actions,
|
|
48
47
|
action_name=END_OF_STUDY_ACTION,
|
|
49
48
|
required=self.reference_obj.approved == YES,
|
|
50
49
|
)
|
|
51
|
-
return next_actions
|
|
@@ -13,7 +13,7 @@ class UnblindingRequestorUserAdmin(BaseUserAdmin):
|
|
|
13
13
|
ordering = ("first_name", "last_name")
|
|
14
14
|
search_fields = ("first_name", "last_name", "username", "email")
|
|
15
15
|
|
|
16
|
-
inlines =
|
|
16
|
+
inlines = ()
|
|
17
17
|
|
|
18
18
|
populate_data_dictionary = False
|
|
19
19
|
|
|
@@ -30,7 +30,7 @@ class UnblindingReviewerUserAdmin(BaseUserAdmin):
|
|
|
30
30
|
ordering = ("first_name", "last_name")
|
|
31
31
|
search_fields = ("first_name", "last_name", "username", "email")
|
|
32
32
|
|
|
33
|
-
inlines =
|
|
33
|
+
inlines = ()
|
|
34
34
|
|
|
35
35
|
populate_data_dictionary = False
|
|
36
36
|
|
|
@@ -37,11 +37,11 @@ class UnblindingRequestAdmin(ModelAdminSubjectDashboardMixin, SimpleHistoryAdmin
|
|
|
37
37
|
audit_fieldset_tuple,
|
|
38
38
|
)
|
|
39
39
|
|
|
40
|
-
autocomplete_fields =
|
|
40
|
+
autocomplete_fields = ("requestor",)
|
|
41
41
|
|
|
42
42
|
readonly_fields = ("approved", "approved_datetime")
|
|
43
43
|
|
|
44
|
-
radio_fields = {"approved": admin.VERTICAL}
|
|
44
|
+
radio_fields = {"approved": admin.VERTICAL} # noqa: RUF012
|
|
45
45
|
|
|
46
46
|
list_display = (
|
|
47
47
|
"subject_identifier",
|
|
@@ -21,7 +21,7 @@ class UnblindingReviewAdmin(ModelAdminSubjectDashboardMixin, SimpleHistoryAdmin)
|
|
|
21
21
|
audit_fieldset_tuple,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
-
autocomplete_fields =
|
|
24
|
+
autocomplete_fields = ("reviewer",)
|
|
25
25
|
|
|
26
26
|
list_display = (
|
|
27
27
|
"subject_identifier",
|
|
@@ -33,4 +33,4 @@ class UnblindingReviewAdmin(ModelAdminSubjectDashboardMixin, SimpleHistoryAdmin)
|
|
|
33
33
|
"created",
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
-
radio_fields = {"approved": admin.VERTICAL}
|
|
36
|
+
radio_fields = {"approved": admin.VERTICAL} # noqa: RUF012
|
edc_unblinding/auths.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
|
|
3
|
+
from edc_auth.site_auths import GroupAlreadyExists, RoleAlreadyExists, site_auths
|
|
2
4
|
|
|
3
5
|
from .auth_objects import (
|
|
4
6
|
UNBLINDING_REQUESTORS,
|
|
@@ -11,10 +13,14 @@ from .auth_objects import (
|
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
def update_site_auths() -> None:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
with suppress(GroupAlreadyExists):
|
|
17
|
+
site_auths.add_group(*unblinding_requestors, name=UNBLINDING_REQUESTORS)
|
|
18
|
+
with suppress(GroupAlreadyExists):
|
|
19
|
+
site_auths.add_group(*unblinding_reviewers, name=UNBLINDING_REVIEWERS)
|
|
20
|
+
with suppress(RoleAlreadyExists):
|
|
21
|
+
site_auths.add_role(UNBLINDING_REQUESTORS, name=UNBLINDING_REQUESTORS_ROLE)
|
|
22
|
+
with suppress(RoleAlreadyExists):
|
|
23
|
+
site_auths.add_role(UNBLINDING_REVIEWERS, name=UNBLINDING_REVIEWERS_ROLE)
|
|
18
24
|
|
|
19
25
|
|
|
20
26
|
update_site_auths()
|
|
@@ -3,7 +3,7 @@ from django.utils import timezone
|
|
|
3
3
|
|
|
4
4
|
from edc_action_item.models.action_model_mixin import ActionModelMixin
|
|
5
5
|
from edc_constants.choices import YES_NO_TBD
|
|
6
|
-
from edc_constants.constants import TBD
|
|
6
|
+
from edc_constants.constants import NULL_STRING, TBD
|
|
7
7
|
from edc_identifier.managers import SubjectIdentifierManager
|
|
8
8
|
from edc_identifier.model_mixins import NonUniqueSubjectIdentifierFieldMixin
|
|
9
9
|
from edc_model.models.base_uuid_model import BaseUuidModel
|
|
@@ -36,7 +36,7 @@ class UnblindingReview(
|
|
|
36
36
|
|
|
37
37
|
approved = models.CharField(max_length=15, default=TBD, choices=YES_NO_TBD)
|
|
38
38
|
|
|
39
|
-
comment = models.TextField(verbose_name="Comment", default=
|
|
39
|
+
comment = models.TextField(verbose_name="Comment", default=NULL_STRING)
|
|
40
40
|
|
|
41
41
|
objects = SubjectIdentifierManager()
|
|
42
42
|
|
edc_utils/age.py
CHANGED
|
@@ -17,6 +17,10 @@ class AgeFormatError(Exception):
|
|
|
17
17
|
pass
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
TWO_MONTHS = 2
|
|
21
|
+
TWELVE_MONTHS = 12
|
|
22
|
+
|
|
23
|
+
|
|
20
24
|
def get_dob(age_in_years: int, now: date | datetime | None = None) -> date:
|
|
21
25
|
"""Returns a DoB for the given age relative to now.
|
|
22
26
|
|
|
@@ -65,12 +69,12 @@ def formatted_age(
|
|
|
65
69
|
age_delta = age(born, reference_dt or timezone.now())
|
|
66
70
|
if age_delta.years == 0 and age_delta.months <= 0:
|
|
67
71
|
age_as_str = f"{age_delta.days}d"
|
|
68
|
-
elif age_delta.years == 0 and 0 < age_delta.months <=
|
|
72
|
+
elif age_delta.years == 0 and 0 < age_delta.months <= TWO_MONTHS:
|
|
69
73
|
age_as_str = f"{age_delta.months}m{age_delta.days}d"
|
|
70
|
-
elif age_delta.years == 0 and age_delta.months >
|
|
74
|
+
elif age_delta.years == 0 and age_delta.months > TWO_MONTHS:
|
|
71
75
|
age_as_str = f"{age_delta.months}m"
|
|
72
76
|
elif age_delta.years == 1:
|
|
73
|
-
m = age_delta.months +
|
|
77
|
+
m = age_delta.months + TWELVE_MONTHS
|
|
74
78
|
age_as_str = f"{m}m"
|
|
75
79
|
else:
|
|
76
80
|
age_as_str = f"{age_delta.years}y"
|
edc_utils/celery.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
|
|
1
3
|
from celery import current_app
|
|
2
4
|
from celery.result import AsyncResult
|
|
3
5
|
from django.conf import settings
|
|
@@ -33,10 +35,8 @@ def get_task_result(obj) -> AsyncResult | None:
|
|
|
33
35
|
"""
|
|
34
36
|
result = None
|
|
35
37
|
if obj.task_id:
|
|
36
|
-
|
|
38
|
+
with contextlib.suppress(TypeError, ValueError):
|
|
37
39
|
result = AsyncResult(str(obj.task_id))
|
|
38
|
-
except (TypeError, ValueError):
|
|
39
|
-
pass
|
|
40
40
|
return result
|
|
41
41
|
|
|
42
42
|
|
|
@@ -10,7 +10,7 @@ def edc_context_processors_check(
|
|
|
10
10
|
if context_processor_name not in template_config.get("OPTIONS").get(
|
|
11
11
|
"context_processors"
|
|
12
12
|
):
|
|
13
|
-
errors.append(
|
|
13
|
+
errors.append( # noqa: PERF401
|
|
14
14
|
Error(
|
|
15
15
|
"Missing item in TEMPLATE.OPTIONS.context_processors. "
|
|
16
16
|
f"Expected `{context_processor_name}`.",
|
edc_utils/get_static_file.py
CHANGED
|
@@ -1,25 +1,35 @@
|
|
|
1
|
-
import
|
|
1
|
+
from pathlib import Path
|
|
2
2
|
from urllib.error import URLError
|
|
3
|
+
from urllib.parse import urljoin
|
|
3
4
|
from urllib.request import urlretrieve
|
|
4
5
|
|
|
5
6
|
from django.conf import settings
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def get_static_file(app_label: str, filename: str) -> str:
|
|
9
|
-
path =
|
|
10
|
-
|
|
10
|
+
path = Path(settings.STATIC_ROOT or "") / app_label / filename
|
|
11
|
+
# path as os path
|
|
12
|
+
if path.is_file():
|
|
11
13
|
try:
|
|
12
|
-
with open(
|
|
14
|
+
with path.open("r"):
|
|
13
15
|
pass
|
|
14
16
|
except FileNotFoundError:
|
|
15
|
-
path =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
path = None
|
|
18
|
+
else:
|
|
19
|
+
path = None
|
|
20
|
+
|
|
21
|
+
# path as a url
|
|
22
|
+
if not path:
|
|
23
|
+
path = urljoin(f"https://{settings.STATIC_URL}", app_label, filename)
|
|
24
|
+
try:
|
|
25
|
+
urlretrieve(path) # noqa: S310
|
|
26
|
+
except URLError:
|
|
27
|
+
path = None
|
|
28
|
+
if not path:
|
|
29
|
+
raise FileNotFoundError(
|
|
30
|
+
f"Static file not found. Tried "
|
|
31
|
+
f"STATIC_ROOT ({settings.STATIC_ROOT}) and "
|
|
32
|
+
f"STATIC_URL ({settings.STATIC_URL}). "
|
|
33
|
+
f"Got {app_label}/{filename}."
|
|
34
|
+
)
|
|
25
35
|
return path
|
|
@@ -7,7 +7,7 @@ class IgnoreSpecificIPDisallowedHost:
|
|
|
7
7
|
|
|
8
8
|
def filter(self, record):
|
|
9
9
|
if record.exc_info:
|
|
10
|
-
|
|
10
|
+
_, exc_value, _ = record.exc_info
|
|
11
11
|
if isinstance(exc_value, DisallowedHost):
|
|
12
12
|
request = getattr(record, "request", None)
|
|
13
13
|
if request and request.META.get("REMOTE_ADDR") == self.ip_to_ignore:
|
edc_utils/message_in_queue.py
CHANGED
|
@@ -3,7 +3,4 @@ from django.contrib.messages import get_messages
|
|
|
3
3
|
|
|
4
4
|
def message_in_queue(request, message_text):
|
|
5
5
|
storage = get_messages(request)
|
|
6
|
-
for message in storage
|
|
7
|
-
if message.message == message_text:
|
|
8
|
-
return True
|
|
9
|
-
return False
|
|
6
|
+
return any(message.message == message_text for message in storage)
|
edc_utils/show_urls.py
CHANGED
|
@@ -110,5 +110,5 @@ def extract_views_from_urlpatterns(urlpatterns, base="", namespace=None): # noq
|
|
|
110
110
|
)
|
|
111
111
|
)
|
|
112
112
|
else:
|
|
113
|
-
raise TypeError("
|
|
113
|
+
raise TypeError(f"{p} does not appear to be a urlpattern object")
|
|
114
114
|
return views
|
edc_utils/text.py
CHANGED
|
@@ -15,7 +15,7 @@ def get_safe_random_string(length=12, safe=None, allowed_chars=None):
|
|
|
15
15
|
)
|
|
16
16
|
if safe:
|
|
17
17
|
allowed_chars = "ABCDEFGHKMNPRTUVWXYZ2346789"
|
|
18
|
-
return "".join([random.choice(allowed_chars) for _ in range(length)]) # nosec B311
|
|
18
|
+
return "".join([random.choice(allowed_chars) for _ in range(length)]) # nosec B311 # noqa: S311
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def convert_php_dateformat(php_format_string):
|
edc_view_utils/model_button.py
CHANGED
|
@@ -85,9 +85,8 @@ class ModelButton:
|
|
|
85
85
|
self._action = VIEW
|
|
86
86
|
if not self.model_obj:
|
|
87
87
|
self._action = ADD
|
|
88
|
-
elif self.model_obj:
|
|
89
|
-
|
|
90
|
-
self._action = CHANGE
|
|
88
|
+
elif self.model_obj and self.perms.change:
|
|
89
|
+
self._action = CHANGE
|
|
91
90
|
return self._action
|
|
92
91
|
|
|
93
92
|
@property
|
edc_visit_schedule/exceptions.py
CHANGED
|
@@ -34,7 +34,7 @@ class UnknownSubjectError(Exception):
|
|
|
34
34
|
pass
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
class InvalidOffscheduleDate(Exception):
|
|
37
|
+
class InvalidOffscheduleDate(Exception): # noqa: N818
|
|
38
38
|
pass
|
|
39
39
|
|
|
40
40
|
|
|
@@ -54,11 +54,11 @@ class SiteVisitScheduleError(Exception):
|
|
|
54
54
|
pass
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
class RegistryNotLoaded(Exception):
|
|
57
|
+
class RegistryNotLoaded(Exception): # noqa: N818
|
|
58
58
|
pass
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
class AlreadyRegisteredVisitSchedule(Exception):
|
|
61
|
+
class AlreadyRegisteredVisitSchedule(Exception): # noqa: N818
|
|
62
62
|
pass
|
|
63
63
|
|
|
64
64
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
1
3
|
from django.apps import apps as django_apps
|
|
2
4
|
from django.core.exceptions import ObjectDoesNotExist
|
|
3
5
|
from django.core.management.base import BaseCommand
|
|
@@ -15,12 +17,12 @@ class Command(BaseCommand):
|
|
|
15
17
|
help="Delete invalid OnSchedule model instances",
|
|
16
18
|
)
|
|
17
19
|
|
|
18
|
-
def handle(self, *args, **options):
|
|
20
|
+
def handle(self, *args, **options): # noqa: ARG002
|
|
19
21
|
allow_delete = False
|
|
20
22
|
if options["delete"]:
|
|
21
23
|
allow_delete = True
|
|
22
24
|
else:
|
|
23
|
-
|
|
25
|
+
sys.stdout.write("Checking only\n")
|
|
24
26
|
subject_schedule_history_cls = django_apps.get_model(
|
|
25
27
|
"edc_visit_schedule.subjectschedulehistory"
|
|
26
28
|
)
|
|
@@ -43,6 +45,6 @@ class Command(BaseCommand):
|
|
|
43
45
|
f"{onschedule_obj.subject_identifier} is invalid."
|
|
44
46
|
)
|
|
45
47
|
if allow_delete:
|
|
46
|
-
msg = f"{msg} deleted
|
|
48
|
+
msg = f"{msg} deleted.\n"
|
|
47
49
|
onschedule_obj.delete()
|
|
48
|
-
|
|
50
|
+
sys.stdout.write(msg)
|
|
@@ -58,7 +58,7 @@ class VisitScheduleCrfModelFormMixin:
|
|
|
58
58
|
),
|
|
59
59
|
)
|
|
60
60
|
except (NotOnScheduleError, NotOnScheduleForDateError) as e:
|
|
61
|
-
raise forms.ValidationError(str(e))
|
|
61
|
+
raise forms.ValidationError(str(e)) from e
|
|
62
62
|
|
|
63
63
|
def report_datetime_within_schedule_datetimes(self) -> None:
|
|
64
64
|
if self.report_datetime:
|
|
@@ -28,7 +28,7 @@ class OffScheduleModelFormMixin(VisitScheduleNonCrfModelFormMixin):
|
|
|
28
28
|
update=False,
|
|
29
29
|
)
|
|
30
30
|
except InvalidOffscheduleDate as e:
|
|
31
|
-
raise forms.ValidationError(e)
|
|
31
|
+
raise forms.ValidationError(e) from e
|
|
32
32
|
self.validate_visit_tracking_reports()
|
|
33
33
|
return cleaned_data
|
|
34
34
|
|
|
@@ -50,11 +50,11 @@ class OffScheduleModelFormMixin(VisitScheduleNonCrfModelFormMixin):
|
|
|
50
50
|
return True
|
|
51
51
|
|
|
52
52
|
class Meta:
|
|
53
|
-
help_text = {
|
|
53
|
+
help_text = { # noqa: RUF012
|
|
54
54
|
"subject_identifier": "(read-only)",
|
|
55
55
|
"action_identifier": "(read-only)",
|
|
56
56
|
}
|
|
57
|
-
widgets = {
|
|
57
|
+
widgets = { # noqa: RUF012
|
|
58
58
|
"subject_identifier": forms.TextInput(attrs={"readonly": "readonly"}),
|
|
59
59
|
"action_identifier": forms.TextInput(attrs={"readonly": "readonly"}),
|
|
60
60
|
}
|
|
@@ -76,7 +76,7 @@ class VisitScheduleNonCrfModelFormMixin:
|
|
|
76
76
|
compare_as_datetimes=self.offschedule_compare_dates_as_datetimes,
|
|
77
77
|
)
|
|
78
78
|
except (NotOnScheduleError, NotOnScheduleForDateError) as e:
|
|
79
|
-
raise forms.ValidationError(str(e))
|
|
79
|
+
raise forms.ValidationError(str(e)) from e
|
|
80
80
|
|
|
81
81
|
def report_datetime_within_schedule_datetimes(self) -> None:
|
|
82
82
|
if self.report_datetime:
|
|
@@ -8,14 +8,9 @@ from ..site_visit_schedules import SiteVisitScheduleError, site_visit_schedules
|
|
|
8
8
|
|
|
9
9
|
@receiver(post_save, weak=False, dispatch_uid="offschedule_model_on_post_save")
|
|
10
10
|
def offschedule_model_on_post_save(sender, instance, raw, update_fields, **kwargs):
|
|
11
|
-
if not raw and not update_fields:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
instance._meta.label_lower
|
|
15
|
-
)
|
|
16
|
-
schedule.take_off_schedule(
|
|
17
|
-
instance.subject_identifier, instance.offschedule_datetime
|
|
18
|
-
)
|
|
11
|
+
if not raw and not update_fields and isinstance(instance, (OffScheduleModelMixin,)):
|
|
12
|
+
_, schedule = site_visit_schedules.get_by_offschedule_model(instance._meta.label_lower)
|
|
13
|
+
schedule.take_off_schedule(instance.subject_identifier, instance.offschedule_datetime)
|
|
19
14
|
|
|
20
15
|
|
|
21
16
|
@receiver(post_delete, weak=False, dispatch_uid="offschedule_model_on_post_delete")
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import itertools
|
|
2
|
-
from collections import OrderedDict
|
|
3
2
|
|
|
4
3
|
|
|
5
|
-
class OrderedCollection(
|
|
4
|
+
class OrderedCollection(dict):
|
|
6
5
|
key: str = None # key name in dictionary key/value pair
|
|
7
6
|
ordering_attr: str = None # value.attrname to order dictionary on.
|
|
8
7
|
|
|
9
|
-
def update(self,
|
|
8
|
+
def update(self, **kwargs) -> None:
|
|
10
9
|
"""Updates and reorders."""
|
|
11
10
|
|
|
12
11
|
def key_order(v):
|
|
13
12
|
return getattr(v, self.ordering_attr)
|
|
14
13
|
|
|
15
|
-
super().update(
|
|
14
|
+
super().update(**kwargs)
|
|
16
15
|
od = self.copy()
|
|
17
16
|
self.clear()
|
|
18
17
|
super().update(**{getattr(v, self.key): v for v in sorted(od.values(), key=key_order)})
|
|
@@ -40,10 +39,7 @@ class OrderedCollection(OrderedDict):
|
|
|
40
39
|
return self.get(self._iter_keys(key=key))
|
|
41
40
|
|
|
42
41
|
def _iter_keys(self, key=None, reverse=None):
|
|
43
|
-
if reverse
|
|
44
|
-
seq = reversed(self.keys())
|
|
45
|
-
else:
|
|
46
|
-
seq = iter(self.keys())
|
|
42
|
+
seq = reversed(self.keys()) if reverse else iter(self.keys())
|
|
47
43
|
keys = itertools.dropwhile(lambda x: x != key, seq)
|
|
48
44
|
try:
|
|
49
45
|
k = next(keys)
|
|
@@ -7,8 +7,8 @@ style = color_style()
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def populate_visit_schedule(sender=None, **kwargs):
|
|
10
|
-
from .models import VisitSchedule
|
|
11
|
-
from .site_visit_schedules import site_visit_schedules
|
|
10
|
+
from .models import VisitSchedule # noqa: PLC0415
|
|
11
|
+
from .site_visit_schedules import site_visit_schedules # noqa: PLC0415
|
|
12
12
|
|
|
13
13
|
sys.stdout.write(style.MIGRATE_HEADING("Populating visit schedule:\n"))
|
|
14
14
|
if getattr(settings, "EDC_VISIT_SCHEDULE_POPULATE_VISIT_SCHEDULE", True):
|