meta-edc 0.3.50__py3-none-any.whl → 0.3.52__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- meta_consent/admin/list_filters.py +22 -0
- meta_consent/admin/subject_consent_v1_ext_admin.py +49 -1
- meta_consent/forms/subject_consent_v1_ext_form.py +33 -1
- meta_consent/migrations/0028_historicalsubjectconsentv1ext_assessment_score_and_more.py +162 -0
- meta_consent/migrations/0029_alter_historicalsubjectconsentv1ext_agrees_to_extension_and_more.py +33 -0
- meta_consent/models/subject_consent_v1_ext.py +7 -2
- meta_edc/settings/debug.py +2 -2
- {meta_edc-0.3.50.dist-info → meta_edc-0.3.52.dist-info}/METADATA +2 -2
- {meta_edc-0.3.50.dist-info → meta_edc-0.3.52.dist-info}/RECORD +22 -16
- meta_lists/list_data.py +6 -1
- meta_prn/action_items.py +1 -1
- meta_prn/admin/end_of_study_admin.py +6 -0
- meta_prn/constants.py +1 -0
- meta_prn/form_validators/end_of_study.py +39 -1
- meta_prn/migrations/0060_alter_onschedule_managers_and_more.py +55 -0
- meta_prn/migrations/0061_auto_20250115_2025.py +57 -0
- meta_prn/migrations/0062_alter_endofstudy_offstudy_reason_and_more.py +72 -0
- meta_prn/models/end_of_study.py +15 -10
- {meta_edc-0.3.50.dist-info → meta_edc-0.3.52.dist-info}/AUTHORS +0 -0
- {meta_edc-0.3.50.dist-info → meta_edc-0.3.52.dist-info}/LICENSE +0 -0
- {meta_edc-0.3.50.dist-info → meta_edc-0.3.52.dist-info}/WHEEL +0 -0
- {meta_edc-0.3.50.dist-info → meta_edc-0.3.52.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
from django.contrib.admin import SimpleListFilter
|
2
|
+
from edc_constants.choices import YES_NO_NA
|
3
|
+
from edc_constants.constants import NO, NOT_APPLICABLE, YES
|
4
|
+
|
5
|
+
|
6
|
+
class AgreesListFilter(SimpleListFilter):
|
7
|
+
title = "Agrees"
|
8
|
+
parameter_name = "agrees"
|
9
|
+
|
10
|
+
def lookups(self, request, model_admin):
|
11
|
+
return YES_NO_NA
|
12
|
+
|
13
|
+
def queryset(self, request, queryset):
|
14
|
+
qs = None
|
15
|
+
if self.value():
|
16
|
+
if self.value() == YES:
|
17
|
+
qs = queryset.filter(agrees_to_extension=YES)
|
18
|
+
elif self.value() == NO:
|
19
|
+
qs = queryset.filter(agrees_to_extension=NO)
|
20
|
+
elif self.value() == NOT_APPLICABLE:
|
21
|
+
qs = queryset.filter(agrees_to_extension=NOT_APPLICABLE)
|
22
|
+
return qs
|
@@ -1,4 +1,5 @@
|
|
1
1
|
from django.contrib import admin
|
2
|
+
from django.utils.translation import gettext_lazy as _
|
2
3
|
from django_audit_fields import audit_fieldset_tuple
|
3
4
|
from edc_model_admin.dashboard import ModelAdminSubjectDashboardMixin
|
4
5
|
from edc_model_admin.history import SimpleHistoryAdmin
|
@@ -6,6 +7,7 @@ from edc_model_admin.history import SimpleHistoryAdmin
|
|
6
7
|
from ..admin_site import meta_consent_admin
|
7
8
|
from ..forms import SubjectConsentV1ExtForm
|
8
9
|
from ..models import SubjectConsentV1Ext
|
10
|
+
from .list_filters import AgreesListFilter
|
9
11
|
|
10
12
|
|
11
13
|
@admin.register(SubjectConsentV1Ext, site=meta_consent_admin)
|
@@ -15,6 +17,11 @@ class SubjectConsentV1ExtAdmin(
|
|
15
17
|
):
|
16
18
|
form = SubjectConsentV1ExtForm
|
17
19
|
|
20
|
+
additional_instructions = (
|
21
|
+
"This consent offers the participant the option to agree to "
|
22
|
+
"extend clinic followup from the original 36 months to 48 months. "
|
23
|
+
)
|
24
|
+
|
18
25
|
fieldsets = (
|
19
26
|
(
|
20
27
|
None,
|
@@ -26,10 +33,41 @@ class SubjectConsentV1ExtAdmin(
|
|
26
33
|
)
|
27
34
|
},
|
28
35
|
),
|
36
|
+
(
|
37
|
+
"Review Questions",
|
38
|
+
{
|
39
|
+
"fields": (
|
40
|
+
"consent_reviewed",
|
41
|
+
"study_questions",
|
42
|
+
"assessment_score",
|
43
|
+
"consent_signature",
|
44
|
+
"consent_copy",
|
45
|
+
),
|
46
|
+
"description": _("The following questions are directed to the interviewer."),
|
47
|
+
},
|
48
|
+
),
|
29
49
|
audit_fieldset_tuple,
|
30
50
|
)
|
31
51
|
|
32
|
-
|
52
|
+
list_display = (
|
53
|
+
"subject_consent",
|
54
|
+
"report_date",
|
55
|
+
"agrees",
|
56
|
+
)
|
57
|
+
|
58
|
+
list_filter = (
|
59
|
+
"report_datetime",
|
60
|
+
AgreesListFilter,
|
61
|
+
)
|
62
|
+
|
63
|
+
radio_fields = {
|
64
|
+
"agrees_to_extension": admin.VERTICAL,
|
65
|
+
"consent_reviewed": admin.VERTICAL,
|
66
|
+
"study_questions": admin.VERTICAL,
|
67
|
+
"assessment_score": admin.VERTICAL,
|
68
|
+
"consent_signature": admin.VERTICAL,
|
69
|
+
"consent_copy": admin.VERTICAL,
|
70
|
+
}
|
33
71
|
|
34
72
|
def get_readonly_fields(self, request, obj=None) -> tuple[str, ...]:
|
35
73
|
if obj:
|
@@ -43,3 +81,13 @@ class SubjectConsentV1ExtAdmin(
|
|
43
81
|
subject_identifier=subject_identifier
|
44
82
|
)
|
45
83
|
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
84
|
+
|
85
|
+
@admin.display(description="Agrees")
|
86
|
+
def agrees(self, obj):
|
87
|
+
return obj.agrees_to_extension
|
88
|
+
|
89
|
+
@admin.display(description="Report date")
|
90
|
+
def report_date(self, obj):
|
91
|
+
if obj:
|
92
|
+
return obj.report_datetime.date()
|
93
|
+
return None
|
@@ -1,11 +1,33 @@
|
|
1
1
|
from django import forms
|
2
|
+
from edc_consent.modelform_mixins import ReviewFieldsModelFormMixin
|
3
|
+
from edc_constants.constants import NOT_APPLICABLE
|
2
4
|
from edc_form_validators import FormValidatorMixin
|
3
5
|
from edc_sites.forms import SiteModelFormMixin
|
6
|
+
from edc_utils.date import to_local
|
4
7
|
|
8
|
+
from ..consents import consent_v1_ext
|
5
9
|
from ..models import SubjectConsentV1Ext
|
6
10
|
|
7
11
|
|
8
|
-
class SubjectConsentV1ExtForm(
|
12
|
+
class SubjectConsentV1ExtForm(
|
13
|
+
SiteModelFormMixin, ReviewFieldsModelFormMixin, FormValidatorMixin, forms.ModelForm
|
14
|
+
):
|
15
|
+
|
16
|
+
def clean(self):
|
17
|
+
cleaned_data = super().clean()
|
18
|
+
start_datetime = to_local(consent_v1_ext.start)
|
19
|
+
if (
|
20
|
+
cleaned_data.get("report_datetime")
|
21
|
+
and cleaned_data.get("report_datetime") < start_datetime
|
22
|
+
):
|
23
|
+
dt = start_datetime.date().strftime("%Y-%m-%d")
|
24
|
+
raise forms.ValidationError(
|
25
|
+
{"report_datetime": f"Cannot be before consent approval date {dt}."}
|
26
|
+
)
|
27
|
+
|
28
|
+
if cleaned_data.get("agrees_to_extension") == NOT_APPLICABLE:
|
29
|
+
raise forms.ValidationError({"agrees_to_extension": "Invalid option"})
|
30
|
+
return cleaned_data
|
9
31
|
|
10
32
|
widgets = {
|
11
33
|
"subject_identifier": forms.TextInput(attrs={"readonly": "readonly"}),
|
@@ -14,3 +36,13 @@ class SubjectConsentV1ExtForm(SiteModelFormMixin, FormValidatorMixin, forms.Mode
|
|
14
36
|
class Meta:
|
15
37
|
model = SubjectConsentV1Ext
|
16
38
|
fields = "__all__"
|
39
|
+
labels = {
|
40
|
+
"study_questions": (
|
41
|
+
"I have answered all questions the participant had about the "
|
42
|
+
"follow-up extension"
|
43
|
+
),
|
44
|
+
"assessment_score": (
|
45
|
+
"I have asked the participant questions about the follow-up extension and "
|
46
|
+
"the participant has demonstrated understanding"
|
47
|
+
),
|
48
|
+
}
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# Generated by Django 5.1.2 on 2025-01-15 23:45
|
2
|
+
|
3
|
+
import edc_consent.validators
|
4
|
+
from django.db import migrations, models
|
5
|
+
|
6
|
+
|
7
|
+
class Migration(migrations.Migration):
|
8
|
+
|
9
|
+
dependencies = [
|
10
|
+
("meta_consent", "0027_auto_20250111_0344"),
|
11
|
+
]
|
12
|
+
|
13
|
+
operations = [
|
14
|
+
migrations.AddField(
|
15
|
+
model_name="historicalsubjectconsentv1ext",
|
16
|
+
name="assessment_score",
|
17
|
+
field=models.CharField(
|
18
|
+
choices=[("Yes", "Yes"), ("No", "No")],
|
19
|
+
help_text="If no, participant is not eligible.",
|
20
|
+
max_length=3,
|
21
|
+
null=True,
|
22
|
+
validators=[edc_consent.validators.eligible_if_yes],
|
23
|
+
verbose_name="I have asked the participant questions about this study and the participant has demonstrated understanding",
|
24
|
+
),
|
25
|
+
),
|
26
|
+
migrations.AddField(
|
27
|
+
model_name="historicalsubjectconsentv1ext",
|
28
|
+
name="consent_copy",
|
29
|
+
field=models.CharField(
|
30
|
+
choices=[
|
31
|
+
("Yes", "Yes"),
|
32
|
+
("No", "No"),
|
33
|
+
("Declined", "Yes, but subject declined copy"),
|
34
|
+
],
|
35
|
+
help_text="If declined, return copy with the consent",
|
36
|
+
max_length=20,
|
37
|
+
null=True,
|
38
|
+
validators=[edc_consent.validators.eligible_if_yes_or_declined],
|
39
|
+
verbose_name="I have provided the participant with a copy of their signed informed consent",
|
40
|
+
),
|
41
|
+
),
|
42
|
+
migrations.AddField(
|
43
|
+
model_name="historicalsubjectconsentv1ext",
|
44
|
+
name="consent_reviewed",
|
45
|
+
field=models.CharField(
|
46
|
+
choices=[("Yes", "Yes"), ("No", "No")],
|
47
|
+
help_text="If no, participant is not eligible.",
|
48
|
+
max_length=3,
|
49
|
+
null=True,
|
50
|
+
validators=[edc_consent.validators.eligible_if_yes],
|
51
|
+
verbose_name="I have reviewed the consent with the participant",
|
52
|
+
),
|
53
|
+
),
|
54
|
+
migrations.AddField(
|
55
|
+
model_name="historicalsubjectconsentv1ext",
|
56
|
+
name="consent_signature",
|
57
|
+
field=models.CharField(
|
58
|
+
choices=[("Yes", "Yes"), ("No", "No")],
|
59
|
+
help_text="If no, participant is not eligible.",
|
60
|
+
max_length=3,
|
61
|
+
null=True,
|
62
|
+
validators=[edc_consent.validators.eligible_if_yes],
|
63
|
+
verbose_name="I have verified that the participant has signed the consent form",
|
64
|
+
),
|
65
|
+
),
|
66
|
+
migrations.AddField(
|
67
|
+
model_name="historicalsubjectconsentv1ext",
|
68
|
+
name="study_questions",
|
69
|
+
field=models.CharField(
|
70
|
+
choices=[("Yes", "Yes"), ("No", "No")],
|
71
|
+
help_text="If no, participant is not eligible.",
|
72
|
+
max_length=3,
|
73
|
+
null=True,
|
74
|
+
validators=[edc_consent.validators.eligible_if_yes],
|
75
|
+
verbose_name="I have answered all questions the participant had about the study",
|
76
|
+
),
|
77
|
+
),
|
78
|
+
migrations.AddField(
|
79
|
+
model_name="subjectconsentv1ext",
|
80
|
+
name="assessment_score",
|
81
|
+
field=models.CharField(
|
82
|
+
choices=[("Yes", "Yes"), ("No", "No")],
|
83
|
+
help_text="If no, participant is not eligible.",
|
84
|
+
max_length=3,
|
85
|
+
null=True,
|
86
|
+
validators=[edc_consent.validators.eligible_if_yes],
|
87
|
+
verbose_name="I have asked the participant questions about this study and the participant has demonstrated understanding",
|
88
|
+
),
|
89
|
+
),
|
90
|
+
migrations.AddField(
|
91
|
+
model_name="subjectconsentv1ext",
|
92
|
+
name="consent_copy",
|
93
|
+
field=models.CharField(
|
94
|
+
choices=[
|
95
|
+
("Yes", "Yes"),
|
96
|
+
("No", "No"),
|
97
|
+
("Declined", "Yes, but subject declined copy"),
|
98
|
+
],
|
99
|
+
help_text="If declined, return copy with the consent",
|
100
|
+
max_length=20,
|
101
|
+
null=True,
|
102
|
+
validators=[edc_consent.validators.eligible_if_yes_or_declined],
|
103
|
+
verbose_name="I have provided the participant with a copy of their signed informed consent",
|
104
|
+
),
|
105
|
+
),
|
106
|
+
migrations.AddField(
|
107
|
+
model_name="subjectconsentv1ext",
|
108
|
+
name="consent_reviewed",
|
109
|
+
field=models.CharField(
|
110
|
+
choices=[("Yes", "Yes"), ("No", "No")],
|
111
|
+
help_text="If no, participant is not eligible.",
|
112
|
+
max_length=3,
|
113
|
+
null=True,
|
114
|
+
validators=[edc_consent.validators.eligible_if_yes],
|
115
|
+
verbose_name="I have reviewed the consent with the participant",
|
116
|
+
),
|
117
|
+
),
|
118
|
+
migrations.AddField(
|
119
|
+
model_name="subjectconsentv1ext",
|
120
|
+
name="consent_signature",
|
121
|
+
field=models.CharField(
|
122
|
+
choices=[("Yes", "Yes"), ("No", "No")],
|
123
|
+
help_text="If no, participant is not eligible.",
|
124
|
+
max_length=3,
|
125
|
+
null=True,
|
126
|
+
validators=[edc_consent.validators.eligible_if_yes],
|
127
|
+
verbose_name="I have verified that the participant has signed the consent form",
|
128
|
+
),
|
129
|
+
),
|
130
|
+
migrations.AddField(
|
131
|
+
model_name="subjectconsentv1ext",
|
132
|
+
name="study_questions",
|
133
|
+
field=models.CharField(
|
134
|
+
choices=[("Yes", "Yes"), ("No", "No")],
|
135
|
+
help_text="If no, participant is not eligible.",
|
136
|
+
max_length=3,
|
137
|
+
null=True,
|
138
|
+
validators=[edc_consent.validators.eligible_if_yes],
|
139
|
+
verbose_name="I have answered all questions the participant had about the study",
|
140
|
+
),
|
141
|
+
),
|
142
|
+
migrations.AlterField(
|
143
|
+
model_name="historicalsubjectconsentv1ext",
|
144
|
+
name="agrees_to_extension",
|
145
|
+
field=models.CharField(
|
146
|
+
choices=[("Yes", "Yes"), ("No", "No"), ("N/A", "Not applicable")],
|
147
|
+
help_text="See above for the definition of extended followup.",
|
148
|
+
max_length=15,
|
149
|
+
verbose_name="Does the participant agree to extend followup as per the protocol amendment?",
|
150
|
+
),
|
151
|
+
),
|
152
|
+
migrations.AlterField(
|
153
|
+
model_name="subjectconsentv1ext",
|
154
|
+
name="agrees_to_extension",
|
155
|
+
field=models.CharField(
|
156
|
+
choices=[("Yes", "Yes"), ("No", "No"), ("N/A", "Not applicable")],
|
157
|
+
help_text="See above for the definition of extended followup.",
|
158
|
+
max_length=15,
|
159
|
+
verbose_name="Does the participant agree to extend followup as per the protocol amendment?",
|
160
|
+
),
|
161
|
+
),
|
162
|
+
]
|
meta_consent/migrations/0029_alter_historicalsubjectconsentv1ext_agrees_to_extension_and_more.py
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Generated by Django 5.1.2 on 2025-01-15 23:53
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
("meta_consent", "0028_historicalsubjectconsentv1ext_assessment_score_and_more"),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.AlterField(
|
14
|
+
model_name="historicalsubjectconsentv1ext",
|
15
|
+
name="agrees_to_extension",
|
16
|
+
field=models.CharField(
|
17
|
+
choices=[("Yes", "Yes"), ("No", "No"), ("N/A", "Not applicable")],
|
18
|
+
help_text="See above for the definition of extended followup.",
|
19
|
+
max_length=15,
|
20
|
+
verbose_name="Does the participant give consent to extend clinic followup as per the protocol amendment?",
|
21
|
+
),
|
22
|
+
),
|
23
|
+
migrations.AlterField(
|
24
|
+
model_name="subjectconsentv1ext",
|
25
|
+
name="agrees_to_extension",
|
26
|
+
field=models.CharField(
|
27
|
+
choices=[("Yes", "Yes"), ("No", "No"), ("N/A", "Not applicable")],
|
28
|
+
help_text="See above for the definition of extended followup.",
|
29
|
+
max_length=15,
|
30
|
+
verbose_name="Does the participant give consent to extend clinic followup as per the protocol amendment?",
|
31
|
+
),
|
32
|
+
),
|
33
|
+
]
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from django.db import models
|
2
2
|
from edc_action_item.models import ActionModelMixin
|
3
|
+
from edc_consent.field_mixins import ReviewFieldsMixin
|
3
4
|
from edc_consent.model_mixins import ConsentExtensionModelMixin
|
4
5
|
from edc_model.models import BaseUuidModel
|
5
6
|
from edc_sites.model_mixins import SiteModelMixin
|
@@ -9,10 +10,14 @@ from .subject_consent_v1 import SubjectConsentV1
|
|
9
10
|
|
10
11
|
|
11
12
|
class SubjectConsentV1Ext(
|
12
|
-
ConsentExtensionModelMixin,
|
13
|
+
ConsentExtensionModelMixin,
|
14
|
+
SiteModelMixin,
|
15
|
+
ActionModelMixin,
|
16
|
+
ReviewFieldsMixin,
|
17
|
+
BaseUuidModel,
|
13
18
|
):
|
14
19
|
"""A consent extension to allow a participant to extend followup
|
15
|
-
up to 48 months.
|
20
|
+
up to 48 months, or not.
|
16
21
|
"""
|
17
22
|
|
18
23
|
subject_consent = models.ForeignKey(SubjectConsentV1, on_delete=models.PROTECT)
|
meta_edc/settings/debug.py
CHANGED
@@ -9,9 +9,9 @@ print(f"Settings file {__file__}")
|
|
9
9
|
# TZ Sites:
|
10
10
|
# SITE_ID = SiteID(default=20) # Amana
|
11
11
|
# SITE_ID = SiteID(default=10) # Hindu Mandal
|
12
|
-
SITE_ID = SiteID(default=40) # Mwananyamala
|
12
|
+
# SITE_ID = SiteID(default=40) # Mwananyamala
|
13
13
|
# SITE_ID = SiteID(default=50) # Mbagala
|
14
|
-
|
14
|
+
SITE_ID = SiteID(default=60) # Mnazi-Moja
|
15
15
|
# SITE_ID = SiteID(default=30) # Temeke
|
16
16
|
INDEX_PAGE = "http://localhost:8000"
|
17
17
|
EDC_SITES_UAT_DOMAIN = False
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: meta-edc
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.52
|
4
4
|
Summary: META Trial EDC (http://www.isrctn.com/ISRCTN76157257)
|
5
5
|
Home-page: https://github.com/meta-trial/meta-edc
|
6
6
|
Author: Erik van Widenfelt
|
@@ -693,7 +693,7 @@ Requires-Python: >=3.12
|
|
693
693
|
Description-Content-Type: text/x-rst
|
694
694
|
License-File: LICENSE
|
695
695
|
License-File: AUTHORS
|
696
|
-
Requires-Dist: edc==0.6.
|
696
|
+
Requires-Dist: edc==0.6.26
|
697
697
|
Requires-Dist: edc-microscopy
|
698
698
|
Requires-Dist: beautifulsoup4
|
699
699
|
Requires-Dist: celery[redis]
|
@@ -120,17 +120,18 @@ meta_consent/consents.py,sha256=JkO2dlt7Fn7ckWRGgdicAaXWgtIANo1amwxK5ykL5uk,1185
|
|
120
120
|
meta_consent/constants.py,sha256=w5n3B4jwUDDZEc4gMpkKeffsHqi724GnoABFtjmvbQk,78
|
121
121
|
meta_consent/urls.py,sha256=YXQLoyH5tFMPUK9PTk33Yve_HCWw-rhUrlXegbadxlw,206
|
122
122
|
meta_consent/admin/__init__.py,sha256=8r1_xNoP3ZdrIB9naPRDTLENYdwBIxZvqO9CCH20aNc,182
|
123
|
+
meta_consent/admin/list_filters.py,sha256=rEz1xfr3WnG6UsJoq6hk1OLMjaa5VTKSGuI5CJl_pP4,743
|
123
124
|
meta_consent/admin/modeladmin_mixins.py,sha256=hMwrkwI7BrjpozvhxRtG1JiyLUWqs5_QNPluN_3Zous,5362
|
124
125
|
meta_consent/admin/subject_consent_admin.py,sha256=IfGANb9TFvVs9yga8WOTxHxy2yRHP--KgsVUWEg30MQ,647
|
125
126
|
meta_consent/admin/subject_consent_v1_admin.py,sha256=nmssuWL7eomQfXi3mVrszs3Ghufhz46JAwr9wFU3gkI,657
|
126
|
-
meta_consent/admin/subject_consent_v1_ext_admin.py,sha256=
|
127
|
+
meta_consent/admin/subject_consent_v1_ext_admin.py,sha256=HUqBp67_IpIW3L8VSf6UbgNqqLBWobZyLKj65HTDXEc,2817
|
127
128
|
meta_consent/admin/actions/__init__.py,sha256=ipklkCOCQY9z9oqij2--MMNlczc-1MdzsRhMHEDDkQY,70
|
128
129
|
meta_consent/admin/actions/create_missing_prescriptions.py,sha256=xx1HeLkJlB-QAa_a9GtZD74d39SkExzwg97U7Gq_86U,1474
|
129
130
|
meta_consent/form_validators/__init__.py,sha256=MFB5Vz9rFKxJaS2uW6jtdfg5G6vA0nyzwNcKGLa--Y0,72
|
130
131
|
meta_consent/form_validators/subject_consent_form_validator.py,sha256=2f4FY1VZ1qU4u35LSoRtBIhmRhrflOhs0Zal8z1Ogo8,1258
|
131
132
|
meta_consent/forms/__init__.py,sha256=fLrim2NPwpbo0Y7PCGbeDk6ryS-4W0ju7nYNF4fZWaY,233
|
132
133
|
meta_consent/forms/subject_consent_form.py,sha256=Dwnwn7y-8AABvzgTa1zfs2b2Mli0OKnNMsx0VOxO9n0,1293
|
133
|
-
meta_consent/forms/subject_consent_v1_ext_form.py,sha256=
|
134
|
+
meta_consent/forms/subject_consent_v1_ext_form.py,sha256=K_qzOGLqKpC3QjYX_fQ8SLsu_EeLwuqrXtTI95yrzkw,1704
|
134
135
|
meta_consent/forms/subject_consent_v1_form.py,sha256=XHrklzP-wh6ny3VJ2Bf9v7BTaUlfTToYVrpIAm3JZFc,766
|
135
136
|
meta_consent/forms/subject_reconsent_form.py,sha256=lJAaVb3TXwTFjg8-iAJo836wAw2D3ofsfgGUZu_svb0,1306
|
136
137
|
meta_consent/locale/lg/LC_MESSAGES/django.po,sha256=qWq1GuH-sIg05d2vydpYIxS71-fjutlJTPiLEJ905rw,1969
|
@@ -165,13 +166,15 @@ meta_consent/migrations/0024_historicalsubjectconsentv1.py,sha256=y2-R0h-leaIWy-
|
|
165
166
|
meta_consent/migrations/0025_alter_historicalsubjectconsent_first_name_and_more.py,sha256=E8VES-7To-09v0IIpv0jBDJGs5lYGcRxOk_Bhbr9QmQ,6307
|
166
167
|
meta_consent/migrations/0026_historicalsubjectconsentv1ext_subjectconsentv1ext.py,sha256=t8MFiBkc_iJ58QTy_Vn8GKZhhe7OOOuUa6sUVWrSJtE,21611
|
167
168
|
meta_consent/migrations/0027_auto_20250111_0344.py,sha256=iQGpH03s_x4iBl47QSzR5NauOdSSZCtSPUF2NdRf4y4,943
|
169
|
+
meta_consent/migrations/0028_historicalsubjectconsentv1ext_assessment_score_and_more.py,sha256=N_pDoynD0y1ssSqlHuq3Doy9lyl8q-ARVz7Dop85bR4,6834
|
170
|
+
meta_consent/migrations/0029_alter_historicalsubjectconsentv1ext_agrees_to_extension_and_more.py,sha256=hH5Wz1qAhaaFog13hOVhkuvdlUBqf0dDN1D5sHAD0EE,1277
|
168
171
|
meta_consent/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
169
172
|
meta_consent/models/__init__.py,sha256=ugL8eQybOHAEFgOtGKxVHLZ3ibAnRCNSaf9wup6J_uI,279
|
170
173
|
meta_consent/models/model_mixins.py,sha256=r5KmTykU0qV4JymMMKP2IBwAP8Z5hDnVJix-rO2PvME,296
|
171
174
|
meta_consent/models/signals.py,sha256=WHaCDREbWOnUfmT01U_Nxiu5x4OIyV3wKRLd98vbfeQ,5152
|
172
175
|
meta_consent/models/subject_consent.py,sha256=bI17d15K7A8UzyKCAoKL8Xe2E4jlhNY1Axwftuo2n4Y,3803
|
173
176
|
meta_consent/models/subject_consent_v1.py,sha256=xJPGTTVfne5KBdeNZm5dXheWcGQ2bFTxjOxm_4sjfVk,458
|
174
|
-
meta_consent/models/subject_consent_v1_ext.py,sha256=
|
177
|
+
meta_consent/models/subject_consent_v1_ext.py,sha256=MpK5TlUqf2JuYRpmRLzYBClOFstC_6pIGwDRUckIztU,1033
|
175
178
|
meta_consent/models/subject_reconsent.py,sha256=4SqwRntG6Ik8JoO73Sa1BddNqFAIrdtFcGrKQ1ZeUV0,3595
|
176
179
|
meta_consent/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
177
180
|
meta_consent/tests/holidays.csv,sha256=Ywr5pK_caGUJMgRwLzjMeSEUuyEpUWyJE5aJkfbg3bg,539
|
@@ -242,7 +245,7 @@ meta_edc/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
242
245
|
meta_edc/management/commands/update_forms_reference.py,sha256=jvCU9YdMtp9wbmjqKQsB9gxNw4DcAP97sZD-kfIxOns,1071
|
243
246
|
meta_edc/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
244
247
|
meta_edc/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
245
|
-
meta_edc/settings/debug.py,sha256=
|
248
|
+
meta_edc/settings/debug.py,sha256=5WkxxapNtYDi9XKXCOCnlZ3q9--zktBtRyGhCVUaMEs,1296
|
246
249
|
meta_edc/settings/defaults.py,sha256=4jIHzK4pSIWCD2uJiyr3U1Rp1Uhhwi2MXPbXrMd7SZs,19704
|
247
250
|
meta_edc/settings/live.py,sha256=uLTlsIc3H6gdE_7pkL6vyOVa6V_74hnXFkiojqGFAYY,264
|
248
251
|
meta_edc/settings/logging.py,sha256=6qVUE37OMkf5rzwTmMq4krFsZ2jcyCpWdFtGih_Cs4U,1938
|
@@ -275,7 +278,7 @@ meta_lists/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
275
278
|
meta_lists/admin.py,sha256=g05vpXfVIEBVG03aiPD8Vn-AnR7gL60MbPJAfzsO6R8,2157
|
276
279
|
meta_lists/admin_site.py,sha256=M4uorELumZDIKD8SHs-AEX1doQFqXhRy-eCcRWYEu9k,165
|
277
280
|
meta_lists/apps.py,sha256=aegHpyrHw188UxhNVFd-v35HP9wO7J7G9BYmliOOE0c,278
|
278
|
-
meta_lists/list_data.py,sha256=
|
281
|
+
meta_lists/list_data.py,sha256=kybIQAevrVKqncQHNoCatdgUpkLTV0S-Avim6hSgPWw,9819
|
279
282
|
meta_lists/models.py,sha256=3amfYtGe4GGtSpLXs36On8k_ZmpDzNs5ga54O-SIABs,3032
|
280
283
|
meta_lists/urls.py,sha256=9NCrRvdkPXzH3L7VMCa0iQnz-j5tujzhvvtRRAGunV8,204
|
281
284
|
meta_lists/migrations/0001_initial.py,sha256=87crhJGEUzUxe9TaNTzzKM6AjE3Pjw032PkhYrnRz9k,13881
|
@@ -340,18 +343,18 @@ meta_pharmacy/models/substitutions.py,sha256=9FjjD4x66B8SxABZR5atan2S5uE4nJH0tn0
|
|
340
343
|
meta_pharmacy/utils/__init__.py,sha256=PiPRCZqgxPiudXWEvQmXOqFZMoj56NEiOeRuYEyE_9w,71
|
341
344
|
meta_pharmacy/utils/update_initial_pharmacy_data.py,sha256=Od0QL41unsDEGDBYsVCe8k-T3GpMae0DtHt61vz4sYM,4621
|
342
345
|
meta_prn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
343
|
-
meta_prn/action_items.py,sha256=
|
346
|
+
meta_prn/action_items.py,sha256=R83aFDxyOEkmzfJwWMcRYXWXZBcv3hTp_QzHNJPLNPM,9108
|
344
347
|
meta_prn/admin_site.py,sha256=E4dJ6ofu9Kp-1SIWIuY3PraVPbEJSWBBqhorJPGUNqU,161
|
345
348
|
meta_prn/apps.py,sha256=NEqrwzEW277BAPGeL1H1KdK9e74FqXfYHK6o46BtaJA,280
|
346
349
|
meta_prn/baker_recipes.py,sha256=jDGg3btxjtvnsph_AnKoZBDUeTfokMs5iQWnQBJD-jw,660
|
347
350
|
meta_prn/choices.py,sha256=QKqCh2mN_Fgt9Z_la_YVeAqlzRxd507TurFVOOc71G0,3210
|
348
|
-
meta_prn/constants.py,sha256=
|
351
|
+
meta_prn/constants.py,sha256=Z0atxRnTIj-_bWbfYY0vZIwfmbwaFRpSZqb1-K1B4Jo,1018
|
349
352
|
meta_prn/list_data.py,sha256=Se-GxHTuxmU_j17tIdLicKlW6d5E9pVKvYVrTLZQFUo,2336
|
350
353
|
meta_prn/pregnancy_action_item_mixin.py,sha256=E_7aFSs_PXkqlRqRbKohWZSnE7rUcFVr0LacF7EjrvM,1018
|
351
354
|
meta_prn/urls.py,sha256=AOKhAkkdXlr5P4z-WK70Da6Zm9DktKWnPDtXfCKYJWs,202
|
352
355
|
meta_prn/admin/__init__.py,sha256=gwAfqcnkJm8rcjYdtd_e1aFVsv7qDq7RCIHUaheo82c,766
|
353
356
|
meta_prn/admin/dm_referral_admin.py,sha256=IUYxEw-PaAKlC0GnBV5kEUL3jMWGymUeA183SNlqfJg,1401
|
354
|
-
meta_prn/admin/end_of_study_admin.py,sha256=
|
357
|
+
meta_prn/admin/end_of_study_admin.py,sha256=G0l0e1V9FYWky6jh-DG6rqwNB1Uv0NjA0uFkoT55Gyw,5444
|
355
358
|
meta_prn/admin/loss_to_followup_admin.py,sha256=N385T_F1PJZBWw7jldG2HZiWuvlJs3SW92Oa_NNvGHo,1736
|
356
359
|
meta_prn/admin/off_study_medication_admin.py,sha256=pUc6vXvPuoAwTBxE24ztvqEOVSK4wFG8yx_4SQOn918,2377
|
357
360
|
meta_prn/admin/offschedule_admin.py,sha256=FjijlMYfv3DhyNF0kTLmvmLhI1ASmIY8lUl0L-NfUx4,1707
|
@@ -364,7 +367,7 @@ meta_prn/admin/pregnancy_notification_admin.py,sha256=fIAbpU1QEVAGeewxc2xcRu7aip
|
|
364
367
|
meta_prn/admin/protocol_incident_admin.py,sha256=fTlYJAxsrPDB8skZc-_2DxKH21k_cG7UH7wCgnviBck,739
|
365
368
|
meta_prn/admin/subject_transfer_admin.py,sha256=YKViIZLAaAquiN5RO-V6lRoGP76bYwcb_EA5QTEeI2Y,723
|
366
369
|
meta_prn/form_validators/__init__.py,sha256=ROZbWrtQ4dLCwy-aeBcOh0mnYm12qFrvUWzV-BkGvTU,111
|
367
|
-
meta_prn/form_validators/end_of_study.py,sha256=
|
370
|
+
meta_prn/form_validators/end_of_study.py,sha256=nhAymg1c_GH74MrrubzYSJUKdBkRVY4qKj7C0GufrQw,12917
|
368
371
|
meta_prn/form_validators/protocol_incident.py,sha256=g28qDxXpb-RiE4szn-KHFA03GcaAp0VWJ5SD5f3NUp8,1068
|
369
372
|
meta_prn/forms/__init__.py,sha256=rQX_sPN77qzktlfH46qQNcZ1yV1WvKThm2c-jzangNQ,565
|
370
373
|
meta_prn/forms/dm_referral_form.py,sha256=TvJSzKyVXXCOHRw67_i3BP9yEjAwE2yT5H6Y9bLRddU,1350
|
@@ -436,10 +439,13 @@ meta_prn/migrations/0056_alter_endofstudy_clinical_withdrawal_reason_and_more.py
|
|
436
439
|
meta_prn/migrations/0057_historicalonscheduledmreferral_and_more.py,sha256=nGu1PaQHenWjkFaEPPfNJelXFBYNAFJkNiN7NV-BW90,46174
|
437
440
|
meta_prn/migrations/0058_dmreferral_referral_note_and_more.py,sha256=i18Cioesp1J1Szop3UQyNYnujMj8aiSMyioqtIidQsU,876
|
438
441
|
meta_prn/migrations/0059_alter_historicaloffstudymedication_reason_and_more.py,sha256=ToRLwJg6pF_6DIglm729xlC8O4u7nfe0jI_XC0aSarY,2057
|
442
|
+
meta_prn/migrations/0060_alter_onschedule_managers_and_more.py,sha256=zTlobJgliy-LuQ3z_BE_cwpdVddrvpHRvhDH4Hj64zY,1791
|
443
|
+
meta_prn/migrations/0061_auto_20250115_2025.py,sha256=u7F2GsXo-9pIN5z0VM1aXW_KoRjS82BaBqqj2xWwWzo,1937
|
444
|
+
meta_prn/migrations/0062_alter_endofstudy_offstudy_reason_and_more.py,sha256=L1ZDX1IMS0nIs77lF4QZ0SX-ye4rXvouTLnijob8SAQ,2421
|
439
445
|
meta_prn/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
440
446
|
meta_prn/models/__init__.py,sha256=l06P7L2_Aye2h2VmAyb1XRcIbQAOyxws08IMc8sIops,717
|
441
447
|
meta_prn/models/dm_referral.py,sha256=XyyB5ylcaMRCumGRFPlh2Op5VvT9ROJ1A9x5q55vUuc,1096
|
442
|
-
meta_prn/models/end_of_study.py,sha256=
|
448
|
+
meta_prn/models/end_of_study.py,sha256=TBvLrJO6hLNBc69w7jBgWesMoJvrdeiT1TzLPWZffd8,5418
|
443
449
|
meta_prn/models/loss_to_followup.py,sha256=k4fXO6_U-IXiEOOnLrxhhuiTYgisPT5lqqA7mavZFfs,2436
|
444
450
|
meta_prn/models/off_study_medication.py,sha256=qq8DadWCNvFOYLkx9VoDMyLnbuZiRGc7-gmPSb-QVFk,1701
|
445
451
|
meta_prn/models/offschedule.py,sha256=DjQvXteecXJOzN6ijdH36FCxXoR1halzxl9EliqQJk8,1717
|
@@ -1172,9 +1178,9 @@ tests/etc/user-rsa-restricted-private.pem,sha256=CUcHW9bznWdmmASN00hCzvxFPAFl4N2
|
|
1172
1178
|
tests/etc/user-rsa-restricted-public.pem,sha256=mt84djoL-uHw6Wc5SJh0zml6VzXulnf8eQSFg7-fheg,450
|
1173
1179
|
tests/etc/user-salt-local.key,sha256=x5anBw9fvbHurczouT3CjrkWb_xs7Ypm1htIJsgiuiw,256
|
1174
1180
|
tests/etc/user-salt-restricted.key,sha256=pxmpcfBRNB-4C6wTvHXz-9fOfJgKIFOjaAF8ZFfa4q4,256
|
1175
|
-
meta_edc-0.3.
|
1176
|
-
meta_edc-0.3.
|
1177
|
-
meta_edc-0.3.
|
1178
|
-
meta_edc-0.3.
|
1179
|
-
meta_edc-0.3.
|
1180
|
-
meta_edc-0.3.
|
1181
|
+
meta_edc-0.3.52.dist-info/AUTHORS,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1182
|
+
meta_edc-0.3.52.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
1183
|
+
meta_edc-0.3.52.dist-info/METADATA,sha256=CHNa2PqSMdSgqa5owxpy-1gC3fZw5wJIeS6Yyqrlwl4,43529
|
1184
|
+
meta_edc-0.3.52.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
1185
|
+
meta_edc-0.3.52.dist-info/top_level.txt,sha256=RkzjNXwRq2kg_uZ_1bDwPUntijSXoY2YBqtByDwvvrc,244
|
1186
|
+
meta_edc-0.3.52.dist-info/RECORD,,
|
meta_lists/list_data.py
CHANGED
@@ -12,7 +12,11 @@ from edc_ltfu.constants import LOST_TO_FOLLOWUP
|
|
12
12
|
from edc_offstudy.constants import COMPLETED_FOLLOWUP, WITHDRAWAL
|
13
13
|
from edc_transfer.constants import TRANSFERRED
|
14
14
|
|
15
|
-
from meta_prn.constants import
|
15
|
+
from meta_prn.constants import (
|
16
|
+
CLINICAL_WITHDRAWAL,
|
17
|
+
COMPLETED_FOLLOWUP_48,
|
18
|
+
LATE_EXCLUSION,
|
19
|
+
)
|
16
20
|
|
17
21
|
list_data = {
|
18
22
|
"meta_lists.dmmedications": [
|
@@ -197,6 +201,7 @@ list_data = {
|
|
197
201
|
],
|
198
202
|
"meta_lists.offstudyreasons": [
|
199
203
|
(COMPLETED_FOLLOWUP, "Patient completed 36 months of follow-up"),
|
204
|
+
(COMPLETED_FOLLOWUP_48, "Patient completed 48 months of follow-up"),
|
200
205
|
(DELIVERY, "Delivered / Completed followup from pregnancy"),
|
201
206
|
(PREGNANCY, "Pregnancy, declined further followup"),
|
202
207
|
(DIABETES, "Patient developed diabetes"),
|
meta_prn/action_items.py
CHANGED
@@ -90,7 +90,7 @@ class EndOfStudyAction(ActionWithNotification):
|
|
90
90
|
display_name = "Submit End of Study Report"
|
91
91
|
notification_display_name = "End of Study Report"
|
92
92
|
parent_action_names = [
|
93
|
-
|
93
|
+
OFFSCHEDULE_ACTION,
|
94
94
|
OFFSTUDY_MEDICATION_ACTION,
|
95
95
|
OFFSCHEDULE_PREGNANCY_ACTION,
|
96
96
|
OFFSCHEDULE_DM_REFERRAL_ACTION,
|
@@ -12,6 +12,7 @@ from edc_sites.admin import SiteModelAdminMixin
|
|
12
12
|
|
13
13
|
from meta_ae.models import DeathReport
|
14
14
|
from meta_consent.models import SubjectConsent
|
15
|
+
from meta_lists.models import OffstudyReasons
|
15
16
|
|
16
17
|
from ..admin_site import meta_prn_admin
|
17
18
|
from ..forms import EndOfStudyForm
|
@@ -111,6 +112,11 @@ class EndOfStudyAdmin(
|
|
111
112
|
"toxicity_withdrawal_reason": admin.VERTICAL,
|
112
113
|
}
|
113
114
|
|
115
|
+
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
116
|
+
if db_field.name == "offstudy_reason":
|
117
|
+
kwargs["queryset"] = OffstudyReasons.objects.order_by("display_index")
|
118
|
+
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
119
|
+
|
114
120
|
def get_list_display(self, request) -> Tuple[str, ...]:
|
115
121
|
list_display = super().get_list_display(request)
|
116
122
|
custom_fields = (
|
meta_prn/constants.py
CHANGED
@@ -19,11 +19,13 @@ from edc_offstudy.utils import OffstudyError
|
|
19
19
|
from edc_prn.modelform_mixins import PrnFormValidatorMixin
|
20
20
|
from edc_transfer.constants import TRANSFERRED
|
21
21
|
from edc_utils import formatted_date
|
22
|
-
from edc_visit_schedule.constants import MONTH36
|
22
|
+
from edc_visit_schedule.constants import MONTH36, MONTH48
|
23
|
+
from edc_visit_schedule.site_visit_schedules import site_visit_schedules
|
23
24
|
from edc_visit_schedule.utils import off_all_schedules_or_raise
|
24
25
|
|
25
26
|
from ..constants import (
|
26
27
|
CLINICAL_WITHDRAWAL,
|
28
|
+
COMPLETED_FOLLOWUP_48,
|
27
29
|
INVESTIGATOR_DECISION,
|
28
30
|
OFFSTUDY_MEDICATION_ACTION,
|
29
31
|
)
|
@@ -44,6 +46,7 @@ class EndOfStudyFormValidator(
|
|
44
46
|
self.validate_offstudy_datetime_against_last_seen_date()
|
45
47
|
|
46
48
|
self.validate_completed_36m()
|
49
|
+
self.validate_completed_48m()
|
47
50
|
|
48
51
|
self.required_if(DEAD, field="offstudy_reason", field_required="death_date")
|
49
52
|
self.validate_death_report_if_deceased()
|
@@ -108,6 +111,41 @@ class EndOfStudyFormValidator(
|
|
108
111
|
{"offstudy_reason": "Invalid. 36 month visit has not been submitted."},
|
109
112
|
INVALID_ERROR,
|
110
113
|
)
|
114
|
+
schedule = site_visit_schedules.get_visit_schedule(
|
115
|
+
visit_schedule_name="visit_schedule"
|
116
|
+
).schedules.get("schedule")
|
117
|
+
if subject_visit_model_cls.objects.filter(
|
118
|
+
subject_identifier=self.subject_identifier,
|
119
|
+
visit_code_sequence=0,
|
120
|
+
appointment__timepoint__gt=schedule.visits.get(MONTH36).timepoint,
|
121
|
+
appointment__schedule_name=schedule.name,
|
122
|
+
):
|
123
|
+
self.raise_validation_error(
|
124
|
+
{
|
125
|
+
"offstudy_reason": (
|
126
|
+
"Invalid. Participant has attended visits past 36 months."
|
127
|
+
)
|
128
|
+
},
|
129
|
+
INVALID_ERROR,
|
130
|
+
)
|
131
|
+
|
132
|
+
def validate_completed_48m(self):
|
133
|
+
if (
|
134
|
+
self.cleaned_data.get("offstudy_reason")
|
135
|
+
and self.cleaned_data.get("offstudy_reason").name == COMPLETED_FOLLOWUP_48
|
136
|
+
):
|
137
|
+
subject_visit_model_cls = django_apps.get_model("meta_subject.subjectvisit")
|
138
|
+
try:
|
139
|
+
subject_visit_model_cls.objects.get(
|
140
|
+
subject_identifier=self.subject_identifier,
|
141
|
+
visit_code=MONTH48,
|
142
|
+
visit_code_sequence=0,
|
143
|
+
)
|
144
|
+
except ObjectDoesNotExist:
|
145
|
+
self.raise_validation_error(
|
146
|
+
{"offstudy_reason": "Invalid. 48 month visit has not been submitted."},
|
147
|
+
INVALID_ERROR,
|
148
|
+
)
|
111
149
|
|
112
150
|
def validate_pregnancy(self):
|
113
151
|
if (
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Generated by Django 5.1.2 on 2025-01-15 13:29
|
2
|
+
|
3
|
+
import edc_sites.managers
|
4
|
+
import edc_visit_schedule.model_mixins.on_schedule_model_mixin
|
5
|
+
from django.db import migrations
|
6
|
+
|
7
|
+
|
8
|
+
class Migration(migrations.Migration):
|
9
|
+
|
10
|
+
dependencies = [
|
11
|
+
("meta_prn", "0059_alter_historicaloffstudymedication_reason_and_more"),
|
12
|
+
]
|
13
|
+
|
14
|
+
operations = [
|
15
|
+
migrations.AlterModelManagers(
|
16
|
+
name="onschedule",
|
17
|
+
managers=[
|
18
|
+
(
|
19
|
+
"objects",
|
20
|
+
edc_visit_schedule.model_mixins.on_schedule_model_mixin.OnScheduleManager(),
|
21
|
+
),
|
22
|
+
("on_site", edc_sites.managers.CurrentSiteManager()),
|
23
|
+
],
|
24
|
+
),
|
25
|
+
migrations.AlterModelManagers(
|
26
|
+
name="onscheduledmreferral",
|
27
|
+
managers=[
|
28
|
+
(
|
29
|
+
"objects",
|
30
|
+
edc_visit_schedule.model_mixins.on_schedule_model_mixin.OnScheduleManager(),
|
31
|
+
),
|
32
|
+
("on_site", edc_sites.managers.CurrentSiteManager()),
|
33
|
+
],
|
34
|
+
),
|
35
|
+
migrations.AlterModelManagers(
|
36
|
+
name="onschedulepostnatal",
|
37
|
+
managers=[
|
38
|
+
(
|
39
|
+
"objects",
|
40
|
+
edc_visit_schedule.model_mixins.on_schedule_model_mixin.OnScheduleManager(),
|
41
|
+
),
|
42
|
+
("on_site", edc_sites.managers.CurrentSiteManager()),
|
43
|
+
],
|
44
|
+
),
|
45
|
+
migrations.AlterModelManagers(
|
46
|
+
name="onschedulepregnancy",
|
47
|
+
managers=[
|
48
|
+
(
|
49
|
+
"objects",
|
50
|
+
edc_visit_schedule.model_mixins.on_schedule_model_mixin.OnScheduleManager(),
|
51
|
+
),
|
52
|
+
("on_site", edc_sites.managers.CurrentSiteManager()),
|
53
|
+
],
|
54
|
+
),
|
55
|
+
]
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Generated by Django 5.1.2 on 2025-01-15 17:25
|
2
|
+
|
3
|
+
from django.db import IntegrityError, migrations, transaction
|
4
|
+
from edc_constants.constants import OTHER
|
5
|
+
from edc_utils import get_utcnow
|
6
|
+
|
7
|
+
from meta_lists.models import OffstudyReasons
|
8
|
+
from meta_prn.action_items import OffStudyMedicationAction
|
9
|
+
from meta_prn.models import EndOfStudy
|
10
|
+
|
11
|
+
|
12
|
+
def update_offstudy_reason(apps, schema_editor):
|
13
|
+
"""Datafix: Correct offstudy_reason from `other` to 'diabetes'
|
14
|
+
based on the `other_offstudy_reason`. These four subjects were
|
15
|
+
taken off study before 'diabetes' was an option.
|
16
|
+
|
17
|
+
Create missing OffStudyMedicationAction if needed.
|
18
|
+
"""
|
19
|
+
subject_identifiers = [
|
20
|
+
"105-60-0029-0",
|
21
|
+
"105-60-0106-6",
|
22
|
+
"105-60-0145-4",
|
23
|
+
"105-60-0166-0",
|
24
|
+
]
|
25
|
+
other_reason_obj = OffstudyReasons.objects.get(name=OTHER)
|
26
|
+
diabetes_reason_obj = OffstudyReasons.objects.get(name="diabetes")
|
27
|
+
for obj in EndOfStudy.objects.filter(
|
28
|
+
subject_identifier__in=subject_identifiers,
|
29
|
+
offstudy_reason=other_reason_obj,
|
30
|
+
other_offstudy_reason__icontains="THE CLIENT HAS DEVELOPED DIABETES",
|
31
|
+
):
|
32
|
+
try:
|
33
|
+
with transaction.atomic():
|
34
|
+
OffStudyMedicationAction(
|
35
|
+
subject_identifier=obj.subject_identifier,
|
36
|
+
skip_get_current_site=True,
|
37
|
+
site_id=obj.site_id,
|
38
|
+
)
|
39
|
+
except IntegrityError:
|
40
|
+
pass
|
41
|
+
|
42
|
+
obj.offstudy_reason = diabetes_reason_obj
|
43
|
+
obj.comment = obj.other_offstudy_reason
|
44
|
+
obj.comment = obj.comment + " [datafix: 0061_auto_20250115_2025]"
|
45
|
+
obj.other_offstudy_reason = ""
|
46
|
+
obj.modified = get_utcnow()
|
47
|
+
obj.user_modified = "erikvw"
|
48
|
+
obj.save()
|
49
|
+
|
50
|
+
|
51
|
+
class Migration(migrations.Migration):
|
52
|
+
|
53
|
+
dependencies = [
|
54
|
+
("meta_prn", "0060_alter_onschedule_managers_and_more"),
|
55
|
+
]
|
56
|
+
|
57
|
+
operations = [migrations.RunPython(update_offstudy_reason)]
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Generated by Django 5.1.2 on 2025-01-15 23:45
|
2
|
+
|
3
|
+
import django.db.models.deletion
|
4
|
+
from django.db import migrations, models
|
5
|
+
|
6
|
+
|
7
|
+
class Migration(migrations.Migration):
|
8
|
+
|
9
|
+
dependencies = [
|
10
|
+
("meta_lists", "0018_missedreferralreasons"),
|
11
|
+
("meta_prn", "0061_auto_20250115_2025"),
|
12
|
+
]
|
13
|
+
|
14
|
+
operations = [
|
15
|
+
migrations.AlterField(
|
16
|
+
model_name="endofstudy",
|
17
|
+
name="offstudy_reason",
|
18
|
+
field=models.ForeignKey(
|
19
|
+
limit_choices_to={
|
20
|
+
"name__in": [
|
21
|
+
"completed_followup",
|
22
|
+
"completed_followup_48",
|
23
|
+
"diabetes",
|
24
|
+
"delivery",
|
25
|
+
"pregnancy",
|
26
|
+
"clinical_withdrawal",
|
27
|
+
"dead",
|
28
|
+
"LTFU",
|
29
|
+
"toxicity",
|
30
|
+
"transferred",
|
31
|
+
"withdrawal",
|
32
|
+
"late_exclusion",
|
33
|
+
"OTHER",
|
34
|
+
]
|
35
|
+
},
|
36
|
+
null=True,
|
37
|
+
on_delete=django.db.models.deletion.PROTECT,
|
38
|
+
to="meta_lists.offstudyreasons",
|
39
|
+
verbose_name="Reason patient was terminated from the study",
|
40
|
+
),
|
41
|
+
),
|
42
|
+
migrations.AlterField(
|
43
|
+
model_name="historicalendofstudy",
|
44
|
+
name="offstudy_reason",
|
45
|
+
field=models.ForeignKey(
|
46
|
+
blank=True,
|
47
|
+
db_constraint=False,
|
48
|
+
limit_choices_to={
|
49
|
+
"name__in": [
|
50
|
+
"completed_followup",
|
51
|
+
"completed_followup_48",
|
52
|
+
"diabetes",
|
53
|
+
"delivery",
|
54
|
+
"pregnancy",
|
55
|
+
"clinical_withdrawal",
|
56
|
+
"dead",
|
57
|
+
"LTFU",
|
58
|
+
"toxicity",
|
59
|
+
"transferred",
|
60
|
+
"withdrawal",
|
61
|
+
"late_exclusion",
|
62
|
+
"OTHER",
|
63
|
+
]
|
64
|
+
},
|
65
|
+
null=True,
|
66
|
+
on_delete=django.db.models.deletion.DO_NOTHING,
|
67
|
+
related_name="+",
|
68
|
+
to="meta_lists.offstudyreasons",
|
69
|
+
verbose_name="Reason patient was terminated from the study",
|
70
|
+
),
|
71
|
+
),
|
72
|
+
]
|
meta_prn/models/end_of_study.py
CHANGED
@@ -26,11 +26,11 @@ from edc_transfer.constants import TRANSFERRED
|
|
26
26
|
from meta_lists.models import OffstudyReasons
|
27
27
|
|
28
28
|
from ..choices import CLINICAL_WITHDRAWAL_REASONS, TOXICITY_WITHDRAWAL_REASONS
|
29
|
+
from ..constants import CLINICAL_WITHDRAWAL, COMPLETED_FOLLOWUP_48
|
29
30
|
|
30
31
|
# TODO: confirm all appointments are either new, incomplete or done
|
31
32
|
# TODO: take off study meds but coninue followup (WITHDRAWAL)
|
32
33
|
# TODO: follow on new schedule, if permanently off drug (Single 36m visit)
|
33
|
-
from ..constants import CLINICAL_WITHDRAWAL
|
34
34
|
|
35
35
|
|
36
36
|
class EndOfStudy(ActionModelMixin, SiteModelMixin, OffstudyModelMixin, BaseUuidModel):
|
@@ -52,24 +52,28 @@ class EndOfStudy(ActionModelMixin, SiteModelMixin, OffstudyModelMixin, BaseUuidM
|
|
52
52
|
null=True,
|
53
53
|
limit_choices_to={
|
54
54
|
"name__in": [
|
55
|
-
CLINICAL_WITHDRAWAL,
|
56
55
|
COMPLETED_FOLLOWUP,
|
57
|
-
|
58
|
-
DELIVERY,
|
56
|
+
COMPLETED_FOLLOWUP_48,
|
59
57
|
DIABETES,
|
60
|
-
|
61
|
-
LOST_TO_FOLLOWUP,
|
62
|
-
OTHER,
|
58
|
+
DELIVERY,
|
63
59
|
PREGNANCY,
|
60
|
+
CLINICAL_WITHDRAWAL,
|
61
|
+
DEAD,
|
62
|
+
LOST_TO_FOLLOWUP,
|
64
63
|
TOXICITY,
|
65
64
|
TRANSFERRED,
|
66
65
|
WITHDRAWAL,
|
66
|
+
LATE_EXCLUSION,
|
67
|
+
OTHER,
|
67
68
|
]
|
68
69
|
},
|
69
70
|
)
|
70
71
|
|
71
72
|
other_offstudy_reason = models.TextField(
|
72
|
-
verbose_name="If OTHER, please specify",
|
73
|
+
verbose_name="If OTHER, please specify",
|
74
|
+
max_length=500,
|
75
|
+
blank=True,
|
76
|
+
null=True,
|
73
77
|
)
|
74
78
|
|
75
79
|
# TODO: 6m off drug and duration ?? See SOP
|
@@ -95,8 +99,9 @@ class EndOfStudy(ActionModelMixin, SiteModelMixin, OffstudyModelMixin, BaseUuidM
|
|
95
99
|
blank=True,
|
96
100
|
null=True,
|
97
101
|
help_text=(
|
98
|
-
"A UPT CRF must be on file and participant not on the
|
99
|
-
"Use UPT date or, if UPT not needed,
|
102
|
+
"A UPT CRF must be on file and participant not on the "
|
103
|
+
"delivery schedule. Use UPT date or, if UPT not needed, "
|
104
|
+
"use report date on last UPT CRF."
|
100
105
|
),
|
101
106
|
)
|
102
107
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|