clinicedc 2.0.24__py3-none-any.whl → 2.0.25__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.

Potentially problematic release.


This version of clinicedc might be problematic. Click here for more details.

Files changed (47) hide show
  1. {clinicedc-2.0.24.dist-info → clinicedc-2.0.25.dist-info}/METADATA +1 -1
  2. {clinicedc-2.0.24.dist-info → clinicedc-2.0.25.dist-info}/RECORD +47 -47
  3. edc_action_item/site_action_items.py +2 -2
  4. edc_appointment/view_utils/appointment_button.py +1 -1
  5. edc_consent/modeladmin_mixins/consent_model_admin_mixin.py +14 -12
  6. edc_consent/models/__init__.py +2 -2
  7. edc_consent/models/signals.py +26 -28
  8. edc_dashboard/view_mixins/message_view_mixin.py +3 -4
  9. edc_identifier/admin.py +3 -2
  10. edc_identifier/identifier.py +5 -5
  11. edc_identifier/model_mixins.py +1 -1
  12. edc_identifier/models.py +7 -6
  13. edc_identifier/research_identifier.py +1 -1
  14. edc_identifier/short_identifier.py +1 -1
  15. edc_identifier/simple_identifier.py +15 -5
  16. edc_identifier/subject_identifier.py +2 -2
  17. edc_identifier/utils.py +13 -14
  18. edc_listboard/view_mixins/search_listboard_view_mixin.py +6 -7
  19. edc_metadata/view_mixins/metadata_view_mixin.py +7 -4
  20. edc_randomization/randomization_list_importer.py +21 -17
  21. edc_randomization/randomization_list_verifier.py +2 -2
  22. edc_randomization/randomizer.py +1 -1
  23. edc_review_dashboard/middleware.py +4 -4
  24. edc_review_dashboard/views/subject_review_listboard_view.py +3 -3
  25. edc_screening/age_evaluator.py +1 -1
  26. edc_screening/eligibility.py +5 -5
  27. edc_screening/exceptions.py +3 -3
  28. edc_screening/gender_evaluator.py +1 -1
  29. edc_screening/modelform_mixins.py +1 -1
  30. edc_screening/screening_eligibility.py +30 -35
  31. edc_screening/utils.py +10 -12
  32. edc_sites/admin/list_filters.py +2 -2
  33. edc_sites/admin/site_model_admin_mixin.py +8 -8
  34. edc_sites/exceptions.py +1 -1
  35. edc_sites/forms.py +3 -1
  36. edc_sites/management/commands/sync_sites.py +11 -17
  37. edc_sites/models/site_profile.py +6 -4
  38. edc_sites/post_migrate_signals.py +2 -2
  39. edc_sites/site.py +8 -8
  40. edc_sites/utils/add_or_update_django_sites.py +3 -3
  41. edc_sites/utils/valid_site_for_subject_or_raise.py +7 -4
  42. edc_sites/view_mixins.py +2 -2
  43. edc_subject_dashboard/view_utils/subject_screening_button.py +5 -3
  44. edc_view_utils/dashboard_model_button.py +3 -3
  45. edc_visit_schedule/site_visit_schedules.py +2 -1
  46. {clinicedc-2.0.24.dist-info → clinicedc-2.0.25.dist-info}/WHEEL +0 -0
  47. {clinicedc-2.0.24.dist-info → clinicedc-2.0.25.dist-info}/licenses/LICENSE +0 -0
@@ -8,6 +8,8 @@ from django.core.exceptions import ObjectDoesNotExist
8
8
  from django.db import models
9
9
  from django.utils import timezone
10
10
 
11
+ from edc_constants.constants import NULL_STRING
12
+
11
13
  from .utils import convert_to_human_readable
12
14
 
13
15
  if TYPE_CHECKING:
@@ -22,10 +24,13 @@ class IdentifierError(Exception):
22
24
  pass
23
25
 
24
26
 
27
+ IDENTIFER_PREFIX_LENGTH = 2
28
+
29
+
25
30
  class SimpleIdentifier:
26
31
  random_string_length: int = 5
27
32
  template: str = "{device_id}{random_string}"
28
- identifier_prefix: str = None
33
+ identifier_prefix: str = NULL_STRING
29
34
 
30
35
  def __init__(
31
36
  self,
@@ -90,7 +95,7 @@ class SimpleSequentialIdentifier:
90
95
  random_number: int = choice(range(1000, 9999)) # nosec B311
91
96
  sequence: str = f"{sequence}{random_number}"
92
97
  chk: int = int(sequence) % 11
93
- self.identifier: str = f"{self.prefix or ''}{sequence}{chk}"
98
+ self.identifier: str = f"{self.prefix or NULL_STRING}{sequence}{chk}"
94
99
 
95
100
  def __str__(self) -> str:
96
101
  return self.identifier
@@ -111,12 +116,13 @@ class SimpleUniqueIdentifier:
111
116
  model: str = "edc_identifier.identifiermodel"
112
117
  template: str = "{device_id}{random_string}"
113
118
  identifier_prefix: str | None = None
119
+ identifier_prefix_length: int = 2
114
120
  identifier_cls = SimpleIdentifier
115
121
  make_human_readable: bool | None = None
116
122
 
117
123
  def __init__(
118
124
  self,
119
- model: str = None,
125
+ model: str | None = None,
120
126
  identifier_attr: str | None = None,
121
127
  identifier_type: str | None = None,
122
128
  identifier_prefix: str | None = None,
@@ -140,9 +146,13 @@ class SimpleUniqueIdentifier:
140
146
  self.identifier_attr = identifier_attr or self.identifier_attr
141
147
  self.identifier_type = identifier_type or self.identifier_type
142
148
  self.identifier_prefix = identifier_prefix or self.identifier_prefix
143
- if self.identifier_prefix and len(self.identifier_prefix) != 2:
149
+ if (
150
+ self.identifier_prefix
151
+ and len(self.identifier_prefix) != self.identifier_prefix_length
152
+ ):
144
153
  raise IdentifierError(
145
- f"Expected identifier_prefix of length=2. Got {len(identifier_prefix)}"
154
+ f"Expected identifier_prefix of length={self.identifier_prefix_length}. "
155
+ f"Got {len(identifier_prefix)}"
146
156
  )
147
157
  self.make_human_readable = make_human_readable or self.make_human_readable
148
158
  self.device_id = django_apps.get_app_config("edc_device").device_id
@@ -10,8 +10,8 @@ class SubjectIdentifier(ResearchIdentifier):
10
10
  label: str = "subjectidentifier"
11
11
  padding: int = 4
12
12
 
13
- def __init__(self, last_name: str = None, **kwargs):
14
- self.last_name = last_name
13
+ def __init__(self, last_name: str | None = None, **kwargs):
14
+ self.last_name = last_name or ""
15
15
  super().__init__(**kwargs)
16
16
 
17
17
  def pre_identifier(self) -> None:
edc_identifier/utils.py CHANGED
@@ -14,23 +14,22 @@ def is_subject_identifier_or_raise(subject_identifier, reference_obj=None, raise
14
14
  * If `subject_identifier` is None, does nothing, unless
15
15
  `raise_on_none` is `True`.
16
16
  """
17
- if subject_identifier or raise_on_none:
18
- if not re.match(
19
- ResearchProtocolConfig().subject_identifier_pattern,
20
- subject_identifier or "",
21
- ):
22
- reference_msg = ""
23
- if reference_obj:
24
- reference_msg = f"See {reference_obj!r}. "
25
- raise SubjectIdentifierError(
26
- f"Invalid format for subject identifier. {reference_msg}"
27
- f"Got `{subject_identifier or ''}`. "
28
- f"Expected pattern `{ResearchProtocolConfig().subject_identifier_pattern}`"
29
- )
17
+ valid_subject_identifier = subject_identifier and re.match(
18
+ ResearchProtocolConfig().subject_identifier_pattern, subject_identifier or ""
19
+ )
20
+ if not valid_subject_identifier or (not subject_identifier and raise_on_none):
21
+ reference_msg = ""
22
+ if reference_obj:
23
+ reference_msg = f"See {reference_obj!r}. "
24
+ raise SubjectIdentifierError(
25
+ f"Invalid format for subject identifier. {reference_msg}"
26
+ f"Got `{subject_identifier or ''}`. "
27
+ f"Expected pattern `{ResearchProtocolConfig().subject_identifier_pattern}`"
28
+ )
30
29
  return subject_identifier
31
30
 
32
31
 
33
- def get_human_phrase(no_hyphen: bool = None) -> str:
32
+ def get_human_phrase(no_hyphen: bool | None = None) -> str:
34
33
  """Returns 6 digits split by a '-', e.g. DEC-96E.
35
34
 
36
35
  There are 213,127,200 permutations from an unambiguous alphabet.
@@ -13,12 +13,12 @@ from edc_model_admin.utils import add_to_messages_once
13
13
 
14
14
 
15
15
  class SearchListboardMixin:
16
- search_fields = ["slug"]
16
+ search_fields = ("slug",)
17
17
 
18
18
  default_querystring_attrs: str = "q"
19
19
  alternate_search_attr: str = "subject_identifier"
20
20
  default_lookup = "icontains"
21
- operators: list[str] = [
21
+ operators: tuple[str, ...] = (
22
22
  "exact",
23
23
  "iexact",
24
24
  "contains",
@@ -33,7 +33,7 @@ class SearchListboardMixin:
33
33
  "endswith",
34
34
  "istartswith",
35
35
  "iendswith",
36
- ]
36
+ )
37
37
 
38
38
  def __init__(self, **kwargs):
39
39
  self._search_term = None
@@ -66,12 +66,11 @@ class SearchListboardMixin:
66
66
 
67
67
  @property
68
68
  def search_term(self) -> str | None:
69
- if not self._search_term:
70
- if search_term := self.raw_search_term:
71
- self._search_term = escape(search_term).strip()
69
+ if (not self._search_term) and (search_term := self.raw_search_term):
70
+ self._search_term = escape(search_term).strip()
72
71
  return self._search_term
73
72
 
74
- def get_search_fields(self) -> list[str]:
73
+ def get_search_fields(self) -> tuple[str, ...]:
75
74
  """Override to add additional search fields"""
76
75
  return self.search_fields
77
76
 
@@ -16,16 +16,19 @@ class MetadataViewError(Exception):
16
16
 
17
17
  class MetadataViewMixin:
18
18
  panel_model: str = "edc_lab.panel"
19
- metadata_show_status: list[str] = [REQUIRED, KEYED]
19
+ metadata_show_status: tuple[str] = (REQUIRED, KEYED)
20
20
 
21
21
  def get_context_data(self, **kwargs) -> dict:
22
22
  if self.appointment:
23
23
  # always refresh metadata / run rules
24
24
  refresh_metadata_for_timepoint(self.appointment, allow_create=True)
25
25
  referer = self.request.headers.get("Referer")
26
- if referer and "subject_review_listboard" in referer:
27
- if self.appointment.related_visit:
28
- update_appt_status_for_timepoint(self.appointment.related_visit)
26
+ if (
27
+ referer
28
+ and "subject_review_listboard" in referer
29
+ and self.appointment.related_visit
30
+ ):
31
+ update_appt_status_for_timepoint(self.appointment.related_visit)
29
32
  crf_qs = self.get_crf_metadata()
30
33
  requisition_qs = self.get_requisition_metadata()
31
34
  kwargs.update(crfs=crf_qs, requisitions=requisition_qs)
@@ -67,19 +67,20 @@ class RandomizationListImporter:
67
67
  def __init__(
68
68
  self,
69
69
  randomizer_model_cls=None,
70
- randomizer_name: str = None,
71
- randomizationlist_path: Path | str = None,
72
- assignment_map: dict[str, int] = None,
73
- verbose: bool = None,
74
- overwrite: bool = None,
75
- add: bool = None,
76
- dryrun: bool = None,
77
- username: str = None,
78
- revision: str = None,
79
- sid_count_for_tests: int = None,
80
- extra_csv_fieldnames: list[str] | None = None,
81
- **kwargs,
70
+ randomizer_name: str | None = None,
71
+ randomizationlist_path: Path | str | None = None,
72
+ assignment_map: dict[str, int] | None = None,
73
+ verbose: bool | None = None,
74
+ overwrite: bool | None = None,
75
+ add: bool | None = None,
76
+ dryrun: bool | None = None,
77
+ username: str | None = None,
78
+ revision: str | None = None,
79
+ sid_count_for_tests: int | None = None,
80
+ extra_csv_fieldnames: tuple[str] | None = None,
81
+ **kwargs, # noqa: ARG002
82
82
  ):
83
+ extra_csv_fieldnames = extra_csv_fieldnames or ()
83
84
  self.verify_messages: str | None = None
84
85
  self.add = add
85
86
  self.overwrite = overwrite
@@ -92,7 +93,7 @@ class RandomizationListImporter:
92
93
  self.randomizer_name = randomizer_name
93
94
  self.assignment_map = assignment_map
94
95
  self.randomizationlist_path: Path = Path(randomizationlist_path).expanduser()
95
- self.required_csv_fieldnames.extend(extra_csv_fieldnames or [])
96
+ self.required_csv_fieldnames = (*self.required_csv_fieldnames, *extra_csv_fieldnames)
96
97
 
97
98
  if self.dryrun:
98
99
  sys.stdout.write(
@@ -223,12 +224,15 @@ class RandomizationListImporter:
223
224
  sid_count = self.sid_count_for_tests
224
225
  else:
225
226
  sid_count = len(self.get_sid_list())
226
- with self.randomizationlist_path.open(mode="r") as csvfile:
227
- reader = csv.DictReader(csvfile)
228
- for row in tqdm(reader, total=sid_count):
227
+ with self.randomizationlist_path.open(mode="r") as f:
228
+ reader = csv.DictReader(f)
229
+ all_rows = [{k: v.strip() for k, v in row.items() if k} for row in reader]
230
+ sorted_rows = sorted(
231
+ all_rows, key=lambda row: (row.get("site_name", ""), row.get("sid", ""))
232
+ )
233
+ for row in tqdm(sorted_rows, total=sid_count):
229
234
  if self.sid_count_for_tests and len(objs) == self.sid_count_for_tests:
230
235
  break
231
- row = {k: v.strip() for k, v in row.items()}
232
236
  try:
233
237
  self.randomizer_model_cls.objects.get(sid=row["sid"])
234
238
  except ObjectDoesNotExist:
@@ -25,12 +25,12 @@ class RandomizationListVerifier:
25
25
  def __init__(
26
26
  self,
27
27
  randomizer_name=None,
28
- randomizationlist_path: Path | str = None,
28
+ randomizationlist_path: Path | str | None = None,
29
29
  randomizer_model_cls=None,
30
30
  assignment_map=None,
31
31
  fieldnames=None,
32
32
  sid_count_for_tests=None,
33
- required_csv_fieldnames: list[str] | None = None,
33
+ required_csv_fieldnames: tuple[str, ...] | None = None,
34
34
  **kwargs, # noqa: ARG002
35
35
  ):
36
36
  self.count: int = 0
@@ -101,7 +101,7 @@ class Randomizer:
101
101
  )
102
102
  filename: str = "randomization_list.csv"
103
103
  randomizationlist_folder: Path | str = get_randomization_list_path()
104
- extra_csv_fieldnames: list[str] | None = None
104
+ extra_csv_fieldnames: tuple[str] | None = None
105
105
  trial_is_blinded: bool = True
106
106
  importer_cls: Any = RandomizationListImporter
107
107
  apps = None # if not using django_apps
@@ -1,3 +1,5 @@
1
+ import contextlib
2
+
1
3
  from django.conf import settings
2
4
 
3
5
  from .dashboard_templates import dashboard_templates
@@ -10,12 +12,10 @@ class DashboardMiddleware:
10
12
  def __call__(self, request):
11
13
  return self.get_response(request)
12
14
 
13
- def process_view(self, request, *args) -> None:
15
+ def process_view(self, request, *args) -> None: # noqa: ARG002
14
16
  template_data = dashboard_templates
15
- try:
17
+ with contextlib.suppress(AttributeError):
16
18
  template_data.update(settings.REVIEW_DASHBOARD_BASE_TEMPLATES)
17
- except AttributeError:
18
- pass
19
19
  request.template_data.update(**template_data)
20
20
 
21
21
  def process_template_response(self, request, response):
@@ -49,15 +49,15 @@ class SubjectReviewListboardView(
49
49
  listboard_view_permission_codename = "edc_review_dashboard.view_subject_review_listboard"
50
50
 
51
51
  navbar_selected_item = "subject_review"
52
- ordering = ["subject_identifier", "visit_code", "visit_code_sequence"]
52
+ ordering = ("subject_identifier", "visit_code", "visit_code_sequence")
53
53
  paginate_by = 25
54
54
  search_form_url = "subject_review_listboard_url"
55
- search_fields = [
55
+ search_fields = (
56
56
  "subject_identifier",
57
57
  "visit_code",
58
58
  "user_created",
59
59
  "user_modified",
60
- ]
60
+ )
61
61
 
62
62
  # attr to call SubjectReviewListboardView.urls in urls.py
63
63
  urlconfig_getattr = "review_listboard_urls"
@@ -25,7 +25,7 @@ class AgeEvaluator(ReportableAgeEvaluator):
25
25
  self.reasons_ineligible = "Age unknown"
26
26
  return eligible
27
27
 
28
- def in_bounds_or_raise(self, age: int = None, **kwargs):
28
+ def in_bounds_or_raise(self, age: int | None = None, **kwargs): # noqa: ARG002
29
29
  self.reasons_ineligible = ""
30
30
  dob = localtime(timezone.now() - relativedelta(years=age)).date()
31
31
  age_units = "years"
@@ -18,14 +18,14 @@ class Eligibility:
18
18
  # default to eligible if >=18
19
19
  age_evaluator = AgeEvaluator(age_lower=18, age_lower_inclusive=True)
20
20
 
21
- custom_reasons_dict: dict = {}
21
+ custom_reasons_dict: dict = {} # noqa: RUF012
22
22
 
23
23
  def __init__(
24
24
  self,
25
- age: int = None,
26
- gender: str = None,
27
- pregnant: bool = None,
28
- breast_feeding: bool = None,
25
+ age: int | None = None,
26
+ gender: str | None = None,
27
+ pregnant: bool | None = None,
28
+ breast_feeding: bool | None = None,
29
29
  **additional_criteria,
30
30
  ) -> None:
31
31
  self.criteria = dict(**additional_criteria)
@@ -17,15 +17,15 @@ class ScreeningEligibilityCleanedDataKeyError(Exception):
17
17
  pass
18
18
 
19
19
 
20
- class ScreeningEligibilityInvalidCombination(Exception):
20
+ class ScreeningEligibilityInvalidCombination(Exception): # noqa: N818
21
21
  pass
22
22
 
23
23
 
24
- class RequiredFieldValueMissing(Exception):
24
+ class RequiredFieldValueMissing(Exception): # noqa: N818
25
25
  pass
26
26
 
27
27
 
28
- class InvalidScreeningIdentifierFormat(Exception):
28
+ class InvalidScreeningIdentifierFormat(Exception): # noqa: N818
29
29
  def __init__(self, *args, **kwargs):
30
30
  self.code = INVALID_SCREENING_IDENTIFIER
31
31
  super().__init__(*args, **kwargs)
@@ -2,7 +2,7 @@ from edc_constants.constants import FEMALE, MALE
2
2
 
3
3
 
4
4
  class GenderEvaluator:
5
- eligible_gender = [MALE, FEMALE]
5
+ eligible_gender = (MALE, FEMALE)
6
6
 
7
7
  def __init__(self, gender=None, **kwargs) -> None: # noqa
8
8
  self.eligible = False
@@ -27,7 +27,7 @@ class AlreadyConsentedFormMixin:
27
27
  raise forms.ValidationError(self.already_consented_validation_message(url))
28
28
  return cleaned_data
29
29
 
30
- def already_consented_validation_url(self, cleaned_data: dict | None = None) -> str:
30
+ def already_consented_validation_url(self, cleaned_data: dict | None = None) -> str: # noqa: ARG002
31
31
  url_name = url_names.get("subject_dashboard_url")
32
32
  return reverse(
33
33
  url_name,
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from django.utils.html import format_html
6
5
  from django.utils.safestring import mark_safe
7
6
 
8
7
  from edc_constants.constants import NO, PENDING, TBD, YES
@@ -33,7 +32,7 @@ class ScreeningEligibility:
33
32
  eligible_fld_name: str = "eligible"
34
33
  eligible_value_default: str = TBD
35
34
  default_display_label = TBD
36
- eligible_values_list: list = [YES, NO, TBD]
35
+ eligible_values_list: list = (YES, NO, TBD)
37
36
  ineligible_display_label: str = "INELIGIBLE"
38
37
  is_eligible_value: str = YES
39
38
  is_ineligible_value: str = NO
@@ -44,9 +43,9 @@ class ScreeningEligibility:
44
43
  def __init__(
45
44
  self,
46
45
  model_obj: SubjectScreeningModel | EligibilityModelMixin = None,
47
- cleaned_data: dict = None,
46
+ cleaned_data: dict | None = None,
48
47
  eligible_value_default: str | None = None,
49
- eligible_values_list: list | None = None,
48
+ eligible_values_list: tuple[str, ...] | None = None,
50
49
  is_eligible_value: str | None = None,
51
50
  is_ineligible_value: str | None = None,
52
51
  eligible_display_label: str | None = None,
@@ -126,7 +125,7 @@ class ScreeningEligibility:
126
125
  @property
127
126
  def is_eligible(self) -> bool:
128
127
  """Returns True if eligible else False"""
129
- return True if self.eligible == self.is_eligible_value else False
128
+ return self.eligible == self.is_eligible_value
130
129
 
131
130
  def _assess_eligibility(self) -> None:
132
131
  self.set_fld_attrs_on_self()
@@ -136,30 +135,29 @@ class ScreeningEligibility:
136
135
  self.reasons_ineligible.update(**missing_data)
137
136
  self.eligible = self.eligible_value_default # probably TBD
138
137
  for fldattr, fc in self.get_required_fields().items():
139
- if fldattr not in missing_data:
140
- if fc and fc.value:
141
- msg = fc.msg if fc.msg else fldattr.title().replace("_", " ")
142
- is_callable = False
143
- try:
144
- value = fc.value(getattr(self, fldattr))
145
- except TypeError:
146
- value = fc.value
147
- else:
148
- is_callable = True
149
- if (
150
- (isinstance(value, str) and getattr(self, fldattr) != value)
151
- or (
152
- isinstance(value, (list, tuple))
153
- and getattr(self, fldattr) not in value
154
- )
155
- or (
156
- isinstance(value, range)
157
- and not (min(value) <= getattr(self, fldattr) <= max(value))
158
- )
159
- or (is_callable and value is False)
160
- ):
161
- self.reasons_ineligible.update({fldattr: msg})
162
- self.eligible = self.is_ineligible_value # probably NO
138
+ if fldattr not in missing_data and fc and fc.value:
139
+ msg = fc.msg if fc.msg else fldattr.title().replace("_", " ")
140
+ is_callable = False
141
+ try:
142
+ value = fc.value(getattr(self, fldattr))
143
+ except TypeError:
144
+ value = fc.value
145
+ else:
146
+ is_callable = True
147
+ if (
148
+ (isinstance(value, str) and getattr(self, fldattr) != value)
149
+ or (
150
+ isinstance(value, (list, tuple))
151
+ and getattr(self, fldattr) not in value
152
+ )
153
+ or (
154
+ isinstance(value, range)
155
+ and not (min(value) <= getattr(self, fldattr) <= max(value))
156
+ )
157
+ or (is_callable and value is False)
158
+ ):
159
+ self.reasons_ineligible.update({fldattr: msg})
160
+ self.eligible = self.is_ineligible_value # probably NO
163
161
  if self.is_eligible:
164
162
  if self.is_eligible and not self.get_required_fields():
165
163
  self.eligible = self.eligible_value_default
@@ -194,7 +192,7 @@ class ScreeningEligibility:
194
192
  "does not exist on class. "
195
193
  f"See {self.__class__.__name__}. "
196
194
  f"Got {e}"
197
- )
195
+ ) from e
198
196
  if self.model_obj:
199
197
  try:
200
198
  value = (
@@ -207,7 +205,7 @@ class ScreeningEligibility:
207
205
  "Attribute does not exist on model. "
208
206
  f"See {self.model_obj.__class__.__name__}. "
209
207
  f"Got {e}"
210
- )
208
+ ) from e
211
209
  else:
212
210
  value = self.cleaned_data.get(fldattr)
213
211
  setattr(self, fldattr, value)
@@ -230,10 +228,7 @@ class ScreeningEligibility:
230
228
 
231
229
  def formatted_reasons_ineligible(self) -> str:
232
230
  str_values = "<BR>".join([x for x in self.reasons_ineligible.values() if x])
233
- return format_html(
234
- "{}",
235
- mark_safe(str_values), # nosec B703 B308
236
- )
231
+ return mark_safe(str_values) # noqa: S308
237
232
 
238
233
  @property
239
234
  def display_label(self) -> str:
edc_screening/utils.py CHANGED
@@ -40,10 +40,8 @@ def format_reasons_ineligible(*str_values: str | None, delimiter: str | None = N
40
40
  str_values = str_values or []
41
41
  str_values = tuple(x for x in str_values if x)
42
42
  if str_values:
43
- reasons = format_html(
44
- "{}",
45
- mark_safe(delimiter.join(str_values)), # noqa: S308
46
- )
43
+ formatted_string = delimiter.join(str_values)
44
+ reasons = mark_safe(formatted_string) # noqa: S308
47
45
  return reasons
48
46
 
49
47
 
@@ -120,15 +118,15 @@ def is_eligible_or_raise(
120
118
  if url and url_name.endswith("changelist"):
121
119
  url = f"{url}?q={subject_screening.screening_identifier}"
122
120
  if not url:
123
- msg = format_html(
124
- "{}",
121
+ safe_string = mark_safe( # noqa: S308
125
122
  "Not allowed. Subject is not eligible. "
126
- f"Got {subject_screening.screening_identifier}",
123
+ f"Got {subject_screening.screening_identifier}.",
127
124
  )
128
125
  else:
129
- msg = format_html(
130
- 'Not allowed. Subject is not eligible. See subject <A href="{}">{}</A>',
131
- mark_safe(url), # noqa: S308
132
- subject_screening.screening_identifier,
126
+ safe_string = format_html(
127
+ "Not allowed. Subject is not eligible. See subject "
128
+ '<A href="{url}">{screening_identifier}</A>',
129
+ url=url,
130
+ screening_identifier=subject_screening.screening_identifier,
133
131
  )
134
- raise forms.ValidationError(msg)
132
+ raise forms.ValidationError(safe_string)
@@ -17,10 +17,10 @@ class SiteListFilter(SimpleListFilter):
17
17
  else:
18
18
  site_ids = sites.get_site_ids_for_user(request=request)
19
19
  for site in Site.objects.filter(id__in=site_ids).order_by("id"):
20
- names.append((site.id, f"{site.id} {sites.get(site.id).description}"))
20
+ names.append((site.id, f"{site.id} {sites.get(site.id).description}")) # noqa: PERF401
21
21
  return tuple(names)
22
22
 
23
- def queryset(self, request, queryset):
23
+ def queryset(self, request, queryset): # noqa: ARG002
24
24
  if self.value() and self.value() != "none":
25
25
  queryset = queryset.filter(site__id=self.value())
26
26
  return queryset
@@ -46,7 +46,7 @@ class SiteModelAdminMixin:
46
46
  ]
47
47
  return sites.get_view_only_site_ids_for_user(request=request)
48
48
 
49
- def has_viewallsites_permission(self, request, obj=None) -> bool:
49
+ def has_viewallsites_permission(self, request, obj=None) -> bool: # noqa: ARG002
50
50
  """Checks if the user has the EDC custom codename
51
51
  "viewallsites" for this model.
52
52
 
@@ -54,7 +54,7 @@ class SiteModelAdminMixin:
54
54
  """
55
55
  opts = self.opts
56
56
  codename_allsites = get_permission_codename("viewallsites", opts)
57
- return request.user.has_perm("%s.%s" % (opts.app_label, codename_allsites))
57
+ return request.user.has_perm(f"{opts.app_label}.{codename_allsites}")
58
58
 
59
59
  @admin.display(description="Site", ordering="site__id")
60
60
  def site_code(self, obj=None):
@@ -75,7 +75,7 @@ class SiteModelAdminMixin:
75
75
  to mulitple sites.
76
76
  """
77
77
  list_filter = super().get_list_filter(request)
78
- list_filter = [x for x in list_filter if x != "site" and x != SiteListFilter]
78
+ list_filter = [x for x in list_filter if x not in ("site", SiteListFilter)]
79
79
  if self.user_may_view_other_sites(request) or self.has_viewallsites_permission(
80
80
  request
81
81
  ):
@@ -95,25 +95,25 @@ class SiteModelAdminMixin:
95
95
  or self.has_viewallsites_permission(request)
96
96
  ) and "site" not in list_display:
97
97
  list_display = tuple(list_display)
98
- list_display = list_display[:pos] + (self.site_code,) + list_display[pos:]
98
+ list_display = list_display[:pos], self.site_code, list_display[pos:]
99
99
  elif "site" in list_display:
100
100
  list_display = tuple(
101
101
  [x for x in list_display if x not in ["site", self.site_code]]
102
102
  )
103
- list_display = list_display[:pos] + (self.site_code,) + list_display[pos:]
103
+ list_display = list_display[:pos], self.site_code, list_display[pos:]
104
104
  return list_display
105
105
 
106
106
  def get_queryset(self, request) -> QuerySet:
107
107
  """Limit modeladmin queryset for the current site only"""
108
108
  qs = super().get_queryset(request)
109
- site_ids = [request.site.id] + self.get_view_only_site_ids_for_user(request=request)
109
+ site_ids = (request.site.id, *self.get_view_only_site_ids_for_user(request=request))
110
110
  try:
111
111
  qs = qs.select_related("site").filter(site_id__in=site_ids)
112
- except FieldError:
112
+ except FieldError as e:
113
113
  raise SiteModeAdminMixinError(
114
114
  f"Model missing field `site`. Model `{self.model}`. Did you mean to use "
115
115
  f"the SiteModelAdminMixin? See `{self}`."
116
- )
116
+ ) from e
117
117
  return qs
118
118
 
119
119
  def get_form(self, request, obj=None, change=False, **kwargs):
edc_sites/exceptions.py CHANGED
@@ -6,5 +6,5 @@ class InvalidSiteForSubjectError(Exception):
6
6
  pass
7
7
 
8
8
 
9
- class InvalidSiteForUser(Exception):
9
+ class InvalidSiteForUser(Exception): # noqa: N818
10
10
  pass
edc_sites/forms.py CHANGED
@@ -1 +1,3 @@
1
- from .modelform_mixins import SiteModelFormMixin # noqa
1
+ from .modelform_mixins import SiteModelFormMixin
2
+
3
+ __all__ = ["SiteModelFormMixin"]