meta-edc 0.3.53__py3-none-any.whl → 1.0.1__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.
Files changed (57) hide show
  1. meta_ae/action_items.py +9 -4
  2. meta_ae/admin/ae_initial_admin.py +5 -2
  3. meta_ae/admin/modeladmin_mixins.py +5 -3
  4. meta_ae/templatetags/meta_ae_extras.py +1 -3
  5. meta_consent/action_items.py +1 -0
  6. meta_consent/migrations/0030_auto_20250120_2114.py +40 -0
  7. meta_consent/migrations/0031_alter_historicalsubjectconsent_guardian_name_and_more.py +124 -0
  8. meta_dashboard/templates/meta_dashboard/{bootstrap3/subject → subject}/dashboard/sidebar.html +1 -1
  9. meta_dashboard/templates/meta_dashboard/{bootstrap3/subject → subject}/dashboard/top_bar.html +1 -1
  10. meta_dashboard/templates/meta_dashboard/subject/dashboard.html +14 -0
  11. meta_dashboard/templatetags/meta_dashboard_extras.py +6 -11
  12. meta_dashboard/views/subject/dashboard/dashboard_view.py +6 -5
  13. meta_edc/admin.py +2 -3
  14. meta_edc/settings/debug.py +2 -2
  15. meta_edc/settings/defaults.py +1 -3
  16. meta_edc/templates/meta_edc/{bootstrap3/base.html → base.html} +1 -1
  17. meta_edc/templates/meta_edc/{bootstrap3/home.html → home.html} +1 -1
  18. meta_edc/views/home_view.py +1 -2
  19. {meta_edc-0.3.53.dist-info → meta_edc-1.0.1.dist-info}/METADATA +2 -2
  20. {meta_edc-0.3.53.dist-info → meta_edc-1.0.1.dist-info}/RECORD +56 -50
  21. meta_prn/action_items.py +23 -16
  22. meta_prn/admin/end_of_study_admin.py +12 -6
  23. meta_prn/admin/offschedule_admin.py +8 -6
  24. meta_prn/admin/offschedule_dm_referral_admin.py +8 -6
  25. meta_prn/admin/offschedule_postnatal_admin.py +10 -1
  26. meta_prn/admin/offschedule_pregnancy_admin.py +10 -1
  27. meta_prn/choices.py +4 -0
  28. meta_prn/form_validators/end_of_study.py +10 -4
  29. meta_prn/templates/meta_prn/eos/additional_instructions.html +3 -0
  30. meta_prn/templates/meta_prn/offschedule/additional_instructions.html +2 -0
  31. meta_prn/tests/tests/test_eos_events.py +134 -0
  32. meta_reports/admin/dbviews/imp_substitutions_admin.py +1 -1
  33. meta_reports/admin/dbviews/missing_screening_ogtt_admin/unmanaged_model_admin.py +1 -1
  34. meta_reports/admin/dbviews/patient_history_missing_baseline_cd4_admin.py +1 -1
  35. meta_reports/admin/last_imp_refill_admin.py +5 -19
  36. meta_reports/templates/meta_reports/last_imp_refill/changelist_note.html +13 -0
  37. meta_screening/admin/subject_screening_admin.py +13 -3
  38. meta_screening/eligibility/eligibility.py +14 -7
  39. meta_screening/model_mixins/part_one_fields_model_mixin.py +15 -14
  40. meta_subject/admin/birth_outcome_admin.py +1 -1
  41. meta_subject/admin/delivery_admin.py +1 -1
  42. meta_subject/form_validators/dm_endpoint_form_validator.py +3 -1
  43. meta_subject/models/diabetes/dm_followup.py +3 -3
  44. tests/test_settings.py +0 -1
  45. meta_dashboard/templates/meta_dashboard/bootstrap3/subject/dashboard.html +0 -14
  46. /meta_ae/templates/meta_ae/{bootstrap3/ae_initial_description.html → aeinitial_description.html} +0 -0
  47. /meta_dashboard/templates/meta_dashboard/{bootstrap3/buttons → buttons}/add_consent_button.html +0 -0
  48. /meta_dashboard/templates/meta_dashboard/{bootstrap3/buttons → buttons}/dashboard_button.html +0 -0
  49. /meta_dashboard/templates/meta_dashboard/{bootstrap3/buttons → buttons}/eligibility_button.html +0 -0
  50. /meta_dashboard/templates/meta_dashboard/{bootstrap3/buttons → buttons}/refusal_button.html +0 -0
  51. /meta_dashboard/templates/meta_dashboard/{bootstrap3/buttons → buttons}/screening_button.html +0 -0
  52. /meta_dashboard/templates/meta_dashboard/{bootstrap3/screening → screening}/listboard.html +0 -0
  53. /meta_dashboard/templates/meta_dashboard/{bootstrap3/subject → subject}/listboard.html +0 -0
  54. {meta_edc-0.3.53.dist-info → meta_edc-1.0.1.dist-info}/AUTHORS +0 -0
  55. {meta_edc-0.3.53.dist-info → meta_edc-1.0.1.dist-info}/LICENSE +0 -0
  56. {meta_edc-0.3.53.dist-info → meta_edc-1.0.1.dist-info}/WHEEL +0 -0
  57. {meta_edc-0.3.53.dist-info → meta_edc-1.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,134 @@
1
+ from django.test import TestCase
2
+ from edc_action_item.models import ActionItem
3
+ from edc_constants.constants import FEMALE, NEW, PATIENT, YES
4
+ from edc_offstudy.constants import END_OF_STUDY_ACTION
5
+ from edc_pharmacy.models import Medication
6
+ from edc_transfer.constants import SUBJECT_TRANSFER_ACTION, TRANSFERRED
7
+ from edc_utils import get_utcnow
8
+ from edc_visit_schedule.constants import OFFSCHEDULE_ACTION
9
+
10
+ from meta_lists.models import OffstudyReasons, TransferReasons
11
+ from meta_pharmacy.constants import METFORMIN
12
+ from meta_prn.action_items import OffscheduleAction, SubjectTransferAction
13
+ from meta_prn.constants import OFFSTUDY_MEDICATION_ACTION
14
+ from meta_prn.models import EndOfStudy, OffSchedule, OffStudyMedication, SubjectTransfer
15
+ from meta_screening.tests.meta_test_case_mixin import MetaTestCaseMixin
16
+
17
+
18
+ class TestEosEvents(MetaTestCaseMixin, TestCase):
19
+ def setUp(self):
20
+ super().setUp()
21
+ self.subject_screening = self.get_subject_screening(gender=FEMALE)
22
+ self.subject_consent = self.get_subject_consent(self.subject_screening)
23
+ self.subject_visit = self.get_subject_visit(
24
+ subject_screening=self.subject_screening,
25
+ subject_consent=self.subject_consent,
26
+ )
27
+ self.data = dict(
28
+ subject_visit=self.subject_visit.pk,
29
+ report_datetime=self.subject_visit.report_datetime,
30
+ )
31
+
32
+ def test_transfer_to_offschedule_in_order(self):
33
+ SubjectTransferAction(
34
+ subject_identifier=self.subject_consent.subject_identifier,
35
+ skip_get_current_site=True,
36
+ site_id=self.subject_consent.site_id,
37
+ )
38
+ action_types = [
39
+ obj.action_type.name
40
+ for obj in ActionItem.objects.filter(status=NEW).order_by("action_type__name")
41
+ ]
42
+ self.assertEqual(action_types, [SUBJECT_TRANSFER_ACTION])
43
+
44
+ # add a subject transfer object, which triggers next action item
45
+ transfer_reason = TransferReasons.objects.get(name="moved")
46
+ subject_transfer = SubjectTransfer.objects.create(
47
+ subject_identifier=self.subject_consent.subject_identifier,
48
+ initiated_by="patient",
49
+ may_return=YES,
50
+ may_contact=YES,
51
+ )
52
+ subject_transfer.transfer_reason.add(transfer_reason)
53
+
54
+ action_types = [
55
+ obj.action_type.name
56
+ for obj in ActionItem.objects.filter(status=NEW).order_by("action_type__name")
57
+ ]
58
+ self.assertEqual(action_types, [OFFSCHEDULE_ACTION, OFFSTUDY_MEDICATION_ACTION])
59
+
60
+ OffSchedule.objects.create(subject_identifier=self.subject_consent.subject_identifier)
61
+
62
+ action_types = [
63
+ obj.action_type.name
64
+ for obj in ActionItem.objects.filter(status=NEW).order_by("action_type__name")
65
+ ]
66
+ self.assertEqual(action_types, [OFFSTUDY_MEDICATION_ACTION])
67
+
68
+ offstudy_rx = OffStudyMedication.objects.create(
69
+ subject_identifier=self.subject_consent.subject_identifier,
70
+ stop_date=get_utcnow().date(),
71
+ last_dose_date=get_utcnow().date(),
72
+ reason=PATIENT,
73
+ )
74
+ offstudy_rx.medications.add(Medication.objects.get(name=METFORMIN))
75
+
76
+ action_types = [
77
+ obj.action_type.name
78
+ for obj in ActionItem.objects.filter(status=NEW).order_by("action_type__name")
79
+ ]
80
+ self.assertEqual(action_types, [END_OF_STUDY_ACTION])
81
+
82
+ EndOfStudy.objects.create(
83
+ subject_identifier=self.subject_consent.subject_identifier,
84
+ last_seen_date=get_utcnow().date(),
85
+ offstudy_reason=OffstudyReasons.objects.get(name=TRANSFERRED),
86
+ )
87
+
88
+ action_types = [
89
+ obj.action_type.name
90
+ for obj in ActionItem.objects.filter(status=NEW).order_by("action_type__name")
91
+ ]
92
+ self.assertEqual(action_types, [])
93
+
94
+ def test_transfer_to_offschedule_raises(self):
95
+
96
+ OffscheduleAction(
97
+ subject_identifier=self.subject_consent.subject_identifier,
98
+ skip_get_current_site=True,
99
+ site_id=self.subject_consent.site_id,
100
+ )
101
+
102
+ OffSchedule.objects.create(subject_identifier=self.subject_consent.subject_identifier)
103
+
104
+ action_types = [
105
+ obj.action_type.name
106
+ for obj in ActionItem.objects.filter(status=NEW).order_by("action_type__name")
107
+ ]
108
+ self.assertEqual(action_types, [OFFSTUDY_MEDICATION_ACTION])
109
+
110
+ offstudy_rx = OffStudyMedication.objects.create(
111
+ subject_identifier=self.subject_consent.subject_identifier,
112
+ stop_date=get_utcnow().date(),
113
+ last_dose_date=get_utcnow().date(),
114
+ reason=PATIENT,
115
+ )
116
+ offstudy_rx.medications.add(Medication.objects.get(name=METFORMIN))
117
+
118
+ action_types = [
119
+ obj.action_type.name
120
+ for obj in ActionItem.objects.filter(status=NEW).order_by("action_type__name")
121
+ ]
122
+ self.assertEqual(action_types, [END_OF_STUDY_ACTION])
123
+
124
+ EndOfStudy.objects.create(
125
+ subject_identifier=self.subject_consent.subject_identifier,
126
+ last_seen_date=get_utcnow().date(),
127
+ offstudy_reason=OffstudyReasons.objects.get(name=TRANSFERRED),
128
+ )
129
+
130
+ action_types = [
131
+ obj.action_type.name
132
+ for obj in ActionItem.objects.filter(status=NEW).order_by("action_type__name")
133
+ ]
134
+ self.assertEqual(action_types, [])
@@ -65,7 +65,7 @@ class ImpSubstitutionsAdmin(
65
65
  pass
66
66
  url = reverse(self.get_subject_dashboard_url_name(obj=obj), kwargs=kwargs)
67
67
  context = dict(title=_("Go to subject's dashboard@1000"), url=url, label=label)
68
- return render_to_string("dashboard_button.html", context=context)
68
+ return render_to_string("edc_subject_dashboard/dashboard_button.html", context=context)
69
69
 
70
70
  @admin.display(description="Subject", ordering="subject_identifier")
71
71
  def subject(self, obj):
@@ -69,7 +69,7 @@ class MissingScreeningOgttAdmin(
69
69
  args=(obj.original_id,),
70
70
  )
71
71
  context = dict(title=_("Go to subject screening"), url=url, label=label)
72
- return render_to_string("dashboard_button.html", context=context)
72
+ return render_to_string("edc_subject_dashboard/dashboard_button.html", context=context)
73
73
 
74
74
  @admin.display(description="Screen date", ordering="screening_datetime")
75
75
  def screening_date(self, obj):
@@ -55,4 +55,4 @@ class PatientHistoryMissingBaselineCd4Admin(
55
55
  pass
56
56
  url = reverse(self.get_subject_dashboard_url_name(obj=obj), kwargs=kwargs)
57
57
  context = dict(title=_("Go to subject's dashboard@1000"), url=url, label=label)
58
- return render_to_string("dashboard_button.html", context=context)
58
+ return render_to_string("edc_subject_dashboard/dashboard_button.html", context=context)
@@ -2,7 +2,8 @@ from django.contrib import admin, messages
2
2
  from django.core.exceptions import FieldDoesNotExist
3
3
  from django.db import models
4
4
  from django.db.models import QuerySet
5
- from django.utils.html import format_html
5
+ from django.template.loader import render_to_string
6
+ from django.utils.safestring import mark_safe
6
7
  from edc_model_admin.dashboard import ModelAdminDashboardMixin
7
8
  from edc_model_admin.list_filters import PastDateListFilter
8
9
  from edc_model_admin.mixins import TemplatesModelAdminMixin
@@ -70,24 +71,9 @@ class LastImpRefillAdmin(
70
71
 
71
72
  change_list_title = "List of most recent IMP refills per subject"
72
73
 
73
- change_list_note = format_html(
74
- """
75
- This report fetches the most recent Study Medication report where IMP was
76
- refilled and adds the subject's next visit. Subjects taken "Off Schedule"
77
- are not included in this report. To update ALL rows in this report, tick
78
- at least one row and select 'Update report' action below.
79
- <BR><BR>
80
- This report has additional search features for numeric columns:
81
- <code>days_since</code>, <code>days_until</code>, <code>imp_visit_code</code>
82
- and <code>next_visit_code</code>.
83
- <BR><BR>For example, type <code>days_until>=25</code> in the search below
84
- to show rows for subjects who have an appointment 25 or more days from the
85
- date this report was created. You might also try typing
86
- <code>days_since>365</code> or <code>days_until<0</code>.
87
- <BR><BR>
88
- This also works: <code>next_visit_code>=1060</code>.
89
- """
90
- )
74
+ change_list_note = mark_safe(
75
+ render_to_string("meta_reports/last_imp_refill/changelist_note.html")
76
+ ) # nosec B308, B703
91
77
 
92
78
  actions = [update_report, export_to_csv]
93
79
 
@@ -0,0 +1,13 @@
1
+ {% load i18n %}
2
+ {% trans "This report fetches the most recent Study Medication report where IMP was refilled and adds the subject's next visit. Subjects taken 'Off Schedule' are not included in this report. To update ALL rows in this report, tick at least one row and select 'Update report' action below." %}
3
+ <BR><BR>
4
+ {% trans "This report has additional search features for numeric columns" %}:
5
+ <code>days_since</code>, <code>days_until</code>, <code>imp_visit_code</code> {% trans "and" %} <code>next_visit_code</code>.
6
+ <BR><BR>
7
+ {% trans "For example, type" %}
8
+ <code>days_until>=25</code>
9
+ {% trans "in the search below to show rows for subjects who have an appointment 25 or more days from the date this report was created. You might also try typing" %}
10
+ <code>days_since>365</code> or <code>days_until<0</code>.
11
+ <BR><BR>
12
+ {% trans "This also works" %}:
13
+ <code>next_visit_code>=1060</code>.
@@ -3,6 +3,7 @@ from django.template.loader import render_to_string
3
3
  from django.urls.base import reverse
4
4
  from django.urls.exceptions import NoReverseMatch
5
5
  from django.utils.html import format_html
6
+ from django.utils.safestring import mark_safe
6
7
  from django.utils.translation import gettext_lazy as _
7
8
  from django_audit_fields.admin import audit_fieldset_tuple
8
9
  from edc_constants.constants import YES
@@ -160,7 +161,10 @@ class SubjectScreeningAdmin(ModelAdminSubjectDashboardMixin, SimpleHistoryAdmin)
160
161
  ]
161
162
  if obj.repeat_glucose_opinion == YES:
162
163
  data.append(f"Contact #: {obj.contact_number or '--'}")
163
- return format_html("<BR>".join(data))
164
+ return format_html(
165
+ "{}",
166
+ mark_safe("<BR>".join(data)), # nosec B703, B308
167
+ )
164
168
 
165
169
  def reasons(self, obj=None):
166
170
  if not obj.reasons_ineligible:
@@ -177,8 +181,14 @@ class SubjectScreeningAdmin(ModelAdminSubjectDashboardMixin, SimpleHistoryAdmin)
177
181
  url=f"{screening_listboard_url}?q={obj.screening_identifier}",
178
182
  label="Screening",
179
183
  )
180
- button = render_to_string("dashboard_button.html", context=context)
181
- return format_html(button + "<BR>" + eligibility.eligibility_status(add_urls=True))
184
+ button = render_to_string(
185
+ "edc_subject_dashboard/dashboard_button.html", context=context
186
+ )
187
+ return format_html(
188
+ "{button}<BR>{status}",
189
+ button=button,
190
+ status=eligibility.eligibility_status(add_urls=True),
191
+ )
182
192
 
183
193
  def dashboard(self, obj=None, label=None):
184
194
  try:
@@ -195,15 +195,22 @@ class MetaEligibility:
195
195
  args=(self.part_three.model_obj.id,),
196
196
  )
197
197
  status_str = format_html(
198
- f'<A href="{url_p1}">P1: {self.part_one.eligible.upper()}</A><BR>'
199
- f'<A href="{url_p2}">P2: {self.part_two.eligible.upper()}</A><BR>'
200
- f'<A href="{url_p3}">P3: {self.part_three.eligible.upper()}</A><BR>'
198
+ '<A href="{url_p1}">P1: {p1_eligible}</A>'
199
+ '<BR><A href="{url_p2}">P2: {p2_eligible}</A>'
200
+ '<BR><A href="{url_p3}">P3: {p3_eligible}</A><BR>',
201
+ url_p1=url_p1,
202
+ p1_eligible=self.part_one.eligible.upper(),
203
+ url_p2=url_p2,
204
+ p2_eligible=self.part_two.eligible.upper(),
205
+ url_p3=url_p3,
206
+ p3_eligible=self.part_three.eligible.upper(),
201
207
  )
202
208
  else:
203
- status_str = (
204
- f"P1: {self.part_one.eligible.upper()}<BR>"
205
- f"P2: {self.part_two.eligible.upper()}<BR>"
206
- f"P3: {self.part_three.eligible.upper()}<BR>"
209
+ status_str = format_html(
210
+ "P1: {p1_eligible}<BR>" "P2: {p2_eligible}<BR>" "P3: {p3_eligible}<BR>",
211
+ p1_eligible=self.part_one.eligible.upper(),
212
+ p2_eligible=self.part_two.eligible.upper(),
213
+ p3_eligible=self.part_three.eligible.upper(),
207
214
  )
208
215
  display_label = self.display_label
209
216
  if "PENDING" in display_label:
@@ -1,5 +1,4 @@
1
1
  from django.db import models
2
- from django.utils.html import format_html
3
2
  from django.utils.safestring import mark_safe
4
3
  from django_crypto_fields.fields import EncryptedCharField
5
4
  from edc_constants.choices import SELECTION_METHOD, YES_NO, YES_NO_NA, YESDEFAULT_NO
@@ -11,10 +10,10 @@ from ..constants import PREG_YES_NO_NA
11
10
 
12
11
  class PartOneFieldsModelMixin(models.Model):
13
12
  screening_consent = models.CharField(
14
- verbose_name=format_html(
13
+ verbose_name=mark_safe(
15
14
  "Has the subject given his/her verbal consent to be screened for "
16
15
  "the <u>META Phase 3</u> trial?"
17
- ),
16
+ ), # nosec B308
18
17
  max_length=15,
19
18
  choices=YES_NO,
20
19
  )
@@ -26,7 +25,9 @@ class PartOneFieldsModelMixin(models.Model):
26
25
  )
27
26
 
28
27
  meta_phase_two = models.CharField(
29
- verbose_name=format_html("Was the subject enrolled in the <u>META Phase 2</u> trial?"),
28
+ verbose_name=mark_safe(
29
+ "Was the subject enrolled in the <u>META Phase 2</u> trial?"
30
+ ), # nosec B308
30
31
  max_length=15,
31
32
  choices=YES_NO,
32
33
  null=True,
@@ -44,9 +45,9 @@ class PartOneFieldsModelMixin(models.Model):
44
45
  )
45
46
 
46
47
  art_six_months = models.CharField(
47
- verbose_name=format_html(
48
+ verbose_name=mark_safe(
48
49
  "Has the patient been on anti-retroviral therapy for <u>at least 6 months</u>"
49
- ),
50
+ ), # nosec B308
50
51
  max_length=15,
51
52
  choices=YES_NO_NA,
52
53
  )
@@ -59,10 +60,10 @@ class PartOneFieldsModelMixin(models.Model):
59
60
  )
