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
@@ -43,10 +43,9 @@ class CreatesMetadataModelMixin(RelatedVisitProtocol, models.Model):
43
43
 
44
44
  Also called by post_save signal after metadata is updated.
45
45
  """
46
- metadata_rule_evaluator = self.metadata_rule_evaluator_cls(
46
+ return self.metadata_rule_evaluator_cls(
47
47
  related_visit=self, allow_create=allow_create
48
- )
49
- metadata_rule_evaluator.evaluate_rules()
48
+ ).evaluate_rules()
50
49
 
51
50
  @property
52
51
  def metadata_query_options(self) -> dict[str, Any]:
@@ -54,14 +53,13 @@ class CreatesMetadataModelMixin(RelatedVisitProtocol, models.Model):
54
53
  the related_visit.
55
54
  """
56
55
  visit: Visit = self.visits.get(self.appointment.visit_code)
57
- options = dict(
56
+ return dict(
58
57
  visit_schedule_name=self.appointment.visit_schedule_name,
59
58
  schedule_name=self.appointment.schedule_name,
60
59
  visit_code=visit.code,
61
60
  visit_code_sequence=self.appointment.visit_code_sequence,
62
61
  timepoint=self.appointment.timepoint,
63
62
  )
64
- return options
65
63
 
66
64
  @property
67
65
  def crf_metadata(self):
@@ -42,7 +42,7 @@ class UpdatesMetadataModelMixin(models.Model):
42
42
  metadata_updater_cls = MetadataUpdater
43
43
  metadata_category: str = CRF
44
44
 
45
- def metadata_update(self: CrfModel | RequisitionModel, entry_status: str = None) -> None:
45
+ def metadata_update(self: CrfModel | RequisitionModel, entry_status: str) -> None:
46
46
  """Updates metatadata."""
47
47
  self.metadata_updater.get_and_update(entry_status=entry_status)
48
48
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  from typing import TYPE_CHECKING
4
5
 
5
6
  from django.apps import apps as django_apps
@@ -10,6 +11,7 @@ from .metadata import CrfMetadataGetter, RequisitionMetadataGetter
10
11
 
11
12
  if TYPE_CHECKING:
12
13
  from edc_appointment.models import Appointment
14
+ from edc_model.utils import CrfLikeModel
13
15
  from edc_visit_schedule.visit import Crf, Requisition, Visit
14
16
 
15
17
  from .metadata import MetadataGetter
@@ -22,10 +24,10 @@ class NextFormGetter:
22
24
 
23
25
  def __init__(
24
26
  self,
25
- model_obj=None,
26
- appointment: Appointment = None,
27
- model: str = None,
28
- panel_name: str = None,
27
+ model_obj: CrfLikeModel | None = None,
28
+ appointment: Appointment | None = None,
29
+ model: str | None = None,
30
+ panel_name: str | None = None,
29
31
  ):
30
32
  self._getter = None
31
33
  self._next_metadata_obj = None
@@ -58,7 +60,7 @@ class NextFormGetter:
58
60
  """
59
61
  if not self._model_obj:
60
62
  model_cls = django_apps.get_model(self.model)
61
- try:
63
+ with contextlib.suppress(ObjectDoesNotExist):
62
64
  self._model_obj = model_cls.objects.get(
63
65
  **{
64
66
  f"{model_cls.related_visit_model_attr()}__appointment": (
@@ -66,8 +68,6 @@ class NextFormGetter:
66
68
  )
67
69
  }
68
70
  )
69
- except ObjectDoesNotExist:
70
- pass
71
71
  return self._model_obj
72
72
 
73
73
  @property
@@ -94,23 +94,19 @@ class NextFormGetter:
94
94
 
95
95
  @property
96
96
  def next_panel(self) -> str | None:
97
- if not self._next_panel:
98
- if self.next_metadata_obj:
99
- try:
100
- self._next_panel = self.next_metadata_obj.panel_name
101
- except AttributeError:
102
- pass
97
+ if not self._next_panel and self.next_metadata_obj:
98
+ with contextlib.suppress(AttributeError):
99
+ self._next_panel = self.next_metadata_obj.panel_name
103
100
  return self._next_panel
104
101
 
105
102
  @property
106
103
  def panel_name(self) -> str | None:
107
104
  """Returns a panel_name or None."""
108
- if not self._panel_name:
109
- if self.model_obj:
110
- try:
111
- self._panel_name = self.model_obj.panel.name
112
- except AttributeError:
113
- self._panel_name = None
105
+ if not self._panel_name and self.model_obj:
106
+ try:
107
+ self._panel_name = self.model_obj.panel.name
108
+ except AttributeError:
109
+ self._panel_name = None
114
110
  return self._panel_name
115
111
 
116
112
  @property
@@ -8,6 +8,6 @@ offline_models = []
8
8
  app_config = django_apps.get_app_config("edc_metadata")
9
9
  for model in app_config.get_models():
10
10
  if not issubclass(model, ListModelMixin):
11
- offline_models.append(model._meta.label_lower)
11
+ offline_models.append(model._meta.label_lower) # noqa: PERF401
12
12
 
13
13
  site_offline_models.register(offline_models, OfflineModel)
@@ -21,24 +21,24 @@ class RequisitionMetadataHandler(MetadataHandler):
21
21
  super().__init__(**kwargs)
22
22
  self.panel = panel
23
23
 
24
- def _create(self, exception_msg: str | None = None) -> RequisitionMetadata:
24
+ def _create(self) -> RequisitionMetadata:
25
25
  """Returns a created RequisitionMetadata model instance for this
26
26
  requisition.
27
27
  """
28
28
  metadata_obj = None
29
29
  try:
30
- requisition_object = [
30
+ requisition_object = next(
31
31
  requisition
32
32
  for requisition in self.creator.related_visit.visit.all_requisitions
33
33
  if requisition.panel.name == self.panel.name
34
- ][0]
35
- except IndexError as e:
34
+ )
35
+ except StopIteration as e:
36
36
  if self.related_visit.reason != MISSED_VISIT:
37
37
  raise MetadataHandlerError(
38
38
  "Panel not found. Not in visit.all_requisitions. "
39
39
  f"Panel `{self.panel}` at `{self.creator.related_visit.visit}`. "
40
40
  f"Got {e}. Check your visit schedule."
41
- )
41
+ ) from e
42
42
  else:
43
43
  metadata_obj = self.creator.create_requisition(requisition_object)
44
44
  return metadata_obj
@@ -33,13 +33,11 @@ class UpdateMetadataOnScheduleChange:
33
33
  f"value for field '{self.fieldname}'.\n"
34
34
  f"Old value='{self.old_value}', New value='{self.new_value}'.\n"
35
35
  )
36
- for name, model_cls in self.models.items():
36
+ for model_cls in self.models.values():
37
37
  count = model_cls.objects.filter(**{self.fieldname: self.old_value}).count()
38
38
  sys.stdout.write(f"{model_cls._meta.label_lower}. {count} records found.\n")
39
39
  sys.stdout.write(
40
- style.ERROR(
41
- "No records have been updated. \n" "Set --dry-run=False to update.\n"
42
- )
40
+ style.ERROR("No records have been updated. \nSet --dry-run=False to update.\n")
43
41
  )
44
42
  else:
45
43
  sys.stdout.write(style.SUCCESS("Updating... \n"))
edc_metadata/utils.py CHANGED
@@ -39,7 +39,7 @@ if TYPE_CHECKING:
39
39
  schedule_name: str
40
40
 
41
41
 
42
- class HasKeyedMetadata(Exception):
42
+ class HasKeyedMetadata(Exception): # noqa: N818
43
43
  pass
44
44
 
45
45
 
@@ -1,4 +1,7 @@
1
+ import contextlib
2
+
1
3
  from django import dispatch
4
+ from django.core.exceptions import ObjectDoesNotExist
2
5
  from django.db.models import expressions
3
6
  from simple_history import signals
4
7
 
@@ -17,12 +20,14 @@ def remove_f_expressions(sender, instance, history_instance, **kwargs) -> None:
17
20
  """
18
21
  f_expression_fields = []
19
22
  for field in history_instance._meta.fields:
20
- field_value = getattr(history_instance, field.name)
23
+ with contextlib.suppress(ObjectDoesNotExist):
24
+ field_value = getattr(history_instance, field.name)
21
25
  if isinstance(field_value, expressions.BaseExpression):
22
26
  f_expression_fields.append(field.name)
23
27
 
24
28
  if f_expression_fields:
25
29
  instance.refresh_from_db()
26
30
  for field_name in f_expression_fields:
27
- field_value = getattr(instance, field_name)
31
+ with contextlib.suppress(ObjectDoesNotExist):
32
+ field_value = getattr(instance, field_name)
28
33
  setattr(history_instance, field_name, field_value)
@@ -6,7 +6,6 @@ from django.contrib import messages
6
6
  from django.http.response import HttpResponseRedirect
7
7
  from django.urls import reverse
8
8
  from django.utils.encoding import force_str
9
-
10
9
  from edc_dashboard.url_names import InvalidDashboardUrlName, url_names
11
10
 
12
11
 
@@ -75,8 +74,10 @@ class ModelAdminRedirectOnDeleteMixin:
75
74
  """Overridden to redirect to `post_url_on_delete`, if not None."""
76
75
  if self.post_url_on_delete:
77
76
  opts = self.model._meta
78
- msg = (f'The {force_str(opts.verbose_name)} "{force_str(obj_display)}" '
79
- 'was deleted successfully.')
77
+ msg = (
78
+ f'The {force_str(opts.verbose_name)} "{force_str(obj_display)}" '
79
+ "was deleted successfully."
80
+ )
80
81
  messages.add_message(request, messages.SUCCESS, msg)
81
82
  return HttpResponseRedirect(self.post_url_on_delete)
82
83
  return super().response_delete(request, obj_display, obj_id)
edc_navbar/apps.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from django.apps import AppConfig as DjangoAppConfig
2
- from django.conf import settings
3
2
  from django.core.management.color import color_style
4
3
 
5
4
  style = color_style()
@@ -9,4 +8,3 @@ class AppConfig(DjangoAppConfig):
9
8
  name = "edc_navbar"
10
9
  verbose_name = "Edc Navbar"
11
10
  register_default_navbar = True
12
- default_navbar_name = getattr(settings, "DEFAULT_NAVBAR_NAME", "default")
edc_navbar/navbar.py CHANGED
@@ -30,7 +30,7 @@ class Navbar:
30
30
  def get(self, name: str) -> NavbarItem | None:
31
31
  try:
32
32
  navbar_item = next(nb for nb in self.navbar_items if nb.name == name)
33
- except IndexError:
33
+ except StopIteration:
34
34
  navbar_item = None
35
35
  return navbar_item
36
36
 
edc_navbar/navbar_item.py CHANGED
@@ -6,7 +6,6 @@ from typing import TYPE_CHECKING
6
6
  from django.core.management.color import color_style
7
7
  from django.urls import NoReverseMatch
8
8
  from django.urls.base import reverse
9
-
10
9
  from edc_dashboard.url_names import InvalidDashboardUrlName, url_names
11
10
 
12
11
  if TYPE_CHECKING:
@@ -23,10 +22,13 @@ class NavbarItem:
23
22
  title: str = field(default=None)
24
23
  label: str | None = field(default=None)
25
24
  codename: str = field(default=None)
26
- url_name: str = field(default=None)
27
- no_url_namespace: bool = field(default=None)
25
+ url_names_key: str | None = field(
26
+ default=None
27
+ ) # must be valid key in url_names dictionary
28
+ url_with_namespace: str | None = field(default=None)
29
+ url_without_namespace: str | None = field(default=None)
28
30
  fa_icon: str | None = field(default=None)
29
- disabled: str = field(default="disabled")
31
+ disabled: str | None = field(default="disabled")
30
32
 
31
33
  active: bool = field(default=None)
32
34
 
@@ -37,24 +39,35 @@ class NavbarItem:
37
39
 
38
40
  def __post_init__(self):
39
41
  self.title = self.title or self.label or self.name.title() # the anchor title
42
+ if self.url_with_namespace and ":" not in self.url_with_namespace:
43
+ raise InvalidDashboardUrlName(
44
+ f"Invalid url_with_namespace. Got {self.url_with_namespace}"
45
+ )
40
46
 
41
47
  def get_url(self, raise_exception: bool | None = None) -> str | None:
48
+ url = (
49
+ self.url_without_namespace
50
+ or self.url_with_namespace
51
+ or url_names.get(self.url_names_key)
52
+ )
42
53
  try:
43
- url = reverse(self.real_url_name)
44
- except NoReverseMatch:
45
- url = None
54
+ url = reverse(url)
55
+ except NoReverseMatch as e:
46
56
  if raise_exception:
47
- raise
57
+ errmsg = (
58
+ f"Reverse for Navbar url not found. Tried {url}. "
59
+ f"See NavbarItem(name={self.name}"
60
+ )
61
+ if self.url_without_namespace == url:
62
+ errmsg = f"{errmsg}, url_without_namespace={self.url_without_namespace})."
63
+ elif self.url_with_namespace == url:
64
+ errmsg = f"{errmsg}, url_with_namespace={self.url_with_namespace})."
65
+ elif url_names.get(self.url_names_key) == url:
66
+ errmsg = f"{errmsg}, url_names_key={self.url_names_key})."
67
+ raise NoReverseMatch(errmsg) from e
68
+ url = None
48
69
  return url
49
70
 
50
- @property
51
- def real_url_name(self) -> str:
52
- try:
53
- url_name = url_names.get(self.url_name)
54
- except InvalidDashboardUrlName:
55
- url_name = self.url_name.split(":")[1] if self.no_url_namespace else self.url_name
56
- return url_name
57
-
58
71
  def set_disabled(self, user: User | None = None):
59
72
  if user and user.has_perm(self.codename):
60
73
  self.disabled = ""
edc_navbar/navbars.py CHANGED
@@ -1,20 +1,17 @@
1
- from django.apps import apps as django_apps
2
-
3
1
  from .navbar import Navbar
4
2
  from .navbar_item import NavbarItem
5
3
  from .site_navbars import site_navbars
4
+ from .utils import get_default_navbar_name, get_register_default_navbar
6
5
 
7
- app_config = django_apps.get_app_config("edc_navbar")
8
-
9
- if app_config.register_default_navbar:
10
- default_navbar = Navbar(name=app_config.default_navbar_name)
6
+ if get_register_default_navbar():
7
+ default_navbar = Navbar(name=get_default_navbar_name())
11
8
 
12
9
  default_navbar.register(
13
10
  NavbarItem(
14
11
  name="home",
15
12
  title="Home",
16
13
  fa_icon="fa-home",
17
- url_name="home_url",
14
+ url_without_namespace="home_url",
18
15
  codename="edc_navbar.nav_home",
19
16
  )
20
17
  )
@@ -25,19 +22,9 @@ if app_config.register_default_navbar:
25
22
  title="Administration",
26
23
  fa_icon="fa-cog",
27
24
  codename="edc_navbar.nav_administration",
28
- url_name="administration_url",
25
+ # url_names_key="administration",
26
+ url_without_namespace="administration_url",
29
27
  )
