clinicedc 2.0.39__py3-none-any.whl → 2.0.41__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 (156) hide show
  1. {clinicedc-2.0.39.dist-info → clinicedc-2.0.41.dist-info}/METADATA +3 -12
  2. {clinicedc-2.0.39.dist-info → clinicedc-2.0.41.dist-info}/RECORD +145 -151
  3. {clinicedc-2.0.39.dist-info → clinicedc-2.0.41.dist-info}/WHEEL +1 -1
  4. edc_adverse_event/dashboard_urls.py +2 -0
  5. edc_adverse_event/middleware.py +7 -6
  6. edc_adverse_event/navbars.py +4 -8
  7. edc_adverse_event/urls.py +14 -6
  8. edc_adverse_event/view_mixins/ae/ae_listboard_view_mixin.py +6 -8
  9. edc_adverse_event/view_mixins/ae/death_report_listboard_view_mixin.py +2 -4
  10. edc_adverse_event/view_mixins/tmg/tmg_ae_listboard_view_mixin.py +2 -3
  11. edc_adverse_event/views/home_view.py +1 -2
  12. edc_adverse_event/views/tmg/death_listboard_view.py +8 -6
  13. edc_adverse_event/views/tmg/home_view.py +4 -3
  14. edc_adverse_event/views/tmg/summary_listboard_view.py +4 -4
  15. edc_appointment/utils.py +3 -6
  16. edc_appointment/views/unscheduled_appointment_view.py +1 -1
  17. edc_consent/form_validators/consent_definition_form_validator_mixin.py +5 -2
  18. edc_consent/model_mixins/consent_version_model_mixin.py +1 -1
  19. edc_consent/navbars.py +2 -1
  20. edc_crf/model_mixins/crf_model_mixin.py +5 -1
  21. edc_crf/model_mixins/crf_no_manager_model_mixin.py +2 -2
  22. edc_crf/model_mixins/singleton_crf_model_mixin.py +1 -1
  23. edc_dashboard/middleware.py +10 -16
  24. edc_dashboard/middleware_mixins.py +10 -0
  25. edc_dashboard/navbars.py +1 -1
  26. edc_dashboard/url_config.py +50 -31
  27. edc_dashboard/url_names.py +23 -17
  28. edc_dashboard/utils.py +4 -4
  29. edc_dashboard/view_mixins/template_request_context_mixin.py +5 -8
  30. edc_dashboard/view_mixins/url_request_context_mixin.py +38 -26
  31. edc_dashboard/views/administration_view.py +2 -2
  32. edc_dashboard/views/dashboard_view.py +5 -10
  33. edc_data_manager/handlers/handlers.py +17 -5
  34. edc_data_manager/migrations/0043_alter_historicalqueryrule_comment_and_more.py +51 -0
  35. edc_data_manager/models/query_rule.py +7 -7
  36. edc_data_manager/navbar_item.py +1 -1
  37. edc_data_manager/rule/query_rule_wrapper.py +1 -1
  38. edc_data_manager/rule/rule_runner.py +6 -6
  39. edc_device/navbars.py +1 -1
  40. edc_export/navbars.py +2 -2
  41. edc_glucose/model_mixin_factories/fasting_model_mixin_factory.py +1 -1
  42. edc_identifier/identifier.py +6 -9
  43. edc_lab_dashboard/dashboard_urls.py +7 -5
  44. edc_lab_dashboard/middleware.py +10 -17
  45. edc_lab_dashboard/navbars.py +9 -9
  46. edc_lab_dashboard/templates/edc_lab_dashboard/listboard/tags/status_column.html +7 -0
  47. edc_lab_dashboard/urls.py +2 -5
  48. edc_lab_dashboard/view_mixins/form_action_view_mixin.py +1 -2
  49. edc_lab_dashboard/views/action_views/action_view.py +6 -6
  50. edc_lab_dashboard/views/action_views/aliquot_view.py +1 -1
  51. edc_lab_dashboard/views/action_views/manage_box_item_view.py +2 -3
  52. edc_lab_dashboard/views/action_views/manage_manifest_view.py +1 -1
  53. edc_lab_dashboard/views/action_views/manifest_view.py +2 -2
  54. edc_lab_dashboard/views/action_views/pack_view.py +2 -2
  55. edc_lab_dashboard/views/action_views/process_view.py +1 -1
  56. edc_lab_dashboard/views/action_views/receive_view.py +1 -1
  57. edc_lab_dashboard/views/action_views/requisition_view.py +1 -1
  58. edc_lab_dashboard/views/action_views/verify_box_item_view.py +1 -1
  59. edc_lab_dashboard/views/listboard_views/manage_box_listboard_view.py +4 -5
  60. edc_lab_dashboard/views/listboard_views/manifest_listboard_view.py +5 -6
  61. edc_lab_dashboard/views/listboard_views/process_listboard_view.py +4 -5
  62. edc_lab_dashboard/views/listboard_views/receive_listboard_view.py +5 -6
  63. edc_lab_dashboard/views/listboard_views/verify_box_listboard_view.py +5 -6
  64. edc_label/navbars.py +1 -1
  65. edc_list_data/admin.py +3 -3
  66. edc_list_data/load_model_data.py +1 -1
  67. edc_list_data/management/commands/load_list_data.py +2 -2
  68. edc_list_data/site_list_data.py +4 -4
  69. edc_listboard/middleware.py +9 -8
  70. edc_listboard/templates/edc_listboard/listboard.html +1 -1
  71. edc_listboard/view_mixins/listboard_filter_view_mixin.py +1 -1
  72. edc_listboard/view_mixins/search_form_view_mixin.py +1 -1
  73. edc_listboard/views/listboard_view.py +16 -25
  74. edc_listboard/views/screen/screening_listboard_view.py +2 -2
  75. edc_listboard/views/subject/subject_listboard_view.py +2 -2
  76. edc_locator/forms/subject_locator_form_validator.py +2 -2
  77. edc_ltfu/action_items.py +1 -2
  78. edc_ltfu/forms/ltfu_form_validator_mixin.py +3 -3
  79. edc_ltfu/modeladmin_mixin.py +1 -1
  80. edc_ltfu/modelform_mixins.py +2 -2
  81. edc_metadata/admin/modeladmin_mixins.py +11 -9
  82. edc_metadata/management/commands/update_metadata.py +1 -1
  83. edc_metadata/management/commands/update_metadata_schedule_names.py +7 -7
  84. edc_metadata/management/commands/validate_entry_status.py +1 -1
  85. edc_metadata/management/commands/validate_rule_groups.py +1 -1
  86. edc_metadata/metadata/metadata_getter.py +3 -5
  87. edc_metadata/metadata_handler.py +5 -5
  88. edc_metadata/metadata_mixins/source_model_metadata_mixin.py +1 -1
  89. edc_metadata/metadata_refresher.py +1 -1
  90. edc_metadata/metadata_rules/crf/crf_rule.py +1 -1
  91. edc_metadata/metadata_rules/logic.py +3 -3
  92. edc_metadata/metadata_rules/persistant_singleton_mixin.py +2 -4
  93. edc_metadata/metadata_rules/requisition/requisition_rule_group.py +1 -1
  94. edc_metadata/metadata_rules/rule.py +4 -3
  95. edc_metadata/metadata_rules/rule_group.py +2 -2
  96. edc_metadata/metadata_rules/rule_group_meta_options.py +2 -2
  97. edc_metadata/metadata_rules/rule_group_metaclass.py +21 -22
  98. edc_metadata/metadata_rules/site.py +1 -1
  99. edc_metadata/metadata_updater.py +4 -3
  100. edc_metadata/model_mixins/creates/creates_metadata_model_mixin.py +3 -5
  101. edc_metadata/model_mixins/updates/updates_metadata_model_mixin.py +1 -1
  102. edc_metadata/next_form_getter.py +15 -19
  103. edc_metadata/offline_models.py +1 -1
  104. edc_metadata/requisition/requisition_metadata_handler.py +5 -5
  105. edc_metadata/update_metadata_on_schedule_change.py +2 -4
  106. edc_metadata/utils.py +1 -1
  107. edc_model/models/signals.py +7 -2
  108. edc_model_admin/mixins/model_admin_redirect_on_delete_mixin.py +4 -3
  109. edc_navbar/apps.py +0 -2
  110. edc_navbar/navbar.py +1 -1
  111. edc_navbar/navbar_item.py +29 -16
  112. edc_navbar/navbars.py +6 -19
  113. edc_navbar/site_navbars.py +6 -7
  114. edc_navbar/system_checks.py +3 -10
  115. edc_navbar/utils.py +14 -0
  116. edc_navbar/view_mixin.py +6 -9
  117. edc_pharmacy/navbars.py +1 -1
  118. edc_pharmacy/views/confirm_stock_from_queryset_view.py +3 -3
  119. edc_protocol/middleware.py +9 -13
  120. edc_protocol/navbars.py +1 -1
  121. edc_refusal/forms.py +1 -3
  122. edc_reportable/utils/convert_units.py +1 -1
  123. edc_review_dashboard/middleware.py +6 -3
  124. edc_review_dashboard/navbars.py +1 -2
  125. edc_review_dashboard/urls.py +3 -2
  126. edc_review_dashboard/views/subject_review_listboard_view.py +4 -2
  127. edc_subject_dashboard/dashboard_templates.py +1 -3
  128. edc_subject_dashboard/dashboard_urls.py +8 -0
  129. edc_subject_dashboard/middleware.py +10 -7
  130. edc_subject_dashboard/templates/edc_subject_dashboard/buttons/refresh_appointments_button.html +1 -1
  131. edc_subject_dashboard/templates/edc_subject_dashboard/dashboard.html +1 -1
  132. edc_subject_dashboard/templatetags/edc_subject_dashboard_extras.py +3 -1
  133. edc_subject_dashboard/urls.py +13 -4
  134. edc_subject_dashboard/views/base_requisition_view.py +2 -1
  135. edc_subject_dashboard/views/subject_dashboard_view.py +1 -2
  136. edc_timepoint/__init__.py +0 -2
  137. edc_timepoint/model_mixins.py +1 -2
  138. edc_timepoint/utils.py +1 -1
  139. edc_timepoint/visit_timepoint_lookup.py +6 -0
  140. edc_visit_schedule/admin/subject_schedule_history_admin.py +1 -2
  141. edc_visit_schedule/navbars.py +3 -4
  142. edc_visit_schedule/visit/visit.py +15 -0
  143. edc_visit_tracking/model_mixins/visit_model_mixin/visit_model_mixin.py +5 -0
  144. edc_visit_tracking/models/subject_visit.py +5 -0
  145. edc_lab_dashboard/model_wrappers/__init__.py +0 -8
  146. edc_lab_dashboard/model_wrappers/aliquot_model_wrapper.py +0 -31
  147. edc_lab_dashboard/model_wrappers/base_box_item_model_wrapper.py +0 -21
  148. edc_lab_dashboard/model_wrappers/box_model_wrapper.py +0 -12
  149. edc_lab_dashboard/model_wrappers/manage_box_item_model_wrapper.py +0 -6
  150. edc_lab_dashboard/model_wrappers/manifest_item_model_wrapper.py +0 -21
  151. edc_lab_dashboard/model_wrappers/manifest_model_wrapper.py +0 -11
  152. edc_lab_dashboard/model_wrappers/requisition_model_wrapper.py +0 -25
  153. edc_lab_dashboard/model_wrappers/result_model_wrapper.py +0 -8
  154. edc_lab_dashboard/model_wrappers/verify_box_model_wrapper.py +0 -10
  155. edc_navbar/get_default_navbar.py +0 -9
  156. {clinicedc-2.0.39.dist-info → clinicedc-2.0.41.dist-info}/licenses/LICENSE +0 -0
