+ Returns a list of matched components and a list of errors encountered during the
+ process. Again, this would be better handled by a Django Form class.
"""
+ components = []
+ component_data_errors = []
- # Mapping for changing the title of the trail.
- title_mapping = {'netbox': 'IP Device', 'netboxgroup': 'Device Group'}
-
- trails = []
- for key in component_keys:
- title = title_mapping.get(key, key)
- for pkey in component_keys[key]:
- trail = []
- try:
- comp = components[key][pkey]
- except KeyError:
- trail.append(
- {
- 'url': None,
- 'title': None,
- 'name': "ID %s (Component was deleted)" % pkey,
- }
- )
- else:
- if key in ('location', 'room', 'netbox', 'service'):
- location_id = comp[FIELD_KEYS[key]['location'] + "id"]
- location_description = comp[
- FIELD_KEYS[key]['location'] + "description"
- ]
- trail.append(
- {
- 'url': reverse('location-info', args=[location_id]),
- 'title': location_description,
- 'name': location_id,
- }
- )
- if key in ('room', 'netbox', 'service'):
- room_id = comp[FIELD_KEYS[key]['room'] + "id"]
- room_description = comp[FIELD_KEYS[key]['room'] + "description"]
- trail.append(
- {
- 'url': reverse('room-info', args=[room_id]),
- 'title': room_description,
- 'name': room_id,
- }
- )
- if key in ('netbox', 'service'):
- netbox_sysname = comp[FIELD_KEYS[key]['netbox'] + "sysname"]
- netbox_ip = comp[FIELD_KEYS[key]['netbox'] + "ip"]
- trail.append(
- {
- 'url': reverse(
- 'ipdevinfo-details-by-name', args=[netbox_sysname]
- ),
- 'title': netbox_ip,
- 'name': netbox_sysname,
- }
- )
- if key == 'service':
- trail.append(
- {
- 'url': None,
- 'title': None,
- 'name': comp['handler'],
- }
- )
- if key == 'netboxgroup':
- trail.append(
- {
- 'url': reverse('netbox-group-detail', args=[comp['id']]),
- 'title': '',
- 'name': comp['id'],
- }
- )
- trails.append(
- {
- 'id': pkey,
- 'type': key,
- 'title': title,
- 'trail': trail,
- }
+ for key, values in component_keys.items():
+ if not values:
+ continue
+ model_class = (
+ LegacyGenericForeignKey.get_model_class(key)
+ if key in ALLOWED_COMPONENTS
+ else None
+ )
+ if not model_class:
+ component_data_errors.append(f"{key}: invalid component type")
+ continue
+
+ objects = model_class.objects.filter(id__in=values)
+ components.extend(objects)
+ if not objects:
+ component_data_errors.append(
+ f"{key}: no elements with the given identifiers found"
)
- return trails
+ return components, component_data_errors
class MaintenanceCalendar(HTMLCalendar):
diff --git a/python/nav/web/maintenance/views.py b/python/nav/web/maintenance/views.py
index 35d6dda95e..449f97ce23 100644
--- a/python/nav/web/maintenance/views.py
+++ b/python/nav/web/maintenance/views.py
@@ -1,5 +1,6 @@
#
# Copyright (C) 2011 Uninett AS
+# Copyright (C) 2024 Sikt
#
# This file is part of Network Administration Visualized (NAV).
#
@@ -15,33 +16,40 @@
#
import logging
-
import time
from datetime import datetime
-from django.db import transaction, connection
+from django.db import connection, transaction
from django.db.models import Count, Q
-from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponseRedirect
+from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.safestring import mark_safe
+import nav.maintengine
from nav.django.utils import get_account
from nav.models.manage import Netbox
-from nav.models.msgmaint import MaintenanceTask, MaintenanceComponent
-from nav.web.message import new_message, Messages
+from nav.models.msgmaint import MaintenanceComponent, MaintenanceTask
+from nav.web.maintenance.forms import (
+ MaintenanceAddSingleNetbox,
+ MaintenanceCalendarForm,
+ MaintenanceTaskForm,
+)
+from nav.web.maintenance.utils import (
+ NAVPATH,
+ COMPONENTS_WITH_INTEGER_PK,
+ TITLE,
+ MaintenanceCalendar,
+ component_to_trail,
+ get_component_keys,
+ get_components,
+ get_components_from_keydict,
+ infodict_by_state,
+ task_form_initial,
+)
+from nav.web.message import Messages, new_message
from nav.web.quickselect import QuickSelect
-from nav.web.maintenance.utils import components_for_keys
-from nav.web.maintenance.utils import task_component_trails
-from nav.web.maintenance.utils import get_component_keys, PRIMARY_KEY_INTEGER
-from nav.web.maintenance.utils import structure_component_data
-from nav.web.maintenance.utils import task_form_initial, infodict_by_state
-from nav.web.maintenance.utils import MaintenanceCalendar, NAVPATH, TITLE
-from nav.web.maintenance.forms import MaintenanceTaskForm, MaintenanceCalendarForm
-from nav.web.maintenance.forms import MaintenanceAddSingleNetbox
-import nav.maintengine
-
INFINITY = datetime.max
_logger = logging.getLogger(__name__)
@@ -189,25 +197,8 @@ def historic(request):
def view(request, task_id):
task = get_object_or_404(MaintenanceTask, pk=task_id)
- maint_components = MaintenanceComponent.objects.filter(
- maintenance_task=task.id
- ).values_list('key', 'value')
-
- component_keys = {
- 'service': [],
- 'netbox': [],
- 'room': [],
- 'location': [],
- 'netboxgroup': [],
- }
- for key, value in maint_components:
- if key in PRIMARY_KEY_INTEGER:
- value = int(value)
- component_keys[key].append(value)
-
- component_data, _ = components_for_keys(component_keys)
- components = structure_component_data(component_data)
- component_trail = task_component_trails(component_keys, components)
+ components = get_components(task)
+ component_trail = [component_to_trail(c) for c in components]
heading = 'Task "%s"' % task.description
infodict = infodict_by_state(task)
@@ -252,7 +243,9 @@ def cancel(request, task_id):
def edit(request, task_id=None, start_time=None, **_):
account = get_account(request)
quickselect = QuickSelect(service=True)
- component_trail = component_keys_errors = component_data = task = None
+ components = task = None
+ component_keys_errors = []
+ component_keys = {}
if task_id:
task = get_object_or_404(MaintenanceTask, pk=task_id)
@@ -260,36 +253,25 @@ def edit(request, task_id=None, start_time=None, **_):
if request.method == 'POST':
component_keys, component_keys_errors = get_component_keys(request.POST)
-
elif task:
- component_keys = {
- 'service': [],
- 'netbox': [],
- 'room': [],
- 'location': [],
- 'netboxgroup': [],
- }
- for key, value in task.maintenance_components.values_list('key', 'value'):
- if key in PRIMARY_KEY_INTEGER:
- value = int(value)
- component_keys[key].append(value)
+ components = get_components(task)
else:
component_keys, component_keys_errors = get_component_keys(request.GET)
if component_keys:
- component_data, component_data_errors = components_for_keys(component_keys)
- components = structure_component_data(component_data)
- component_trail = task_component_trails(component_keys, components)
- if component_data_errors:
- new_message(request, ",".join(component_data_errors), Messages.ERROR)
+ components, component_data_errors = get_components_from_keydict(component_keys)
+ for error in component_data_errors:
+ new_message(request, error, Messages.ERROR)
+
+ component_trail = [component_to_trail(c) for c in components]
- if component_keys_errors:
- new_message(request, ",".join(component_keys_errors), Messages.ERROR)
+ for error in component_keys_errors:
+ new_message(request, error, Messages.ERROR)
if request.method == 'POST':
if 'save' in request.POST:
task_form = MaintenanceTaskForm(request.POST)
- if component_keys and not any(component_data.values()):
+ if component_keys and not components:
new_message(request, "No components selected.", Messages.ERROR)
elif not component_keys_errors and task_form.is_valid():
start_time = task_form.cleaned_data['start_time']
@@ -321,14 +303,18 @@ def edit(request, task_id=None, start_time=None, **_):
sql = """DELETE FROM maint_component
WHERE maint_taskid = %s"""
cursor.execute(sql, (new_task.id,))
- for key in component_data:
- for component in component_data[key]:
- task_component = MaintenanceComponent(
- maintenance_task=new_task,
- key=key,
- value="%s" % component['id'],
- )
- task_component.save()
+ for component in components:
+ table = component._meta.db_table
+ descr = (
+ str(component) if table in COMPONENTS_WITH_INTEGER_PK else None
+ )
+ task_component = MaintenanceComponent(
+ maintenance_task=new_task,
+ key=table,
+ value=component.pk,
+ description=descr,
+ )
+ task_component.save()
new_message(
request, "Saved task %s" % new_task.description, Messages.SUCCESS
)
diff --git a/python/nav/web/templates/maintenance/details.html b/python/nav/web/templates/maintenance/details.html
index 4c9c5b2d93..49ad48356b 100644
--- a/python/nav/web/templates/maintenance/details.html
+++ b/python/nav/web/templates/maintenance/details.html
@@ -1,4 +1,5 @@
{% extends "maintenance/base.html" %}
+{% load maintenance %}
{% block content %}
@@ -43,20 +44,13 @@
{% if components %}
- {% for comp in components %}
- -
- {{ comp.title|capfirst }}:
- {% for elem in comp.trail %}
- {% if elem.url %}
- {{ elem.name }}
- {% else %}
- {{ elem.name }}
- {% endif %}
- {% if not forloop.last %}
- →
- {% endif %}
- {% endfor %}
-
+ {% for trail in components %}
+ -
+ {% with trail|last as component %}
+ {{ component|model_verbose_name }}:
+ {% include "maintenance/frag-component-trail.html" %}
+ {% endwith %}
+
{% endfor %}
{% else %}
diff --git a/python/nav/web/templates/maintenance/frag-component-trail.html b/python/nav/web/templates/maintenance/frag-component-trail.html
new file mode 100644
index 0000000000..fc91b656cc
--- /dev/null
+++ b/python/nav/web/templates/maintenance/frag-component-trail.html
@@ -0,0 +1,11 @@
+{% load maintenance %}
+{% for element in trail %}
+ {% with element|component_name as name %}
+ {% if element.get_absolute_url %}
+ {{ name }}
+ {% else %}
+ {{ name }}
+ {% endif %}
+ {% endwith %}
+ {% if not forloop.last %} → {% endif %}
+{% endfor %}
diff --git a/python/nav/web/templates/maintenance/new_task.html b/python/nav/web/templates/maintenance/new_task.html
index 526f99a876..1fed9ce69c 100644
--- a/python/nav/web/templates/maintenance/new_task.html
+++ b/python/nav/web/templates/maintenance/new_task.html
@@ -1,4 +1,5 @@
{% extends "maintenance/base.html" %}
+{% load maintenance %}
{% block base_header_additional_head %}
{{ block.super }}
@@ -57,26 +58,19 @@ {{ heading }}
{% if components %}
|