30
28
  )
31
29
 
32
- # remove to use a form. See edc_navbar.html
33
- # default_navbar.register(
34
- # NavbarItem(
35
- # name="logout",
36
- # title="Logout",
37
- # fa_icon="fa-sign-out-alt",
38
- # url_name="edc_auth_admin:logout",
39
- # codename="edc_navbar.nav_logout",
40
- # )
41
- # )
42
-
43
30
  site_navbars.register(default_navbar)
@@ -44,16 +44,16 @@ class NavbarCollection:
44
44
  navbar_name=name,
45
45
  )
46
46
 
47
- def get_navbar(self, name: str = None, selected_item: str = None) -> Navbar:
47
+ def get_navbar(self, name: str, selected_item: str | None = None) -> Navbar:
48
48
  """Returns a selected navbar in the collection."""
49
49
  # does the navbar exist?
50
50
  try:
51
51
  navbar: Navbar = self.registry[name]
52
- except KeyError:
52
+ except KeyError as e:
53
53
  raise NavbarError(
54
54
  f"Navbar '{name}' does not exist. Expected one of "
55
55
  f"{list(self.registry.keys())}. See {self!r}."
56
- )
56
+ ) from e
57
57
  else:
58
58
  # does the navbar have items?
59
59
  if not navbar.navbar_items:
