meta-edc 0.3.50__py3-none-any.whl → 0.3.51__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- radio_fields = {"agrees_to_extension": admin.VERTICAL}
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(SiteModelFormMixin, FormValidatorMixin, forms.ModelForm):
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
+ ]
@@ -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, SiteModelMixin, ActionModelMixin, BaseUuidModel
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)
@@ -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
- # SITE_ID = SiteID(default=60) # Mnazi-Moja
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.50
3
+ Version: 0.3.51
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.24
696
+ Requires-Dist: edc==0.6.25
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=QV3vL2aXSEH3Qi8cd4wQJLpkEJbJj93xqIUk2h8uWYA,1439
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=LVbMSYEkJNSeaU0QWttFRPekrgr8bb10GL6ukr0tRmY,437
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=LHBR-27E5kDv0MyQKq0E7yKi-wETvOmHqDM0QO5M4Fk,934
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=pqPPuRAuwopIx8FAkJ3ZpZwBp5NRGugEe4yq4eUQTKM,1296
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=yf2LMdTHd85cQW91UO-K3dVsALA7st9P3NQ9OaQJJiA,9702
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=wz6X7Ve8ibqPBO5kB6S9rYYkWfx8JxsoYRVppzIGAzk,9110
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=Sx5yFdV39CxalQ74cXvhIUwlJaKVn3CCBVRJwZJLALw,970
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=uidlvWj2280kSxocNDX0mKP63lsMtCmiuWCR_ySn_0Q,5121
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=jSLn5nFTrg7b3b5h86ynN6dT8VavKwUQNLvTAyBH6iI,11246
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=rAA5H5m2cXKSTwQAxYyXSrzlm0jG7RnWZ1EInBYfOP4,5316
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.50.dist-info/AUTHORS,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1176
- meta_edc-0.3.50.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
1177
- meta_edc-0.3.50.dist-info/METADATA,sha256=vVYCwcmwZIY7HWi1cz8w3BIk8u-V2Dt_fuuhBqVhJKI,43529
1178
- meta_edc-0.3.50.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
1179
- meta_edc-0.3.50.dist-info/top_level.txt,sha256=RkzjNXwRq2kg_uZ_1bDwPUntijSXoY2YBqtByDwvvrc,244
1180
- meta_edc-0.3.50.dist-info/RECORD,,
1181
+ meta_edc-0.3.51.dist-info/AUTHORS,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1182
+ meta_edc-0.3.51.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
1183
+ meta_edc-0.3.51.dist-info/METADATA,sha256=tIZcCLmKTFyK47iHNEMztEeSh7uOzT0ZCfeFiMcCAIQ,43529
1184
+ meta_edc-0.3.51.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
1185
+ meta_edc-0.3.51.dist-info/top_level.txt,sha256=RkzjNXwRq2kg_uZ_1bDwPUntijSXoY2YBqtByDwvvrc,244
1186
+ meta_edc-0.3.51.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 CLINICAL_WITHDRAWAL, LATE_EXCLUSION
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
- # OFFSCHEDULE_ACTION,
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
@@ -1,5 +1,6 @@
1
1
  CLINICAL_WITHDRAWAL = "clinical_withdrawal"
2
2
  CLINICIAN = "clinician"
3
+ COMPLETED_FOLLOWUP_48 = "completed_followup_48"
3
4
  DEATH_REPORT_ACTION = "submit_death_report"
4
5
  DEVIATION = "protocol_deviation"
5
6
  DM_REFFERAL_ACTION = "dm_refferal_action"
@@ -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
+ ]
@@ -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
- DEAD,
58
- DELIVERY,
56
+ COMPLETED_FOLLOWUP_48,
59
57
  DIABETES,
60
- LATE_EXCLUSION,
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", max_length=500, blank=True, null=True
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 delivery schedule. "
99
- "Use UPT date or, if UPT not needed, use report date on last UPT CRF."
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