From 6a9ec035d618afb4e156d774f0a528b728d513b1 Mon Sep 17 00:00:00 2001 From: Mark Date: Sun, 2 Apr 2017 00:38:40 +0200 Subject: [PATCH] Initial commit for Device History --- netbox/dcim/filters.py | 6 ++ netbox/dcim/forms.py | 25 +++++++- netbox/dcim/models.py | 39 ++++++++++++ netbox/dcim/tables.py | 24 +++++++- netbox/dcim/urls.py | 13 ++++ netbox/dcim/views.py | 59 ++++++++++++++++++- netbox/templates/dcim/device_historylog.html | 46 +++++++++++++++ .../templates/dcim/historylogrole_list.html | 21 +++++++ netbox/templates/dcim/inc/device_header.html | 1 + netbox/templates/dcim/inc/historylog.html | 19 ++++++ 10 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 netbox/templates/dcim/device_historylog.html create mode 100644 netbox/templates/dcim/historylogrole_list.html create mode 100644 netbox/templates/dcim/inc/historylog.html diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 6eab5ae34..106607ee8 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -484,6 +484,12 @@ class InventoryItemFilter(DeviceComponentFilterSet): fields = ['name'] +class HistoryLogFilter(DeviceComponentFilterSet): + + class Meta: + model = InventoryItem + fields = ['name'] + class ConsoleConnectionFilter(django_filters.FilterSet): site = django_filters.CharFilter( method='filter_site', diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 6473e5e56..a77eec894 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -23,7 +23,7 @@ from .models import ( Interface, IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, InterfaceConnection, InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, RackReservation, RackRole, Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD, - SUBDEVICE_ROLE_PARENT, VIRTUAL_IFACE_TYPES + SUBDEVICE_ROLE_PARENT, VIRTUAL_IFACE_TYPES, HistoryRole, HistoryLog ) @@ -1710,3 +1710,26 @@ class InventoryItemForm(BootstrapMixin, forms.ModelForm): class Meta: model = InventoryItem fields = ['name', 'manufacturer', 'part_id', 'serial'] + + +# +# HistoryRole +# + +class HistoryRoleForm(BootstrapMixin, forms.ModelForm): + slug = SlugField() + + class Meta: + model = HistoryRole + fields = ['name', 'slug', 'color'] + + +# +# HistoryLog +# + +class HistoryLogForm(BootstrapMixin, forms.ModelForm): + + class Meta: + model = HistoryLog + fields = ['device', 'role', 'message'] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index d0971b556..ca528dad5 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1421,3 +1421,42 @@ class InventoryItem(models.Model): def __str__(self): return self.name + + +# +# History +# + +@python_2_unicode_compatible +class HistoryRole(models.Model): + """ + Grouping the history activity type + """ + name = models.CharField(max_length=50, unique=True) + slug = models.SlugField(unique=True) + color = ColorField() + + class Meta: + ordering = ['name'] + + def __str__(self): + return self.name + + +@python_2_unicode_compatible +class HistoryLog(models.Model): + """ + A history is a free text form which gives capability to store any comment or change in time based view. + """ + time = models.DateTimeField(auto_now_add=True, editable=False) + user = models.ForeignKey(User, related_name='history_log', blank=True, null=True, on_delete=models.SET_NULL) + device = models.ForeignKey('Device', related_name='history_log', on_delete=models.CASCADE) + + role = models.ForeignKey('HistoryRole', related_name='history_log', blank=True, null=True, on_delete=models.SET_NULL) + message = models.TextField(blank=True) + + class Meta: + ordering = ['-time'] + + def __str__(self): + return self.message diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index b6ebb1be2..04a68b3b6 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -6,7 +6,7 @@ from utilities.tables import BaseTable, SearchTable, ToggleColumn from .models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPortTemplate, Device, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, Manufacturer, Platform, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, - RackGroup, Region, Site, + RackGroup, Region, Site, HistoryRole, ) @@ -70,6 +70,12 @@ DEVICEROLE_ACTIONS = """ {% endif %} """ +HISTORYLOGROLE_ACTIONS = """ +{% if perms.dcim.change_historylogrole %} + +{% endif %} +""" + MANUFACTURER_ACTIONS = """ {% if perms.dcim.change_manufacturer %} @@ -498,3 +504,19 @@ class InterfaceConnectionTable(BaseTable): class Meta(BaseTable.Meta): model = Interface fields = ('device_a', 'interface_a', 'device_b', 'interface_b') + +# +# HistoryLog roles +# + +class HistoryLogRoleTable(BaseTable): + pk = ToggleColumn() + name = tables.Column(verbose_name='Name') + color = tables.TemplateColumn(COLOR_LABEL, verbose_name='Color') + slug = tables.Column(verbose_name='Slug') + actions = tables.TemplateColumn(template_code=HISTORYLOGROLE_ACTIONS, attrs={'td': {'class': 'text-right'}}, + verbose_name='') + + class Meta(BaseTable.Meta): + model = HistoryRole + fields = ('pk', 'name', 'color', 'slug', 'actions') diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index b4731df33..babac1fa9 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -113,6 +113,7 @@ urlpatterns = [ url(r'^devices/(?P\d+)/edit/$', views.DeviceEditView.as_view(), name='device_edit'), url(r'^devices/(?P\d+)/delete/$', views.DeviceDeleteView.as_view(), name='device_delete'), url(r'^devices/(?P\d+)/inventory/$', views.device_inventory, name='device_inventory'), + url(r'^devices/(?P\d+)/history-log/$', views.device_historylog, name='device_historylog'), url(r'^devices/(?P\d+)/lldp-neighbors/$', views.device_lldp_neighbors, name='device_lldp_neighbors'), url(r'^devices/(?P\d+)/ip-addresses/assign/$', views.ipaddress_assign, name='ipaddress_assign'), url(r'^devices/(?P\d+)/add-secret/$', secret_add, name='device_addsecret'), @@ -178,6 +179,18 @@ urlpatterns = [ url(r'^inventory-items/(?P\d+)/edit/$', views.InventoryItemEditView.as_view(), name='inventoryitem_edit'), url(r'^inventory-items/(?P\d+)/delete/$', views.InventoryItemDeleteView.as_view(), name='inventoryitem_delete'), + # HistoryLog + url(r'^devices/(?P\d+)/history-log/add/$', views.HistoryLogEditView.as_view(), + name='historylog_add'), + url(r'^history-log/(?P\d+)/edit/$', views.HistoryLogEditView.as_view(), name='historylog_edit'), + url(r'^history-log/(?P\d+)/delete/$', views.HistoryLogDeleteView.as_view(), name='historylog_delete'), + + # HistoryLogRoles + url(r'^history-log-roles/$', views.HistoryLogRoleListView.as_view(), name='historylogrole_list'), + url(r'^history-log-roles/add/$', views.HistoryLogRoleEditView.as_view(), name='historylogrole_add'), + url(r'^history-log-roles/delete/$', views.HistoryLogRoleBulkDeleteView.as_view(), name='historylogrole_bulk_delete'), + url(r'^history-log-roles/(?P\d+)/edit/$', views.HistoryLogRoleEditView.as_view(), name='historylogrole_edit'), + # Console/power/interface connections url(r'^console-connections/$', views.ConsoleConnectionsListView.as_view(), name='console_connections_list'), url(r'^console-connections/import/$', views.ConsoleConnectionsBulkImportView.as_view(), name='console_connections_import'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 20e51d34e..4a64bc2f4 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -26,7 +26,7 @@ from .models import ( CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, - RackReservation, RackRole, Region, Site, + RackReservation, RackRole, Region, Site, HistoryLog, HistoryRole, ) @@ -808,6 +808,17 @@ def device_inventory(request, pk): }) +def device_historylog(request, pk): + + device = get_object_or_404(Device, pk=pk) + historylog_items = HistoryLog.objects.filter(device=device) + + return render(request, 'dcim/device_historylog.html', { + 'device': device, + 'historylog_items': historylog_items, + }) + + def device_lldp_neighbors(request, pk): device = get_object_or_404(Device, pk=pk) @@ -1611,3 +1622,49 @@ class InventoryItemEditView(PermissionRequiredMixin, ComponentEditView): class InventoryItemDeleteView(PermissionRequiredMixin, ComponentDeleteView): permission_required = 'dcim.delete_inventoryitem' model = InventoryItem + + +# +# HistoryLog +# + +class HistoryLogEditView(PermissionRequiredMixin, ComponentEditView): + permission_required = 'dcim.change_historylog' + model = HistoryLog + form_class = forms.HistoryLogForm + + def alter_obj(self, obj, request, url_args, url_kwargs): + if 'device' in url_kwargs: + obj.device = get_object_or_404(Device, pk=url_kwargs['device']) + obj.user = request.user + return obj + + +class HistoryLogDeleteView(PermissionRequiredMixin, ComponentDeleteView): + permission_required = 'dcim.delete_historylog' + model = HistoryLog + + +# +# HistoryLogRole +# + +class HistoryLogRoleListView(ObjectListView): + queryset = HistoryRole.objects.annotate(historylog_count=Count('history_log')) + table = tables.HistoryLogRoleTable + template_name = 'dcim/historylogrole_list.html' + + +class HistoryLogRoleEditView(PermissionRequiredMixin, ObjectEditView): + permission_required = 'dcim.change_historylogrole' + model = HistoryRole + form_class = forms.HistoryRoleForm + + def get_return_url(self, obj): + return reverse('dcim:historylogrole_list') + + +class HistoryLogRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): + permission_required = 'dcim.delete_historylogrole' + cls = HistoryRole + default_return_url = 'dcim:historylogrole_list' diff --git a/netbox/templates/dcim/device_historylog.html b/netbox/templates/dcim/device_historylog.html new file mode 100644 index 000000000..dc7f2f50e --- /dev/null +++ b/netbox/templates/dcim/device_historylog.html @@ -0,0 +1,46 @@ +{% extends '_base.html' %} + +{% block title %}{{ device }} - Inventory{% endblock %} + +{% block content %} +{% include 'dcim/inc/device_header.html' with active_tab='historylog' %} +
+
+
+
+ History Log +
+ + + + + + + + + + + + {% for item in historylog_items %} + {% with template_name='dcim/inc/historylog.html' indent=0 %} + {% include template_name %} + {% endwith %} + {% endfor %} + +
TimeUserRoleMessage
+
+ {% if perms.dcim.add_historylog %} + + + Add History Log + + {% endif %} + {% if perms.dcim.add_historylogrole %} + + + History Log Roles + + {% endif %} +
+
+{% endblock %} diff --git a/netbox/templates/dcim/historylogrole_list.html b/netbox/templates/dcim/historylogrole_list.html new file mode 100644 index 000000000..902e1f544 --- /dev/null +++ b/netbox/templates/dcim/historylogrole_list.html @@ -0,0 +1,21 @@ +{% extends '_base.html' %} +{% load helpers %} + +{% block title %}History Role{% endblock %} + +{% block content %} +
+ {% if perms.dcim.add_historylogrole %} + + + Add a historylog role + + {% endif %} +
+

History Roles

+
+
+ {% include 'utilities/obj_table.html' with bulk_delete_url='dcim:historylogrole_bulk_delete' %} +
+
+{% endblock %} diff --git a/netbox/templates/dcim/inc/device_header.html b/netbox/templates/dcim/inc/device_header.html index 9f31c73fc..da6a92715 100644 --- a/netbox/templates/dcim/inc/device_header.html +++ b/netbox/templates/dcim/inc/device_header.html @@ -48,4 +48,5 @@ {% if device.status %} {% endif %} + diff --git a/netbox/templates/dcim/inc/historylog.html b/netbox/templates/dcim/inc/historylog.html new file mode 100644 index 000000000..cac014bda --- /dev/null +++ b/netbox/templates/dcim/inc/historylog.html @@ -0,0 +1,19 @@ + + {{ item.time }} + {{ item.user }} + {{ item.role }} + {{ item.message }} + + {% if perms.dcim.change_historylog_item %} + + {% endif %} + {% if perms.dcim.delete_historylog_item %} + + {% endif %} + + +{% for item in item.child_items.all %} + {% with template_name='dcim/inc/historylog.html' indent=indent|add:20 %} + {% include template_name %} + {% endwith %} +{% endfor %}