@@ -66,10 +66,9 @@ class NavbarCollection:
66
66
  navbar.set_active(selected_item)
67
67
  return navbar
68
68
 
69
- def show_user_permissions(self, username: str = None, navbar_name: str = None):
69
+ def show_user_permissions(self, username: str, navbar_name: str):
70
70
  user = django_apps.get_model("auth.user").objects.get(username=username)
71
- navbar: Navbar = self.registry.get(navbar_name)
72
- return navbar.show_user_permissions(user=user)
71
+ return self.registry.get(navbar_name).show_user_permissions(user=user)
73
72
 
74
73
  def show_user_codenames(self, username=None, navbar_name=None):
75
74
  user_permissions = self.show_user_permissions(username, navbar_name)
@@ -101,7 +100,7 @@ class NavbarCollection:
101
100
  except ImportError as e:
102
101
  site_navbars.registry = before_import_registry
103
102
  if module_has_submodule(mod, module_name):
104
- raise NavbarError(e)
103
+ raise NavbarError(e) from e
105
104
  except ImportError:
106
105
  pass
107
106
 
@@ -8,7 +8,7 @@ from edc_navbar import site_navbars
8
8
  def edc_navbar_checks(app_configs, **kwargs) -> list[CheckMessage]:
9
9
  errors = []
