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.
@@ -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.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.24
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=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.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 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