meta-edc 0.3.50__py3-none-any.whl → 0.3.52__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.
- 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
|