10
10
 
11
- for navbar_name, navbar in site_navbars.registry.items():
11
+ for navbar in site_navbars.registry.values():
12
12
  for navbar_item in navbar.navbar_items:
13
13
  try:
14
14
  app_label, codename = navbar_item.codename.split(".")
@@ -30,13 +30,6 @@ def edc_navbar_checks(app_configs, **kwargs) -> list[CheckMessage]:
30
30
  errors.append(Error(msg, id="edc_navbar.E002"))
31
31
  try:
32
32
  navbar_item.get_url(raise_exception=True)
33
- except NoReverseMatch:
34
- errors.append(
35
- Error(
36
- f"NoReverseMatch for url. Got '{navbar_item.real_url_name}'. "
37
- f"See {navbar_item.name}.",
38
- id="edc_navbar.E003",
39
- )
40
- )
41
-
33
+ except NoReverseMatch as e:
34
+ errors.append(Error(str(e), id="edc_navbar.E003"))
42
35
  return errors
edc_navbar/utils.py CHANGED
@@ -7,3 +7,17 @@ def get_autodiscover():
7
7
 
8
8
  def get_verify_on_load():
9
9
  return getattr(settings, "EDC_NAVBAR_VERIFY_ON_LOAD", "")
10
+
11
+
12
+ def get_register_default_navbar():
13
+ return getattr(settings, "EDC_NAVBAR_REGISTER_DEFAULT_NAVBAR", True)
14
+
15
+
16
+ def get_default_navbar_name():
17
+ """Returns the default navbar name.
18
+
19
+ For example: inte_dashboard for project INTE.
20
+ """
21
+ return getattr(
22
+ settings, "EDC_NAVBAR_DEFAULT_NAVBAR_NAME", f"{settings.APP_NAME}_dashboard".lower()
23
+ )
edc_navbar/view_mixin.py CHANGED
@@ -1,14 +1,12 @@
1
1
  from typing import Any
