clinicedc 2.0.28__py3-none-any.whl → 2.0.29__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.28.dist-info → clinicedc-2.0.29.dist-info}/METADATA +2 -2
- {clinicedc-2.0.28.dist-info → clinicedc-2.0.29.dist-info}/RECORD +95 -95
- edc_auth/admin/fieldsets.py +11 -15
- edc_auth/admin/group_admin.py +2 -5
- edc_auth/admin/list_filters.py +8 -7
- edc_auth/admin/role_admin.py +7 -10
- edc_auth/admin/user_admin.py +2 -2
- edc_auth/admin/user_profile_admin.py +3 -7
- edc_auth/apps.py +2 -2
- edc_auth/auth_updater/group_updater.py +4 -4
- edc_auth/auth_updater/role_updater.py +1 -3
- edc_auth/backends.py +1 -1
- edc_auth/export_users.py +6 -5
- edc_auth/fix_export_permissions.py +8 -8
- edc_auth/forms.py +21 -24
- edc_auth/get_app_codenames.py +2 -2
- edc_auth/import_users.py +25 -24
- edc_auth/management/commands/export_users.py +1 -1
- edc_auth/management/commands/fix_export_permissions.py +1 -1
- edc_auth/management/commands/import_users.py +1 -1
- edc_auth/management/commands/reset_password.py +2 -2
- edc_auth/models/signals.py +1 -1
- edc_auth/models/user_profile.py +4 -5
- edc_auth/password_setter.py +4 -4
- edc_auth/post_migrate_signals.py +2 -2
- edc_auth/send_new_credentials_to_user.py +1 -1
- edc_auth/system_checks.py +7 -6
- edc_auth/utils.py +9 -11
- edc_consent/consent_definition.py +4 -1
- edc_model_admin/mixins/model_admin_form_instructions_mixin.py +9 -3
- edc_model_admin/mixins/model_admin_protect_pii_mixin.py +7 -7
- edc_pharmacy/admin_mixin.py +1 -1
- edc_pharmacy/approve_prescription.py +10 -8
- edc_pharmacy/auth_objects.py +1 -1
- edc_pharmacy/exceptions.py +7 -7
- edc_pharmacy/settings.py +1 -1
- edc_pharmacy/views/add_to_storage_bin_view.py +22 -24
- edc_pharmacy/views/allocate_to_subject_view.py +10 -18
- edc_pharmacy/views/celery_task_status_view.py +1 -2
- edc_pharmacy/views/confirm_stock_from_instance_view.py +1 -1
- edc_pharmacy/views/confirm_stock_from_queryset_view.py +5 -8
- edc_pharmacy/views/dispense_view.py +1 -1
- edc_pharmacy/views/move_to_storage_bin_view.py +7 -3
- edc_pharmacy/views/prepare_and_review_stock_request_view.py +62 -70
- edc_pharmacy/views/print_labels_view.py +1 -1
- edc_pharmacy/views/transfer_stock_view.py +1 -1
- edc_prn/modelform_mixins.py +8 -9
- edc_prn/models.py +3 -1
- edc_prn/site_prn_forms.py +2 -2
- edc_prn/templatetags/edc_prn_extras.py +1 -1
- edc_protocol/middleware.py +1 -1
- edc_protocol/research_protocol_config.py +7 -5
- edc_protocol_incident/admin/protocol_deviation_violation_admin.py +1 -1
- edc_protocol_incident/form_validators/mixins.py +9 -6
- edc_protocol_incident/modeladmin_mixins.py +1 -1
- edc_pylabels/admin/label_configuration_admin.py +1 -1
- edc_pylabels/admin/label_specification_admin.py +1 -1
- edc_pylabels/auth_objects.py +1 -1
- edc_pylabels/site_label_configs.py +2 -2
- edc_qareports/admin/qa_report_log_admin.py +5 -5
- edc_qareports/admin/qa_report_log_summary_admin.py +1 -1
- edc_qareports/forms/note_form.py +2 -3
- edc_qareports/modeladmin_mixins/list_filters.py +23 -19
- edc_qareports/modeladmin_mixins/note_modeladmin_mixin.py +7 -7
- edc_qareports/modeladmin_mixins/on_study_missing_values_modeladmin_mixin.py +8 -8
- edc_qareports/modeladmin_mixins/qa_report_modeladmin_mixin.py +3 -7
- edc_qareports/sql_generator/crf_case.py +1 -1
- edc_qareports/sql_generator/crf_subquery.py +1 -1
- edc_qareports/sql_generator/requisition_subquery.py +1 -1
- edc_qareports/sql_generator/sql_view_generator.py +1 -1
- edc_qareports/sql_generator/subquery_from_dict.py +39 -39
- edc_qareports/utils.py +12 -13
- edc_randomization/model_mixins.py +1 -1
- edc_randomization/system_checks.py +1 -1
- edc_refusal/admin.py +4 -2
- edc_registration/modeladmin_mixins.py +18 -20
- edc_registration/modelform_mixins.py +2 -2
- edc_registration/models/signals.py +1 -1
- edc_registration/utils.py +1 -1
- edc_reportable/age_evaluator.py +4 -4
- edc_reportable/data/grading_data/daids_july_2017.py +3 -3
- edc_reportable/evaluator.py +15 -15
- edc_reportable/exceptions.py +4 -4
- edc_reportable/forms/reportables_form_validator_mixin.py +6 -2
- edc_reportable/management/commands/export_reportables.py +1 -1
- edc_reportable/models/grading_exception.py +1 -6
- edc_reportable/models/normal_data.py +3 -3
- edc_reportable/models/reference_range_collection.py +1 -6
- edc_reportable/utils/get_grade_for_value.py +15 -15
- edc_reportable/utils/get_normal_data_or_raise.py +14 -14
- edc_reportable/utils/in_normal_bounds_or_raise.py +6 -6
- edc_reportable/utils/load_data.py +1 -1
- edc_reportable/utils/update_grading_exceptions.py +5 -5
- {clinicedc-2.0.28.dist-info → clinicedc-2.0.29.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.28.dist-info → clinicedc-2.0.29.dist-info}/licenses/LICENSE +0 -0
|
@@ -54,7 +54,7 @@ class TransferStockView(EdcViewMixin, NavbarViewMixin, EdcProtocolViewMixin, Tem
|
|
|
54
54
|
def model_cls(self):
|
|
55
55
|
return django_apps.get_model("edc_pharmacy.stocktransfer")
|
|
56
56
|
|
|
57
|
-
def post(self, request, *args, **kwargs):
|
|
57
|
+
def post(self, request, *args, **kwargs): # noqa: ARG002
|
|
58
58
|
stock_transfer = StockTransfer.objects.get(pk=self.kwargs.get("stock_transfer"))
|
|
59
59
|
stock_codes = request.POST.getlist("codes")
|
|
60
60
|
if stock_codes:
|
edc_prn/modelform_mixins.py
CHANGED
|
@@ -8,7 +8,6 @@ from django.utils.text import format_lazy
|
|
|
8
8
|
from django.utils.translation import gettext_lazy as _
|
|
9
9
|
|
|
10
10
|
from edc_consent import site_consents
|
|
11
|
-
from edc_consent.consent_definition import ConsentDefinition
|
|
12
11
|
from edc_crf.crf_form_validator_mixins import BaseFormValidatorMixin
|
|
13
12
|
|
|
14
13
|
|
|
@@ -19,17 +18,17 @@ class PrnFormValidatorMixin(BaseFormValidatorMixin):
|
|
|
19
18
|
|
|
20
19
|
@property
|
|
21
20
|
def subject_consent(self):
|
|
22
|
-
return
|
|
21
|
+
return site_consents.get_consent_definition(
|
|
23
22
|
report_datetime=self.report_datetime
|
|
24
23
|
).model_cls.objects.get(subject_identifier=self.subject_identifier)
|
|
25
24
|
|
|
26
|
-
def get_consent_definition(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
) -> ConsentDefinition:
|
|
32
|
-
|
|
25
|
+
# def get_consent_definition(
|
|
26
|
+
# self,
|
|
27
|
+
# report_datetime: datetime | None = None,
|
|
28
|
+
# fldname: str | None = None,
|
|
29
|
+
# error_code: str | None = None,
|
|
30
|
+
# ) -> ConsentDefinition:
|
|
31
|
+
# return site_consents.get_consent_definition(report_datetime=self.report_datetime)
|
|
33
32
|
|
|
34
33
|
@property
|
|
35
34
|
def report_datetime(self) -> datetime:
|
edc_prn/models.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from django.db import models
|
|
4
4
|
|
|
5
|
+
from edc_constants.constants import NULL_STRING
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class SingletonPrnModelMixin(models.Model):
|
|
7
9
|
"""Enforces one record per subject."""
|
|
@@ -11,7 +13,7 @@ class SingletonPrnModelMixin(models.Model):
|
|
|
11
13
|
max_length=50,
|
|
12
14
|
unique=True,
|
|
13
15
|
help_text="auto updated for unique constraint",
|
|
14
|
-
|
|
16
|
+
default=NULL_STRING,
|
|
15
17
|
editable=False,
|
|
16
18
|
)
|
|
17
19
|
|
edc_prn/site_prn_forms.py
CHANGED
|
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
|
|
|
14
14
|
from edc_prn import Prn
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class AlreadyRegistered(Exception):
|
|
17
|
+
class AlreadyRegistered(Exception): # noqa: N818
|
|
18
18
|
pass
|
|
19
19
|
|
|
20
20
|
|
|
@@ -72,7 +72,7 @@ class PrnFormsCollection:
|
|
|
72
72
|
except ImportError as e:
|
|
73
73
|
site_prn_forms.registry = before_import_registry
|
|
74
74
|
if module_has_submodule(mod, module_name):
|
|
75
|
-
raise SitePrnFormsError(str(e))
|
|
75
|
+
raise SitePrnFormsError(str(e)) from e
|
|
76
76
|
except ImportError:
|
|
77
77
|
pass
|
|
78
78
|
|
|
@@ -23,7 +23,7 @@ def prn_list_items(subject_identifier, **kwargs):
|
|
|
23
23
|
prn_forms = []
|
|
24
24
|
for prn in site_prn_forms:
|
|
25
25
|
if prn.get_show_on_dashboard(subject_identifier=subject_identifier):
|
|
26
|
-
prn_forms.append(prn)
|
|
26
|
+
prn_forms.append(prn) # noqa: PERF401
|
|
27
27
|
if prn_forms:
|
|
28
28
|
prn_forms = sorted(prn_forms, key=lambda x: x.verbose_name)
|
|
29
29
|
return dict(prn_forms=prn_forms, subject_identifier=subject_identifier)
|
edc_protocol/middleware.py
CHANGED
|
@@ -11,7 +11,7 @@ class ResearchProtocolConfigMiddleware:
|
|
|
11
11
|
def process_view(self, request, *args):
|
|
12
12
|
pass
|
|
13
13
|
|
|
14
|
-
def process_template_response(self, request, response):
|
|
14
|
+
def process_template_response(self, request, response): # noqa: ARG002
|
|
15
15
|
if not response.context_data:
|
|
16
16
|
response.context_data = {}
|
|
17
17
|
protocol_config = ResearchProtocolConfig()
|
|
@@ -134,7 +134,9 @@ class ResearchProtocolConfig:
|
|
|
134
134
|
return getattr(
|
|
135
135
|
settings,
|
|
136
136
|
"EDC_PROTOCOL_SUBJECT_IDENTIFIER_PATTERN",
|
|
137
|
-
r"
|
|
137
|
+
r"{protocol_number}\-[0-9\-]+".format(
|
|
138
|
+
**dict(protocol_number=self.protocol_number)
|
|
139
|
+
),
|
|
138
140
|
)
|
|
139
141
|
|
|
140
142
|
@property
|
|
@@ -145,14 +147,14 @@ class ResearchProtocolConfig:
|
|
|
145
147
|
def study_open_datetime(self) -> datetime:
|
|
146
148
|
try:
|
|
147
149
|
study_open_datetime = settings.EDC_PROTOCOL_STUDY_OPEN_DATETIME
|
|
148
|
-
except AttributeError:
|
|
150
|
+
except AttributeError as e:
|
|
149
151
|
raise ImproperlyConfigured(
|
|
150
152
|
self.error_msg1
|
|
151
153
|
% {
|
|
152
154
|
"attr": "study_open_datetime",
|
|
153
155
|
"settings_attr": "EDC_PROTOCOL_STUDY_OPEN_DATETIME",
|
|
154
156
|
}
|
|
155
|
-
)
|
|
157
|
+
) from e
|
|
156
158
|
if not study_open_datetime:
|
|
157
159
|
raise ImproperlyConfigured(
|
|
158
160
|
self.error_msg2
|
|
@@ -167,14 +169,14 @@ class ResearchProtocolConfig:
|
|
|
167
169
|
def study_close_datetime(self) -> datetime:
|
|
168
170
|
try:
|
|
169
171
|
study_close_datetime = settings.EDC_PROTOCOL_STUDY_CLOSE_DATETIME
|
|
170
|
-
except AttributeError:
|
|
172
|
+
except AttributeError as e:
|
|
171
173
|
raise ImproperlyConfigured(
|
|
172
174
|
self.error_msg1
|
|
173
175
|
% {
|
|
174
176
|
"attr": "study_close_datetime",
|
|
175
177
|
"settings_attr": "EDC_PROTOCOL_STUDY_CLOSE_DATETIME",
|
|
176
178
|
}
|
|
177
|
-
)
|
|
179
|
+
) from e
|
|
178
180
|
if not study_close_datetime:
|
|
179
181
|
raise ImproperlyConfigured(
|
|
180
182
|
self.error_msg2
|
|
@@ -65,7 +65,7 @@ class ProtocolDeviationViolationAdmin(ModelAdminSubjectDashboardMixin, SimpleHis
|
|
|
65
65
|
audit_fieldset_tuple,
|
|
66
66
|
)
|
|
67
67
|
|
|
68
|
-
radio_fields = {
|
|
68
|
+
radio_fields = { # noqa: RUF012
|
|
69
69
|
"action_required": admin.VERTICAL,
|
|
70
70
|
"report_status": admin.VERTICAL,
|
|
71
71
|
"report_type": admin.VERTICAL,
|
|
@@ -40,9 +40,12 @@ class IncidentFormvalidatorMixin:
|
|
|
40
40
|
self.validate_date_not_before_incident("report_closed_datetime")
|
|
41
41
|
|
|
42
42
|
def validate_date_not_before_incident(self, fld_name):
|
|
43
|
-
if
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
if (
|
|
44
|
+
self.cleaned_data.get(fld_name)
|
|
45
|
+
and self.cleaned_data.get("incident_datetime")
|
|
46
|
+
and self.cleaned_data.get(fld_name) < self.cleaned_data.get("incident_datetime")
|
|
47
|
+
):
|
|
48
|
+
self.raise_validation_error(
|
|
49
|
+
{fld_name: "May not be before incident date/time"},
|
|
50
|
+
error_code=INVALID_ERROR,
|
|
51
|
+
)
|
|
@@ -26,7 +26,7 @@ class LabelSpecificationAdmin(
|
|
|
26
26
|
ModelAdminRedirectOnDeleteMixin,
|
|
27
27
|
admin.ModelAdmin,
|
|
28
28
|
):
|
|
29
|
-
actions =
|
|
29
|
+
actions = (copy_label_specification, export_to_csv)
|
|
30
30
|
|
|
31
31
|
instructions = (
|
|
32
32
|
"This model captures the dimensions, rows, columns, "
|
edc_pylabels/auth_objects.py
CHANGED
|
@@ -11,5 +11,5 @@ for app_config in django_apps.get_app_configs():
|
|
|
11
11
|
for model_cls in app_config.get_models():
|
|
12
12
|
app_name, model_name = model_cls._meta.label_lower.split(".")
|
|
13
13
|
for prefix in ["add", "change", "view", "delete"]:
|
|
14
|
-
codenames.append(f"{app_name}.{prefix}_{model_name}")
|
|
14
|
+
codenames.append(f"{app_name}.{prefix}_{model_name}") # noqa: PERF401
|
|
15
15
|
codenames.sort()
|
|
@@ -11,7 +11,7 @@ from django.core.management.color import color_style
|
|
|
11
11
|
from django.utils.module_loading import module_has_submodule
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class AlreadyRegistered(Exception):
|
|
14
|
+
class AlreadyRegistered(Exception): # noqa: N818
|
|
15
15
|
pass
|
|
16
16
|
|
|
17
17
|
|
|
@@ -104,7 +104,7 @@ class SiteLabelConfigs:
|
|
|
104
104
|
except ImportError as e:
|
|
105
105
|
site_label_configs.registry = before_import_registry
|
|
106
106
|
if module_has_submodule(mod, module_name):
|
|
107
|
-
raise SitePharmacyError(str(e))
|
|
107
|
+
raise SitePharmacyError(str(e)) from e
|
|
108
108
|
except ImportError:
|
|
109
109
|
pass
|
|
110
110
|
|
|
@@ -17,15 +17,15 @@ class QaReportLogAdmin(
|
|
|
17
17
|
TemplatesModelAdminMixin,
|
|
18
18
|
admin.ModelAdmin,
|
|
19
19
|
):
|
|
20
|
-
ordering =
|
|
20
|
+
ordering = ("-accessed",)
|
|
21
21
|
|
|
22
|
-
list_display =
|
|
22
|
+
list_display = ("report", "username", "site", "accessed", "report_model")
|
|
23
23
|
|
|
24
|
-
list_filter =
|
|
24
|
+
list_filter = ("accessed", "report_model", "username", "site")
|
|
25
25
|
|
|
26
|
-
search_fields =
|
|
26
|
+
search_fields = ("report_model", "username")
|
|
27
27
|
|
|
28
|
-
readonly_fields =
|
|
28
|
+
readonly_fields = ("report_model", "username", "site", "accessed")
|
|
29
29
|
|
|
30
30
|
@admin.display(description="Report", ordering="report_model")
|
|
31
31
|
def report(self, obj=None):
|
edc_qareports/forms/note_form.py
CHANGED
|
@@ -18,15 +18,14 @@ class NoteForm(
|
|
|
18
18
|
FormValidatorMixin,
|
|
19
19
|
forms.ModelForm,
|
|
20
20
|
):
|
|
21
|
-
|
|
22
21
|
report_datetime_field_attr = "report_datetime"
|
|
23
22
|
form_validator_cls = NoteFormValidator
|
|
24
23
|
|
|
25
24
|
class Meta:
|
|
26
25
|
model = Note
|
|
27
26
|
fields = "__all__"
|
|
28
|
-
help_text = {"subject_identifier": "(read-only)", "name": "(read-only)"}
|
|
29
|
-
widgets = {
|
|
27
|
+
help_text = {"subject_identifier": "(read-only)", "name": "(read-only)"} # noqa: RUF012
|
|
28
|
+
widgets = { # noqa: RUF012
|
|
30
29
|
"report_model": forms.TextInput(attrs={"readonly": "readonly"}),
|
|
31
30
|
"subject_identifier": forms.TextInput(attrs={"readonly": "readonly"}),
|
|
32
31
|
"name": forms.TextInput(attrs={"readonly": "readonly"}),
|
|
@@ -17,7 +17,7 @@ class NoteStatusListFilter(SimpleListFilter):
|
|
|
17
17
|
self.note_model_cls = model_admin.note_model_cls
|
|
18
18
|
super().__init__(request, params, model, model_admin)
|
|
19
19
|
|
|
20
|
-
def lookups(self, request, model_admin):
|
|
20
|
+
def lookups(self, request, model_admin): # noqa: ARG002
|
|
21
21
|
status_dict = {tpl[0]: tpl[1] for tpl in self.note_model_status_choices}
|
|
22
22
|
names = [(NEW, status_dict.get(NEW, "New"))]
|
|
23
23
|
qs = (
|
|
@@ -27,7 +27,7 @@ class NoteStatusListFilter(SimpleListFilter):
|
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
for obj in qs:
|
|
30
|
-
names.append((f"{obj.get('status')}", status_dict[obj.get("status")]))
|
|
30
|
+
names.append((f"{obj.get('status')}", status_dict[obj.get("status")])) # noqa: PERF401
|
|
31
31
|
return tuple(names)
|
|
32
32
|
|
|
33
33
|
@staticmethod
|
|
@@ -39,22 +39,26 @@ class NoteStatusListFilter(SimpleListFilter):
|
|
|
39
39
|
)
|
|
40
40
|
for obj in qs:
|
|
41
41
|
return obj.get("report_model")
|
|
42
|
+
return ""
|
|
42
43
|
|
|
43
|
-
def queryset(self, request, queryset):
|
|
44
|
-
if
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
44
|
+
def queryset(self, request, queryset): # noqa: ARG002
|
|
45
|
+
if (
|
|
46
|
+
self.value()
|
|
47
|
+
and self.value() != "none"
|
|
48
|
+
and (report_model := self.report_model(queryset))
|
|
49
|
+
):
|
|
50
|
+
if self.value() == NEW:
|
|
51
|
+
qs = self.note_model_cls.objects.values("subject_identifier").filter(
|
|
52
|
+
report_model=report_model
|
|
53
|
+
)
|
|
54
|
+
queryset = queryset.exclude(
|
|
55
|
+
subject_identifier__in=[obj.get("subject_identifier") for obj in qs]
|
|
56
|
+
)
|
|
57
|
+
elif self.value() in [tpl[0] for tpl in self.note_model_status_choices]:
|
|
58
|
+
qs = self.note_model_cls.objects.values("subject_identifier").filter(
|
|
59
|
+
report_model=report_model, status=self.value()
|
|
60
|
+
)
|
|
61
|
+
queryset = queryset.filter(
|
|
62
|
+
subject_identifier__in=[obj.get("subject_identifier") for obj in qs]
|
|
63
|
+
)
|
|
60
64
|
return queryset
|
|
@@ -32,7 +32,7 @@ class NoteModelAdminMixin(
|
|
|
32
32
|
"""A modeladmin mixin class for the Note model."""
|
|
33
33
|
|
|
34
34
|
form = NoteForm
|
|
35
|
-
ordering =
|
|
35
|
+
ordering = ("site", "subject_identifier")
|
|
36
36
|
|
|
37
37
|
note_template_name = "edc_qareports/qa_report_note.html"
|
|
38
38
|
|
|
@@ -52,26 +52,26 @@ class NoteModelAdminMixin(
|
|
|
52
52
|
audit_fieldset_tuple,
|
|
53
53
|
)
|
|
54
54
|
|
|
55
|
-
list_display =
|
|
55
|
+
list_display = (
|
|
56
56
|
"dashboard",
|
|
57
57
|
"subject_identifier",
|
|
58
58
|
"report",
|
|
59
59
|
"status",
|
|
60
60
|
"report_note",
|
|
61
61
|
"report_datetime",
|
|
62
|
-
|
|
62
|
+
)
|
|
63
63
|
|
|
64
|
-
radio_fields = {"status": admin.VERTICAL}
|
|
64
|
+
radio_fields = {"status": admin.VERTICAL} # noqa: RUF012
|
|
65
65
|
|
|
66
|
-
list_filter =
|
|
66
|
+
list_filter = (
|
|
67
67
|
"report_datetime",
|
|
68
68
|
"status",
|
|
69
69
|
"report_model",
|
|
70
70
|
"user_created",
|
|
71
71
|
"user_modified",
|
|
72
|
-
|
|
72
|
+
)
|
|
73
73
|
|
|
74
|
-
search_fields =
|
|
74
|
+
search_fields = ("subject_identifier", "name")
|
|
75
75
|
|
|
76
76
|
@admin.display(description="Report", ordering="report_name")
|
|
77
77
|
def report(self, obj=None):
|
|
@@ -23,9 +23,9 @@ class OnStudyMissingValuesModelAdminMixin(
|
|
|
23
23
|
include_note_column: bool = True
|
|
24
24
|
site_list_display_insert_pos: int = 2
|
|
25
25
|
qa_report_list_display_insert_pos = 4
|
|
26
|
-
ordering =
|
|
26
|
+
ordering = ("site", "subject_identifier")
|
|
27
27
|
|
|
28
|
-
list_display =
|
|
28
|
+
list_display = (
|
|
29
29
|
"dashboard",
|
|
30
30
|
"render_button",
|
|
31
31
|
"subject",
|
|
@@ -35,16 +35,16 @@ class OnStudyMissingValuesModelAdminMixin(
|
|
|
35
35
|
"visit",
|
|
36
36
|
"report_date",
|
|
37
37
|
"created",
|
|
38
|
-
|
|
38
|
+
)
|
|
39
39
|
|
|
40
|
-
list_filter =
|
|
40
|
+
list_filter = (
|
|
41
41
|
ScheduleStatusListFilter,
|
|
42
42
|
"label",
|
|
43
43
|
"visit_code",
|
|
44
44
|
"report_datetime",
|
|
45
|
-
|
|
45
|
+
)
|
|
46
46
|
|
|
47
|
-
search_fields =
|
|
47
|
+
search_fields = ("subject_identifier", "label")
|
|
48
48
|
|
|
49
49
|
@admin.display(description="Update")
|
|
50
50
|
def render_button(self, obj=None):
|
|
@@ -64,7 +64,7 @@ class OnStudyMissingValuesModelAdminMixin(
|
|
|
64
64
|
f"&appointment={self.related_visit(obj).appointment.id}"
|
|
65
65
|
f"&requisition={obj.original_id}"
|
|
66
66
|
)
|
|
67
|
-
title = _(
|
|
67
|
+
title = _("Add {}") % crf_model_cls._meta.verbose_name
|
|
68
68
|
label = _("Add CRF")
|
|
69
69
|
crf_button = render_to_string(
|
|
70
70
|
"edc_qareports/columns/add_button.html",
|
|
@@ -80,7 +80,7 @@ class OnStudyMissingValuesModelAdminMixin(
|
|
|
80
80
|
f"{url}?next={self.admin_site.name}:"
|
|
81
81
|
f"{self.model._meta.label_lower.replace('.', '_')}_changelist"
|
|
82
82
|
)
|
|
83
|
-
title = _(
|
|
83
|
+
title = _("Change {}") % crf_model_cls._meta.verbose_name
|
|
84
84
|
label = _("Change CRF")
|
|
85
85
|
crf_button = render_to_string(
|
|
86
86
|
"edc_qareports/columns/change_button.html",
|
|
@@ -62,13 +62,9 @@ class QaReportModelAdminMixin:
|
|
|
62
62
|
return tuple(list_filter)
|
|
63
63
|
|
|
64
64
|
def get_note_model_obj_or_raise(self, obj=None):
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
69
|
-
except ObjectDoesNotExist:
|
|
70
|
-
raise
|
|
71
|
-
return note_model_obj
|
|
65
|
+
return self.note_model_cls.objects.get(
|
|
66
|
+
report_model=obj.report_model, subject_identifier=obj.subject_identifier
|
|
67
|
+
)
|
|
72
68
|
|
|
73
69
|
@admin.display(description="Status")
|
|
74
70
|
def status(self, obj) -> str:
|
|
@@ -27,7 +27,7 @@ class RequisitionSubquery(CrfSubquery):
|
|
|
27
27
|
template: str = field(
|
|
28
28
|
init=False,
|
|
29
29
|
default=Template(
|
|
30
|
-
"select req.subject_identifier, req.id as original_id, " # nosec B608
|
|
30
|
+
"select req.subject_identifier, req.id as original_id, " # nosec B608 # noqa: S608
|
|
31
31
|
"req.subject_visit_id, req.report_datetime, req.site_id, v.visit_code, "
|
|
32
32
|
"v.visit_code_sequence, "
|
|
33
33
|
"v.schedule_name, req.modified, '${label_lower}' as label_lower, "
|
|
@@ -30,7 +30,7 @@ class SqlViewGenerator:
|
|
|
30
30
|
self.footer = f") as A ORDER BY {self.order_by}"
|
|
31
31
|
|
|
32
32
|
@staticmethod
|
|
33
|
-
def transpile(sql: str, read: str | None = None, write: str = None) -> str:
|
|
33
|
+
def transpile(sql: str, read: str | None = None, write: str | None = None) -> str:
|
|
34
34
|
read = read or "mysql"
|
|
35
35
|
sql = sql.replace(";", "")
|
|
36
36
|
return sqlglot.transpile(sql, read=read, write=write)[0]
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
|
-
from .subquery import Subquery
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def subquery_from_dict(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
) -> str | list:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
# from __future__ import annotations
|
|
2
|
+
#
|
|
3
|
+
# from typing import TYPE_CHECKING
|
|
4
|
+
#
|
|
5
|
+
# from .subquery import Subquery
|
|
6
|
+
#
|
|
7
|
+
# if TYPE_CHECKING:
|
|
8
|
+
# from .qa_case import QaCase
|
|
9
|
+
#
|
|
10
|
+
#
|
|
11
|
+
# def subquery_from_dict(
|
|
12
|
+
# cases: list[dict[str:str, str:str, str:str] | QaCase],
|
|
13
|
+
# as_list: bool | None = False,
|
|
14
|
+
# ) -> str | list:
|
|
15
|
+
# """Returns an SQL select statement as a union of the select
|
|
16
|
+
# statements of each case.
|
|
17
|
+
#
|
|
18
|
+
# args:
|
|
19
|
+
# cases = [{
|
|
20
|
+
# "label_lower": "my_app.hivhistory",
|
|
21
|
+
# "dbtable": "my_app_hivhistory",
|
|
22
|
+
# "field": "hiv_init_date",
|
|
23
|
+
# "label": "missing HIV initiation date",
|
|
24
|
+
# "list_tables": [(list_field, list_dbtable, alias), ...],
|
|
25
|
+
# }, ...]
|
|
26
|
+
#
|
|
27
|
+
# Note: `list_field` is the CRF id field, for example:
|
|
28
|
+
# left join <list_dbtable> as <alias> on crf.<list_field>=<alias>.id
|
|
29
|
+
# """
|
|
30
|
+
# subqueries = []
|
|
31
|
+
# for case in cases:
|
|
32
|
+
# try:
|
|
33
|
+
# subquery = case.sql
|
|
34
|
+
# except AttributeError:
|
|
35
|
+
# subquery = Subquery(**case).sql
|
|
36
|
+
# subqueries.append(subquery)
|
|
37
|
+
# if as_list:
|
|
38
|
+
# return subqueries
|
|
39
|
+
# return " UNION ".join(subqueries)
|
edc_qareports/utils.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import sys
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from warnings import warn
|
|
@@ -27,10 +28,10 @@ def read_unmanaged_model_sql(
|
|
|
27
28
|
parsed_sql = []
|
|
28
29
|
with fullpath.open("r") as f:
|
|
29
30
|
for line in f:
|
|
30
|
-
line = line.split("#", maxsplit=1)[0]
|
|
31
|
-
line = line.split("-- ", maxsplit=1)[0]
|
|
32
|
-
line = line.replace("\n", "")
|
|
33
|
-
line = line.strip()
|
|
31
|
+
line = line.split("#", maxsplit=1)[0] # noqa: PLW2901
|
|
32
|
+
line = line.split("-- ", maxsplit=1)[0] # noqa: PLW2901
|
|
33
|
+
line = line.replace("\n", "") # noqa: PLW2901
|
|
34
|
+
line = line.strip() # noqa: PLW2901
|
|
34
35
|
if line:
|
|
35
36
|
parsed_sql.append(line)
|
|
36
37
|
|
|
@@ -88,17 +89,15 @@ def recreate_db_view(model_cls, drop: bool | None = None, verbose: bool | None =
|
|
|
88
89
|
except AttributeError as e:
|
|
89
90
|
raise AttributeError(
|
|
90
91
|
f"Is this model linked to a view? Declare model with `DBView`. Got {e}"
|
|
91
|
-
)
|
|
92
|
+
) from e
|
|
92
93
|
else:
|
|
93
94
|
sql = sql.replace(";", "")
|
|
94
95
|
if verbose:
|
|
95
|
-
|
|
96
|
+
sys.stdout.write(f"create view {model_cls._meta.db_table} as {sql};\n")
|
|
96
97
|
with connection.cursor() as c:
|
|
97
98
|
if drop:
|
|
98
|
-
|
|
99
|
+
with contextlib.suppress(OperationalError):
|
|
99
100
|
c.execute(f"drop view {model_cls._meta.db_table};")
|
|
100
|
-
except OperationalError:
|
|
101
|
-
pass
|
|
102
101
|
c.execute(f"create view {model_cls._meta.db_table} as {sql};")
|
|
103
102
|
if verbose:
|
|
104
103
|
sys.stdout.write(
|
|
@@ -107,14 +106,14 @@ def recreate_db_view(model_cls, drop: bool | None = None, verbose: bool | None =
|
|
|
107
106
|
|
|
108
107
|
|
|
109
108
|
def recreate_dbview_for_all():
|
|
110
|
-
from .model_mixins import QaReportModelMixin
|
|
109
|
+
from .model_mixins import QaReportModelMixin # noqa: PLC0415
|
|
111
110
|
|
|
112
111
|
for model_cls in django_apps.get_models():
|
|
113
112
|
if issubclass(model_cls, (QaReportModelMixin,)):
|
|
114
|
-
|
|
113
|
+
sys.stdout.write(f"{model_cls}\n")
|
|
115
114
|
try:
|
|
116
115
|
model_cls.recreate_db_view()
|
|
117
116
|
except AttributeError as e:
|
|
118
|
-
|
|
117
|
+
sys.stdout.write(f"{e}\n")
|
|
119
118
|
except TypeError as e:
|
|
120
|
-
|
|
119
|
+
sys.stdout.write(f"{e}\n")
|
|
@@ -71,7 +71,7 @@ class RandomizationListModelMixin(models.Model):
|
|
|
71
71
|
def save(self, *args, **kwargs):
|
|
72
72
|
self.randomizer_name = self.randomizer_cls.name
|
|
73
73
|
try:
|
|
74
|
-
self.assignment_description
|
|
74
|
+
self.assignment_description # noqa: B018
|
|
75
75
|
except RandomizationError as e:
|
|
76
76
|
raise RandomizationListModelError(e) from e
|
|
77
77
|
try:
|