@@ -17,25 +17,44 @@ if TYPE_CHECKING:
17
17
  class View(UrlRequestContextMixin, BaseView): ...
18
18
 
19
19
 
20
+ class UrlConfigError(Exception):
21
+ pass
22
+
23
+
20
24
  class UrlConfig:
25
+ """A class to generate url_patterns for edc DashboardViews,
26
+ ListBoardViews and SubjectReviewDashboardView.
27
+
28
+ * registers the url_with_namespace to `url_names`
29
+ * The pretty url uses the `url_names_key` less the '_url' suffix
30
+ * the url pattern name is the same as the given `url_names_key`
31
+
32
+ """
33
+
21
34
  def __init__(
22
35
  self,
23
36
  *,
24
- url_name: str,
37
+ url_names_key: str,
25
38
  namespace: str,
26
39
  view_class: type[View | UrlRequestContextMixin],
27
- label: str,
28
40
  identifier_label: str,
29
41
  identifier_pattern: str,
30
42
  ):
43
+ if not url_names_key.endswith("_url"):
44
+ raise UrlConfigError(
45
+ f"Invalid `url_names_key`. Must end with '_url'. Got {url_names_key}."
46
+ )
47
+ self.url_pattern_name = url_names_key
48
+ self.url_pretty_label = url_names_key.replace("_url", "")
49
+ self.view_class = view_class
31
50
  self.identifier_label = identifier_label