60
61
 
61
62
  vl_undetectable = models.CharField(
62
- verbose_name=format_html(
63
+ verbose_name=mark_safe(
63
64
  "Does the patient have a viral load measure of less than 1000 copies per ml "
64
65
  "taken <u>within the last 12 months</u>"
65
- ),
66
+ ), # nosec B308
66
67
  max_length=15,
67
68
  choices=YES_NO_NA,
68
69
  )
@@ -86,10 +87,10 @@ class PartOneFieldsModelMixin(models.Model):
86
87
  )
87
88
 
88
89
  staying_nearby_12 = models.CharField(
89
- verbose_name=format_html(
90
+ verbose_name=mark_safe(
90
91
  "Is the patient planning to remain in the catchment area "
91
92
  "for <u>at least 12 months</u>"
92
- ),
93
+ ), # nosec B308
93
94
  max_length=15,
94
95
  choices=YES_NO,
95
96
  null=True,
@@ -101,18 +102,18 @@ class PartOneFieldsModelMixin(models.Model):
101
102
  )
102
103
 
103
104
  continue_part_two = models.CharField(
104
- verbose_name=mark_safe( # nosec B308
105
+ verbose_name=mark_safe(
105
106
  "Continue with <U>part two</U> of the screening process?"
106
- ),
107
+ ), # nosec B308
107
108
  max_length=15,
108
109
  choices=YESDEFAULT_NO,
109
110
  default=YES,
110
- help_text=mark_safe( # nosec B308
111
+ help_text=mark_safe(
111
112
  "<B>Important</B>: This response will be be automatically "
112
113
  "set to YES if:<BR><BR>"
113
114
  "- the participant meets the eligibility criteria for part one, or;<BR><BR>"
114
115
  "- the eligibility criteria for part two is already complete.<BR>"
115
- ),
116
+ ), # nosec B308
116
117
  )
