clinicedc 2.0.25__py3-none-any.whl → 2.0.27__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.25.dist-info → clinicedc-2.0.27.dist-info}/METADATA +1 -1
- {clinicedc-2.0.25.dist-info → clinicedc-2.0.27.dist-info}/RECORD +20 -19
- edc_constants/choices.py +26 -0
- edc_constants/constants.py +10 -0
- edc_navbar/navbar.py +2 -2
- edc_randomization/admin.py +3 -1
- edc_randomization/apps.py +8 -11
- edc_randomization/auth_objects.py +9 -0
- edc_randomization/blinding.py +6 -12
- edc_randomization/decorators.py +2 -3
- edc_randomization/exceptions.py +73 -0
- edc_randomization/model_mixins.py +2 -3
- edc_randomization/randomization_list_importer.py +14 -19
- edc_randomization/randomization_list_verifier.py +17 -19
- edc_randomization/randomizer.py +11 -27
- edc_randomization/site_randomizers.py +2 -16
- edc_randomization/system_checks.py +1 -1
- edc_randomization/utils.py +3 -10
- {clinicedc-2.0.25.dist-info → clinicedc-2.0.27.dist-info}/WHEEL +0 -0
- {clinicedc-2.0.25.dist-info → clinicedc-2.0.27.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clinicedc
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.27
|
|
4
4
|
Summary: A clinical trials data management framework built on Django
|
|
5
5
|
Keywords: django,clinicedc,edc,clinical trials,research,data management,esource
|
|
6
6
|
Author: Erik van Widenfelt, Jonathan Willitts
|
|
@@ -611,8 +611,8 @@ edc_consent/views/home_view.py,sha256=TRqEcysATqzVGTHPhn6HxPsWjIGAPDwYaCylIJOWM7
|
|
|
611
611
|
edc_consent/wsgi.py,sha256=IuY8oXnc033507PBykHhWuZxUDpg4MaMvTmVSM6uJ54,390
|
|
612
612
|
edc_constants/__init__.py,sha256=jHg-osFAN-UgMbnvV1MnQ0sD37lq0QisRbJ_oqrGUdw,84
|
|
613
613
|
edc_constants/apps.py,sha256=H3gCke2ogPgCL2sSD44Nb56ZBurei4uXWEhcUTdIlYI,318
|
|
614
|
-
edc_constants/choices.py,sha256=
|
|
615
|
-
edc_constants/constants.py,sha256=
|
|
614
|
+
edc_constants/choices.py,sha256=OvsfqZvb872tANzN0LMLgGLMkqaqRRb1FooXBcTr4cY,19638
|
|
615
|
+
edc_constants/constants.py,sha256=Utlax5PHmnKXe0DQebnz7aEwlE1KM9ItUEET6jPqgN0,5585
|
|
616
616
|
edc_constants/context_processor.py,sha256=JwfB03QXX9AFcVI_-ohdNVTGWpP2DDpH_t_kdiAe-7s,809
|
|
617
617
|
edc_constants/date_constants.py,sha256=5XrhZHz0caO1qghW4_r2slDMVdHsz6QG8OfSo2mMKSg,616
|
|
618
618
|
edc_constants/disease_constants.py,sha256=ZXzwomCaDMtrghHkAHdUUuNbZloIFn0lnG0-nZfdEvE,743
|
|
@@ -1987,7 +1987,7 @@ edc_navbar/migrations/0009_alter_edcpermissions_device_created_and_more.py,sha25
|
|
|
1987
1987
|
edc_navbar/migrations/0010_alter_edcpermissions_revision.py,sha256=fbma4ZbSTYMqZug7ZJJ_D2ckFO2A8Koqwp1sn_BxJws,789
|
|
1988
1988
|
edc_navbar/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1989
1989
|
edc_navbar/models.py,sha256=K4enndi4ov-N--3LQWeez3yuCMzmbaVGd7J72q_f73Q,306
|
|
1990
|
-
edc_navbar/navbar.py,sha256=
|
|
1990
|
+
edc_navbar/navbar.py,sha256=rtB6t9h1etGahyYIFMVLVy_1s9FYL3dfvxUXI73bAJY,1705
|
|
1991
1991
|
edc_navbar/navbar_item.py,sha256=bCj301HLa1ECzlFm_U1kYw4NPuBlyfWMovQQraI6iB0,1782
|
|
1992
1992
|
edc_navbar/navbars.py,sha256=gyEgEowvluQheEVfejWzLbog7FwUl-oRu8KvTG3oEO4,1157
|
|
1993
1993
|
edc_navbar/site_navbars.py,sha256=OcutGzhI2uyEqi8eGhhihId8mmZFRSIFcjpTKPszvyY,4220
|
|
@@ -2764,15 +2764,16 @@ edc_qareports/templates/edc_qareports/qa_report_note.html,sha256=FnHwiHvucF90gnQ
|
|
|
2764
2764
|
edc_qareports/urls.py,sha256=2QK5ajM8JPflnnp-nPybb9n_SxUZF-tzoW0WpcpFFZs,296
|
|
2765
2765
|
edc_qareports/utils.py,sha256=yy_gHhLgKabU_lfVTU4QTE1hSBfipy7yny2WD2-dZBE,4058
|
|
2766
2766
|
edc_randomization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2767
|
-
edc_randomization/admin.py,sha256=
|
|
2767
|
+
edc_randomization/admin.py,sha256=g9x71IyTXgcrg8dNL_kJ1lSU2asfC-GqlMv62jofFuM,5939
|
|
2768
2768
|
edc_randomization/admin_site.py,sha256=FC1NwilwEAiUkD8ZrXARElDHugMrzmfL_CPrKR8_MP8,185
|
|
2769
|
-
edc_randomization/apps.py,sha256=
|
|
2770
|
-
edc_randomization/auth_objects.py,sha256=
|
|
2769
|
+
edc_randomization/apps.py,sha256=QKnAPbUF_PNkUjadujqWD8FvgQW0HhMV1TCHfHXHCIM,728
|
|
2770
|
+
edc_randomization/auth_objects.py,sha256=wfQZyQNKBcQNs5kVIfmm5enWbn9NPScz4s_IYu63RIM,3200
|
|
2771
2771
|
edc_randomization/auths.py,sha256=SxhYd1vZkjgblT2_YoabhcLIroi-A0VJ7IExnlFNpr8,860
|
|
2772
|
-
edc_randomization/blinding.py,sha256=
|
|
2772
|
+
edc_randomization/blinding.py,sha256=Lo2XKlBwqobOsw0EoCe3CxU3Byfzy47AdRKiLAeAd-M,2198
|
|
2773
2773
|
edc_randomization/choices.py,sha256=OZWHp-QqeEoLSr0R4XhJpscPy9k1HCBeyVvWMLB13kk,137
|
|
2774
2774
|
edc_randomization/constants.py,sha256=WcbX-XPWydixQ6jBwk2rs1xEXAfEvcdBEyU7w5vUtI8,385
|
|
2775
|
-
edc_randomization/decorators.py,sha256=
|
|
2775
|
+
edc_randomization/decorators.py,sha256=X9BbD3RZLmbshOLZHRKWg1mM9biNYvC9zoLQLmi7zUE,676
|
|
2776
|
+
edc_randomization/exceptions.py,sha256=l7LWBVWFiMIb1IiWh0PLamazqTVXG4SlvkW28ABVYqA,1141
|
|
2776
2777
|
edc_randomization/migrations/0001_initial.py,sha256=AIwB1QuiIdnBqLXcJRTTRvR98IRuWuj5LCGhn_xt9kA,6098
|
|
2777
2778
|
edc_randomization/migrations/0002_historicalrandomizationlist.py,sha256=6ucmdv3E1FoWYvl4XPs5EhQKRIcBkjMiQQS4EXCGMaY,7191
|
|
2778
2779
|
edc_randomization/migrations/0003_auto_20191024_0426.py,sha256=H0ezbsuy80W5RyilNhWLqGHMjni9mPvHKQrwVdGhzdw,485
|
|
@@ -2789,18 +2790,18 @@ edc_randomization/migrations/0013_alter_edcpermissions_revision_and_more.py,sha2
|
|
|
2789
2790
|
edc_randomization/migrations/0014_alter_edcpermissions_device_created_and_more.py,sha256=oLNjuC-TkyTZNsos10e1GOisepx4X-r4P4yGnwu-95U,4771
|
|
2790
2791
|
edc_randomization/migrations/0015_alter_edcpermissions_revision_and_more.py,sha256=cPhN0Lh_MBe7e2CkprclFIyj-110FZ3waZULUeCt-Jg,1806
|
|
2791
2792
|
edc_randomization/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2792
|
-
edc_randomization/model_mixins.py,sha256=
|
|
2793
|
+
edc_randomization/model_mixins.py,sha256=BJe7eLxveTFNPIS7hz7796hSL0DFvTml03HmNih3tQs,3809
|
|
2793
2794
|
edc_randomization/models/__init__.py,sha256=iaJ6FM7VVqLpp8M-OjVWjohp4ELdxtPXqbEiT3iN5Tw,94
|
|
2794
2795
|
edc_randomization/models/edc_permissions.py,sha256=f6P4zXxLQdTAbn2KnFlwfE86Lmzj7Ecp4JJ2F8mrwTc,286
|
|
2795
2796
|
edc_randomization/models/randomization_list.py,sha256=Hd5yUSZ5HBKhNzYdlbJufab8U2x3Dzw8n4WXnYzJxyc,253
|
|
2796
|
-
edc_randomization/randomization_list_importer.py,sha256
|
|
2797
|
-
edc_randomization/randomization_list_verifier.py,sha256=
|
|
2798
|
-
edc_randomization/randomizer.py,sha256=
|
|
2797
|
+
edc_randomization/randomization_list_importer.py,sha256=YZG5Z0faOD85UpIFLkwt9Hq-2IAyI43xGINdhzI5YKQ,12815
|
|
2798
|
+
edc_randomization/randomization_list_verifier.py,sha256=A0LRQkI-pMt9TaJ3kps5LncMJZaz5eMdRFDeI35fIGo,6918
|
|
2799
|
+
edc_randomization/randomizer.py,sha256=_LYZp7gmpOIkovCs7TfAt78wb_Kl4iV6I85YIxgQ5pg,12729
|
|
2799
2800
|
edc_randomization/randomizers.py,sha256=JvMDAnsgvRZkl50Bawm2iLsyw3bfWfGwBafn54K5g44,297
|
|
2800
|
-
edc_randomization/site_randomizers.py,sha256=
|
|
2801
|
-
edc_randomization/system_checks.py,sha256=
|
|
2801
|
+
edc_randomization/site_randomizers.py,sha256=CcHRz_J6OlJ64-iNul_LOHM0ai6SbcX5AMDrFFMewDA,4390
|
|
2802
|
+
edc_randomization/system_checks.py,sha256=ryGqND9mS0u05MSU-wBBfX-HaOdoFMdWok25uM2Mcdo,3039
|
|
2802
2803
|
edc_randomization/urls.py,sha256=yQnOoy5U5uL00f7_JJcZk7toskmZunG_kJdl-epCV9U,223
|
|
2803
|
-
edc_randomization/utils.py,sha256=
|
|
2804
|
+
edc_randomization/utils.py,sha256=Os5YtrI4RDGnudmE5nuTxXiuBWqekObipWfF79etkmE,5697
|
|
2804
2805
|
edc_randomization/views.py,sha256=mQnAoYzIbqXFXablJbPY3NdJc4K3fD4le-GOF2kpMmE,340
|
|
2805
2806
|
edc_refusal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2806
2807
|
edc_refusal/admin.py,sha256=upce4HEPyPYKTe_R_krQ9TaLufcJN50c0lw4JrzbTwE,2151
|
|
@@ -3406,7 +3407,7 @@ edc_vitals/models/fields/waist_circumference.py,sha256=fZcHFDdEwWLjIVLktKrFCD9UU
|
|
|
3406
3407
|
edc_vitals/models/fields/weight.py,sha256=zo9_9e3Cpu0UqoRbWS-iDkcDo6fK80b1dDQy4x4MyxE,921
|
|
3407
3408
|
edc_vitals/utils.py,sha256=vXid44KUXxeaSyund_y5MNXc5DFJs052_PwUAjszE2k,1384
|
|
3408
3409
|
edc_vitals/validators.py,sha256=vNiElWMs0rRnHRNuVoPLRf0rW_C_0xcfUyep1Y_Si5s,156
|
|
3409
|
-
clinicedc-2.0.
|
|
3410
|
-
clinicedc-2.0.
|
|
3411
|
-
clinicedc-2.0.
|
|
3412
|
-
clinicedc-2.0.
|
|
3410
|
+
clinicedc-2.0.27.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
3411
|
+
clinicedc-2.0.27.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
|
|
3412
|
+
clinicedc-2.0.27.dist-info/METADATA,sha256=8L752Me7nR7AH9xGK6t20ujMHs_CD8Tkb2cUPIURzFo,15787
|
|
3413
|
+
clinicedc-2.0.27.dist-info/RECORD,,
|
edc_constants/choices.py
CHANGED
|
@@ -4,6 +4,7 @@ from .constants import (
|
|
|
4
4
|
ABNORMAL,
|
|
5
5
|
ABSENT,
|
|
6
6
|
AFTERNOON,
|
|
7
|
+
AGREE,
|
|
7
8
|
ALIVE,
|
|
8
9
|
ALWAYS,
|
|
9
10
|
ANYTIME,
|
|
@@ -11,8 +12,11 @@ from .constants import (
|
|
|
11
12
|
COMPLETE,
|
|
12
13
|
DEAD,
|
|
13
14
|
DECLINED,
|
|
15
|
+
DIFFICULT,
|
|
16
|
+
DISAGREE,
|
|
14
17
|
DONT_KNOW,
|
|
15
18
|
DWTA,
|
|
19
|
+
EASY,
|
|
16
20
|
EVENING,
|
|
17
21
|
FALSE,
|
|
18
22
|
FASTING,
|
|
@@ -26,6 +30,7 @@ from .constants import (
|
|
|
26
30
|
MORNING,
|
|
27
31
|
NAIVE,
|
|
28
32
|
NEG,
|
|
33
|
+
NEUTRAL,
|
|
29
34
|
NEVER,
|
|
30
35
|
NO,
|
|
31
36
|
NON_FASTING,
|
|
@@ -51,9 +56,13 @@ from .constants import (
|
|
|
51
56
|
REFUSED,
|
|
52
57
|
SMOKER,
|
|
53
58
|
SOMETIMES,
|
|
59
|
+
STRONGLY_AGREE,
|
|
60
|
+
STRONGLY_DISAGREE,
|
|
54
61
|
TBD,
|
|
55
62
|
TRUE,
|
|
56
63
|
UNKNOWN,
|
|
64
|
+
VERY_DIFFICULT,
|
|
65
|
+
VERY_EASY,
|
|
57
66
|
VERY_OFTEN,
|
|
58
67
|
WEEKDAYS,
|
|
59
68
|
WEEKENDS,
|
|
@@ -833,3 +842,20 @@ WHYNOPARTICIPATE_CHOICE = (
|
|
|
833
842
|
("OTHER", _("Other, specify:")),
|
|
834
843
|
("not_answering", _("Don't want to answer")),
|
|
835
844
|
)
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
DISAGREE_TO_AGREE_CHOICE = (
|
|
848
|
+
(STRONGLY_DISAGREE, _("Strongly disagree")),
|
|
849
|
+
(DISAGREE, _("disagree")),
|
|
850
|
+
(NEUTRAL, _("Neutral")),
|
|
851
|
+
(AGREE, _("Agree")),
|
|
852
|
+
(STRONGLY_AGREE, _("Strongly agree")),
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
DIFFICULT_TO_EASY_CHOICE = (
|
|
856
|
+
(VERY_DIFFICULT, _("Very difficult")),
|
|
857
|
+
(DIFFICULT, _("Difficult")),
|
|
858
|
+
(NEUTRAL, _("Neutral")),
|
|
859
|
+
(EASY, _("Easy")),
|
|
860
|
+
(VERY_EASY, _("Very easy")),
|
|
861
|
+
)
|
edc_constants/constants.py
CHANGED
|
@@ -8,6 +8,7 @@ ABNORMAL = "ABNORMAL"
|
|
|
8
8
|
ABSENT = "absent"
|
|
9
9
|
ADDITIONAL = True
|
|
10
10
|
AFTERNOON = "afternoon"
|
|
11
|
+
AGREE = "agree"
|
|
11
12
|
ALIVE = "alive"
|
|
12
13
|
ALWAYS = "always"
|
|
13
14
|
ANONYMOUS = "anonymous"
|
|
@@ -40,6 +41,7 @@ DEFAULTER = "defaulter"
|
|
|
40
41
|
DELETE = "DELETE"
|
|
41
42
|
DELIVERY = "delivery"
|
|
42
43
|
DIABETES = "diabetes"
|
|
44
|
+
DISAGREE = "disagree"
|
|
43
45
|
DIVORCED = "divorced"
|
|
44
46
|
DM = "dm"
|
|
45
47
|
DONE = "done"
|
|
@@ -109,6 +111,7 @@ MULTI_MORBIDITY = "multi"
|
|
|
109
111
|
NAIVE = "NAIVE"
|
|
110
112
|
NCD = "NCD"
|
|
111
113
|
NEG = "NEG"
|
|
114
|
+
NEUTRAL = "neutral"
|
|
112
115
|
NEVER = "NEVER"
|
|
113
116
|
NEW = "New"
|
|
114
117
|
NEW_FIELD = "new_field"
|
|
@@ -192,6 +195,8 @@ SMOKER = "smoker"
|
|
|
192
195
|
SOMETIMES = "sometimes"
|
|
193
196
|
STEROIDS = "steroids"
|
|
194
197
|
STOPPED = "stopped"
|
|
198
|
+
STRONGLY_AGREE = "strongly_agree"
|
|
199
|
+
STRONGLY_DISAGREE = "strongly_disagree"
|
|
195
200
|
STUDY_DEFINED_TIMEPOINT = "study_defined_timepoint"
|
|
196
201
|
SUBJECT = "subject"
|
|
197
202
|
SYSTEMATIC = "systematic"
|
|
@@ -238,3 +243,8 @@ THURSDAY = "thursday"
|
|
|
238
243
|
FRIDAY = "friday"
|
|
239
244
|
SATURDAY = "saturday"
|
|
240
245
|
SUNDAY = "sunday"
|
|
246
|
+
|
|
247
|
+
VERY_DIFFICULT = "very_difficult"
|
|
248
|
+
DIFFICULT = "difficult"
|
|
249
|
+
EASY = "easy"
|
|
250
|
+
VERY_EASY = "very_easy"
|
edc_navbar/navbar.py
CHANGED
|
@@ -29,7 +29,7 @@ class Navbar:
|
|
|
29
29
|
|
|
30
30
|
def get(self, name: str) -> NavbarItem | None:
|
|
31
31
|
try:
|
|
32
|
-
navbar_item =
|
|
32
|
+
navbar_item = next(nb for nb in self.navbar_items if nb.name == name)
|
|
33
33
|
except IndexError:
|
|
34
34
|
navbar_item = None
|
|
35
35
|
return navbar_item
|
|
@@ -37,7 +37,7 @@ class Navbar:
|
|
|
37
37
|
def set_active(self, name: str) -> None:
|
|
38
38
|
if name:
|
|
39
39
|
for navbar_item in self.navbar_items:
|
|
40
|
-
navbar_item.active =
|
|
40
|
+
navbar_item.active = navbar_item.name == name
|
|
41
41
|
|
|
42
42
|
def show_user_permissions(self, user: User = None) -> dict[str, dict[str, bool]]:
|
|
43
43
|
"""Returns the permissions required to access this Navbar
|
edc_randomization/admin.py
CHANGED
|
@@ -17,6 +17,8 @@ from .auth_objects import RANDO_UNBLINDED
|
|
|
17
17
|
from .blinding import user_is_blinded
|
|
18
18
|
from .site_randomizers import site_randomizers
|
|
19
19
|
|
|
20
|
+
__all__ = ["RandomizationListModelAdmin", "print_pharmacy_labels"]
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
@admin.action(permissions=["view"], description="Print labels for pharmacy")
|
|
22
24
|
def print_pharmacy_labels(modeladmin, request, queryset):
|
|
@@ -59,7 +61,7 @@ class RandomizationListModelAdmin(TemplatesModelAdminMixin, admin.ModelAdmin):
|
|
|
59
61
|
|
|
60
62
|
search_fields = ("subject_identifier", "sid")
|
|
61
63
|
|
|
62
|
-
def get_fieldsets(self, request, obj=None):
|
|
64
|
+
def get_fieldsets(self, request, obj=None): # noqa: ARG002
|
|
63
65
|
return (
|
|
64
66
|
(None, {"fields": self.get_fieldnames(request)}),
|
|
65
67
|
audit_fieldset_tuple,
|
edc_randomization/apps.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from warnings import warn
|
|
3
|
-
|
|
4
1
|
from django.apps import AppConfig as DjangoAppConfig
|
|
5
|
-
from django.conf import settings
|
|
6
2
|
from django.core.checks.registry import register
|
|
7
3
|
|
|
8
4
|
from .system_checks import randomizationlist_check
|
|
@@ -17,10 +13,11 @@ class AppConfig(DjangoAppConfig):
|
|
|
17
13
|
def ready(self):
|
|
18
14
|
register(randomizationlist_check, deploy=True)
|
|
19
15
|
|
|
20
|
-
@property
|
|
21
|
-
def randomization_list_path(self):
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
# @property
|
|
17
|
+
# def randomization_list_path(self):
|
|
18
|
+
# warn(
|
|
19
|
+
# "Use of settings.RANDOMIZATION_LIST_PATH has been deprecated. "
|
|
20
|
+
# "See site_randomizers in edc_randomization",
|
|
21
|
+
# stacklevel=2,
|
|
22
|
+
# )
|
|
23
|
+
# return os.path.join(settings.RANDOMIZATION_LIST_PATH)
|
|
@@ -2,6 +2,15 @@ import sys
|
|
|
2
2
|
|
|
3
3
|
from .site_randomizers import site_randomizers
|
|
4
4
|
|
|
5
|
+
__all__ = [
|
|
6
|
+
"RANDO_BLINDED",
|
|
7
|
+
"RANDO_UNBLINDED",
|
|
8
|
+
"get_rando_permissions_codenames",
|
|
9
|
+
"get_rando_permissions_tuples",
|
|
10
|
+
"make_randomizationlist_view_only",
|
|
11
|
+
"update_rando_group_permissions",
|
|
12
|
+
]
|
|
13
|
+
|
|
5
14
|
RANDO_UNBLINDED = "RANDO_UNBLINDED"
|
|
6
15
|
RANDO_BLINDED = "RANDO_BLINDED"
|
|
7
16
|
|
edc_randomization/blinding.py
CHANGED
|
@@ -4,7 +4,6 @@ from django import forms
|
|
|
4
4
|
from django.conf import settings
|
|
5
5
|
from django.contrib.auth import get_user_model
|
|
6
6
|
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
|
7
|
-
from django.utils.html import format_html
|
|
8
7
|
from django.utils.safestring import mark_safe
|
|
9
8
|
|
|
10
9
|
from .auth_objects import RANDO_UNBLINDED
|
|
@@ -43,13 +42,11 @@ def user_is_blinded(username) -> bool:
|
|
|
43
42
|
return blinded
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
def user_is_blinded_from_request(request):
|
|
47
|
-
|
|
45
|
+
def user_is_blinded_from_request(request) -> bool:
|
|
46
|
+
return user_is_blinded(request.user.username) or (
|
|
48
47
|
not user_is_blinded(request.user.username)
|
|
49
48
|
and RANDO_UNBLINDED not in [g.name for g in request.user.groups.all()]
|
|
50
|
-
)
|
|
51
|
-
return True
|
|
52
|
-
return False
|
|
49
|
+
)
|
|
53
50
|
|
|
54
51
|
|
|
55
52
|
def raise_if_prohibited_from_unblinded_rando_group(username: str, groups: Iterable) -> None:
|
|
@@ -61,12 +58,9 @@ def raise_if_prohibited_from_unblinded_rando_group(username: str, groups: Iterab
|
|
|
61
58
|
if RANDO_UNBLINDED in [grp.name for grp in groups] and user_is_blinded(username):
|
|
62
59
|
raise forms.ValidationError(
|
|
63
60
|
{
|
|
64
|
-
"groups":
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
"This user is not unblinded and may not added "
|
|
68
|
-
"to the <U>RANDO_UNBLINDED</U> group."
|
|
69
|
-
), # nosec B703 B308
|
|
61
|
+
"groups": mark_safe(
|
|
62
|
+
"This user is not unblinded and may not added "
|
|
63
|
+
"to the <U>RANDO_UNBLINDED</U> group."
|
|
70
64
|
)
|
|
71
65
|
}
|
|
72
66
|
)
|
edc_randomization/decorators.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
+
from .exceptions import RegisterRandomizerError
|
|
3
4
|
from .randomizer import Randomizer
|
|
4
5
|
from .site_randomizers import site_randomizers
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
class RegisterRandomizerError(Exception):
|
|
8
|
-
pass
|
|
7
|
+
__all__ = ["register"]
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
def register(site=None, **kwargs) -> Any:
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from django.core.exceptions import ValidationError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class InvalidAssignment(Exception): # noqa: N818
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RandomizationListImportError(Exception):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RandomizationListAlreadyImported(Exception): # noqa: N818
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RandomizationListError(Exception):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RegistryNotLoaded(Exception): # noqa: N818
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NotRegistered(Exception): # noqa: N818
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AlreadyRegistered(Exception): # noqa: N818
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SiteRandomizerError(Exception):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RandomizationListExporterError(Exception):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SubjectNotRandomization(Exception): # noqa: N818
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class InvalidAssignmentDescriptionMap(Exception): # noqa: N818
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class RandomizationListFileNotFound(Exception): # noqa: N818
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RandomizationListNotLoaded(Exception): # noqa: N818
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RandomizationError(Exception):
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AlreadyRandomized(ValidationError): # noqa: N818
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class AllocationError(Exception):
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class RegisterRandomizerError(Exception):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class RandomizationListModelError(Exception):
|
|
73
|
+
pass
|
|
@@ -7,12 +7,11 @@ from django_crypto_fields.fields import EncryptedCharField
|
|
|
7
7
|
from edc_model.models import HistoricalRecords
|
|
8
8
|
from edc_sites.managers import CurrentSiteManager
|
|
9
9
|
|
|
10
|
+
from .exceptions import RandomizationListModelError
|
|
10
11
|
from .randomizer import RandomizationError
|
|
11
12
|
from .site_randomizers import site_randomizers
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
class RandomizationListModelError(Exception):
|
|
15
|
-
pass
|
|
14
|
+
__all__ = ["RandomizationListManager", "RandomizationListModelMixin"]
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class RandomizationListManager(models.Manager):
|
|
@@ -12,21 +12,16 @@ from tqdm import tqdm
|
|
|
12
12
|
|
|
13
13
|
from edc_sites.site import sites as site_sites
|
|
14
14
|
|
|
15
|
+
from .exceptions import (
|
|
16
|
+
InvalidAssignment,
|
|
17
|
+
RandomizationListAlreadyImported,
|
|
18
|
+
RandomizationListImportError,
|
|
19
|
+
)
|
|
15
20
|
from .randomization_list_verifier import RandomizationListVerifier
|
|
16
21
|
|
|
17
22
|
style = color_style()
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
class RandomizationListImportError(Exception):
|
|
21
|
-
pass
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class RandomizationListAlreadyImported(Exception):
|
|
25
|
-
pass
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class InvalidAssignment(Exception):
|
|
29
|
-
pass
|
|
24
|
+
__all__ = ["RandomizationListImporter"]
|
|
30
25
|
|
|
31
26
|
|
|
32
27
|
class RandomizationListImporter:
|
|
@@ -157,7 +152,7 @@ class RandomizationListImporter:
|
|
|
157
152
|
index = 0
|
|
158
153
|
with self.randomizationlist_path.open(mode="r") as csvfile:
|
|
159
154
|
reader = csv.DictReader(csvfile)
|
|
160
|
-
for index,
|
|
155
|
+
for index, _ in enumerate(reader):
|
|
161
156
|
if index == 0:
|
|
162
157
|
continue
|
|
163
158
|
if index == 0:
|
|
@@ -179,11 +174,11 @@ class RandomizationListImporter:
|
|
|
179
174
|
elif index == 1:
|
|
180
175
|
if self.dryrun:
|
|
181
176
|
row_as_dict = {k: v for k, v in row.items()}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
177
|
+
sys.stdout.write(" --> First row:\n")
|
|
178
|
+
sys.stdout.write(f" --> {list(row_as_dict.keys())}\n")
|
|
179
|
+
sys.stdout.write(f" --> {list(row_as_dict.values())}\n")
|
|
185
180
|
obj = self.randomizer_model_cls(**self.get_import_options(row))
|
|
186
|
-
pprint(obj.__dict__)
|
|
181
|
+
pprint(obj.__dict__) # noqa: T203
|
|
187
182
|
else:
|
|
188
183
|
break
|
|
189
184
|
|
|
@@ -304,7 +299,7 @@ class RandomizationListImporter:
|
|
|
304
299
|
**self.get_extra_import_options(row),
|
|
305
300
|
)
|
|
306
301
|
|
|
307
|
-
def get_extra_import_options(self, row):
|
|
302
|
+
def get_extra_import_options(self, row): # noqa: ARG002
|
|
308
303
|
return {}
|
|
309
304
|
|
|
310
305
|
@staticmethod
|
|
@@ -318,9 +313,9 @@ class RandomizationListImporter:
|
|
|
318
313
|
"""Returns the site name or raises"""
|
|
319
314
|
try:
|
|
320
315
|
site_name = self.get_site_names()[row["site_name"].lower()]
|
|
321
|
-
except KeyError:
|
|
316
|
+
except KeyError as e:
|
|
322
317
|
raise RandomizationListImportError(
|
|
323
318
|
f"Invalid site. Got {row['site_name']}. "
|
|
324
319
|
f"Expected one of {self.get_site_names().keys()}"
|
|
325
|
-
)
|
|
320
|
+
) from e
|
|
326
321
|
return site_name
|
|
@@ -6,17 +6,12 @@ from django.core.exceptions import ObjectDoesNotExist
|
|
|
6
6
|
from django.core.management.color import color_style
|
|
7
7
|
from django.db.utils import OperationalError, ProgrammingError
|
|
8
8
|
|
|
9
|
+
from .exceptions import InvalidAssignment, RandomizationListError
|
|
9
10
|
from .site_randomizers import site_randomizers
|
|
10
11
|
|
|
11
12
|
style = color_style()
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
class RandomizationListError(Exception):
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class InvalidAssignment(Exception):
|
|
19
|
-
pass
|
|
14
|
+
__all__ = ["RandomizationListVerifier"]
|
|
20
15
|
|
|
21
16
|
|
|
22
17
|
class RandomizationListVerifier:
|
|
@@ -78,28 +73,31 @@ class RandomizationListVerifier:
|
|
|
78
73
|
def verify(self) -> str | None:
|
|
79
74
|
message = None
|
|
80
75
|
|
|
76
|
+
# read and sort from CSV file
|
|
81
77
|
with self.randomizationlist_path.open(mode="r") as f:
|
|
82
78
|
reader = csv.DictReader(f)
|
|
83
79
|
all_rows = [{k: v.strip() for k, v in row.items() if k} for row in reader]
|
|
84
80
|
sorted_rows = sorted(
|
|
85
|
-
all_rows, key=lambda row: (row.get("site_name", ""), row.get("sid",
|
|
81
|
+
all_rows, key=lambda row: (row.get("site_name", ""), int(row.get("sid", 0)))
|
|
86
82
|
)
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
break
|
|
93
|
-
if self.sid_count_for_tests and index == self.sid_count_for_tests:
|
|
94
|
-
break
|
|
95
|
-
|
|
96
|
-
if not message and self.count != index:
|
|
84
|
+
# compare sorted CSV length with DB
|
|
85
|
+
if len(sorted_rows) != self.randomizer_model_cls.objects.all().count():
|
|
86
|
+
expected_cnt = len(sorted_rows)
|
|
87
|
+
actual_cnt = self.randomizer_model_cls.objects.all().count()
|
|
97
88
|
message = (
|
|
98
|
-
f"Randomization list count is off. Expected {
|
|
99
|
-
f"Got {
|
|
89
|
+
f"Randomization list count is off. Expected {expected_cnt} (CSV). "
|
|
90
|
+
f"Got {actual_cnt} (model_cls). See file "
|
|
100
91
|
f"{self.randomizationlist_path}. "
|
|
101
92
|
f"Resolve this issue before using the system."
|
|
102
93
|
)
|
|
94
|
+
if not message:
|
|
95
|
+
# compare sorted CSV data to DB
|
|
96
|
+
for index, row in enumerate(sorted_rows, start=1):
|
|
97
|
+
sys.stdout.write(f"Index: {index}, SID: {row.get('sid')}, Row: {row}\n")
|
|
98
|
+
message = self.inspect_row(index - 1, row)
|
|
99
|
+
if message:
|
|
100
|
+
break
|
|
103
101
|
return message
|
|
104
102
|
|
|
105
103
|
def inspect_row(self, index: int, row) -> str | None:
|
edc_randomization/randomizer.py
CHANGED
|
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
8
8
|
|
|
9
9
|
from django.apps import apps as django_apps
|
|
10
10
|
from django.conf import settings
|
|
11
|
-
from django.core.exceptions import ObjectDoesNotExist
|
|
11
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
12
12
|
from django.db.models import Q
|
|
13
13
|
|
|
14
14
|
from edc_registration.utils import get_registered_subject_model_cls
|
|
@@ -18,38 +18,22 @@ from .constants import (
|
|
|
18
18
|
DEFAULT_ASSIGNMENT_MAP,
|
|
19
19
|
RANDOMIZED,
|
|
20
20
|
)
|
|
21
|
-
from .
|
|
21
|
+
from .exceptions import (
|
|
22
|
+
AllocationError,
|
|
23
|
+
AlreadyRandomized,
|
|
24
|
+
InvalidAssignmentDescriptionMap,
|
|
25
|
+
RandomizationError,
|
|
22
26
|
RandomizationListAlreadyImported,
|
|
23
|
-
|
|
27
|
+
RandomizationListFileNotFound,
|
|
24
28
|
)
|
|
29
|
+
from .randomization_list_importer import RandomizationListImporter
|
|
25
30
|
from .utils import get_randomization_list_path
|
|
26
31
|
|
|
27
32
|
if TYPE_CHECKING:
|
|
28
33
|
from edc_registration.models import RegisteredSubject
|
|
29
34
|
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
pass
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class RandomizationListFileNotFound(Exception): # noqa: N818
|
|
36
|
-
pass
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class RandomizationListNotLoaded(Exception): # noqa: N818
|
|
40
|
-
pass
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class RandomizationError(Exception):
|
|
44
|
-
pass
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class AlreadyRandomized(ValidationError): # noqa: N818
|
|
48
|
-
pass
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class AllocationError(Exception):
|
|
52
|
-
pass
|
|
36
|
+
__all__ = ["Randomizer"]
|
|
53
37
|
|
|
54
38
|
|
|
55
39
|
class Randomizer:
|
|
@@ -61,8 +45,8 @@ class Randomizer:
|
|
|
61
45
|
`site_randomizer` by default. To prevent registration set
|
|
62
46
|
settings.EDC_RANDOMIZATION_REGISTER_DEFAULT_RANDOMIZER=False.
|
|
63
47
|
|
|
64
|
-
assignment_map: {<assignment:str
|
|
65
|
-
assignment_description_map: {<assignment:str
|
|
48
|
+
assignment_map: {<assignment:str>: <allocation:int>, ...}
|
|
49
|
+
assignment_description_map: {<assignment:str>: <description:str>, ...}
|
|
66
50
|
|
|
67
51
|
|
|
68
52
|
Usage:
|
|
@@ -7,26 +7,12 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
from django.apps import apps as django_apps
|
|
8
8
|
from django.utils.module_loading import import_module, module_has_submodule
|
|
9
9
|
|
|
10
|
+
from .exceptions import AlreadyRegistered, NotRegistered, RegistryNotLoaded
|
|
11
|
+
|
|
10
12
|
if TYPE_CHECKING:
|
|
11
13
|
from edc_randomization.randomizer import Randomizer
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
class RegistryNotLoaded(Exception):
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class NotRegistered(Exception):
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class AlreadyRegistered(Exception):
|
|
23
|
-
pass
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class SiteRandomizerError(Exception):
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
|
|
30
16
|
class SiteRandomizers:
|
|
31
17
|
"""Main controller of :class:`SiteRandomizers` objects.
|
|
32
18
|
|
|
@@ -3,7 +3,7 @@ import sys
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
|
|
5
5
|
from django.conf import settings
|
|
6
|
-
from django.core.checks import Error, Warning
|
|
6
|
+
from django.core.checks import Error, Warning # noqa: A004
|
|
7
7
|
from django.core.exceptions import ImproperlyConfigured
|
|
8
8
|
from django.core.management import color_style
|
|
9
9
|
|
edc_randomization/utils.py
CHANGED
|
@@ -14,17 +14,10 @@ from django_pandas.io import read_frame
|
|
|
14
14
|
from edc_pdutils.constants import SYSTEM_COLUMNS
|
|
15
15
|
from edc_sites.site import sites
|
|
16
16
|
|
|
17
|
+
from .exceptions import RandomizationListExporterError, SubjectNotRandomization
|
|
17
18
|
from .site_randomizers import site_randomizers
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
class RandomizationListExporterError(Exception):
|
|
21
|
-
pass
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class SubjectNotRandomization(Exception): # noqa: N818
|
|
25
|
-
pass
|
|
26
|
-
|
|
27
|
-
|
|
28
21
|
def get_randomization_list_path() -> Path:
|
|
29
22
|
return Path(
|
|
30
23
|
getattr(
|
|
@@ -149,7 +142,7 @@ def generate_fake_randomization_list(
|
|
|
149
142
|
|
|
150
143
|
def export_randomization_list(
|
|
151
144
|
randomizer_name: str, path: str | None = None, username: str | None = None
|
|
152
|
-
):
|
|
145
|
+
) -> Path:
|
|
153
146
|
randomizer_cls = site_randomizers.get(randomizer_name)
|
|
154
147
|
|
|
155
148
|
try:
|
|
@@ -184,4 +177,4 @@ def export_randomization_list(
|
|
|
184
177
|
)
|
|
185
178
|
df.to_csv(**opts)
|
|
186
179
|
sys.stdout.write(f"{filename!s}\n")
|
|
187
|
-
return filename
|
|
180
|
+
return Path(filename)
|
|
File without changes
|
|
File without changes
|