32
51
  self.identifier_pattern = identifier_pattern
33
- self.label = label
34
- self.url_name = url_name
35
- self.view_class = view_class
36
52
 
37
- # register {urlname, namespace:urlname} with url_names
38
- url_names.register(url=self.url_name, namespace=namespace)
53
+ # register with url_names dictionary / registry
54
+ url_names.register(
55
+ key=url_names_key,
56
+ url_with_namespace=f"{namespace}:{self.url_pattern_name}",
57
+ )
39
58
 
40
59
  @property
41
60
  def dashboard_urls(self) -> list[URLPattern]:
@@ -49,13 +68,13 @@ class UrlConfig:
49
68
  r"(?P<visit_code>\w+)/"
50
69
  r"(?P<unscheduled>\w+)/".format(
51
70
  **dict(
52
- label=self.label,
71
+ label=self.url_pretty_label,
53
72
  identifier_label=self.identifier_label,
54
73
  identifier_pattern=self.identifier_pattern,
55
74
  )
56
75
  ),
57
76
  self.view_class.as_view(),
58
- name=self.url_name,
77
+ name=self.url_pattern_name,
59
78
  ),
60
79
  re_path(
61
80
  "{label}/"
@@ -64,13 +83,13 @@ class UrlConfig:
64
83
  r"(?P<schedule_name>\w+)/"
65
84
  r"(?P<visit_code>\w+)/".format(
66
85
  **dict(
67
- label=self.label,
86
+ label=self.url_pretty_label,
68
87
  identifier_label=self.identifier_label,
69
88
  identifier_pattern=self.identifier_pattern,
70
89
  )
71
90
  ),
72
91
  self.view_class.as_view(),
73
- name=self.url_name,
92
+ name=self.url_pattern_name,
74
93
  ),
