clinicedc 2.0.14__py3-none-any.whl → 2.0.16__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.14.dist-info → clinicedc-2.0.16.dist-info}/METADATA +1 -1
- {clinicedc-2.0.14.dist-info → clinicedc-2.0.16.dist-info}/RECORD +64 -64
- edc_analytics/custom_tables/age.py +1 -0
- edc_analytics/custom_tables/art.py +1 -0
- edc_analytics/custom_tables/bmi.py +1 -0
- edc_analytics/custom_tables/bp.py +1 -0
- edc_analytics/custom_tables/fasting.py +1 -0
- edc_analytics/custom_tables/fbg.py +1 -0
- edc_analytics/custom_tables/fbg_ogtt.py +1 -0
- edc_analytics/custom_tables/hba1c.py +1 -0
- edc_analytics/custom_tables/ogtt.py +1 -0
- edc_analytics/custom_tables/waist.py +1 -0
- edc_analytics/row/row_definitions.py +1 -1
- edc_analytics/row/row_statistics_with_gender.py +1 -0
- edc_analytics/stata/get_stata_labels_from_model.py +5 -6
- edc_analytics/table.py +1 -0
- edc_appointment/creators/appointments_creator.py +5 -9
- edc_dx/diagnoses.py +2 -2
- edc_dx/form_validators/diagnosis_form_validator_mixin.py +1 -0
- edc_dx/form_validators/result_form_validator_mixin.py +14 -16
- edc_dx_review/form_mixins/clinical_review_baseline_required_form_mixin.py +1 -0
- edc_dx_review/medical_date.py +2 -1
- edc_dx_review/model_mixins/clinical_review_baseline_model_mixin.py +1 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_chol_model_mixin.py +1 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_dm_model_mixin.py +1 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_hiv_model_mixin.py +1 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_htn_model_mixin.py +1 -0
- edc_dx_review/model_mixins/clinical_review_followup/clinical_review_followup_model_mixin.py +1 -0
- edc_dx_review/model_mixins/dx_location_model_mixin.py +1 -0
- edc_dx_review/model_mixins/factory/baseline_review_model_mixin_factory.py +1 -0
- edc_dx_review/model_mixins/factory/calculate_date.py +1 -0
- edc_dx_review/model_mixins/factory/dx_initial_review_model_mixin_factory.py +2 -1
- edc_dx_review/model_mixins/factory/followup_review_model_mixin_factory.py +1 -0
- edc_dx_review/model_mixins/factory/rx_initial_review_model_mixin_factory.py +1 -0
- edc_dx_review/model_mixins/followup_review/followup_review_model_mixin.py +1 -0
- edc_dx_review/model_mixins/followup_review/hiv_followup_review_model_mixin.py +1 -0
- edc_dx_review/model_mixins/initial_review/chol_initial_review_model_mixin.py +1 -0
- edc_dx_review/model_mixins/initial_review/hiv_initial_model_mixins.py +1 -0
- edc_dx_review/model_mixins/initial_review/ncd_initial_review_model_mixin.py +1 -0
- edc_dx_review/radio_fields.py +3 -3
- edc_dx_review/utils.py +2 -1
- edc_form_describer/form_describer.py +13 -8
- edc_form_describer/forms_reference.py +24 -25
- edc_form_describer/make_forms_reference.py +10 -12
- edc_form_describer/management/commands/make_forms_reference.py +8 -8
- edc_form_describer/markdown_writer.py +11 -10
- edc_randomization/randomizer.py +2 -5
- edc_randomization/utils.py +10 -0
- edc_visit_schedule/schedule/schedule.py +13 -13
- edc_visit_tracking/action_items.py +1 -2
- edc_visit_tracking/apps.py +1 -1
- edc_visit_tracking/context_processors.py +1 -2
- edc_visit_tracking/crf_date_validator.py +4 -4
- edc_visit_tracking/form_validators/visit_form_validator.py +51 -45
- edc_visit_tracking/form_validators/visit_missed_form_validator.py +6 -1
- edc_visit_tracking/model_mixins/utils.py +1 -1
- edc_visit_tracking/modelform_mixins/crf/visit_tracking_crf_modelform_mixin.py +4 -4
- edc_visit_tracking/modelform_mixins/utils.py +2 -2
- edc_visit_tracking/models/signals.py +1 -1
- edc_visit_tracking/typing_stubs.py +3 -3
- edc_visit_tracking/utils.py +1 -1
- edc_visit_tracking/visit_sequence.py +3 -4
- {clinicedc-2.0.14.dist-info → clinicedc-2.0.16.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.14.dist-info → clinicedc-2.0.16.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from datetime import date
|
|
4
4
|
|
|
5
5
|
from django.db import models
|
|
6
|
+
|
|
6
7
|
from edc_constants.choices import YES_NO
|
|
7
8
|
from edc_constants.constants import NOT_APPLICABLE, YES
|
|
8
9
|
from edc_dx import Diagnoses
|
|
@@ -36,7 +37,7 @@ def dx_initial_review_methods_model_mixin_factory():
|
|
|
36
37
|
|
|
37
38
|
@property
|
|
38
39
|
def diagnoses(self):
|
|
39
|
-
subject_identifier =
|
|
40
|
+
subject_identifier = self.subject_identifier
|
|
40
41
|
return Diagnoses(
|
|
41
42
|
subject_identifier=subject_identifier,
|
|
42
43
|
report_datetime=getattr(self, get_report_datetime_field_name()),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
|
2
2
|
from django.db import models
|
|
3
|
+
|
|
3
4
|
from edc_constants.choices import YES_NO, YES_NO_NA, YES_NO_PENDING_NA
|
|
4
5
|
from edc_constants.constants import NOT_APPLICABLE, YES
|
|
5
6
|
from edc_lab.choices import VL_QUANTIFIER_NA
|
edc_dx_review/radio_fields.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
from typing import Dict
|
|
2
1
|
|
|
3
2
|
from django.contrib import admin
|
|
3
|
+
|
|
4
4
|
from edc_dx import get_diagnosis_labels_prefixes
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def get_clinical_review_cond_radio_fields() ->
|
|
7
|
+
def get_clinical_review_cond_radio_fields() -> dict[str, int]:
|
|
8
8
|
radio_fields = {}
|
|
9
9
|
for prefix in get_diagnosis_labels_prefixes():
|
|
10
10
|
cond = prefix.lower()
|
|
@@ -17,7 +17,7 @@ def get_clinical_review_cond_radio_fields() -> Dict[str, int]:
|
|
|
17
17
|
return radio_fields
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def get_clinical_review_baseline_cond_radio_fields() ->
|
|
20
|
+
def get_clinical_review_baseline_cond_radio_fields() -> dict[str, int]:
|
|
21
21
|
radio_fields = {}
|
|
22
22
|
for prefix in get_diagnosis_labels_prefixes():
|
|
23
23
|
cond = prefix.lower()
|
edc_dx_review/utils.py
CHANGED
|
@@ -7,6 +7,7 @@ from django import forms
|
|
|
7
7
|
from django.apps import apps as django_apps
|
|
8
8
|
from django.conf import settings
|
|
9
9
|
from django.core.exceptions import ObjectDoesNotExist
|
|
10
|
+
|
|
10
11
|
from edc_constants.constants import HIV, YES
|
|
11
12
|
from edc_model.utils import model_exists_or_raise
|
|
12
13
|
from edc_visit_schedule.baseline import VisitScheduleBaselineError
|
|
@@ -78,7 +79,7 @@ def get_extra_attrs():
|
|
|
78
79
|
"review": "review",
|
|
79
80
|
}
|
|
80
81
|
try:
|
|
81
|
-
data =
|
|
82
|
+
data = settings.EDC_DX_REVIEW_EXTRA_ATTRS
|
|
82
83
|
except AttributeError:
|
|
83
84
|
pass
|
|
84
85
|
else:
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
import re
|
|
4
5
|
import string
|
|
5
6
|
import sys
|
|
6
|
-
from datetime import datetime
|
|
7
7
|
from math import floor
|
|
8
8
|
|
|
9
9
|
from django.core.management.color import color_style
|
|
10
|
+
from django.utils import timezone
|
|
10
11
|
|
|
11
12
|
from edc_fieldsets.fieldsets import Fieldsets
|
|
12
13
|
from edc_model.constants import DEFAULT_BASE_FIELDS
|
|
@@ -57,14 +58,20 @@ class FormDescriber:
|
|
|
57
58
|
self.markdown: list[str] = []
|
|
58
59
|
add_timestamp = True if add_timestamp is None else add_timestamp
|
|
59
60
|
self.anchor_prefix = anchor_prefix or self.anchor_prefix
|
|
60
|
-
timestamp =
|
|
61
|
+
timestamp = timezone.now().strftime("%Y-%m-%d %H:%M")
|
|
61
62
|
self.level = level or self.level
|
|
62
63
|
self.conditional_fieldset = None
|
|
63
64
|
self.admin_cls = admin_cls
|
|
64
65
|
try:
|
|
65
66
|
self.model_cls = admin_cls.model
|
|
66
67
|
except AttributeError:
|
|
67
|
-
|
|
68
|
+
try:
|
|
69
|
+
self.model_cls = admin_cls.form._meta.model
|
|
70
|
+
except AttributeError as e:
|
|
71
|
+
raise FormDescriberError(
|
|
72
|
+
f"Unable to determine admin class model. Got {admin_cls}"
|
|
73
|
+
) from e
|
|
74
|
+
|
|
68
75
|
self.visit_code = visit_code
|
|
69
76
|
self.models_fields = {fld.name: fld for fld in self.model_cls._meta.get_fields()}
|
|
70
77
|
|
|
@@ -115,7 +122,7 @@ class FormDescriber:
|
|
|
115
122
|
def anchor(self):
|
|
116
123
|
allow = string.ascii_letters + string.digits + "-"
|
|
117
124
|
slug = self.verbose_name.lower().replace(" ", "-")
|
|
118
|
-
slug = re.sub("[
|
|
125
|
+
slug = re.sub(f"[^{allow}]", "", slug)
|
|
119
126
|
return f"{self.anchor_prefix}-{slug}"
|
|
120
127
|
|
|
121
128
|
def describe(self):
|
|
@@ -135,7 +142,7 @@ class FormDescriber:
|
|
|
135
142
|
|
|
136
143
|
for fieldset_name, fieldset in self.fieldsets:
|
|
137
144
|
if fieldset_name not in ["Audit"]:
|
|
138
|
-
fieldset_name = fieldset_name or "Main"
|
|
145
|
+
fieldset_name = fieldset_name or "Main" # noqa: PLW2901
|
|
139
146
|
self.markdown.append(f"\n**Section: {fieldset_name}**")
|
|
140
147
|
if fieldset.get("classes") != "collapse":
|
|
141
148
|
for fname in fieldset.get("fields"):
|
|
@@ -172,10 +179,8 @@ class FormDescriber:
|
|
|
172
179
|
self.markdown.append(f"* custom_prompt: *{self.custom_form_labels.get(fname)}*")
|
|
173
180
|
self.markdown.append(f"- db_table: {self.model_cls._meta.db_table}")
|
|
174
181
|
self.markdown.append(f"- column: {field_cls.name}")
|
|
175
|
-
|
|
182
|
+
with contextlib.suppress(AttributeError):
|
|
176
183
|
self.markdown.append(f"- metadata: {field_cls.metadata}")
|
|
177
|
-
except AttributeError:
|
|
178
|
-
pass
|
|
179
184
|
self.markdown.append(f"- type: {field_cls.get_internal_type()}")
|
|
180
185
|
if field_cls.max_length:
|
|
181
186
|
self.markdown.append(f"- length: {field_cls.max_length}")
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
from importlib.metadata import version
|
|
2
|
+
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
from django.apps import apps as django_apps
|
|
5
5
|
from django.conf import settings
|
|
6
|
+
from django.utils import timezone
|
|
6
7
|
|
|
7
8
|
from .form_describer import FormDescriber
|
|
8
9
|
from .markdown_writer import MarkdownWriter
|
|
@@ -36,24 +37,20 @@ class FormsReference:
|
|
|
36
37
|
self.add_per_form_timestamp = (
|
|
37
38
|
True if add_per_form_timestamp is None else add_per_form_timestamp
|
|
38
39
|
)
|
|
39
|
-
self.timestamp =
|
|
40
|
+
self.timestamp = timezone.now().strftime("%Y-%m-%d %H:%M")
|
|
40
41
|
for visit_schedule in self.visit_schedules:
|
|
41
42
|
self.plans.update({visit_schedule.name: {}})
|
|
42
43
|
for schedule in visit_schedule.schedules.values():
|
|
43
44
|
for visit_code, visit in schedule.visits.items():
|
|
44
|
-
crfs = []
|
|
45
|
-
requisitions = []
|
|
46
|
-
for c in visit.crfs:
|
|
47
|
-
crfs.append(c.model)
|
|
48
|
-
for r in visit.requisitions:
|
|
49
|
-
requisitions.append(r.panel.name)
|
|
45
|
+
crfs = [c.model for c in visit.crfs]
|
|
46
|
+
requisitions = [r.panel.name for r in visit.requisitions]
|
|
50
47
|
self.plans[visit_schedule.name].update(
|
|
51
48
|
{visit_code: {"crfs": crfs, "requisitions": requisitions}}
|
|
52
49
|
)
|
|
53
50
|
|
|
54
51
|
def to_file(
|
|
55
52
|
self,
|
|
56
|
-
path: str | None = None,
|
|
53
|
+
path: Path | str | None = None,
|
|
57
54
|
overwrite: bool | None = None,
|
|
58
55
|
pad: int | None = None,
|
|
59
56
|
):
|
|
@@ -95,23 +92,25 @@ class FormsReference:
|
|
|
95
92
|
for index, model in enumerate(documents.get("crfs")):
|
|
96
93
|
model_cls = django_apps.get_model(model)
|
|
97
94
|
admin_cls = self.admin_site._registry.get(model_cls)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
95
|
+
if admin_cls:
|
|
96
|
+
describer = self.describer_cls(
|
|
97
|
+
admin_cls=admin_cls,
|
|
98
|
+
include_hidden_fields=self.include_hidden_fields,
|
|
99
|
+
visit_code=visit_code,
|
|
100
|
+
level=self.h4,
|
|
101
|
+
anchor_prefix=self.anchor_prefix,
|
|
102
|
+
add_timestamp=self.add_per_form_timestamp,
|
|
103
|
+
)
|
|
104
|
+
describer.markdown.append("\n")
|
|
105
|
+
anchor = f"{self.get_anchor(describer.anchor)}"
|
|
106
|
+
toc.append(
|
|
107
|
+
f'{index + 1}. <a href="#{anchor}">{describer.verbose_name}</a>'
|
|
108
|
+
)
|
|
109
|
+
markdown.extend(describer.markdown)
|
|
112
110
|
markdown.append(f"{self.h4} Requisitions\n")
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
markdown.extend(
|
|
112
|
+
[f"* {panel_name}\n" for panel_name in documents.get("requisitions")]
|
|
113
|
+
)
|
|
115
114
|
markdown = self.insert_toc(toc, markdown)
|
|
116
115
|
markdown.insert(0, f"{self.h1} {self.title}")
|
|
117
116
|
markdown.append(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
import sys
|
|
5
4
|
from importlib import import_module
|
|
6
5
|
|
|
@@ -16,11 +15,10 @@ style = color_style()
|
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
def make_forms_reference(
|
|
19
|
-
app_label: str
|
|
20
|
-
admin_site_name: str
|
|
21
|
-
visit_schedule_name: str
|
|
22
|
-
title: str = None,
|
|
23
|
-
path: str | None = None,
|
|
18
|
+
app_label: str,
|
|
19
|
+
admin_site_name: str,
|
|
20
|
+
visit_schedule_name: str,
|
|
21
|
+
title: str | None = None,
|
|
24
22
|
):
|
|
25
23
|
module = import_module(app_label)
|
|
26
24
|
admin_site = getattr(module.admin_site, admin_site_name)
|
|
@@ -29,9 +27,9 @@ def make_forms_reference(
|
|
|
29
27
|
sys.stdout.write(
|
|
30
28
|
style.MIGRATE_HEADING(f"Refreshing CRF reference document for {app_label}\n")
|
|
31
29
|
)
|
|
32
|
-
doc_folder =
|
|
33
|
-
if not
|
|
34
|
-
|
|
30
|
+
doc_folder = settings.BASE_DIR / "docs"
|
|
31
|
+
if not doc_folder.exists():
|
|
32
|
+
doc_folder.mkdir()
|
|
35
33
|
|
|
36
34
|
forms = FormsReference(
|
|
37
35
|
visit_schedules=[visit_schedule],
|
|
@@ -40,8 +38,8 @@ def make_forms_reference(
|
|
|
40
38
|
add_per_form_timestamp=False,
|
|
41
39
|
)
|
|
42
40
|
|
|
43
|
-
path =
|
|
41
|
+
path = doc_folder / f"forms_reference_{app_label}.md"
|
|
44
42
|
forms.to_file(path=path, overwrite=True)
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
sys.stdout.write(f"{path}\n")
|
|
45
|
+
sys.stdout.write("Done.\n")
|
|
@@ -17,16 +17,16 @@ style = color_style()
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def update_forms_reference(
|
|
20
|
-
app_label: str
|
|
21
|
-
admin_site_name: str
|
|
22
|
-
visit_schedule_name: str
|
|
23
|
-
title: str = None,
|
|
20
|
+
app_label: str,
|
|
21
|
+
admin_site_name: str,
|
|
22
|
+
visit_schedule_name: str,
|
|
23
|
+
title: str | None = None,
|
|
24
24
|
filename: str | None = None,
|
|
25
25
|
doc_folder: str | None = None,
|
|
26
26
|
):
|
|
27
27
|
module = import_module(app_label)
|
|
28
28
|
default_doc_folder = Path(settings.BASE_DIR / "docs")
|
|
29
|
-
filename = filename or "
|
|
29
|
+
filename = filename or f"forms_reference_{app_label}.md"
|
|
30
30
|
admin_site = getattr(module.admin_site, admin_site_name)
|
|
31
31
|
visit_schedule = site_visit_schedules.get_visit_schedule(visit_schedule_name)
|
|
32
32
|
title = title or _("%(title_app)s Forms Reference") % dict(title_app=app_label.upper())
|
|
@@ -47,8 +47,8 @@ def update_forms_reference(
|
|
|
47
47
|
path = doc_folder / filename
|
|
48
48
|
forms.to_file(path=path, overwrite=True)
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
sys.stdout.write(f"{path}\n")
|
|
51
|
+
sys.stdout.write("Done\n")
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
class Command(BaseCommand):
|
|
@@ -85,7 +85,7 @@ class Command(BaseCommand):
|
|
|
85
85
|
default=None,
|
|
86
86
|
)
|
|
87
87
|
|
|
88
|
-
def handle(self, *args, **options):
|
|
88
|
+
def handle(self, *args, **options): # noqa: ARG002
|
|
89
89
|
app_label = options["app_label"]
|
|
90
90
|
admin_site_name = options["admin_site_name"]
|
|
91
91
|
visit_schedule_name = options["visit_schedule_name"]
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from django.utils import timezone
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class MarkdownWriter:
|
|
@@ -11,23 +12,23 @@ class MarkdownWriter:
|
|
|
11
12
|
@staticmethod
|
|
12
13
|
def get_path(path: str | None = None, overwrite: bool | None = None) -> str:
|
|
13
14
|
if not path:
|
|
14
|
-
timestamp =
|
|
15
|
+
timestamp = timezone.now().strftime("%Y%m%d%H%M")
|
|
15
16
|
path = f"forms_{timestamp}.md"
|
|
16
|
-
if
|
|
17
|
+
if Path(path).exists():
|
|
17
18
|
if overwrite:
|
|
18
|
-
|
|
19
|
+
Path(path).unlink()
|
|
19
20
|
else:
|
|
20
21
|
raise FileExistsError(f"File exists. Got '{path}'")
|
|
21
22
|
return path
|
|
22
23
|
|
|
23
24
|
@staticmethod
|
|
24
|
-
def to_markdown(markdown: list[str]
|
|
25
|
+
def to_markdown(markdown: list[str]) -> str:
|
|
25
26
|
"""Returns the markdown as a text string."""
|
|
26
27
|
return "\n".join(markdown)
|
|
27
28
|
|
|
28
29
|
def to_file(
|
|
29
30
|
self,
|
|
30
|
-
markdown: list[str]
|
|
31
|
+
markdown: list[str],
|
|
31
32
|
pad: int | None = None,
|
|
32
33
|
append: bool | None = None,
|
|
33
34
|
prepend: bool | None = None,
|
|
@@ -42,9 +43,9 @@ class MarkdownWriter:
|
|
|
42
43
|
else:
|
|
43
44
|
self._write(markdown)
|
|
44
45
|
|
|
45
|
-
def _write(self, markdown: str
|
|
46
|
+
def _write(self, markdown: str, mode: str | None = None) -> None:
|
|
46
47
|
mode = mode or "w"
|
|
47
|
-
with
|
|
48
|
+
with Path(self.path).open(mode) as f:
|
|
48
49
|
f.write(markdown)
|
|
49
50
|
|
|
50
51
|
def _append(self, markdown) -> None:
|
|
@@ -53,7 +54,7 @@ class MarkdownWriter:
|
|
|
53
54
|
|
|
54
55
|
def _prepend(self, markdown=None) -> None:
|
|
55
56
|
mode = "r+"
|
|
56
|
-
with
|
|
57
|
+
with Path(self.path).open(mode) as f:
|
|
57
58
|
content = f.read()
|
|
58
59
|
f.seek(0, 0)
|
|
59
60
|
f.write(markdown + "\n" + content)
|
edc_randomization/randomizer.py
CHANGED
|
@@ -22,6 +22,7 @@ from .randomization_list_importer import (
|
|
|
22
22
|
RandomizationListAlreadyImported,
|
|
23
23
|
RandomizationListImporter,
|
|
24
24
|
)
|
|
25
|
+
from .utils import get_randomization_list_path
|
|
25
26
|
|
|
26
27
|
if TYPE_CHECKING:
|
|
27
28
|
from edc_registration.models import RegisteredSubject
|
|
@@ -99,11 +100,7 @@ class Randomizer:
|
|
|
99
100
|
DEFAULT_ASSIGNMENT_DESCRIPTION_MAP,
|
|
100
101
|
)
|
|
101
102
|
filename: str = "randomization_list.csv"
|
|
102
|
-
randomizationlist_folder: Path | str =
|
|
103
|
-
settings,
|
|
104
|
-
"EDC_RANDOMIZATION_LIST_PATH",
|
|
105
|
-
Path(settings.BASE_DIR).expanduser() / ".etc",
|
|
106
|
-
)
|
|
103
|
+
randomizationlist_folder: Path | str = get_randomization_list_path()
|
|
107
104
|
extra_csv_fieldnames: list[str] | None = None
|
|
108
105
|
trial_is_blinded: bool = True
|
|
109
106
|
importer_cls: Any = RandomizationListImporter
|
edc_randomization/utils.py
CHANGED
|
@@ -25,6 +25,16 @@ class SubjectNotRandomization(Exception): # noqa: N818
|
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def get_randomization_list_path() -> Path:
|
|
29
|
+
return Path(
|
|
30
|
+
getattr(
|
|
31
|
+
settings,
|
|
32
|
+
"EDC_RANDOMIZATION_LIST_PATH",
|
|
33
|
+
settings.ETC_DIR,
|
|
34
|
+
)
|
|
35
|
+
).expanduser()
|
|
36
|
+
|
|
37
|
+
|
|
28
38
|
def get_assignment_for_subject(
|
|
29
39
|
subject_identifier: str,
|
|
30
40
|
randomizer_name: str,
|
|
@@ -45,7 +45,7 @@ class ScheduleNameError(Exception):
|
|
|
45
45
|
pass
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
class AlreadyRegisteredVisit(Exception):
|
|
48
|
+
class AlreadyRegisteredVisit(Exception): # noqa: N818
|
|
49
49
|
pass
|
|
50
50
|
|
|
51
51
|
|
|
@@ -69,15 +69,15 @@ class Schedule:
|
|
|
69
69
|
|
|
70
70
|
def __init__(
|
|
71
71
|
self,
|
|
72
|
-
name
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
offschedule_model: str = None,
|
|
76
|
-
loss_to_followup_model: str = None,
|
|
77
|
-
appointment_model: str | None = None,
|
|
78
|
-
history_model: str | None = None,
|
|
72
|
+
name: str,
|
|
73
|
+
onschedule_model: str,
|
|
74
|
+
offschedule_model: str,
|
|
79
75
|
consent_definitions: list[ConsentDefinition] | ConsentDefinition = None,
|
|
76
|
+
loss_to_followup_model: str | None = None,
|
|
77
|
+
appointment_model: str | None = None,
|
|
80
78
|
offstudymedication_model: str | None = None,
|
|
79
|
+
history_model: str | None = None,
|
|
80
|
+
verbose_name: str | None = None,
|
|
81
81
|
sequence: str | None = None,
|
|
82
82
|
base_timepoint: float | Decimal | None = None,
|
|
83
83
|
):
|
|
@@ -146,10 +146,10 @@ class Schedule:
|
|
|
146
146
|
|
|
147
147
|
def visits_for_subject(
|
|
148
148
|
self,
|
|
149
|
-
subject_identifier: str
|
|
150
|
-
report_datetime: datetime
|
|
151
|
-
site_id: int = None,
|
|
152
|
-
consent_definition: ConsentDefinition = None,
|
|
149
|
+
subject_identifier: str,
|
|
150
|
+
report_datetime: datetime,
|
|
151
|
+
site_id: int | None = None,
|
|
152
|
+
consent_definition: ConsentDefinition | None = None,
|
|
153
153
|
) -> VisitCollection:
|
|
154
154
|
"""Returns a deep copy of visits collection filtered for a
|
|
155
155
|
given consented subject.
|
|
@@ -352,7 +352,7 @@ class Schedule:
|
|
|
352
352
|
|
|
353
353
|
def get_consent_definition(
|
|
354
354
|
self,
|
|
355
|
-
report_datetime: datetime
|
|
355
|
+
report_datetime: datetime,
|
|
356
356
|
site: SingleSite = None,
|
|
357
357
|
consent_definition: ConsentDefinition = None,
|
|
358
358
|
) -> ConsentDefinition:
|
|
@@ -26,12 +26,11 @@ class VisitMissedAction(ActionWithNotification):
|
|
|
26
26
|
|
|
27
27
|
def get_next_actions(self) -> list[Action]:
|
|
28
28
|
next_actions: list[Action] = []
|
|
29
|
-
|
|
29
|
+
return self.append_to_next_if_required(
|
|
30
30
|
next_actions=next_actions,
|
|
31
31
|
action_name=self.get_loss_to_followup_action_name(),
|
|
32
32
|
required=self.is_ltfu(),
|
|
33
33
|
)
|
|
34
|
-
return next_actions
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
class MissedVisitAction(VisitMissedAction):
|
edc_visit_tracking/apps.py
CHANGED
|
@@ -12,7 +12,7 @@ class AppConfig(DjangoAppConfig):
|
|
|
12
12
|
verbose_name = "Edc Visit Tracking"
|
|
13
13
|
report_datetime_allowance: int = 30
|
|
14
14
|
allow_crf_report_datetime_before_visit: bool = False
|
|
15
|
-
reason_field: dict = {}
|
|
15
|
+
reason_field: dict = {} # noqa: RUF012
|
|
16
16
|
|
|
17
17
|
def ready(self):
|
|
18
18
|
register(context_processors_check)
|
|
@@ -2,11 +2,10 @@ from .constants import DEFERRED_VISIT, LOST_VISIT, MISSED_VISIT, SCHEDULED, UNSC
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def constants(request) -> dict:
|
|
5
|
-
|
|
5
|
+
return dict(
|
|
6
6
|
DEFERRED_VISIT=DEFERRED_VISIT,
|
|
7
7
|
LOST_VISIT=LOST_VISIT,
|
|
8
8
|
MISSED_VISIT=MISSED_VISIT,
|
|
9
9
|
SCHEDULED=SCHEDULED,
|
|
10
10
|
UNSCHEDULED=UNSCHEDULED,
|
|
11
11
|
)
|
|
12
|
-
return dct
|
|
@@ -14,11 +14,11 @@ class CrfReportDateAllowanceError(Exception):
|
|
|
14
14
|
pass
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class CrfReportDateBeforeStudyStart(Exception):
|
|
17
|
+
class CrfReportDateBeforeStudyStart(Exception): # noqa: N818
|
|
18
18
|
pass
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class CrfReportDateIsFuture(Exception):
|
|
21
|
+
class CrfReportDateIsFuture(Exception): # noqa: N818
|
|
22
22
|
pass
|
|
23
23
|
|
|
24
24
|
|
|
@@ -58,13 +58,13 @@ class CrfDateValidator:
|
|
|
58
58
|
datetime_not_before_study_start(self.report_datetime)
|
|
59
59
|
except ValidationError as e:
|
|
60
60
|
message = e.message if hasattr(e, "message") else str(e)
|
|
61
|
-
raise CrfReportDateBeforeStudyStart(message)
|
|
61
|
+
raise CrfReportDateBeforeStudyStart(message) from e
|
|
62
62
|
# datetime_not_future
|
|
63
63
|
try:
|
|
64
64
|
datetime_not_future(self.report_datetime)
|
|
65
65
|
except ValidationError as e:
|
|
66
66
|
message = e.message if hasattr(e, "message") else str(e)
|
|
67
|
-
raise CrfReportDateIsFuture(message)
|
|
67
|
+
raise CrfReportDateIsFuture(message) from e
|
|
68
68
|
|
|
69
69
|
formatted_visit_datetime = self.visit_report_datetime.strftime(
|
|
70
70
|
convert_php_dateformat(settings.SHORT_DATE_FORMAT)
|