clinicedc 2.0.38__py3-none-any.whl → 2.0.40__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 (157) hide show
  1. {clinicedc-2.0.38.dist-info → clinicedc-2.0.40.dist-info}/METADATA +3 -12
  2. {clinicedc-2.0.38.dist-info → clinicedc-2.0.40.dist-info}/RECORD +146 -153
  3. {clinicedc-2.0.38.dist-info → clinicedc-2.0.40.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/templates/edc_adverse_event/tmg/tmg_ae_listboard_result.html +27 -23
  8. edc_adverse_event/templatetags/edc_adverse_event_extras.py +21 -34
  9. edc_adverse_event/urls.py +14 -6
  10. edc_adverse_event/view_mixins/ae/ae_listboard_view_mixin.py +6 -8
  11. edc_adverse_event/view_mixins/ae/death_report_listboard_view_mixin.py +2 -4
  12. edc_adverse_event/view_mixins/tmg/tmg_ae_listboard_view_mixin.py +9 -7
  13. edc_adverse_event/views/home_view.py +1 -2
  14. edc_adverse_event/views/tmg/death_listboard_view.py +8 -6
  15. edc_adverse_event/views/tmg/home_view.py +4 -3
  16. edc_adverse_event/views/tmg/summary_listboard_view.py +4 -4
  17. edc_appointment/utils.py +3 -6
  18. edc_appointment/views/unscheduled_appointment_view.py +1 -1
  19. edc_consent/form_validators/consent_definition_form_validator_mixin.py +5 -2
  20. edc_consent/model_mixins/consent_version_model_mixin.py +1 -1
  21. edc_consent/navbars.py +2 -1
  22. edc_crf/model_mixins/crf_model_mixin.py +5 -1
  23. edc_crf/model_mixins/crf_no_manager_model_mixin.py +2 -2
  24. edc_crf/model_mixins/singleton_crf_model_mixin.py +1 -1
  25. edc_dashboard/middleware.py +10 -16
  26. edc_dashboard/middleware_mixins.py +10 -0
  27. edc_dashboard/navbars.py +1 -1
  28. edc_dashboard/url_config.py +50 -31
  29. edc_dashboard/url_names.py +23 -17
  30. edc_dashboard/utils.py +4 -4
  31. edc_dashboard/view_mixins/template_request_context_mixin.py +5 -8
  32. edc_dashboard/view_mixins/url_request_context_mixin.py +38 -26
  33. edc_dashboard/views/administration_view.py +2 -2
  34. edc_dashboard/views/dashboard_view.py +5 -10
  35. edc_data_manager/handlers/handlers.py +17 -5
  36. edc_data_manager/models/query_rule.py +7 -7
  37. edc_data_manager/navbar_item.py +1 -1
  38. edc_data_manager/rule/query_rule_wrapper.py +1 -1
  39. edc_data_manager/rule/rule_runner.py +6 -6
  40. edc_device/navbars.py +1 -1
  41. edc_export/navbars.py +2 -2
  42. edc_glucose/model_mixin_factories/fasting_model_mixin_factory.py +1 -1
  43. edc_identifier/identifier.py +6 -9
  44. edc_lab_dashboard/dashboard_urls.py +7 -5
  45. edc_lab_dashboard/middleware.py +10 -17
  46. edc_lab_dashboard/navbars.py +9 -9
  47. edc_lab_dashboard/templates/edc_lab_dashboard/listboard/tags/status_column.html +7 -0
  48. edc_lab_dashboard/urls.py +2 -5
  49. edc_lab_dashboard/view_mixins/form_action_view_mixin.py +1 -2
  50. edc_lab_dashboard/views/action_views/action_view.py +6 -6
  51. edc_lab_dashboard/views/action_views/aliquot_view.py +1 -1
  52. edc_lab_dashboard/views/action_views/manage_box_item_view.py +2 -3
  53. edc_lab_dashboard/views/action_views/manage_manifest_view.py +1 -1
  54. edc_lab_dashboard/views/action_views/manifest_view.py +2 -2
  55. edc_lab_dashboard/views/action_views/pack_view.py +2 -2
  56. edc_lab_dashboard/views/action_views/process_view.py +1 -1
  57. edc_lab_dashboard/views/action_views/receive_view.py +1 -1
  58. edc_lab_dashboard/views/action_views/requisition_view.py +1 -1
  59. edc_lab_dashboard/views/action_views/verify_box_item_view.py +1 -1
  60. edc_lab_dashboard/views/listboard_views/manage_box_listboard_view.py +4 -5
  61. edc_lab_dashboard/views/listboard_views/manifest_listboard_view.py +5 -6
  62. edc_lab_dashboard/views/listboard_views/process_listboard_view.py +4 -5
  63. edc_lab_dashboard/views/listboard_views/receive_listboard_view.py +5 -6
  64. edc_lab_dashboard/views/listboard_views/verify_box_listboard_view.py +5 -6
  65. edc_label/navbars.py +1 -1
  66. edc_list_data/admin.py +3 -3
  67. edc_list_data/load_model_data.py +1 -1
  68. edc_list_data/management/commands/load_list_data.py +2 -2
  69. edc_list_data/site_list_data.py +4 -4
  70. edc_listboard/middleware.py +9 -8
  71. edc_listboard/templates/edc_listboard/listboard.html +1 -1
  72. edc_listboard/view_mixins/listboard_filter_view_mixin.py +1 -1
  73. edc_listboard/view_mixins/search_form_view_mixin.py +1 -1
  74. edc_listboard/views/listboard_view.py +16 -25
  75. edc_listboard/views/screen/screening_listboard_view.py +2 -2
  76. edc_listboard/views/subject/subject_listboard_view.py +2 -2
  77. edc_locator/forms/subject_locator_form_validator.py +2 -2
  78. edc_ltfu/action_items.py +1 -2
  79. edc_ltfu/forms/ltfu_form_validator_mixin.py +3 -3
  80. edc_ltfu/modeladmin_mixin.py +1 -1
  81. edc_ltfu/modelform_mixins.py +2 -2
  82. edc_metadata/admin/modeladmin_mixins.py +11 -9
  83. edc_metadata/management/commands/update_metadata.py +1 -1
  84. edc_metadata/management/commands/update_metadata_schedule_names.py +7 -7
  85. edc_metadata/management/commands/validate_entry_status.py +1 -1
  86. edc_metadata/management/commands/validate_rule_groups.py +1 -1
  87. edc_metadata/metadata/metadata_getter.py +3 -5
  88. edc_metadata/metadata_handler.py +5 -5
  89. edc_metadata/metadata_mixins/source_model_metadata_mixin.py +1 -1
  90. edc_metadata/metadata_refresher.py +1 -1
  91. edc_metadata/metadata_rules/crf/crf_rule.py +1 -1
  92. edc_metadata/metadata_rules/logic.py +3 -3
  93. edc_metadata/metadata_rules/persistant_singleton_mixin.py +2 -4
  94. edc_metadata/metadata_rules/requisition/requisition_rule_group.py +1 -1
  95. edc_metadata/metadata_rules/rule.py +4 -3
  96. edc_metadata/metadata_rules/rule_group.py +2 -2
  97. edc_metadata/metadata_rules/rule_group_meta_options.py +2 -2
  98. edc_metadata/metadata_rules/rule_group_metaclass.py +21 -22
  99. edc_metadata/metadata_rules/site.py +1 -1
  100. edc_metadata/metadata_updater.py +4 -3
  101. edc_metadata/model_mixins/creates/creates_metadata_model_mixin.py +3 -5
  102. edc_metadata/model_mixins/updates/updates_metadata_model_mixin.py +1 -1
  103. edc_metadata/next_form_getter.py +15 -19
  104. edc_metadata/offline_models.py +1 -1
  105. edc_metadata/requisition/requisition_metadata_handler.py +5 -5
  106. edc_metadata/update_metadata_on_schedule_change.py +2 -4
  107. edc_metadata/utils.py +1 -1
  108. edc_model/models/signals.py +7 -2
  109. edc_model_admin/mixins/model_admin_redirect_on_delete_mixin.py +4 -3
  110. edc_navbar/apps.py +0 -2
  111. edc_navbar/navbar.py +1 -1
  112. edc_navbar/navbar_item.py +29 -16
  113. edc_navbar/navbars.py +6 -19
  114. edc_navbar/site_navbars.py +6 -7
  115. edc_navbar/system_checks.py +3 -10
  116. edc_navbar/utils.py +14 -0
  117. edc_navbar/view_mixin.py +6 -9
  118. edc_pharmacy/navbars.py +1 -1
  119. edc_pharmacy/views/confirm_stock_from_queryset_view.py +3 -3
  120. edc_protocol/middleware.py +9 -13
  121. edc_protocol/navbars.py +1 -1
  122. edc_refusal/forms.py +1 -3
  123. edc_reportable/utils/convert_units.py +1 -1
  124. edc_review_dashboard/middleware.py +6 -3
  125. edc_review_dashboard/navbars.py +1 -2
  126. edc_review_dashboard/urls.py +3 -2
  127. edc_review_dashboard/views/subject_review_listboard_view.py +4 -2
  128. edc_subject_dashboard/dashboard_templates.py +1 -3
  129. edc_subject_dashboard/dashboard_urls.py +8 -0
  130. edc_subject_dashboard/middleware.py +10 -7
  131. edc_subject_dashboard/templates/edc_subject_dashboard/buttons/refresh_appointments_button.html +1 -1
  132. edc_subject_dashboard/templates/edc_subject_dashboard/dashboard.html +1 -1
  133. edc_subject_dashboard/templatetags/edc_subject_dashboard_extras.py +3 -1
  134. edc_subject_dashboard/urls.py +13 -4
  135. edc_subject_dashboard/views/base_requisition_view.py +2 -1
  136. edc_subject_dashboard/views/subject_dashboard_view.py +1 -2
  137. edc_timepoint/__init__.py +0 -2
  138. edc_timepoint/model_mixins.py +1 -2
  139. edc_timepoint/utils.py +1 -1
  140. edc_timepoint/visit_timepoint_lookup.py +6 -0
  141. edc_visit_schedule/admin/subject_schedule_history_admin.py +1 -2
  142. edc_visit_schedule/navbars.py +3 -4
  143. edc_visit_schedule/visit/visit.py +15 -0
  144. edc_visit_tracking/model_mixins/visit_model_mixin/visit_model_mixin.py +5 -0
  145. edc_visit_tracking/models/subject_visit.py +5 -0
  146. edc_lab_dashboard/model_wrappers/__init__.py +0 -8
  147. edc_lab_dashboard/model_wrappers/aliquot_model_wrapper.py +0 -31
  148. edc_lab_dashboard/model_wrappers/base_box_item_model_wrapper.py +0 -21
  149. edc_lab_dashboard/model_wrappers/box_model_wrapper.py +0 -12
  150. edc_lab_dashboard/model_wrappers/manage_box_item_model_wrapper.py +0 -6
  151. edc_lab_dashboard/model_wrappers/manifest_item_model_wrapper.py +0 -21
  152. edc_lab_dashboard/model_wrappers/manifest_model_wrapper.py +0 -11
  153. edc_lab_dashboard/model_wrappers/requisition_model_wrapper.py +0 -25
  154. edc_lab_dashboard/model_wrappers/result_model_wrapper.py +0 -8
  155. edc_lab_dashboard/model_wrappers/verify_box_model_wrapper.py +0 -10
  156. edc_navbar/get_default_navbar.py +0 -9
  157. {clinicedc-2.0.38.dist-info → clinicedc-2.0.40.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import contextlib
4
+ from collections.abc import Callable
4
5
  from typing import TYPE_CHECKING
5
6
 
6
7
  from edc_appointment.constants import MISSED_APPT
@@ -28,9 +29,9 @@ class Rule:
28
29
 
29
30
  def __init__(
30
31
  self,
31
- predicate: P | PF | callable | str = None,
32
- consequence: str = None,
33
- alternative: str = None,
32
+ predicate: P | PF | Callable | str,
33
+ consequence: str,
34
+ alternative: str,
34
35
  ) -> None:
35
36
  self.predicate = predicate
36
37
  self.consequence = consequence
@@ -11,7 +11,7 @@ class RuleGroupError(Exception):
11
11
  pass
12
12
 
13
13
 
14
- class TargetModelConflict(Exception):
14
+ class TargetModelConflict(Exception): # noqa: N818
15
15
  pass
16
16
 
17
17
 
@@ -61,7 +61,7 @@ class RuleGroup:
61
61
  )
62
62
 
63
63
  @classmethod
64
- def _lookup_model(cls, model: str = None, category: str = None) -> Any:
64
+ def _lookup_model(cls, model: str, category: str) -> Any:
65
65
  sys.stdout.write(f" ( ) {model}\r")
66
66
  model_cls = None
67
67
  try:
@@ -42,13 +42,13 @@ class RuleGroupMetaOptions:
42
42
  # source model
43
43
  self.source_model = self.options.get("source_model")
44
44
  if self.source_model:
45
- if len(self.source_model.split(".")) != 2:
45
+ if len(self.source_model.split(".")) != 2: # noqa: PLR2004
46
46
  self.source_model = f"{self.app_label}.{self.source_model}"
47
47
  self.options.update(source_model=self.source_model)
48
48
  # related visit model
49
49
  self.related_visit_model = self.options.get("related_visit_model")
50
50
  if self.related_visit_model:
51
- if len(self.related_visit_model.split(".")) != 2:
51
+ if len(self.related_visit_model.split(".")) != 2: # noqa: PLR2004
52
52
  raise RuleGroupMetaError(
53
53
  "Invalid _meta attr. Expected _meta.related_visit_model to be in "
54
54
  f"label_lower format. Got '{self.related_visit_model}'. See {group_name}."
@@ -51,7 +51,7 @@ class RuleGroupMetaclass(type):
51
51
  return super().__new__(mcs, name, bases, attrs)
52
52
 
53
53
  @classmethod
54
- def __get_rules(mcs, name: str, attrs: dict, meta: Any) -> tuple:
54
+ def __get_rules(mcs, name: str, attrs: dict, meta: Any) -> tuple: # noqa: N804
55
55
  """Returns a list of rules after updating each rule's attrs
56
56
  with values from Meta.
57
57
 
@@ -59,28 +59,27 @@ class RuleGroupMetaclass(type):
59
59
  """
60
60
  rules = []
61
61
  for key, value in attrs.items():
62
- if not key.startswith("_"):
63
- if isinstance(value, Rule):
64
- rule = value
65
- rule.name = key
66
- rule.group = name
67
- if isinstance(rule.predicate, (str,)):
68
- predicates = getattr(meta, "predicates", None)
69
- if not predicates:
70
- raise RuleGroupError(
71
- "RuleGroup Meta attr `predicates` may not be `None` if a "
72
- "rule.predicate in the RuleGroup is a string. "
73
- f"See {attrs.get('__qualname__')}."
74
- )
75
- rule.predicate = getattr(meta.predicates, rule.predicate)
76
- for k, v in meta.options.items():
77
- setattr(rule, k, v)
78
- rule.target_models = mcs.__get_target_models(rule, meta)
79
- rules.append(rule)
62
+ if not key.startswith("_") and isinstance(value, Rule):
63
+ rule = value
64
+ rule.name = key
65
+ rule.group = name
66
+ if isinstance(rule.predicate, (str,)):
67
+ predicates = getattr(meta, "predicates", None)
68
+ if not predicates:
69
+ raise RuleGroupError(
70
+ "RuleGroup Meta attr `predicates` may not be `None` if a "
71
+ "rule.predicate in the RuleGroup is a string. "
72
+ f"See {attrs.get('__qualname__')}."
73
+ )
74
+ rule.predicate = getattr(meta.predicates, rule.predicate)
75
+ for k, v in meta.options.items():
76
+ setattr(rule, k, v)
77
+ rule.target_models = mcs.__get_target_models(rule, meta)
78
+ rules.append(rule)
80
79
  return tuple(rules)
81
80
 
82
81
  @classmethod
83
- def __get_target_models(mcs, rule: Any, meta: Any) -> Any:
82
+ def __get_target_models(mcs, rule: Any, meta: Any) -> Any: # noqa: N804
84
83
  """Returns target models as a list of label_lowers.
85
84
 
86
85
  Target models are the models whose metadata is acted upon.
@@ -90,7 +89,7 @@ class RuleGroupMetaclass(type):
90
89
  """
91
90
  target_models = []
92
91
  for target_model in rule.target_models:
93
- if len(target_model.split(".")) != 2:
94
- target_model = f"{meta.app_label}.{target_model}"
92
+ if len(target_model.split(".")) != 2: # noqa: PLR2004
93
+ target_model = f"{meta.app_label}.{target_model}" # noqa: PLW2901
95
94
  target_models.append(target_model)
96
95
  return target_models
@@ -9,7 +9,7 @@ from django.utils.module_loading import import_module, module_has_submodule
9
9
  style = color_style()
10
10
 
11
11
 
12
- class SiteMetadataRulesAlreadyRegistered(Exception):
12
+ class SiteMetadataRulesAlreadyRegistered(Exception): # noqa: N818
13
13
  pass
14
14
 
15
15
 
@@ -31,8 +31,9 @@ class MetadataUpdater(SourceModelMetadataMixin):
31
31
 
32
32
  def __init__(
33
33
  self,
34
- related_visit: RelatedVisitModel = None,
35
- source_model: str = None,
34
+ *,
35
+ related_visit: RelatedVisitModel,
36
+ source_model: str,
36
37
  allow_create: bool | None = None,
37
38
  ):
38
39
  super().__init__(source_model, related_visit)
@@ -46,7 +47,7 @@ class MetadataUpdater(SourceModelMetadataMixin):
46
47
  f"source_model={self.source_model})"
47
48
  )
48
49
 
49
- def get_and_update(self, entry_status: str = None) -> CrfMetadata | RequisitionMetadata:
50
+ def get_and_update(self, entry_status: str) -> CrfMetadata | RequisitionMetadata:
50
51
  metadata_obj = self.metadata_handler.metadata_obj
51
52
  if entry_status != KEYED and self.source_model_obj_exists:
52
53
  entry_status = KEYED
@@ -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
+ )