117
118
 
118
119
  class Meta:
@@ -72,7 +72,7 @@ class BirthOutcomesAdmin(
72
72
  url = reverse("meta_subject_admin:meta_subject_delivery_changelist")
73
73
  url = f"{url}?q={obj.subject_identifier}"
74
74
  context = dict(title="Delivery", url=url, label="Delivery")
75
- return render_to_string("dashboard_button.html", context=context)
75
+ return render_to_string("edc_subject_dashboard/dashboard_button.html", context=context)
76
76
 
77
77
  def get_subject_dashboard_url_kwargs(self, obj):
78
78
  return dict(subject_identifier=obj.subject_identifier)
@@ -116,4 +116,4 @@ class DeliveryAdmin(
116
116
  url = reverse("meta_subject_admin:meta_subject_birthoutcomes_changelist")
117
117
  url = f"{url}?q={obj.subject_identifier}"
118
118
  context = dict(title="Outcomes", url=url, label="Outcomes")
119
- return render_to_string("dashboard_button.html", context=context)
119
+ return render_to_string("edc_subject_dashboard/dashboard_button.html", context=context)
@@ -2,6 +2,7 @@ from django.core.exceptions import ObjectDoesNotExist
2
2
  from django.template.loader import render_to_string
3
3
  from django.urls import reverse
4
4
  from django.utils.html import format_html
5
+ from django.utils.safestring import mark_safe
5
6
  from edc_crf.crf_form_validator import CrfFormValidator
6
7
  from edc_form_validators import INVALID_ERROR
7
8
 
@@ -28,7 +29,8 @@ class DmEndpointFormValidator(CrfFormValidator):
28
29
  self.raise_validation_error(
29
30
  {
30
31
  "__all__": format_html(
31
- f"Subject has not reached the protocol endpoint. See {link}"
32
+ "Subject has not reached the protocol endpoint. See {link}",
33
+ link=mark_safe(link), # nosec B703, B308
32
34
  )
33
35
  },
34
36
  INVALID_ERROR,
@@ -1,6 +1,6 @@
1
1
  from django.core.validators import MaxValueValidator, MinValueValidator
2
2
  from django.db import models
3
- from django.utils.html import format_html
3
+ from django.utils.safestring import mark_safe
4
4
  from edc_action_item.models import ActionItem
5
5
  from edc_adherence.choices import MISSED_PILLS
6
6
  from edc_constants.choices import YES_NO, YES_NO_NA
@@ -186,10 +186,10 @@ class DmFollowup(CrfWithActionModelMixin, BaseUuidModel):
186
186
  )
187
187
 
188
188
  visual_score_confirmed = models.IntegerField(
189
- verbose_name=format_html(
189
+ verbose_name=mark_safe(
190
190
  "<B><font color='orange'>Interviewer</font></B>: "
191
191
  "please transcribe the score indicated from above."
192
- ),
192
+ ), # nosec B308
193
193
  validators=[MinValueValidator(0), MaxValueValidator(100)],
194
194
  help_text="%",
195
195
  null=True,
tests/test_settings.py CHANGED
@@ -74,7 +74,6 @@ project_settings = DefaultTestSettings(
74
74
  subject_dashboard_template="meta_dashboard/subject/dashboard.html",
75
75
  subject_review_listboard_template="edc_review_dashboard/subject_review_listboard.html",
76
76
  ),
77
- EDC_BOOTSTRAP=3,
78
77
  EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend",
79
78
  EMAIL_CONTACTS={
80
79
  "ae_reports": "someone@example.com",
@@ -1,14 +0,0 @@
1
- {% extends 'edc_subject_dashboard/bootstrap3/dashboard.html' %}
2
-
3
- {% load static %}
4
-
5
- {% block locator_information %}{% endblock locator_information %}
6
-
7
- {% block top_bar %}
8
-
9
- {% include "meta_dashboard/bootstrap3/subject/dashboard/top_bar.html" %}
10
-
11
- {% endblock top_bar %}
12
-
13
-
14
- {% block side_bar %}{% include 'meta_dashboard/bootstrap3/subject/dashboard/sidebar.html' %}{% endblock side_bar %}