2
2
 
3
- from django.apps import apps as django_apps
4
-
5
- from .get_default_navbar import get_default_navbar
6
3
  from .site_navbars import site_navbars
4
+ from .utils import get_default_navbar_name
7
5
 
8
6
 
9
7
  class NavbarViewMixin:
10
8
  navbar_selected_item = None
11
- navbar_name = get_default_navbar()
9
+ navbar_name = get_default_navbar_name()
12
10
 
13
11
  def get_context_data(self, **kwargs) -> dict[str, Any]:
14
12
  """Add rendered navbar <navbar_name> to the context for
@@ -16,18 +14,17 @@ class NavbarViewMixin:
16
14
 
17
15
  Also adds the "default" navbar.
18
16
  """
19
- kwargs = self.get_navbar_context_data(kwargs)
17
+ kwargs = self.get_context_data_for_navbars(kwargs)
20
18
  return super().get_context_data(**kwargs)
21
19
 
22
20
  def get_navbar_name(self):
23
21
  return self.navbar_name
24
22
 
25
- def get_navbar_context_data(self, context) -> dict:
23
+ def get_context_data_for_navbars(self, context) -> dict:
26
24
  navbar = site_navbars.get_navbar(name=self.get_navbar_name())
27
25
  navbar.set_active(self.get_navbar_selected(**context))