75
94
  re_path(
76
95
  "{label}/"
@@ -79,14 +98,14 @@ class UrlConfig:
79
98
  r"(?P<scanning>\d)/"
80
99
  r"(?P<error>\d)/".format(
81
100
  **dict(
82
- label=self.label,
101
+ label=self.url_pretty_label,
83
102
  identifier_label=self.identifier_label,
84
103
  identifier_pattern=self.identifier_pattern,
85
104
  uuid_pattern=UUID_PATTERN.pattern,
86
105
  )
87
106
  ),
88
107
  self.view_class.as_view(),
89
- name=self.url_name,
108
+ name=self.url_pattern_name,
90
109
  ),
91
110
  re_path(
92
111
  "{label}/"
@@ -94,52 +113,52 @@ class UrlConfig:
94
113
  "(?P<appointment>{uuid_pattern})/"
95
114
  r"(?P<reason>\w+)/".format(
96
115
  **dict(
97
- label=self.label,
116
+ label=self.url_pretty_label,
98
117
  identifier_label=self.identifier_label,
99
118
  identifier_pattern=self.identifier_pattern,
100
119
  uuid_pattern=UUID_PATTERN.pattern,
101
120
  )
102
121
  ),
103
122
  self.view_class.as_view(),
104
- name=self.url_name,
123
+ name=self.url_pattern_name,
105
124
  ),
106
125
  re_path(
107
126
  "{label}/"
108
127
  "(?P<{identifier_label}>{identifier_pattern})/"
109
128
  "(?P<appointment>{uuid_pattern})/".format(
110
129
  **dict(
111
- label=self.label,
130
+ label=self.url_pretty_label,
112
131
  identifier_label=self.identifier_label,
113
132
  identifier_pattern=self.identifier_pattern,
114
133
  uuid_pattern=UUID_PATTERN.pattern,
115
134
  )
116
135
  ),
117
136
  self.view_class.as_view(),
118
- name=self.url_name,
137
+ name=self.url_pattern_name,
119
138
  ),
120
139
  re_path(
121
140
  "{label}/"
122
141
  "(?P<{identifier_label}>{identifier_pattern})/"
123
142
  r"(?P<schedule_name>\w+)/".format(
124
143
  **dict(
125
- label=self.label,
144
+ label=self.url_pretty_label,
126
145
  identifier_label=self.identifier_label,
127
146
  identifier_pattern=self.identifier_pattern,
128
147
  )
129
148
  ),
130
149
  self.view_class.as_view(),
131
- name=self.url_name,
150
+ name=self.url_pattern_name,
132
151
  ),
133
152
  re_path(
134
153
  "{label}/(?P<{identifier_label}>{identifier_pattern})/".format(
135
154
  **dict(
136
- label=self.label,
155
+ label=self.url_pretty_label,
137
156
  identifier_label=self.identifier_label,
138
157
  identifier_pattern=self.identifier_pattern,
139
158
  )
140
159
  ),
141
160
  self.view_class.as_view(),
142
- name=self.url_name,
161
+ name=self.url_pattern_name,
143
162
  ),
144
163
  ]
145
164
 
@@ -154,34 +173,34 @@ class UrlConfig:
154
173
  "{label}/(?P<{identifier_label}>{identifier_pattern})/"
155
174
  r"(?P<page>\d+)/".format(
156
175
  **dict(
157
- label=self.label,
176
+ label=self.url_pretty_label,
158
177
  identifier_label=self.identifier_label,
159
178
  identifier_pattern=self.identifier_pattern,
160
179
  )
161
180
  ),
162
181
  self.view_class.as_view(),
163
- name=self.url_name,
182
+ name=self.url_pattern_name,
164
183
  ),
165
184
  re_path(
166
185
  "{label}/(?P<{identifier_label}>{identifier_pattern})/".format(
167
186
  **dict(
168
- label=self.label,
187
+ label=self.url_pretty_label,
169
188
  identifier_label=self.identifier_label,
170
189
  identifier_pattern=self.identifier_pattern,
171
190
  )
172
191
  ),
173
192
  self.view_class.as_view(),
174
- name=self.url_name,
193
+ name=self.url_pattern_name,
175
194
  ),
176
195
  re_path(
177
- r"{label}/(?P<page>\d+)/".format(**dict(label=self.label)),
196
+ r"{label}/(?P<page>\d+)/".format(**dict(label=self.url_pretty_label)),
178
197
  self.view_class.as_view(),
179
- name=self.url_name,
198
+ name=self.url_pattern_name,
180
199
  ),
