NEMO-CE 7.3.5__py3-none-any.whl → 7.3.7__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.
NEMO/admin.py CHANGED
@@ -287,7 +287,7 @@ class ToolAdmin(admin.ModelAdmin):
287
287
  "parent_tool",
288
288
  "_category",
289
289
  "_operation_mode",
290
- "_qualifications_never_expire",
290
+ "qualified_users",
291
291
  "_problem_shutdown_enabled",
292
292
  "_reservation_required",
293
293
  "_logout_grace_period",
@@ -342,6 +342,17 @@ class ToolAdmin(admin.ModelAdmin):
342
342
  )
343
343
  },
344
344
  ),
345
+ (
346
+ "Qualification expiration",
347
+ {
348
+ "fields": (
349
+ "_qualification_reminder_days",
350
+ "_qualification_expiration_days",
351
+ "_qualification_expiration_never_used_days",
352
+ "_qualification_notification_email",
353
+ )
354
+ },
355
+ ),
345
356
  (
346
357
  "Area Access",
347
358
  {
@@ -0,0 +1,103 @@
1
+ # Generated by Django 4.2.27 on 2026-01-26 20:42
2
+
3
+ import re
4
+
5
+ import django.core.validators
6
+ import django.db.models.deletion
7
+ from django.db import migrations, models
8
+
9
+ import NEMO.fields
10
+
11
+
12
+ def migrate_tool_qualification_expiration_forward(apps, schema_editor):
13
+ Tool = apps.get_model("NEMO", "Tool")
14
+ Customization = apps.get_model("NEMO", "Customization")
15
+ tool_qualification_reminder_days = Customization.objects.filter(name="tool_qualification_reminder_days").first()
16
+ tool_qualification_expiration_days = Customization.objects.filter(name="tool_qualification_expiration_days").first()
17
+ tool_qualification_expiration_never_used_days = Customization.objects.filter(
18
+ name="tool_qualification_expiration_never_used_days"
19
+ ).first()
20
+ tool_qualification_notification = Customization.objects.filter(name="tool_qualification_cc").first()
21
+ for tool in Tool.objects.filter(_qualifications_never_expire=False, parent_tool__isnull=True):
22
+ if tool_qualification_expiration_days or tool_qualification_expiration_never_used_days:
23
+ if tool_qualification_reminder_days and tool_qualification_reminder_days.value:
24
+ tool._qualification_reminder_days = tool_qualification_reminder_days.value
25
+ if tool_qualification_expiration_days and tool_qualification_expiration_days.value:
26
+ tool._qualification_expiration_days = tool_qualification_expiration_days.value
27
+ if tool_qualification_expiration_never_used_days and tool_qualification_expiration_never_used_days.value:
28
+ tool._qualification_expiration_never_used_days = tool_qualification_expiration_never_used_days.value
29
+ if tool_qualification_notification and tool_qualification_notification.value:
30
+ tool._qualification_notification_email = tool_qualification_notification.value
31
+ tool.save()
32
+ if tool_qualification_reminder_days:
33
+ tool_qualification_reminder_days.delete()
34
+ if tool_qualification_expiration_days:
35
+ tool_qualification_expiration_days.delete()
36
+ if tool_qualification_expiration_never_used_days:
37
+ tool_qualification_expiration_never_used_days.delete()
38
+ if tool_qualification_notification:
39
+ tool_qualification_notification.delete()
40
+
41
+
42
+ class Migration(migrations.Migration):
43
+
44
+ dependencies = [
45
+ ("NEMO", "0140_alter_user_options"),
46
+ ]
47
+
48
+ operations = [
49
+ migrations.AddField(
50
+ model_name="tool",
51
+ name="_qualification_expiration_days",
52
+ field=models.PositiveIntegerField(
53
+ db_column="qualification_expiration_days",
54
+ blank=True,
55
+ help_text="The number of days from the user’s last tool use until the qualification expires.",
56
+ null=True,
57
+ ),
58
+ ),
59
+ migrations.AddField(
60
+ model_name="tool",
61
+ name="_qualification_expiration_never_used_days",
62
+ field=models.PositiveIntegerField(
63
+ db_column="qualification_expiration_never_used_days",
64
+ blank=True,
65
+ help_text="Number of days from the user's first qualification until the qualification expires (if the user never used the tool).",
66
+ null=True,
67
+ ),
68
+ ),
69
+ migrations.AddField(
70
+ model_name="tool",
71
+ name="_qualification_notification_email",
72
+ field=NEMO.fields.MultiEmailField(
73
+ db_column="qualification_notification_email",
74
+ blank=True,
75
+ help_text="The email addresses to cc on tool qualification expiration and on reminders. Separate multiple emails with commas.",
76
+ max_length=2000,
77
+ null=True,
78
+ ),
79
+ ),
80
+ migrations.AddField(
81
+ model_name="tool",
82
+ name="_qualification_reminder_days",
83
+ field=models.CharField(
84
+ db_column="qualification_reminder_days",
85
+ blank=True,
86
+ help_text="The (optional) number of days to send a reminder prior to the user's tool qualification expiration (below). A comma-separated list can be used for multiple reminders. This applies to both expiration cases.",
87
+ max_length=255,
88
+ null=True,
89
+ validators=[
90
+ django.core.validators.RegexValidator(
91
+ re.compile("^\\d+(?:,\\d+)*\\Z"),
92
+ code="invalid",
93
+ message="Enter only digits separated by commas.",
94
+ )
95
+ ],
96
+ ),
97
+ ),
98
+ migrations.RunPython(migrate_tool_qualification_expiration_forward, migrations.RunPython.noop),
99
+ migrations.RemoveField(
100
+ model_name="tool",
101
+ name="_qualifications_never_expire",
102
+ ),
103
+ ]
NEMO/models.py CHANGED
@@ -1349,10 +1349,32 @@ class Tool(SerializationByNameModel):
1349
1349
  _interlock = models.OneToOneField(
1350
1350
  "Interlock", db_column="interlock_id", blank=True, null=True, on_delete=models.SET_NULL
1351
1351
  )
1352
- _qualifications_never_expire = models.BooleanField(
1353
- default=False,
1354
- db_column="qualifications_never_expire",
1355
- help_text="Check this box if qualifications for this tool should never expire (even if the tool qualification expiration feature is enabled).",
1352
+ # Qualification expiration fields
1353
+ _qualification_reminder_days = models.CharField(
1354
+ db_column="qualification_reminder_days",
1355
+ null=True,
1356
+ blank=True,
1357
+ max_length=CHAR_FIELD_MEDIUM_LENGTH,
1358
+ validators=[validate_comma_separated_integer_list],
1359
+ help_text="The (optional) number of days to send a reminder prior to the user's tool qualification expiration (below). A comma-separated list can be used for multiple reminders. This applies to both expiration cases.",
1360
+ )
1361
+ _qualification_expiration_days = models.PositiveIntegerField(
1362
+ db_column="qualification_expiration_days",
1363
+ null=True,
1364
+ blank=True,
1365
+ help_text="The number of days from the user’s last tool use until the qualification expires.",
1366
+ )
1367
+ _qualification_expiration_never_used_days = models.PositiveIntegerField(
1368
+ db_column="qualification_expiration_never_used_days",
1369
+ null=True,
1370
+ blank=True,
1371
+ help_text="Number of days from the user's first qualification until the qualification expires (if the user never used the tool).",
1372
+ )
1373
+ _qualification_notification_email = fields.MultiEmailField(
1374
+ db_column="qualification_notification_email",
1375
+ null=True,
1376
+ blank=True,
1377
+ help_text="The email addresses to cc on tool qualification expiration and on reminders. Separate multiple emails with commas.",
1356
1378
  )
1357
1379
  # Policy fields:
1358
1380
  _requires_area_access = TreeForeignKey(
@@ -1530,17 +1552,6 @@ class Tool(SerializationByNameModel):
1530
1552
  self.raise_setter_error_if_child_tool("category")
1531
1553
  self._category = value
1532
1554
 
1533
- @property
1534
- def qualifications_never_expire(self):
1535
- return (
1536
- self.parent_tool.qualifications_never_expire if self.is_child_tool() else self._qualifications_never_expire
1537
- )
1538
-
1539
- @qualifications_never_expire.setter
1540
- def qualifications_never_expire(self, value):
1541
- self.raise_setter_error_if_child_tool("qualifications_never_expire")
1542
- self._qualifications_never_expire = value
1543
-
1544
1555
  @property
1545
1556
  def description(self):
1546
1557
  return self.parent_tool.description if self.is_child_tool() else self._description
@@ -1689,6 +1700,56 @@ class Tool(SerializationByNameModel):
1689
1700
  self.raise_setter_error_if_child_tool("interlock")
1690
1701
  self._interlock = value
1691
1702
 
1703
+ @property
1704
+ def qualification_reminder_days(self):
1705
+ return (
1706
+ self.parent_tool.qualification_reminder_days if self.is_child_tool() else self._qualification_reminder_days
1707
+ )
1708
+
1709
+ @qualification_reminder_days.setter
1710
+ def qualification_reminder_days(self, value):
1711
+ self.raise_setter_error_if_child_tool("qualification_reminder_days")
1712
+ self._qualification_reminder_days = value
1713
+
1714
+ @property
1715
+ def qualification_expiration_days(self):
1716
+ return (
1717
+ self.parent_tool.qualification_expiration_days
1718
+ if self.is_child_tool()
1719
+ else self._qualification_expiration_days
1720
+ )
1721
+
1722
+ @qualification_expiration_days.setter
1723
+ def qualification_expiration_days(self, value):
1724
+ self.raise_setter_error_if_child_tool("qualification_expiration_days")
1725
+ self._qualification_expiration_days = value
1726
+
1727
+ @property
1728
+ def qualification_expiration_never_used_days(self):
1729
+ return (
1730
+ self.parent_tool.qualification_expiration_never_used_days
1731
+ if self.is_child_tool()
1732
+ else self._qualification_expiration_never_used_days
1733
+ )
1734
+
1735
+ @qualification_expiration_never_used_days.setter
1736
+ def qualification_expiration_never_used_days(self, value):
1737
+ self.raise_setter_error_if_child_tool("qualification_expiration_never_used_days")
1738
+ self._qualification_expiration_never_used_days = value
1739
+
1740
+ @property
1741
+ def qualification_notification_email(self):
1742
+ return (
1743
+ self.parent_tool.qualification_notification_email
1744
+ if self.is_child_tool()
1745
+ else self._qualification_notification_email
1746
+ )
1747
+
1748
+ @qualification_notification_email.setter
1749
+ def qualification_notification_email(self, value):
1750
+ self.raise_setter_error_if_child_tool("qualification_notification_email")
1751
+ self._qualification_notification_email = value
1752
+
1692
1753
  @property
1693
1754
  def requires_area_access(self):
1694
1755
  return self.parent_tool.requires_area_access if self.is_child_tool() else self._requires_area_access
@@ -2379,6 +2440,11 @@ class Tool(SerializationByNameModel):
2379
2440
  else:
2380
2441
  raise ValueError(f"A {'project' if user else 'user'} must be provided for usage questions")
2381
2442
 
2443
+ def get_qualification_reminder_days(self) -> List[int]:
2444
+ if not self.qualification_reminder_days:
2445
+ return []
2446
+ return [int(days) for days in self.qualification_reminder_days.split(",") if days]
2447
+
2382
2448
  def clean(self):
2383
2449
  errors = {}
2384
2450
  if self.parent_tool_id:
@@ -646,7 +646,7 @@ function create_calendar()
646
646
  {
647
647
  "header":
648
648
  {
649
- left: "prev,next today{% if user.is_staff or user.is_tool_staff %} proxyReservation scheduledOutage{% endif %}{% if customizations|get_item:"training_module_enabled" == "enabled" %}{% if user|is_trainer %} scheduleTraining{% endif %} requestTraining{% endif %} login logout",
649
+ left: "prev,next today{% if user.is_staff or user.is_tool_staff %} proxyReservation scheduledOutage{% endif %}{% if customizations|get_item:"training_module_enabled" == "enabled" %}{% if user|is_trainer %} scheduleTraining{% endif %} {% if customizations|get_item:"training_requests_enabled" == "enabled" %}requestTraining{% endif %}{% endif %} login logout",
650
650
  center: "title",
651
651
  right: "agendaDay,agendaWeek,month"
652
652
  },
@@ -752,6 +752,7 @@ function reserve_for_someone_else_callback(response, status, xml_http_request)
752
752
 
753
753
  function request_training()
754
754
  {
755
+ {% if customizations|get_item:"training_requests_enabled" == "enabled" %}
755
756
  let item = get_selected_item();
756
757
  if (item && item !== "personal_schedule" && item !== "all_tools" && item !== "all_areas" && item !== "all_areastools")
757
758
  {
@@ -779,6 +780,7 @@ function request_training()
779
780
  }
780
781
  ajax_get(url, event_changes, function(response, status, xml_http_request) { return event_creation_success_callback(response, status, xml_http_request, post_data, callback) }, [ajax_failure_callback("Training request creation failed")])
781
782
  }
783
+ {% endif %}
782
784
  }
783
785
 
784
786
  function login_to_area()
@@ -623,97 +623,14 @@
623
623
  </div>
624
624
  </div>
625
625
  <div class="customization-separation" style="margin-bottom: 15px"></div>
626
- <h3 class="customization-section-title">Tool qualification expiration</h3>
627
- <p>
628
- If active, this feature will remove tool qualification from a user if the user has not used the tool after a while or never used it since qualified (configured separately).
629
- <p>
630
- The <a href="{% url 'customization' 'templates' %}?#tool_qualification_expiration_email_id">user tool qualification expiration email</a> need to be set to enable this feature.
631
- </p>
632
- <br />
633
- <div class="form-group {% if errors.tool_qualification_reminder_days %}has-error{% endif %}">
634
- <label class="control-label col-md-3" for="tool_qualification_reminder_days">Reminder days</label>
635
- <div class="col-md-5">
636
- <input type="text"
637
- id="tool_qualification_reminder_days"
638
- name="tool_qualification_reminder_days"
639
- class="form-control"
640
- value="{% if errors.tool_qualification_reminder_days %}{{ errors.tool_qualification_reminder_days.value }}{% else %}{{ tool_qualification_reminder_days }}{% endif %}" />
641
- </div>
642
- <div class="col-md-offset-3 col-md-9 help-block light-grey">
643
- {% if errors.tool_qualification_reminder_days %}
644
- {{ errors.tool_qualification_reminder_days.error }}
645
- {% else %}
646
- The (optional) number of days to send a reminder prior to the user's tool qualification expiration (below). A comma-separated list can be used for multiple reminders. This applies to both expiration cases.
647
- {% endif %}
648
- </div>
649
- </div>
650
- <div class="form-group {% if errors.tool_qualification_expiration_days %}has-error{% endif %}">
651
- <label class="control-label col-md-3" for="tool_qualification_expiration_days">
652
- Expiration days (previous tool usage)
653
- </label>
654
- <div class="col-md-5">
655
- <input type="number"
656
- step="1"
657
- id="tool_qualification_expiration_days"
658
- name="tool_qualification_expiration_days"
659
- class="form-control"
660
- value="{% if errors.tool_qualification_expiration_days %}{{ errors.tool_qualification_expiration_days.value }}{% else %}{{ tool_qualification_expiration_days }}{% endif %}" />
661
- </div>
662
- <div class="col-md-offset-3 col-md-9 help-block light-grey">
663
- {% if errors.tool_qualification_expiration_days %}
664
- {{ errors.tool_qualification_expiration_days.error }}
665
- {% else %}
666
- The number of days before the user's tool qualification expires since the user last used the tool.
667
- {% endif %}
668
- </div>
669
- </div>
670
- <div class="form-group {% if errors.tool_qualification_expiration_never_used_days %}has-error{% endif %}">
671
- <label class="control-label col-md-3" for="tool_qualification_expiration_never_used_days">
672
- Expiration days (no tool usage)
673
- </label>
674
- <div class="col-md-5">
675
- <input type="number"
676
- step="1"
677
- id="tool_qualification_expiration_never_used_days"
678
- name="tool_qualification_expiration_never_used_days"
679
- class="form-control"
680
- value="{% if errors.tool_qualification_expiration_never_used_days %}{{ errors.tool_qualification_expiration_never_used_days.value }}{% else %}{{ tool_qualification_expiration_never_used_days }}{% endif %}" />
681
- </div>
682
- <div class="col-md-offset-3 col-md-9 help-block light-grey">
683
- {% if errors.tool_qualification_expiration_never_used_days %}
684
- {{ errors.tool_qualification_expiration_never_used_days.error }}
685
- {% else %}
686
- The number of days before the user's tool qualification expires since the user qualified for the first time.
687
- {% endif %}
688
- </div>
689
- </div>
690
- <div class="form-group {% if errors.tool_qualification_cc %}has-error{% endif %}">
691
- <label class="control-label col-md-3" for="tool_qualification_cc">Reminder/expiration CC</label>
692
- <div class="col-md-5">
693
- <input type="text"
694
- id="tool_qualification_cc"
695
- name="tool_qualification_cc"
696
- class="form-control"
697
- value="{% if errors.tool_qualification_cc %}{{ errors.tool_qualification_cc.value }}{% else %}{{ tool_qualification_cc }}{% endif %}"
698
- placeholder="information@example.org" />
699
- </div>
700
- <div class="col-md-offset-3 col-md-9 help-block light-grey">
701
- {% if errors.tool_qualification_cc %}
702
- {{ errors.tool_qualification_cc.error }}
703
- {% else %}
704
- Extra email address to copy when a user's tool qualification reminder/expiration email is sent. A comma-separated list can be used.
705
- {% endif %}
706
- </div>
707
- </div>
708
- <div class="customization-separation" style="margin-bottom: 15px"></div>
709
- <div class="text-center">{% button type="save" value="Save settings" %}</div>
710
- </div>
711
- <script type="text/javascript">
626
+ <div class="text-center">{% button type="save" value="Save settings" %}</div>
627
+ </div>
628
+ <script type="text/javascript">
712
629
  $("#tool-tab-link").click(function() {setTimeout(on_tool_tab_show, 50)});
713
630
  function on_tool_tab_show()
714
631
  {
715
632
  auto_size_textarea(document.getElementById('tool_control_configuration_setting_template'))
716
633
  }
717
634
  on_tool_tab_show();
718
- </script>
719
- </form>
635
+ </script>
636
+ </form>
@@ -82,6 +82,15 @@
82
82
  Enable training requests/sessions scheduling
83
83
  </label>
84
84
  <br />
85
+ <input type="hidden" name="training_requests_enabled" value="off" />
86
+ <label>
87
+ <input type="checkbox"
88
+ name="training_requests_enabled"
89
+ {% if training_requests_enabled == "enabled" %}checked{% endif %}
90
+ value="enabled">
91
+ Allow users to request training (via calendar or training module)
92
+ </label>
93
+ <br />
85
94
  <label>
86
95
  <input type="checkbox"
87
96
  name="training_show_in_user_requests"
@@ -17,7 +17,7 @@
17
17
  History</a>
18
18
  </li>
19
19
  {% endif %}
20
- {% if show_usage_data_tab and not hide_data_history or show_usage_data_tab and user.is_any_part_of_staff %}
20
+ {% if not hide_data_history or user.is_any_part_of_staff %}
21
21
  <li style="width: 48%; text-align: center">
22
22
  <a href="#usage_data" style="padding: 10px 5px;" onclick="load_usage_data('{{ tool.id }}');">Usage Data History</a>
23
23
  </li>
@@ -50,7 +50,7 @@
50
50
  <a href="#config" onclick="load_config_history('{{ tool.id }}');">Config History</a>
51
51
  </li>
52
52
  {% endif %}
53
- {% if show_usage_data_tab and not hide_data_history or show_usage_data_tab and user.is_any_part_of_staff %}
53
+ {% if not hide_data_history or user.is_any_part_of_staff %}
54
54
  <li>
55
55
  <a href="#usage_data" onclick="load_usage_data('{{ tool.id }}');">Run Data History</a>
56
56
  </li>
@@ -98,21 +98,61 @@
98
98
  </div>
99
99
  </div>
100
100
  </form>
101
- {% if run_data_table.rows or pre_run_data_table.rows %}
101
+ {% if data_table.rows or run_data_table.rows or pre_run_data_table.rows %}
102
102
  <ul class="nav nav-tabs" id="usage-tabs">
103
+ {% if data_table.rows %}
104
+ <li class="active">
105
+ <a data-toggle="tab" href="#run-data-tab">Usage history</a>
106
+ </li>
107
+ {% endif %}
103
108
  {% if pre_run_data_table.rows %}
104
- <li class="{% if pre_run_data_table.rows %}active{% endif %}">
109
+ <li>
105
110
  <a data-toggle="tab" href="#pre-run-data-tab">Pre-Run Data</a>
106
111
  </li>
107
112
  {% endif %}
108
113
  {% if run_data_table.rows %}
109
- <li class="{% if not pre_run_data_table.rows %}active{% endif %}">
110
- <a data-toggle="tab" href="#run-data-tab">Run Data</a>
114
+ <li>
115
+ <a data-toggle="tab" href="#post-run-data-tab">Post-Run Data</a>
111
116
  </li>
112
117
  {% endif %}
113
118
  </ul>
114
119
  <div class="tab-content" style="margin-bottom: 0;">
115
- <div class="tab-pane {% if pre_run_data_table.rows %}active{% endif %}" id="pre-run-data-tab">
120
+ <div class="tab-pane active" id="run-data-tab">
121
+ <div class="col-md-12">
122
+ <div class="form-group pull-right extra-side-padding" style="padding-top: 10px">
123
+ {% button type="export" value="Export" onclick="load_usage_data('"|concat:tool_id|concat:"', 'all');" %}
124
+ </div>
125
+ </div>
126
+ <div class="panel-body">
127
+ {% if data_table.rows %}
128
+ <table class="table table-bordered table-condensed" style="margin-top: 10px">
129
+ <thead>
130
+ <tr>
131
+ {% for header in data_table.flat_headers %}<th>{{ header }}</th>{% endfor %}
132
+ </tr>
133
+ </thead>
134
+ <tbody>
135
+ {% for row in data_table.flat_rows %}
136
+ <tr>
137
+ {% for item in row %}
138
+ <td>
139
+ {% if item|class_name == "list" %}
140
+ {{ item|join:", " }}
141
+ {% else %}
142
+ {{ item|default_if_none:"" }}
143
+ {% endif %}
144
+ </td>
145
+ {% endfor %}
146
+ </tr>
147
+ {% endfor %}
148
+ </tbody>
149
+ </table>
150
+ {% else %}
151
+ <span class="italic">No run data was found between these dates</span>
152
+ {% endif %}
153
+ </div>
154
+ </div>
155
+ <div class="tab-pane" id="pre-run-data-tab">
116
156
  <div class="col-md-12">
117
157
  <div class="form-group pull-right extra-side-padding" style="padding-top: 10px">
118
158
  {% button type="export" value="Export" onclick="load_usage_data('"|concat:tool_id|concat:"', 'pre-run');" %}
@@ -147,7 +187,7 @@
147
187
  {% endif %}
148
188
  </div>
149
189
  </div>
150
- <div class="tab-pane {% if not pre_run_data_table.rows %}active{% endif %}" id="run-data-tab">
190
+ <div class="tab-pane" id="post-run-data-tab">
151
191
  <div class="col-md-12">
152
192
  <div class="form-group pull-right extra-side-padding" style="padding-top: 10px">
153
193
  {% button type="export" value="Export" onclick="load_usage_data('"|concat:tool_id|concat:"', 'run');" %}
@@ -1,5 +1,6 @@
1
1
  {% extends "base.html" %}
2
2
  {% load training_tags %}
3
+ {% load custom_tags_and_filters %}
3
4
  {% block extrahead %}
4
5
  {% load static %}
5
6
  <script type="text/javascript" src="{% static "datetimepicker/bootstrap-datetimepicker.js" %}"></script>
@@ -13,14 +14,16 @@
13
14
  {% block content %}
14
15
  <h1 style="margin-top: 0; margin-bottom: 25px">Training dashboard</h1>
15
16
  <ul class="nav nav-tabs">
16
- {% url 'training_requests' as training_requests_url %}
17
- <li class="nav-item{% if training_requests_url in request.path %} active{% endif %}">
18
- <a class="nav-link" href="{{ training_requests_url }}">My training requests
19
- {% if training_invitation_notification_count %}
20
- <span class="badge badge-tab-top">{{ training_invitation_notification_count }}</span>
21
- {% endif %}
22
- </a>
23
- </li>
17
+ {% if customizations|get_item:"training_requests_enabled" == "enabled" %}
18
+ {% url 'training_requests' as training_requests_url %}
19
+ <li class="nav-item{% if training_requests_url in request.path %} active{% endif %}">
20
+ <a class="nav-link" href="{{ training_requests_url }}">My training requests
21
+ {% if training_invitation_notification_count %}
22
+ <span class="badge badge-tab-top">{{ training_invitation_notification_count }}</span>
23
+ {% endif %}
24
+ </a>
25
+ </li>
26
+ {% endif %}
24
27
  {% url 'training_history' as training_history_url %}
25
28
  <li class="nav-item{% if training_history_url in request.path %} active{% endif %}">
26
29
  <a class="nav-link" href="{{ training_history_url }}">Training history</a>