28
26
  context.update(navbar=navbar)
29
- app_config = django_apps.get_app_config("edc_navbar")
30
- default_navbar_name = app_config.default_navbar_name
27
+ default_navbar_name = get_default_navbar_name()
31
28
  if default_navbar_name and self.get_navbar_name() != default_navbar_name:
32
29
  default_navbar = site_navbars.get_navbar(name=default_navbar_name)
33
30
  default_navbar.set_active(self.navbar_selected_item)
@@ -36,5 +33,5 @@ class NavbarViewMixin:
36
33
  )
37
34
  return context
38
35
 
39
- def get_navbar_selected(self, **kwargs) -> str:
36
+ def get_navbar_selected(self, **kwargs) -> str: # noqa: ARG002
40
37
  return self.navbar_selected_item
edc_pharmacy/navbars.py CHANGED
@@ -6,7 +6,7 @@ pharmacy_navbar_item = NavbarItem(
6
6
  title="",
7
7
  fa_icon="fa-prescription",
8
8
  codename="edc_pharmacy.nav_pharmacy_section",
9
- url_name="edc_pharmacy:home_url",
9
+ url_with_namespace="edc_pharmacy:home_url",
10
10
  )
11
11
 
12
12
  navbar = Navbar(name="pharmacy")
@@ -9,7 +9,6 @@ from django.urls import reverse
9
9
  from django.utils.decorators import method_decorator
10
10
  from django.utils.translation import gettext as _
11
11
  from django.views.generic.base import TemplateView
12
-
13
12
  from edc_constants.constants import CONFIRMED
14
13
  from edc_dashboard.view_mixins import EdcViewMixin
15
14
  from edc_navbar import NavbarViewMixin