181
200
  re_path(
182
- r"{label}/".format(**dict(label=self.label)),
201
+ r"{label}/".format(**dict(label=self.url_pretty_label)),
183
202
  self.view_class.as_view(),
184
- name=self.url_name,
203
+ name=self.url_pattern_name,
185
204
  ),
186
205
  ]
187
206
 
@@ -192,14 +211,14 @@ class UrlConfig:
192
211
  "{label}/(?P<{identifier_label}>{identifier_pattern})/"
193
212
  "(?P<appointment>{uuid_pattern})/".format(
194
213
  **dict(
195
- label=self.label,
214
+ label=self.url_pretty_label,
196
215
  identifier_label=self.identifier_label,
197
216
  identifier_pattern=self.identifier_pattern,
198
217
  uuid_pattern=UUID_PATTERN.pattern,
199
218
  )
200
219
  ),
201
220
  self.view_class.as_view(),
202
- name=self.url_name,
221
+ name=self.url_pattern_name,
203
222
  )
204
223
  ]
205
224
  url_patterns.extend(self.listboard_urls)
@@ -16,35 +16,41 @@ class UrlNames:
16
16
  registry: dict[str, str] = field(default_factory=dict)
17
17
 
18
18
  def register(
19
- self, name: str | None = None, url: str | None = None, namespace: str | None = None
19
+ self,
20
+ key: str,
21
+ namespace: str | None = None,
22
+ url: str | None = None,
23
+ url_with_namespace: str | None = None,
20
24
  ) -> None:
21
- name = name or url
22
- complete_url = f"{namespace}:{url}" if namespace else url
23
- if name in self.registry:
24
- raise AlreadyRegistered(f"Url already registered. Got {complete_url}.")
25
- self.registry.update({name: complete_url})
25
+ url_with_namespace = url_with_namespace or f"{namespace}:{url}"
26
+ if key in self.registry:
27
+ raise AlreadyRegistered(
28
+ "Url already registered with url_names. "
29
+ f"See {key}:{self.registry[key]}. Got {url_with_namespace}."
30
+ )
31
+ self.registry.update({key: url_with_namespace})
26
32
 
27
33
  def register_from_dict(self, **urldata: str) -> None:
28
- for name, complete_url in urldata.items():
34
+ for key, url_with_namespace in urldata.items():
29
35
  try:
30
- namespace, url = complete_url.split(":")
36
+ namespace, url = url_with_namespace.split(":")
31
37
  except ValueError:
32
- namespace, url = complete_url, None
33
- self.register(name=name, url=url, namespace=namespace)
38
+ namespace, url = url_with_namespace, None
39
+ self.register(key, namespace, url=url)
34
40
 
35
41
  def all(self) -> dict[str, str]:
36
42
  return self.registry
37
43
 
38
- def get(self, name: str) -> str:
39
- if name not in self.registry:
44
+ def get(self, key: str) -> str:
45
+ if key not in self.registry:
40
46
  raise InvalidDashboardUrlName(
41
- f"Invalid dashboard url name. Expected one of {self.registry.keys()}. "
42
- f"Got '{name}'."
47
+ f"Invalid key for url_names. Expected one of {self.registry.keys()}. "
48
+ f"Got '{key}'."
43
49
  )
44
- return self.registry.get(name)
50
+ return self.registry.get(key)
45
51
 
46
- def get_or_raise(self, name: str) -> str:
47
- return self.get(name)
52
+ def get_or_raise(self, key: str) -> str:
53
+ return self.get(key)
48
54
 
49
55
 
50
56
  url_names = UrlNames()
edc_dashboard/utils.py CHANGED
@@ -5,14 +5,14 @@ from django.conf import settings
5
5
  from django.template.loader import select_template
6
6
 
7
7
 
8
- class EdcTemplateDoesNotExist(Exception):
8
+ class EdcTemplateDoesNotExist(Exception): # noqa: N818
9
9
  pass
10
10
 
11
11
 
12
12
  def get_index_page() -> int:
13
13
  index_page = getattr(settings, "INDEX_PAGE", None)
14
14
  if not index_page:
15
- warn("Settings attribute not set. See settings.INDEX_PAGE")
15
+ warn("Settings attribute not set. See settings.INDEX_PAGE", stacklevel=2)
16
16
  return getattr(settings, "INDEX_PAGE", None)
17
17
 
18
18
 
@@ -44,8 +44,8 @@ def select_edc_template(relative_path, default_app_label):
44
44
  default_path = default_app_label
45
45
  return select_template(
46
46
  [
47
- os.path.join(local_path, relative_path),
48
- os.path.join(default_path, relative_path),
47
+ str(os.path.join(local_path, relative_path)), # noqa: PTH118
48
+ str(os.path.join(default_path, relative_path)), # noqa: PTH118
49
49
  ]
50
50
  )
51
51
 
@@ -1,6 +1,3 @@
1
- from typing import Any
2
-
3
-
4
1
  class TemplateRequestContextError(Exception):
5
2
  pass
6
3
 
@@ -13,10 +10,10 @@ class TemplateRequestContextMixin:
13
10
  return [self.get_template_from_context(self.listboard_template)]
14
11
  """
15
12
 
16
- def get_context_data(self, **kwargs) -> dict[str, Any]:
17
- """Adds template data to context."""
18
- kwargs.update(self.request.template_data)
19
- return super().get_context_data(**kwargs)
13
+ # def get_context_data(self, **kwargs) -> dict[str, Any]:
14
+ # """Adds template data to context."""
15
+ # kwargs.update(self.request.template_data)
16
+ # return super().get_context_data(**kwargs)
20
17
 
21
18
  def get_template_from_context(self, key=None):
22
19
  """Returns a template_name from request.context_data."""
@@ -26,5 +23,5 @@ class TemplateRequestContextMixin:
26
23
  raise TemplateRequestContextError(
27
24
  f"Template name not defined in request context data. "
28
25
  f"Expected one of {list(self.request.template_data.keys())}. Got {e}. "
29
- )
26
+ ) from e
30
27
  return template_name
@@ -6,7 +6,6 @@ from edc_protocol.research_protocol_config import ResearchProtocolConfig
6
6
  from edc_utils.text import convert_from_camel
7
7
 
8
8
  from ..url_config import UrlConfig
9
- from ..url_names import InvalidDashboardUrlName, url_names
10
9
 
11
10
  if TYPE_CHECKING:
12
11
  from django.urls import URLPattern
@@ -23,45 +22,58 @@ class UrlRequestContextMixin:
23
22
  urlconfig_label = None
24
23
  url_name = None
25
24
 
26
- @classmethod
27
- def get_urlname(cls):
28
- return cls.url_name
25
+ # @classmethod
26
+ # def get_urlname(cls):
27
+ # return cls.url_name
29
28
 
30
29
  @classmethod
31
30
  def urls(
32
31
  cls,
33
32
  *,
34
- label: str,
35
- identifier_pattern: str,
36
- namespace: str | None = None,
33
+ namespace: str,
34
+ url_names_key: str,
37
35
  identifier_label: str | None = None,
36
+ identifier_pattern: str | None = None,
38
37
  ) -> list[URLPattern]:
39
- label = (
40
- label
38
+ """Returns a list of url patterns generated by UrlConfig.
39
+
40
+ Each url pattern has the same name; `cls.url_name`.
41
+ """
42
+ url_names_key = (
43
+ url_names_key
41
44
  or cls.urlconfig_label
42
45
  or convert_from_camel(cls.__name__.replace("view", "")).lower()
43
46
  )
44
47
  urlconfig = UrlConfig(
45
- url_name=cls.get_urlname(),
46
- namespace=namespace,
47
48
  view_class=cls,
48
- label=label,
49
+ namespace=namespace,
50
+ url_names_key=url_names_key,
49
51
  identifier_label=identifier_label or cls.urlconfig_identifier_label,
50
52
  identifier_pattern=identifier_pattern or cls.urlconfig_identifier_pattern,
51
53
  )
54
+ if cls.urlconfig_getattr not in [
55
+ "dashboard_urls",
56
+ "listboard_urls",
57
+ "review_listboard_urls",
58
+ ]:
59
+ raise UrlRequestContextError(
60
+ f"Invalid urlconfig attr. Got {cls.urlconfig_getattr}."
61
+ )
52
62
  return getattr(urlconfig, cls.urlconfig_getattr)
53
63
 
54
- @staticmethod
55
- def add_url_to_context(new_key=None, existing_key=None) -> dict[str, str]:
56
- """Add url as new_key to the context using the value
57
- of the existing_key from request.context_data.
58
- """
59
- try:
60
- url_data = {new_key: url_names.get(existing_key)}
61
- except InvalidDashboardUrlName as e:
62
- raise UrlRequestContextError(
63
- f"Url name not defined in url_names. "
64
- f"Expected one of {url_names.registry}. Got {e}. "
65
- f"Hint: check if dashboard middleware is loaded."
66
- ) from e
67
- return url_data
64
+ # @staticmethod
65
+ # def add_url_to_context(new_key=None, existing_key=None) -> dict[str, str]:
66
+ # """Add url as new_key to the context using the value
67
+ # of the existing_key from request.context_data.
68
+ # """
69
+ # if new_key != existing_key:
70
+ # try:
71
+ # url_data = {new_key: url_names.get(existing_key)}
72
+ # except InvalidDashboardUrlName as e:
73
+ # raise UrlRequestContextError(
74
+ # f"Url name not defined in url_names. "
75
+ # f"Expected one of {url_names.registry}. Got {e}. "
76
+ # f"Hint: check if dashboard middleware is loaded."
77
+ # ) from e
78
+ # return url_data
79
+ # return {existing_key: url_names.get(existing_key)}
@@ -1,5 +1,5 @@
1
+ from django.conf import settings
1
2
  from django.views.generic import TemplateView