@@ -29,8 +28,8 @@ class ConfirmStockFromQuerySetView(
29
28
  navbar_selected_item = "pharmacy"
30
29
  codes_per_page = 12
31
30
 
32
- def get_context_data(self, **kwargs): # noqa: ARG002
33
- return dict(
31
+ def get_context_data(self, **kwargs):
32
+ kwargs.update(
34
33
  CONFIRMED=CONFIRMED,
35
34
  ALREADY_CONFIRMED=ALREADY_CONFIRMED,
36
35
  INVALID=INVALID,
@@ -40,6 +39,7 @@ class ConfirmStockFromQuerySetView(
40
39
  source_changelist_url=self.source_changelist_url,
41
40
  **self.session_data,
42
41
  )
42
+ return super().get_context_data(**kwargs)
43
43
 
44
44
  @property
45
45
  def session_data(self):
@@ -8,18 +8,14 @@ class ResearchProtocolConfigMiddleware:
8
8
  def __call__(self, request):
9
9
  return self.get_response(request)
10
10
 
11
- def process_view(self, request, *args):
12
- pass
13
-
14
11
  def process_template_response(self, request, response): # noqa: ARG002
15
- if not response.context_data:
16
- response.context_data = {}
17
- protocol_config = ResearchProtocolConfig()
18
- response.context_data.update(
19
- copyright=protocol_config.copyright,
20
- disclaimer=protocol_config.disclaimer,
21
- institution=protocol_config.institution,
22
- license=protocol_config.license,
23
- project_name=protocol_config.project_name,
24
- )
12
+ if getattr(response, "context_data", None):
13
+ protocol_config = ResearchProtocolConfig()
14
+ response.context_data.update(
15
+ copyright=protocol_config.copyright,
16
+ disclaimer=protocol_config.disclaimer,
17
+ institution=protocol_config.institution,
18
+ license=protocol_config.license,
19
+ project_name=protocol_config.project_name,
20
+ )
25
21
  return response
edc_protocol/navbars.py CHANGED
@@ -8,7 +8,7 @@ protocol.register(
8
8
  title="Protocol",
9
9
  label="protocol",
10
10
  codename="edc_navbar.nav_edc_protocol",
11
- url_name="edc_protocol:home_url",
11
+ url_with_namespace="edc_protocol:home_url",
12
12
  )
13
13
  )
14
14
 
edc_refusal/forms.py CHANGED
@@ -2,7 +2,6 @@ from django import forms
2
2
  from django.core.exceptions import ObjectDoesNotExist
3
3
  from django.urls.base import reverse
4
4
  from django.utils.html import format_html
5
-
6
5
  from edc_constants.constants import OTHER
7
6
  from edc_dashboard.url_names import url_names
8
7
  from edc_form_validators import FormValidator, FormValidatorMixin
@@ -47,8 +46,7 @@ class AlreadyConsentedFormMixin:
47
46
  kwargs={"subject_identifier": obj.subject_identifier},
48
47
  )
49
48
  msg = format_html(
50
- "Not allowed. Subject has already consented. "
51
- 'See subject <A href="{}">{}</A>',
49
+ 'Not allowed. Subject has already consented. See subject <A href="{}">{}</A>',
52
50
  url,
53
51
  obj.subject_identifier,
54
52
  )
@@ -153,7 +153,7 @@ def convert_units(
153
153
  units_from: str | None = None,
154
154
  units_to: str | None = None,
155
155
  places: int | None = None,
156
- ):
156
+ ) -> int | float:
157
157
  return UnitsConverter(
158
158
  label=label,
159
159
  value=value,
@@ -1,24 +1,27 @@
1
1
  import contextlib
2
2
 
3
3
  from django.conf import settings
4
+ from edc_dashboard.middleware_mixins import EdcTemplateMiddlewareMixin
4
5
 
5
6
  from .dashboard_templates import dashboard_templates
6
7
 
7
8
 
8
- class DashboardMiddleware:
9
+ class DashboardMiddleware(EdcTemplateMiddlewareMixin):
9
10
  def __init__(self, get_response):
10
11
  self.get_response = get_response
11
12
 
12
13
  def __call__(self, request):
14
+ self.check_for_required_request_attrs(request)
13
15
  return self.get_response(request)
14
16
 
15
- def process_view(self, request, *args) -> None: # noqa: ARG002
17
+ def process_view(self, request, *args) -> None:
16
18
  template_data = dashboard_templates
17
19
  with contextlib.suppress(AttributeError):
18
20
  template_data.update(settings.REVIEW_DASHBOARD_BASE_TEMPLATES)
19
21
  request.template_data.update(**template_data)
20
22
 
21
23
  def process_template_response(self, request, response):
22
- if response.context_data:
24
+ if getattr(response, "context_data", None):
23
25
  response.context_data.update(**request.template_data)
26
+ request.template_data.update(**request.template_data)
24
27
  return response
@@ -7,10 +7,9 @@ navbar_item = NavbarItem(
7
7
  label="Review",
8
8
  title="Subject Review",
9
9
  codename="edc_review_dashboard.view_subject_review_listboard",
10
- url_name="subject_review_listboard_url",
10
+ url_names_key="subject_review_listboard_url",
11
11
  )
12
12
 
13
-
14
13
  navbar.register(navbar_item)
15
14
 
16
15
  site_navbars.register(navbar)