2
-
3
3
  from edc_navbar import NavbarViewMixin
4
4
 
5
5
  from ..view_mixins import AdministrationViewMixin, EdcViewMixin
@@ -7,4 +7,4 @@ from ..view_mixins import AdministrationViewMixin, EdcViewMixin
7
7
 
8
8
  class AdministrationView(EdcViewMixin, NavbarViewMixin, AdministrationViewMixin, TemplateView):
9
9
  navbar_selected_item = "administration"
10
- navbar_name = "default" # settings.APP_NAME
10
+ navbar_name = getattr(settings, "APP_NAME", "default")
@@ -8,7 +8,7 @@ from ..view_mixins import TemplateRequestContextMixin, UrlRequestContextMixin
8
8
 
9
9
 
10
10
  class DashboardView(UrlRequestContextMixin, TemplateRequestContextMixin, TemplateView):
11
- dashboard_url_name = None
11
+ dashboard_url_name = None # see url_names dictionary
12
12
  dashboard_template = None # may be None if `dashboard_template_name` is defined
13
13
  dashboard_template_name = None # may be None if `dashboard_template` is defined
14
14
 
@@ -31,15 +31,10 @@ class DashboardView(UrlRequestContextMixin, TemplateRequestContextMixin, Templat
31
31
  return url_names.get(self.dashboard_url_name)
32
32
 
33
33
  def get_template_names(self):
34
- if self.dashboard_template_name:
35
- return [self.dashboard_template_name]
36
- return [self.get_template_from_context(self.dashboard_template)]
34
+ if self.dashboard_template:
35
+ return [self.dashboard_template]
36
+ return [self.get_template_from_context(self.dashboard_template_name)]
37
37
 
38
38
  def get_context_data(self, **kwargs) -> dict[str, Any]:
39
- kwargs.update(
40
- **self.add_url_to_context(
41
- new_key="dashboard_url_name",
42
- existing_key=self.dashboard_url_name,
43
- )
44
- )
39
+ kwargs.update(**{self.dashboard_url_name: url_names.get(self.dashboard_url_name)})
45
40
  return super().get_context_data(**kwargs)
@@ -1,3 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from typing import TYPE_CHECKING
5
+
1
6
  import arrow
2
7
  from django.conf import settings
3
8
  from django.core.exceptions import ObjectDoesNotExist
@@ -15,6 +20,12 @@ from edc_visit_tracking.utils import (
15
20
  from ..constants import AUTO_RESOLVED
16
21
  from ..models import DataQuery
17
22
 
23
+ if TYPE_CHECKING:
24
+ from edc_registration.models import RegisteredSubject
25
+ from edc_visit_schedule.visit_schedule import VisitSchedule
26
+
27
+ from ..models import QueryRule
28
+
18
29
 
19
30
  class QueryRuleHandlerError(Exception):
20
31
  pass
@@ -57,11 +68,12 @@ class QueryRuleHandler:
57
68
 
58
69
  def __init__(
59
70
  self,
60
- query_rule_obj=None,
61
- registered_subject=None,
62
- visit_schedule_obj=None,
63
- visit_code_sequence=None,
64
- now=None,
71
+ *,
72
+ query_rule_obj: QueryRule,
73
+ registered_subject: RegisteredSubject,
74
+ visit_schedule_obj: VisitSchedule,
75
+ now: datetime,
76
+ visit_code_sequence: int | None = None,
65
77
  ):
66
78
  self._field_values = {}
67
79
  self._model_obj = None
@@ -0,0 +1,51 @@
1
+ # Generated by Django 5.2.7 on 2025-10-24 04:17
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("edc_data_manager", "0042_alter_datadictionary_revision_and_more"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name="historicalqueryrule",
15
+ name="comment",
16
+ field=models.TextField(blank=True, default=""),
17
+ ),
18
+ migrations.AlterField(
19
+ model_name="historicalqueryrule",
20
+ name="query_text",
21
+ field=models.TextField(
22
+ blank=True,
23
+ default="",
24
+ help_text="Generic query text for auto-generated queries.",
25
+ ),
26
+ ),
27
+ migrations.AlterField(
28
+ model_name="historicalqueryrule",
29
+ name="reference_model",
30
+ field=models.CharField(default="", editable=False, max_length=150),
31
+ ),
32
+ migrations.AlterField(
33
+ model_name="queryrule",
34
+ name="comment",
35
+ field=models.TextField(blank=True, default=""),
36
+ ),
37
+ migrations.AlterField(
38
+ model_name="queryrule",
39
+ name="query_text",
40
+ field=models.TextField(
41
+ blank=True,
42
+ default="",
43
+ help_text="Generic query text for auto-generated queries.",
44
+ ),
45
+ ),
46
+ migrations.AlterField(
47
+ model_name="queryrule",
48
+ name="reference_model",
49
+ field=models.CharField(default="", editable=False, max_length=150),
50
+ ),
51
+ ]
@@ -7,7 +7,7 @@ from django.db import models
7
7
  from django.db.models.deletion import PROTECT
8
8
  from django.template.loader import render_to_string
9
9
 
10
- from edc_constants.constants import NORMAL
10
+ from edc_constants.constants import NORMAL, NULL_STRING
11
11
  from edc_model.models import BaseUuidModel, HistoricalRecords
12
12
  from edc_visit_schedule.constants import DAYS, HOURS, MONTHS, WEEKS
13
13
 
@@ -31,7 +31,7 @@ class QueryRuleManager(models.Manager):
31
31
  def get_rule_handler_choices(model_name=None):
32
32
  choices = []
33
33
  for rule_handler in site_data_manager.get_rule_handlers(model_name=model_name):
34
- choices.append((rule_handler.name, rule_handler.display_name))
34
+ choices.append((rule_handler.name, rule_handler.display_name)) # noqa: PERF401
35
35
  return tuple(choices) or (
36
36
  (DEFAULT_RULE_HANDLER, DEFAULT_RULE_HANDLER.replace("_", " ").title()),
37
37
  )
@@ -105,7 +105,7 @@ class QueryRule(BaseUuidModel):
105
105
 
106
106
  title = models.CharField(max_length=150, unique=True)
107
107
 
108
- reference_model = models.CharField(max_length=150, null=True, editable=False)
108
+ reference_model = models.CharField(max_length=150, default=NULL_STRING, editable=False)
109
109
 
110
110
  sender = models.ForeignKey(
111
111
  DataManagerUser,
@@ -152,7 +152,7 @@ class QueryRule(BaseUuidModel):
152
152
 
153
153
  query_text = models.TextField(
154
154
  help_text="Generic query text for auto-generated queries.",
155
- null=True,
155
+ default=NULL_STRING,
156
156
  blank=True,
157
157
  )
158
158
 
@@ -192,7 +192,7 @@ class QueryRule(BaseUuidModel):
192
192
 
193
193
  reference = models.CharField(max_length=36, default=uuid4, unique=True)
194
194
 
195
- comment = models.TextField(null=True, blank=True)
195
+ comment = models.TextField(default=NULL_STRING, blank=True)
196
196
 
197
197
  objects = QueryRuleManager()
198
198
 
@@ -209,13 +209,13 @@ class QueryRule(BaseUuidModel):
209
209
  def natural_key(self):
210
210
  return (self.title,)
211
211
 
212
- natural_key.dependencies = [
212
+ natural_key.dependencies = (
213
213
  "edc_data_manager.CrfDataDictionary",
214
214
  "edc_data_manager.DataManagerUser",
215
215
  "edc_data_manager.QueryUser",
216
216
  "edc_data_manager.queryvisitschedule",
217
217
  "edc_data_manager.RequisitionPanel",
218
- ]
218
+ )
219
219
 
220
220
  @property
221
221
  def rendered_query_text(self) -> str:
@@ -5,5 +5,5 @@ dm_navbar_item = NavbarItem(
5
5
  title="Data Management",
6
6
  label="DM",
7
7
  codename="edc_data_manager.nav_data_manager_section",
8
- url_name="edc_data_manager:home_url",
8
+ url_with_namespace="edc_data_manager:home_url",
9
9
  )
@@ -87,7 +87,7 @@ class QueryRuleWrapper:
87
87
  return getattr(
88
88
  self.subject_identifiers,
89
89
  "all",
90
- lambda: [{"subject_identifier": s for s in self.subject_identifiers}],
90
+ lambda: [{"subject_identifier": s} for s in self.subject_identifiers],
91
91
  )
92
92
 
93
93
  @staticmethod
@@ -21,7 +21,7 @@ class RuleRunner:
21
21
  def __init__(
22
22
  self,
23
23
  query_rule_obj: QueryRule = None,
24
- now: datetime = None,
24
+ now: datetime | None = None,
25
25
  verbose: bool | None = None,
26
26
  ):
27
27
  self.query_rule_obj = query_rule_obj # query rule model instance
@@ -55,11 +55,11 @@ class RuleRunner:
55
55
 
56
56
  def run_one(
57
57
  self,
58
- subject_identifier: str = None,
59
- visit_schedule_name: str = None,
60
- schedule_name: str = None,
61
- visit_code: str = None,
62
- timepoint: Decimal = None,
58
+ subject_identifier: str,
59
+ visit_schedule_name: str,
60
+ schedule_name: str,
61
+ visit_code: str,
62
+ timepoint: Decimal,
63
63
  ):
64
64
  visit_schedule_obj = QueryVisitSchedule.objects.get(
65
65
  visit_schedule_name=visit_schedule_name,