Implemented changelog views

This commit is contained in:
Jeremy Stretch 2018-06-20 13:52:54 -04:00
parent a8b11e45c1
commit ddd878683d
17 changed files with 317 additions and 87 deletions

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.conf.urls import url from django.conf.urls import url
from extras.views import ChangeLogView from extras.views import ObjectChangeLogView
from . import views from . import views
from .models import Circuit, CircuitType, Provider from .models import Circuit, CircuitType, Provider
@ -18,7 +18,7 @@ urlpatterns = [
url(r'^providers/(?P<slug>[\w-]+)/$', views.ProviderView.as_view(), name='provider'), url(r'^providers/(?P<slug>[\w-]+)/$', views.ProviderView.as_view(), name='provider'),
url(r'^providers/(?P<slug>[\w-]+)/edit/$', views.ProviderEditView.as_view(), name='provider_edit'), url(r'^providers/(?P<slug>[\w-]+)/edit/$', views.ProviderEditView.as_view(), name='provider_edit'),
url(r'^providers/(?P<slug>[\w-]+)/delete/$', views.ProviderDeleteView.as_view(), name='provider_delete'), url(r'^providers/(?P<slug>[\w-]+)/delete/$', views.ProviderDeleteView.as_view(), name='provider_delete'),
url(r'^providers/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='provider_changelog', kwargs={'model': Provider}), url(r'^providers/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='provider_changelog', kwargs={'model': Provider}),
# Circuit types # Circuit types
url(r'^circuit-types/$', views.CircuitTypeListView.as_view(), name='circuittype_list'), url(r'^circuit-types/$', views.CircuitTypeListView.as_view(), name='circuittype_list'),
@ -26,7 +26,7 @@ urlpatterns = [
url(r'^circuit-types/import/$', views.CircuitTypeBulkImportView.as_view(), name='circuittype_import'), url(r'^circuit-types/import/$', views.CircuitTypeBulkImportView.as_view(), name='circuittype_import'),
url(r'^circuit-types/delete/$', views.CircuitTypeBulkDeleteView.as_view(), name='circuittype_bulk_delete'), url(r'^circuit-types/delete/$', views.CircuitTypeBulkDeleteView.as_view(), name='circuittype_bulk_delete'),
url(r'^circuit-types/(?P<slug>[\w-]+)/edit/$', views.CircuitTypeEditView.as_view(), name='circuittype_edit'), url(r'^circuit-types/(?P<slug>[\w-]+)/edit/$', views.CircuitTypeEditView.as_view(), name='circuittype_edit'),
url(r'^circuit-types/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='circuittype_changelog', kwargs={'model': CircuitType}), url(r'^circuit-types/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='circuittype_changelog', kwargs={'model': CircuitType}),
# Circuits # Circuits
url(r'^circuits/$', views.CircuitListView.as_view(), name='circuit_list'), url(r'^circuits/$', views.CircuitListView.as_view(), name='circuit_list'),
@ -37,7 +37,7 @@ urlpatterns = [
url(r'^circuits/(?P<pk>\d+)/$', views.CircuitView.as_view(), name='circuit'), url(r'^circuits/(?P<pk>\d+)/$', views.CircuitView.as_view(), name='circuit'),
url(r'^circuits/(?P<pk>\d+)/edit/$', views.CircuitEditView.as_view(), name='circuit_edit'), url(r'^circuits/(?P<pk>\d+)/edit/$', views.CircuitEditView.as_view(), name='circuit_edit'),
url(r'^circuits/(?P<pk>\d+)/delete/$', views.CircuitDeleteView.as_view(), name='circuit_delete'), url(r'^circuits/(?P<pk>\d+)/delete/$', views.CircuitDeleteView.as_view(), name='circuit_delete'),
url(r'^circuits/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='circuit_changelog', kwargs={'model': Circuit}), url(r'^circuits/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='circuit_changelog', kwargs={'model': Circuit}),
url(r'^circuits/(?P<pk>\d+)/terminations/swap/$', views.circuit_terminations_swap, name='circuit_terminations_swap'), url(r'^circuits/(?P<pk>\d+)/terminations/swap/$', views.circuit_terminations_swap, name='circuit_terminations_swap'),
# Circuit terminations # Circuit terminations

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.conf.urls import url from django.conf.urls import url
from extras.views import ChangeLogView, ImageAttachmentEditView from extras.views import ObjectChangeLogView, ImageAttachmentEditView
from ipam.views import ServiceCreateView from ipam.views import ServiceCreateView
from secrets.views import secret_add from secrets.views import secret_add
from . import views from . import views
@ -20,7 +20,7 @@ urlpatterns = [
url(r'^regions/import/$', views.RegionBulkImportView.as_view(), name='region_import'), url(r'^regions/import/$', views.RegionBulkImportView.as_view(), name='region_import'),
url(r'^regions/delete/$', views.RegionBulkDeleteView.as_view(), name='region_bulk_delete'), url(r'^regions/delete/$', views.RegionBulkDeleteView.as_view(), name='region_bulk_delete'),
url(r'^regions/(?P<pk>\d+)/edit/$', views.RegionEditView.as_view(), name='region_edit'), url(r'^regions/(?P<pk>\d+)/edit/$', views.RegionEditView.as_view(), name='region_edit'),
url(r'^regions/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='region_changelog', kwargs={'model': Region}), url(r'^regions/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='region_changelog', kwargs={'model': Region}),
# Sites # Sites
url(r'^sites/$', views.SiteListView.as_view(), name='site_list'), url(r'^sites/$', views.SiteListView.as_view(), name='site_list'),
@ -30,7 +30,7 @@ urlpatterns = [
url(r'^sites/(?P<slug>[\w-]+)/$', views.SiteView.as_view(), name='site'), url(r'^sites/(?P<slug>[\w-]+)/$', views.SiteView.as_view(), name='site'),
url(r'^sites/(?P<slug>[\w-]+)/edit/$', views.SiteEditView.as_view(), name='site_edit'), url(r'^sites/(?P<slug>[\w-]+)/edit/$', views.SiteEditView.as_view(), name='site_edit'),
url(r'^sites/(?P<slug>[\w-]+)/delete/$', views.SiteDeleteView.as_view(), name='site_delete'), url(r'^sites/(?P<slug>[\w-]+)/delete/$', views.SiteDeleteView.as_view(), name='site_delete'),
url(r'^sites/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='site_changelog', kwargs={'model': Site}), url(r'^sites/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='site_changelog', kwargs={'model': Site}),
url(r'^sites/(?P<object_id>\d+)/images/add/$', ImageAttachmentEditView.as_view(), name='site_add_image', kwargs={'model': Site}), url(r'^sites/(?P<object_id>\d+)/images/add/$', ImageAttachmentEditView.as_view(), name='site_add_image', kwargs={'model': Site}),
# Rack groups # Rack groups
@ -39,7 +39,7 @@ urlpatterns = [
url(r'^rack-groups/import/$', views.RackGroupBulkImportView.as_view(), name='rackgroup_import'), url(r'^rack-groups/import/$', views.RackGroupBulkImportView.as_view(), name='rackgroup_import'),
url(r'^rack-groups/delete/$', views.RackGroupBulkDeleteView.as_view(), name='rackgroup_bulk_delete'), url(r'^rack-groups/delete/$', views.RackGroupBulkDeleteView.as_view(), name='rackgroup_bulk_delete'),
url(r'^rack-groups/(?P<pk>\d+)/edit/$', views.RackGroupEditView.as_view(), name='rackgroup_edit'), url(r'^rack-groups/(?P<pk>\d+)/edit/$', views.RackGroupEditView.as_view(), name='rackgroup_edit'),
url(r'^rack-groups/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='rackgroup_changelog', kwargs={'model': RackGroup}), url(r'^rack-groups/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='rackgroup_changelog', kwargs={'model': RackGroup}),
# Rack roles # Rack roles
url(r'^rack-roles/$', views.RackRoleListView.as_view(), name='rackrole_list'), url(r'^rack-roles/$', views.RackRoleListView.as_view(), name='rackrole_list'),
@ -47,7 +47,7 @@ urlpatterns = [
url(r'^rack-roles/import/$', views.RackRoleBulkImportView.as_view(), name='rackrole_import'), url(r'^rack-roles/import/$', views.RackRoleBulkImportView.as_view(), name='rackrole_import'),
url(r'^rack-roles/delete/$', views.RackRoleBulkDeleteView.as_view(), name='rackrole_bulk_delete'), url(r'^rack-roles/delete/$', views.RackRoleBulkDeleteView.as_view(), name='rackrole_bulk_delete'),
url(r'^rack-roles/(?P<pk>\d+)/edit/$', views.RackRoleEditView.as_view(), name='rackrole_edit'), url(r'^rack-roles/(?P<pk>\d+)/edit/$', views.RackRoleEditView.as_view(), name='rackrole_edit'),
url(r'^rack-roles/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='rackrole_changelog', kwargs={'model': RackRole}), url(r'^rack-roles/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='rackrole_changelog', kwargs={'model': RackRole}),
# Rack reservations # Rack reservations
url(r'^rack-reservations/$', views.RackReservationListView.as_view(), name='rackreservation_list'), url(r'^rack-reservations/$', views.RackReservationListView.as_view(), name='rackreservation_list'),
@ -55,7 +55,7 @@ urlpatterns = [
url(r'^rack-reservations/delete/$', views.RackReservationBulkDeleteView.as_view(), name='rackreservation_bulk_delete'), url(r'^rack-reservations/delete/$', views.RackReservationBulkDeleteView.as_view(), name='rackreservation_bulk_delete'),
url(r'^rack-reservations/(?P<pk>\d+)/edit/$', views.RackReservationEditView.as_view(), name='rackreservation_edit'), url(r'^rack-reservations/(?P<pk>\d+)/edit/$', views.RackReservationEditView.as_view(), name='rackreservation_edit'),
url(r'^rack-reservations/(?P<pk>\d+)/delete/$', views.RackReservationDeleteView.as_view(), name='rackreservation_delete'), url(r'^rack-reservations/(?P<pk>\d+)/delete/$', views.RackReservationDeleteView.as_view(), name='rackreservation_delete'),
url(r'^rack-reservations/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='rackreservation_changelog', kwargs={'model': RackReservation}), url(r'^rack-reservations/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='rackreservation_changelog', kwargs={'model': RackReservation}),
# Racks # Racks
url(r'^racks/$', views.RackListView.as_view(), name='rack_list'), url(r'^racks/$', views.RackListView.as_view(), name='rack_list'),
@ -67,7 +67,7 @@ urlpatterns = [
url(r'^racks/(?P<pk>\d+)/$', views.RackView.as_view(), name='rack'), url(r'^racks/(?P<pk>\d+)/$', views.RackView.as_view(), name='rack'),
url(r'^racks/(?P<pk>\d+)/edit/$', views.RackEditView.as_view(), name='rack_edit'), url(r'^racks/(?P<pk>\d+)/edit/$', views.RackEditView.as_view(), name='rack_edit'),
url(r'^racks/(?P<pk>\d+)/delete/$', views.RackDeleteView.as_view(), name='rack_delete'), url(r'^racks/(?P<pk>\d+)/delete/$', views.RackDeleteView.as_view(), name='rack_delete'),
url(r'^racks/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='rack_changelog', kwargs={'model': Rack}), url(r'^racks/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='rack_changelog', kwargs={'model': Rack}),
url(r'^racks/(?P<rack>\d+)/reservations/add/$', views.RackReservationCreateView.as_view(), name='rack_add_reservation'), url(r'^racks/(?P<rack>\d+)/reservations/add/$', views.RackReservationCreateView.as_view(), name='rack_add_reservation'),
url(r'^racks/(?P<object_id>\d+)/images/add/$', ImageAttachmentEditView.as_view(), name='rack_add_image', kwargs={'model': Rack}), url(r'^racks/(?P<object_id>\d+)/images/add/$', ImageAttachmentEditView.as_view(), name='rack_add_image', kwargs={'model': Rack}),
@ -77,7 +77,7 @@ urlpatterns = [
url(r'^manufacturers/import/$', views.ManufacturerBulkImportView.as_view(), name='manufacturer_import'), url(r'^manufacturers/import/$', views.ManufacturerBulkImportView.as_view(), name='manufacturer_import'),
url(r'^manufacturers/delete/$', views.ManufacturerBulkDeleteView.as_view(), name='manufacturer_bulk_delete'), url(r'^manufacturers/delete/$', views.ManufacturerBulkDeleteView.as_view(), name='manufacturer_bulk_delete'),
url(r'^manufacturers/(?P<slug>[\w-]+)/edit/$', views.ManufacturerEditView.as_view(), name='manufacturer_edit'), url(r'^manufacturers/(?P<slug>[\w-]+)/edit/$', views.ManufacturerEditView.as_view(), name='manufacturer_edit'),
url(r'^manufacturers/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='manufacturer_changelog', kwargs={'model': Manufacturer}), url(r'^manufacturers/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='manufacturer_changelog', kwargs={'model': Manufacturer}),
# Device types # Device types
url(r'^device-types/$', views.DeviceTypeListView.as_view(), name='devicetype_list'), url(r'^device-types/$', views.DeviceTypeListView.as_view(), name='devicetype_list'),
@ -88,7 +88,7 @@ urlpatterns = [
url(r'^device-types/(?P<pk>\d+)/$', views.DeviceTypeView.as_view(), name='devicetype'), url(r'^device-types/(?P<pk>\d+)/$', views.DeviceTypeView.as_view(), name='devicetype'),
url(r'^device-types/(?P<pk>\d+)/edit/$', views.DeviceTypeEditView.as_view(), name='devicetype_edit'), url(r'^device-types/(?P<pk>\d+)/edit/$', views.DeviceTypeEditView.as_view(), name='devicetype_edit'),
url(r'^device-types/(?P<pk>\d+)/delete/$', views.DeviceTypeDeleteView.as_view(), name='devicetype_delete'), url(r'^device-types/(?P<pk>\d+)/delete/$', views.DeviceTypeDeleteView.as_view(), name='devicetype_delete'),
url(r'^device-types/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='devicetype_changelog', kwargs={'model': DeviceType}), url(r'^device-types/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='devicetype_changelog', kwargs={'model': DeviceType}),
# Console port templates # Console port templates
url(r'^device-types/(?P<pk>\d+)/console-ports/add/$', views.ConsolePortTemplateCreateView.as_view(), name='devicetype_add_consoleport'), url(r'^device-types/(?P<pk>\d+)/console-ports/add/$', views.ConsolePortTemplateCreateView.as_view(), name='devicetype_add_consoleport'),
@ -121,7 +121,7 @@ urlpatterns = [
url(r'^device-roles/import/$', views.DeviceRoleBulkImportView.as_view(), name='devicerole_import'), url(r'^device-roles/import/$', views.DeviceRoleBulkImportView.as_view(), name='devicerole_import'),
url(r'^device-roles/delete/$', views.DeviceRoleBulkDeleteView.as_view(), name='devicerole_bulk_delete'), url(r'^device-roles/delete/$', views.DeviceRoleBulkDeleteView.as_view(), name='devicerole_bulk_delete'),
url(r'^device-roles/(?P<slug>[\w-]+)/edit/$', views.DeviceRoleEditView.as_view(), name='devicerole_edit'), url(r'^device-roles/(?P<slug>[\w-]+)/edit/$', views.DeviceRoleEditView.as_view(), name='devicerole_edit'),
url(r'^device-roles/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='devicerole_changelog', kwargs={'model': DeviceRole}), url(r'^device-roles/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='devicerole_changelog', kwargs={'model': DeviceRole}),
# Platforms # Platforms
url(r'^platforms/$', views.PlatformListView.as_view(), name='platform_list'), url(r'^platforms/$', views.PlatformListView.as_view(), name='platform_list'),
@ -129,7 +129,7 @@ urlpatterns = [
url(r'^platforms/import/$', views.PlatformBulkImportView.as_view(), name='platform_import'), url(r'^platforms/import/$', views.PlatformBulkImportView.as_view(), name='platform_import'),
url(r'^platforms/delete/$', views.PlatformBulkDeleteView.as_view(), name='platform_bulk_delete'), url(r'^platforms/delete/$', views.PlatformBulkDeleteView.as_view(), name='platform_bulk_delete'),
url(r'^platforms/(?P<slug>[\w-]+)/edit/$', views.PlatformEditView.as_view(), name='platform_edit'), url(r'^platforms/(?P<slug>[\w-]+)/edit/$', views.PlatformEditView.as_view(), name='platform_edit'),
url(r'^platforms/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='platform_changelog', kwargs={'model': Platform}), url(r'^platforms/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='platform_changelog', kwargs={'model': Platform}),
# Devices # Devices
url(r'^devices/$', views.DeviceListView.as_view(), name='device_list'), url(r'^devices/$', views.DeviceListView.as_view(), name='device_list'),
@ -141,7 +141,7 @@ urlpatterns = [
url(r'^devices/(?P<pk>\d+)/$', views.DeviceView.as_view(), name='device'), url(r'^devices/(?P<pk>\d+)/$', views.DeviceView.as_view(), name='device'),
url(r'^devices/(?P<pk>\d+)/edit/$', views.DeviceEditView.as_view(), name='device_edit'), url(r'^devices/(?P<pk>\d+)/edit/$', views.DeviceEditView.as_view(), name='device_edit'),
url(r'^devices/(?P<pk>\d+)/delete/$', views.DeviceDeleteView.as_view(), name='device_delete'), url(r'^devices/(?P<pk>\d+)/delete/$', views.DeviceDeleteView.as_view(), name='device_delete'),
url(r'^devices/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='device_changelog', kwargs={'model': Device}), url(r'^devices/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='device_changelog', kwargs={'model': Device}),
url(r'^devices/(?P<pk>\d+)/inventory/$', views.DeviceInventoryView.as_view(), name='device_inventory'), url(r'^devices/(?P<pk>\d+)/inventory/$', views.DeviceInventoryView.as_view(), name='device_inventory'),
url(r'^devices/(?P<pk>\d+)/status/$', views.DeviceStatusView.as_view(), name='device_status'), url(r'^devices/(?P<pk>\d+)/status/$', views.DeviceStatusView.as_view(), name='device_status'),
url(r'^devices/(?P<pk>\d+)/lldp-neighbors/$', views.DeviceLLDPNeighborsView.as_view(), name='device_lldp_neighbors'), url(r'^devices/(?P<pk>\d+)/lldp-neighbors/$', views.DeviceLLDPNeighborsView.as_view(), name='device_lldp_neighbors'),
@ -235,7 +235,7 @@ urlpatterns = [
url(r'^virtual-chassis/add/$', views.VirtualChassisCreateView.as_view(), name='virtualchassis_add'), url(r'^virtual-chassis/add/$', views.VirtualChassisCreateView.as_view(), name='virtualchassis_add'),
url(r'^virtual-chassis/(?P<pk>\d+)/edit/$', views.VirtualChassisEditView.as_view(), name='virtualchassis_edit'), url(r'^virtual-chassis/(?P<pk>\d+)/edit/$', views.VirtualChassisEditView.as_view(), name='virtualchassis_edit'),
url(r'^virtual-chassis/(?P<pk>\d+)/delete/$', views.VirtualChassisDeleteView.as_view(), name='virtualchassis_delete'), url(r'^virtual-chassis/(?P<pk>\d+)/delete/$', views.VirtualChassisDeleteView.as_view(), name='virtualchassis_delete'),
url(r'^virtual-chassis/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='virtualchassis_changelog', kwargs={'model': VirtualChassis}), url(r'^virtual-chassis/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='virtualchassis_changelog', kwargs={'model': VirtualChassis}),
url(r'^virtual-chassis/(?P<pk>\d+)/add-member/$', views.VirtualChassisAddMemberView.as_view(), name='virtualchassis_add_member'), url(r'^virtual-chassis/(?P<pk>\d+)/add-member/$', views.VirtualChassisAddMemberView.as_view(), name='virtualchassis_add_member'),
url(r'^virtual-chassis-members/(?P<pk>\d+)/delete/$', views.VirtualChassisRemoveMemberView.as_view(), name='virtualchassis_remove_member'), url(r'^virtual-chassis-members/(?P<pk>\d+)/delete/$', views.VirtualChassisRemoveMemberView.as_view(), name='virtualchassis_remove_member'),

View File

@ -129,6 +129,7 @@ class ObjectChangeFilter(django_filters.FilterSet):
method='search', method='search',
label='Search', label='Search',
) )
time = django_filters.DateTimeFromToRangeFilter()
class Meta: class Meta:
model = ObjectChange model = ObjectChange

View File

@ -3,12 +3,16 @@ from __future__ import unicode_literals
from collections import OrderedDict from collections import OrderedDict
from django import forms from django import forms
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from taggit.models import Tag from taggit.models import Tag
from utilities.forms import BootstrapMixin, BulkEditForm, LaxURLField, SlugField from utilities.forms import add_blank_choice, BootstrapMixin, BulkEditForm, LaxURLField, SlugField
from .constants import CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL from .constants import (
from .models import CustomField, CustomFieldValue, ImageAttachment CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,
OBJECTCHANGE_ACTION_CHOICES,
)
from .models import CustomField, CustomFieldValue, ImageAttachment, ObjectChange
# #
@ -189,3 +193,38 @@ class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
class Meta: class Meta:
model = ImageAttachment model = ImageAttachment
fields = ['name', 'image'] fields = ['name', 'image']
#
# Change logging
#
class ObjectChangeFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = ObjectChange
q = forms.CharField(
required=False,
label='Search'
)
# TODO: Change time_0 and time_1 to time_after and time_before for django-filter==2.0
time_0 = forms.DateTimeField(
label='After',
required=False,
widget=forms.TextInput(
attrs={'placeholder': 'YYYY-MM-DD hh:mm:ss'}
)
)
time_1 = forms.DateTimeField(
label='Before',
required=False,
widget=forms.TextInput(
attrs={'placeholder': 'YYYY-MM-DD hh:mm:ss'}
)
)
action = forms.ChoiceField(
choices=add_blank_choice(OBJECTCHANGE_ACTION_CHOICES),
required=False
)
user = forms.ModelChoiceField(
queryset=User.objects.order_by('username'),
required=False
)

View File

@ -9,6 +9,7 @@ from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from django.core.urlresolvers import reverse
from django.core.validators import ValidationError from django.core.validators import ValidationError
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
@ -704,15 +705,18 @@ class ObjectChange(models.Model):
editable=False editable=False
) )
serializer = 'extras.api.serializers.ObjectChangeSerializer'
csv_headers = ['time', 'user', 'request_id', 'action', 'content_type', 'object_id', 'object_repr', 'object_data']
class Meta: class Meta:
ordering = ['-time'] ordering = ['-time']
def __str__(self): def __str__(self):
attribution = 'by {}'.format(self.user_name) if self.user_name else '(no attribution)' return '{} {} {} by {}'.format(
return '{} {} {}'.format( self.content_type,
self.object_repr, self.object_repr,
self.get_action_display().lower(), self.get_action_display().lower(),
attribution self.user_name
) )
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -724,6 +728,21 @@ class ObjectChange(models.Model):
return super(ObjectChange, self).save(*args, **kwargs) return super(ObjectChange, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('extras:objectchange', args=[self.pk])
def to_csv(self):
return (
self.time,
self.user or self.user_name,
self.request_id,
self.get_action_display(),
self.content_type,
self.object_id,
self.object_repr,
self.object_data,
)
@property @property
def object_data_pretty(self): def object_data_pretty(self):
return json.dumps(self.object_data, indent=4, sort_keys=True) return json.dumps(self.object_data, indent=4, sort_keys=True)

View File

@ -4,6 +4,7 @@ import django_tables2 as tables
from taggit.models import Tag from taggit.models import Tag
from utilities.tables import BaseTable, ToggleColumn from utilities.tables import BaseTable, ToggleColumn
from .models import ObjectChange
TAG_ACTIONS = """ TAG_ACTIONS = """
{% if perms.taggit.change_tag %} {% if perms.taggit.change_tag %}
@ -14,6 +15,24 @@ TAG_ACTIONS = """
{% endif %} {% endif %}
""" """
OBJECTCHANGE_ACTION = """
{% if record.action == 1 %}
<span class="label label-success">Created</span>
{% elif record.action == 2 %}
<span class="label label-primary">Updated</span>
{% elif record.action == 3 %}
<span class="label label-danger">Deleted</span>
{% endif %}
"""
OBJECTCHANGE_OBJECT = """
{% if record.action != 3 and record.changed_object.get_absolute_url %}
<a href="{{ record.changed_object.get_absolute_url }}">{{ record.object_repr }}</a>
{% else %}
{{ record.object_repr }}
{% endif %}
"""
class TagTable(BaseTable): class TagTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
@ -26,3 +45,21 @@ class TagTable(BaseTable):
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
model = Tag model = Tag
fields = ('pk', 'name', 'items') fields = ('pk', 'name', 'items')
class ObjectChangeTable(BaseTable):
time = tables.LinkColumn()
action = tables.TemplateColumn(
template_code=OBJECTCHANGE_ACTION
)
object_repr = tables.TemplateColumn(
template_code=OBJECTCHANGE_OBJECT,
verbose_name='Object'
)
request_id = tables.Column(
verbose_name='Request ID'
)
class Meta(BaseTable.Meta):
model = ObjectChange
fields = ('time', 'user_name', 'action', 'content_type', 'object_repr', 'request_id')

View File

@ -22,4 +22,8 @@ urlpatterns = [
url(r'^reports/(?P<name>[^/]+\.[^/]+)/$', views.ReportView.as_view(), name='report'), url(r'^reports/(?P<name>[^/]+\.[^/]+)/$', views.ReportView.as_view(), name='report'),
url(r'^reports/(?P<name>[^/]+\.[^/]+)/run/$', views.ReportRunView.as_view(), name='report_run'), url(r'^reports/(?P<name>[^/]+\.[^/]+)/run/$', views.ReportRunView.as_view(), name='report_run'),
# Change logging
url(r'^changelog/$', views.ObjectChangeListView.as_view(), name='objectchange_list'),
url(r'^changelog/(?P<pk>\d+)/$', views.ObjectChangeView.as_view(), name='objectchange'),
] ]

View File

@ -13,10 +13,11 @@ from taggit.models import Tag
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
from utilities.views import BulkDeleteView, ObjectDeleteView, ObjectEditView, ObjectListView from utilities.views import BulkDeleteView, ObjectDeleteView, ObjectEditView, ObjectListView
from .forms import ImageAttachmentForm, TagForm from . import filters
from .forms import ObjectChangeFilterForm, ImageAttachmentForm, TagForm
from .models import ImageAttachment, ObjectChange, ReportResult, UserAction from .models import ImageAttachment, ObjectChange, ReportResult, UserAction
from .reports import get_report, get_reports from .reports import get_report, get_reports
from .tables import TagTable from .tables import ObjectChangeTable, TagTable
# #
@ -56,9 +57,36 @@ class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
# Change logging # Change logging
# #
class ChangeLogView(View): class ObjectChangeListView(ObjectListView):
queryset = ObjectChange.objects.select_related('user', 'content_type')
filter = filters.ObjectChangeFilter
filter_form = ObjectChangeFilterForm
table = ObjectChangeTable
template_name = 'extras/objectchange_list.html'
class ObjectChangeView(View):
def get(self, request, pk):
objectchange = get_object_or_404(ObjectChange, pk=pk)
related_changes = ObjectChange.objects.filter(request_id=objectchange.request_id).exclude(pk=objectchange.pk)
related_changes_table = ObjectChangeTable(
data=related_changes[:50],
orderable=False
)
return render(request, 'extras/objectchange.html', {
'objectchange': objectchange,
'related_changes_table': related_changes_table,
'related_changes_count': related_changes.count()
})
class ObjectChangeLogView(View):
""" """
Present a history of changes made to an object. Present a history of changes made to a particular object.
""" """
def get(self, request, model, **kwargs): def get(self, request, model, **kwargs):
@ -68,7 +96,16 @@ class ChangeLogView(View):
# Gather all changes for this object # Gather all changes for this object
content_type = ContentType.objects.get_for_model(model) content_type = ContentType.objects.get_for_model(model)
changes = ObjectChange.objects.filter(content_type=content_type, object_id=obj.pk) objectchanges = ObjectChange.objects.select_related(
'user', 'content_type'
).filter(
content_type=content_type,
object_id=obj.pk
)
objectchanges_table = ObjectChangeTable(
data=objectchanges,
orderable=False
)
# Check whether a header template exists for this model # Check whether a header template exists for this model
base_template = '{}/{}.html'.format(model._meta.app_label, model._meta.model_name) base_template = '{}/{}.html'.format(model._meta.app_label, model._meta.model_name)
@ -79,9 +116,9 @@ class ChangeLogView(View):
base_template = '_base.html' base_template = '_base.html'
object_var = 'obj' object_var = 'obj'
return render(request, 'extras/changelog.html', { return render(request, 'extras/object_changelog.html', {
object_var: obj, object_var: obj,
'changes': changes, 'objectchanges_table': objectchanges_table,
'base_template': base_template, 'base_template': base_template,
'active_tab': 'changelog', 'active_tab': 'changelog',
}) })

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.conf.urls import url from django.conf.urls import url
from extras.views import ChangeLogView from extras.views import ObjectChangeLogView
from . import views from . import views
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
@ -19,7 +19,7 @@ urlpatterns = [
url(r'^vrfs/(?P<pk>\d+)/$', views.VRFView.as_view(), name='vrf'), url(r'^vrfs/(?P<pk>\d+)/$', views.VRFView.as_view(), name='vrf'),
url(r'^vrfs/(?P<pk>\d+)/edit/$', views.VRFEditView.as_view(), name='vrf_edit'), url(r'^vrfs/(?P<pk>\d+)/edit/$', views.VRFEditView.as_view(), name='vrf_edit'),
url(r'^vrfs/(?P<pk>\d+)/delete/$', views.VRFDeleteView.as_view(), name='vrf_delete'), url(r'^vrfs/(?P<pk>\d+)/delete/$', views.VRFDeleteView.as_view(), name='vrf_delete'),
url(r'^vrfs/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='vrf_changelog', kwargs={'model': VRF}), url(r'^vrfs/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='vrf_changelog', kwargs={'model': VRF}),
# RIRs # RIRs
url(r'^rirs/$', views.RIRListView.as_view(), name='rir_list'), url(r'^rirs/$', views.RIRListView.as_view(), name='rir_list'),
@ -27,7 +27,7 @@ urlpatterns = [
url(r'^rirs/import/$', views.RIRBulkImportView.as_view(), name='rir_import'), url(r'^rirs/import/$', views.RIRBulkImportView.as_view(), name='rir_import'),
url(r'^rirs/delete/$', views.RIRBulkDeleteView.as_view(), name='rir_bulk_delete'), url(r'^rirs/delete/$', views.RIRBulkDeleteView.as_view(), name='rir_bulk_delete'),
url(r'^rirs/(?P<slug>[\w-]+)/edit/$', views.RIREditView.as_view(), name='rir_edit'), url(r'^rirs/(?P<slug>[\w-]+)/edit/$', views.RIREditView.as_view(), name='rir_edit'),
url(r'^vrfs/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='rir_changelog', kwargs={'model': RIR}), url(r'^vrfs/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='rir_changelog', kwargs={'model': RIR}),
# Aggregates # Aggregates
url(r'^aggregates/$', views.AggregateListView.as_view(), name='aggregate_list'), url(r'^aggregates/$', views.AggregateListView.as_view(), name='aggregate_list'),
@ -38,7 +38,7 @@ urlpatterns = [
url(r'^aggregates/(?P<pk>\d+)/$', views.AggregateView.as_view(), name='aggregate'), url(r'^aggregates/(?P<pk>\d+)/$', views.AggregateView.as_view(), name='aggregate'),
url(r'^aggregates/(?P<pk>\d+)/edit/$', views.AggregateEditView.as_view(), name='aggregate_edit'), url(r'^aggregates/(?P<pk>\d+)/edit/$', views.AggregateEditView.as_view(), name='aggregate_edit'),
url(r'^aggregates/(?P<pk>\d+)/delete/$', views.AggregateDeleteView.as_view(), name='aggregate_delete'), url(r'^aggregates/(?P<pk>\d+)/delete/$', views.AggregateDeleteView.as_view(), name='aggregate_delete'),
url(r'^aggregates/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='aggregate_changelog', kwargs={'model': Aggregate}), url(r'^aggregates/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='aggregate_changelog', kwargs={'model': Aggregate}),
# Roles # Roles
url(r'^roles/$', views.RoleListView.as_view(), name='role_list'), url(r'^roles/$', views.RoleListView.as_view(), name='role_list'),
@ -46,7 +46,7 @@ urlpatterns = [
url(r'^roles/import/$', views.RoleBulkImportView.as_view(), name='role_import'), url(r'^roles/import/$', views.RoleBulkImportView.as_view(), name='role_import'),
url(r'^roles/delete/$', views.RoleBulkDeleteView.as_view(), name='role_bulk_delete'), url(r'^roles/delete/$', views.RoleBulkDeleteView.as_view(), name='role_bulk_delete'),
url(r'^roles/(?P<slug>[\w-]+)/edit/$', views.RoleEditView.as_view(), name='role_edit'), url(r'^roles/(?P<slug>[\w-]+)/edit/$', views.RoleEditView.as_view(), name='role_edit'),
url(r'^roles/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='role_changelog', kwargs={'model': Role}), url(r'^roles/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='role_changelog', kwargs={'model': Role}),
# Prefixes # Prefixes
url(r'^prefixes/$', views.PrefixListView.as_view(), name='prefix_list'), url(r'^prefixes/$', views.PrefixListView.as_view(), name='prefix_list'),
@ -57,7 +57,7 @@ urlpatterns = [
url(r'^prefixes/(?P<pk>\d+)/$', views.PrefixView.as_view(), name='prefix'), url(r'^prefixes/(?P<pk>\d+)/$', views.PrefixView.as_view(), name='prefix'),
url(r'^prefixes/(?P<pk>\d+)/edit/$', views.PrefixEditView.as_view(), name='prefix_edit'), url(r'^prefixes/(?P<pk>\d+)/edit/$', views.PrefixEditView.as_view(), name='prefix_edit'),
url(r'^prefixes/(?P<pk>\d+)/delete/$', views.PrefixDeleteView.as_view(), name='prefix_delete'), url(r'^prefixes/(?P<pk>\d+)/delete/$', views.PrefixDeleteView.as_view(), name='prefix_delete'),
url(r'^prefixes/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='prefix_changelog', kwargs={'model': Prefix}), url(r'^prefixes/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='prefix_changelog', kwargs={'model': Prefix}),
url(r'^prefixes/(?P<pk>\d+)/prefixes/$', views.PrefixPrefixesView.as_view(), name='prefix_prefixes'), url(r'^prefixes/(?P<pk>\d+)/prefixes/$', views.PrefixPrefixesView.as_view(), name='prefix_prefixes'),
url(r'^prefixes/(?P<pk>\d+)/ip-addresses/$', views.PrefixIPAddressesView.as_view(), name='prefix_ipaddresses'), url(r'^prefixes/(?P<pk>\d+)/ip-addresses/$', views.PrefixIPAddressesView.as_view(), name='prefix_ipaddresses'),
@ -68,7 +68,7 @@ urlpatterns = [
url(r'^ip-addresses/import/$', views.IPAddressBulkImportView.as_view(), name='ipaddress_import'), url(r'^ip-addresses/import/$', views.IPAddressBulkImportView.as_view(), name='ipaddress_import'),
url(r'^ip-addresses/edit/$', views.IPAddressBulkEditView.as_view(), name='ipaddress_bulk_edit'), url(r'^ip-addresses/edit/$', views.IPAddressBulkEditView.as_view(), name='ipaddress_bulk_edit'),
url(r'^ip-addresses/delete/$', views.IPAddressBulkDeleteView.as_view(), name='ipaddress_bulk_delete'), url(r'^ip-addresses/delete/$', views.IPAddressBulkDeleteView.as_view(), name='ipaddress_bulk_delete'),
url(r'^ip-addresses/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='ipaddress_changelog', kwargs={'model': IPAddress}), url(r'^ip-addresses/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='ipaddress_changelog', kwargs={'model': IPAddress}),
url(r'^ip-addresses/assign/$', views.IPAddressAssignView.as_view(), name='ipaddress_assign'), url(r'^ip-addresses/assign/$', views.IPAddressAssignView.as_view(), name='ipaddress_assign'),
url(r'^ip-addresses/(?P<pk>\d+)/$', views.IPAddressView.as_view(), name='ipaddress'), url(r'^ip-addresses/(?P<pk>\d+)/$', views.IPAddressView.as_view(), name='ipaddress'),
url(r'^ip-addresses/(?P<pk>\d+)/edit/$', views.IPAddressEditView.as_view(), name='ipaddress_edit'), url(r'^ip-addresses/(?P<pk>\d+)/edit/$', views.IPAddressEditView.as_view(), name='ipaddress_edit'),
@ -80,7 +80,7 @@ urlpatterns = [
url(r'^vlan-groups/import/$', views.VLANGroupBulkImportView.as_view(), name='vlangroup_import'), url(r'^vlan-groups/import/$', views.VLANGroupBulkImportView.as_view(), name='vlangroup_import'),
url(r'^vlan-groups/delete/$', views.VLANGroupBulkDeleteView.as_view(), name='vlangroup_bulk_delete'), url(r'^vlan-groups/delete/$', views.VLANGroupBulkDeleteView.as_view(), name='vlangroup_bulk_delete'),
url(r'^vlan-groups/(?P<pk>\d+)/edit/$', views.VLANGroupEditView.as_view(), name='vlangroup_edit'), url(r'^vlan-groups/(?P<pk>\d+)/edit/$', views.VLANGroupEditView.as_view(), name='vlangroup_edit'),
url(r'^vlan-groups/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='vlangroup_changelog', kwargs={'model': VLANGroup}), url(r'^vlan-groups/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='vlangroup_changelog', kwargs={'model': VLANGroup}),
# VLANs # VLANs
url(r'^vlans/$', views.VLANListView.as_view(), name='vlan_list'), url(r'^vlans/$', views.VLANListView.as_view(), name='vlan_list'),
@ -92,11 +92,11 @@ urlpatterns = [
url(r'^vlans/(?P<pk>\d+)/members/$', views.VLANMembersView.as_view(), name='vlan_members'), url(r'^vlans/(?P<pk>\d+)/members/$', views.VLANMembersView.as_view(), name='vlan_members'),
url(r'^vlans/(?P<pk>\d+)/edit/$', views.VLANEditView.as_view(), name='vlan_edit'), url(r'^vlans/(?P<pk>\d+)/edit/$', views.VLANEditView.as_view(), name='vlan_edit'),
url(r'^vlans/(?P<pk>\d+)/delete/$', views.VLANDeleteView.as_view(), name='vlan_delete'), url(r'^vlans/(?P<pk>\d+)/delete/$', views.VLANDeleteView.as_view(), name='vlan_delete'),
url(r'^vlans/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='vlan_changelog', kwargs={'model': VLAN}), url(r'^vlans/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='vlan_changelog', kwargs={'model': VLAN}),
# Services # Services
url(r'^services/(?P<pk>\d+)/edit/$', views.ServiceEditView.as_view(), name='service_edit'), url(r'^services/(?P<pk>\d+)/edit/$', views.ServiceEditView.as_view(), name='service_edit'),
url(r'^services/(?P<pk>\d+)/delete/$', views.ServiceDeleteView.as_view(), name='service_delete'), url(r'^services/(?P<pk>\d+)/delete/$', views.ServiceDeleteView.as_view(), name='service_delete'),
url(r'^services/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='service_changelog', kwargs={'model': Service}), url(r'^services/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='service_changelog', kwargs={'model': Service}),
] ]

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.conf.urls import url from django.conf.urls import url
from extras.views import ChangeLogView from extras.views import ObjectChangeLogView
from . import views from . import views
from .models import Secret, SecretRole from .models import Secret, SecretRole
@ -15,7 +15,7 @@ urlpatterns = [
url(r'^secret-roles/import/$', views.SecretRoleBulkImportView.as_view(), name='secretrole_import'), url(r'^secret-roles/import/$', views.SecretRoleBulkImportView.as_view(), name='secretrole_import'),
url(r'^secret-roles/delete/$', views.SecretRoleBulkDeleteView.as_view(), name='secretrole_bulk_delete'), url(r'^secret-roles/delete/$', views.SecretRoleBulkDeleteView.as_view(), name='secretrole_bulk_delete'),
url(r'^secret-roles/(?P<slug>[\w-]+)/edit/$', views.SecretRoleEditView.as_view(), name='secretrole_edit'), url(r'^secret-roles/(?P<slug>[\w-]+)/edit/$', views.SecretRoleEditView.as_view(), name='secretrole_edit'),
url(r'^secret-roles/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='secretrole_changelog', kwargs={'model': SecretRole}), url(r'^secret-roles/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='secretrole_changelog', kwargs={'model': SecretRole}),
# Secrets # Secrets
url(r'^secrets/$', views.SecretListView.as_view(), name='secret_list'), url(r'^secrets/$', views.SecretListView.as_view(), name='secret_list'),
@ -25,6 +25,6 @@ urlpatterns = [
url(r'^secrets/(?P<pk>\d+)/$', views.SecretView.as_view(), name='secret'), url(r'^secrets/(?P<pk>\d+)/$', views.SecretView.as_view(), name='secret'),
url(r'^secrets/(?P<pk>\d+)/edit/$', views.secret_edit, name='secret_edit'), url(r'^secrets/(?P<pk>\d+)/edit/$', views.secret_edit, name='secret_edit'),
url(r'^secrets/(?P<pk>\d+)/delete/$', views.SecretDeleteView.as_view(), name='secret_delete'), url(r'^secrets/(?P<pk>\d+)/delete/$', views.SecretDeleteView.as_view(), name='secret_delete'),
url(r'^secrets/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='secret_changelog', kwargs={'model': Secret}), url(r'^secrets/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='secret_changelog', kwargs={'model': Secret}),
] ]

View File

@ -1,36 +0,0 @@
{% extends base_template %}
{% block title %}{% if obj %}{{ obj }}{% else %}{{ block.super }}{% endif %} - Changelog{% endblock %}
{% block content %}
{% if obj %}<h1>{{ obj }}</h1>{% endif %}
<table class="table">
<thead>
<tr>
<th>Time</th>
<th>User</th>
<th>Action</th>
<th></th>
</tr>
</thead>
<tbody>
{% for change in changes %}
<tr>
<td>{{ change.time }}</td>
<td>{{ change.user }}</td>
<td>{{ change.get_action_display }}</td>
<td>
<button class="btn btn-xs btn-primary" type="button" data-toggle="collapse" data-target="#change{{ change.pk }}" aria-expanded="false" aria-controls="collapseExample">
<i class="fa fa-search"></i>
</button>
</td>
</tr>
<tr class="collapse" id="change{{ change.pk }}">
<td colspan="4">
<pre class="well">{{ change.object_data_pretty }}</pre>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends base_template %}
{% block title %}{% if obj %}{{ obj }}{% else %}{{ block.super }}{% endif %} - Changelog{% endblock %}
{% block content %}
{% if obj %}<h1>{{ obj }}</h1>{% endif %}
{% include 'panel_table.html' with table=objectchanges_table %}
{% endblock %}

View File

@ -0,0 +1,101 @@
{% extends '_base.html' %}
{% load helpers %}
{% block title %}{{ objectchange }}{% endblock %}
{% block header %}
<div class="row">
<div class="col-sm-8 col-md-9">
<ol class="breadcrumb">
<li><a href="{% url 'extras:objectchange_list' %}">Changelog</a></li>
<li>{{ objectchange }}</li>
</ol>
</div>
<div class="col-sm-4 col-md-3">
<form action="{% url 'extras:objectchange_list' %}" method="get">
<div class="input-group">
<input type="text" name="q" class="form-control" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">
<span class="fa fa-search" aria-hidden="true"></span>
</button>
</span>
</div>
</form>
</div>
</div>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-5">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Change</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Time</td>
<td>
{{ objectchange.time }}
</td>
</tr>
<tr>
<td>User</td>
<td>
{{ objectchange.user|default:objectchange.user_name }}
</td>
</tr>
<tr>
<td>Action</td>
<td>
{{ objectchange.get_action_display }}
</td>
</tr>
<tr>
<td>Content Type</td>
<td>
{{ objectchange.content_type }}
</td>
</tr>
<tr>
<td>Object</td>
<td>
{% if objectchange.changed_object.get_absolute_url %}
<a href="{{ objectchange.changed_object.get_aboslute_url }}">{{ objectchange.changed_object }}</a>
{% else %}
{{ objectchange.object_repr }}
{% endif %}
</td>
</tr>
<tr>
<td>Request ID</td>
<td>
{{ objectchange.request_id }}
</td>
</tr>
</table>
</div>
</div>
<div class="col-md-7">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Object Data</strong>
</div>
<div class="panel-body">
<pre>{{ objectchange.object_data_pretty }}</pre>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
{% include 'panel_table.html' with table=related_changes_table heading='Related Changes' %}
{% if related_changes_count > related_changes_table.rows|length %}
<div class="pull-right">
<a href="{% url 'extras:objectchange_list' %}?request_id={{ objectchange.request_id }}" class="btn btn-primary">See all {{ related_changes_count|add:"1" }} changes</a>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends '_base.html' %}
{% load buttons %}
{% block content %}
<div class="pull-right">
{% export_button content_type %}
</div>
<h1>{% block title %}Changelog{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'utilities/obj_table.html' %}
</div>
<div class="col-md-3">
{% include 'inc/search_panel.html' %}
</div>
</div>
{% endblock %}

View File

@ -66,6 +66,9 @@
<li> <li>
<a href="{% url 'extras:report_list' %}">Reports</a> <a href="{% url 'extras:report_list' %}">Reports</a>
</li> </li>
<li>
<a href="{% url 'extras:objectchange_list' %}">Changelog</a>
</li>
</ul> </ul>
</li> </li>
<li class="dropdown{% if request.path|contains:'/dcim/rack' %} active{% endif %}"> <li class="dropdown{% if request.path|contains:'/dcim/rack' %} active{% endif %}">

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.conf.urls import url from django.conf.urls import url
from extras.views import ChangeLogView from extras.views import ObjectChangeLogView
from . import views from . import views
from .models import Tenant, TenantGroup from .models import Tenant, TenantGroup
@ -15,7 +15,7 @@ urlpatterns = [
url(r'^tenant-groups/import/$', views.TenantGroupBulkImportView.as_view(), name='tenantgroup_import'), url(r'^tenant-groups/import/$', views.TenantGroupBulkImportView.as_view(), name='tenantgroup_import'),
url(r'^tenant-groups/delete/$', views.TenantGroupBulkDeleteView.as_view(), name='tenantgroup_bulk_delete'), url(r'^tenant-groups/delete/$', views.TenantGroupBulkDeleteView.as_view(), name='tenantgroup_bulk_delete'),
url(r'^tenant-groups/(?P<slug>[\w-]+)/edit/$', views.TenantGroupEditView.as_view(), name='tenantgroup_edit'), url(r'^tenant-groups/(?P<slug>[\w-]+)/edit/$', views.TenantGroupEditView.as_view(), name='tenantgroup_edit'),
url(r'^tenant-groups/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='tenantgroup_changelog', kwargs={'model': TenantGroup}), url(r'^tenant-groups/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='tenantgroup_changelog', kwargs={'model': TenantGroup}),
# Tenants # Tenants
url(r'^tenants/$', views.TenantListView.as_view(), name='tenant_list'), url(r'^tenants/$', views.TenantListView.as_view(), name='tenant_list'),
@ -26,6 +26,6 @@ urlpatterns = [
url(r'^tenants/(?P<slug>[\w-]+)/$', views.TenantView.as_view(), name='tenant'), url(r'^tenants/(?P<slug>[\w-]+)/$', views.TenantView.as_view(), name='tenant'),
url(r'^tenants/(?P<slug>[\w-]+)/edit/$', views.TenantEditView.as_view(), name='tenant_edit'), url(r'^tenants/(?P<slug>[\w-]+)/edit/$', views.TenantEditView.as_view(), name='tenant_edit'),
url(r'^tenants/(?P<slug>[\w-]+)/delete/$', views.TenantDeleteView.as_view(), name='tenant_delete'), url(r'^tenants/(?P<slug>[\w-]+)/delete/$', views.TenantDeleteView.as_view(), name='tenant_delete'),
url(r'^tenants/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='tenant_changelog', kwargs={'model': Tenant}), url(r'^tenants/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='tenant_changelog', kwargs={'model': Tenant}),
] ]

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.conf.urls import url from django.conf.urls import url
from extras.views import ChangeLogView from extras.views import ObjectChangeLogView
from ipam.views import ServiceCreateView from ipam.views import ServiceCreateView
from . import views from . import views
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@ -16,7 +16,7 @@ urlpatterns = [
url(r'^cluster-types/import/$', views.ClusterTypeBulkImportView.as_view(), name='clustertype_import'), url(r'^cluster-types/import/$', views.ClusterTypeBulkImportView.as_view(), name='clustertype_import'),
url(r'^cluster-types/delete/$', views.ClusterTypeBulkDeleteView.as_view(), name='clustertype_bulk_delete'), url(r'^cluster-types/delete/$', views.ClusterTypeBulkDeleteView.as_view(), name='clustertype_bulk_delete'),
url(r'^cluster-types/(?P<slug>[\w-]+)/edit/$', views.ClusterTypeEditView.as_view(), name='clustertype_edit'), url(r'^cluster-types/(?P<slug>[\w-]+)/edit/$', views.ClusterTypeEditView.as_view(), name='clustertype_edit'),
url(r'^cluster-types/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='clustertype_changelog', kwargs={'model': ClusterType}), url(r'^cluster-types/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='clustertype_changelog', kwargs={'model': ClusterType}),
# Cluster groups # Cluster groups
url(r'^cluster-groups/$', views.ClusterGroupListView.as_view(), name='clustergroup_list'), url(r'^cluster-groups/$', views.ClusterGroupListView.as_view(), name='clustergroup_list'),
@ -24,7 +24,7 @@ urlpatterns = [
url(r'^cluster-groups/import/$', views.ClusterGroupBulkImportView.as_view(), name='clustergroup_import'), url(r'^cluster-groups/import/$', views.ClusterGroupBulkImportView.as_view(), name='clustergroup_import'),
url(r'^cluster-groups/delete/$', views.ClusterGroupBulkDeleteView.as_view(), name='clustergroup_bulk_delete'), url(r'^cluster-groups/delete/$', views.ClusterGroupBulkDeleteView.as_view(), name='clustergroup_bulk_delete'),
url(r'^cluster-groups/(?P<slug>[\w-]+)/edit/$', views.ClusterGroupEditView.as_view(), name='clustergroup_edit'), url(r'^cluster-groups/(?P<slug>[\w-]+)/edit/$', views.ClusterGroupEditView.as_view(), name='clustergroup_edit'),
url(r'^cluster-groups/(?P<slug>[\w-]+)/changelog/$', ChangeLogView.as_view(), name='clustergroup_changelog', kwargs={'model': ClusterGroup}), url(r'^cluster-groups/(?P<slug>[\w-]+)/changelog/$', ObjectChangeLogView.as_view(), name='clustergroup_changelog', kwargs={'model': ClusterGroup}),
# Clusters # Clusters
url(r'^clusters/$', views.ClusterListView.as_view(), name='cluster_list'), url(r'^clusters/$', views.ClusterListView.as_view(), name='cluster_list'),
@ -35,7 +35,7 @@ urlpatterns = [
url(r'^clusters/(?P<pk>\d+)/$', views.ClusterView.as_view(), name='cluster'), url(r'^clusters/(?P<pk>\d+)/$', views.ClusterView.as_view(), name='cluster'),
url(r'^clusters/(?P<pk>\d+)/edit/$', views.ClusterEditView.as_view(), name='cluster_edit'), url(r'^clusters/(?P<pk>\d+)/edit/$', views.ClusterEditView.as_view(), name='cluster_edit'),
url(r'^clusters/(?P<pk>\d+)/delete/$', views.ClusterDeleteView.as_view(), name='cluster_delete'), url(r'^clusters/(?P<pk>\d+)/delete/$', views.ClusterDeleteView.as_view(), name='cluster_delete'),
url(r'^clusters/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='cluster_changelog', kwargs={'model': Cluster}), url(r'^clusters/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='cluster_changelog', kwargs={'model': Cluster}),
url(r'^clusters/(?P<pk>\d+)/devices/add/$', views.ClusterAddDevicesView.as_view(), name='cluster_add_devices'), url(r'^clusters/(?P<pk>\d+)/devices/add/$', views.ClusterAddDevicesView.as_view(), name='cluster_add_devices'),
url(r'^clusters/(?P<pk>\d+)/devices/remove/$', views.ClusterRemoveDevicesView.as_view(), name='cluster_remove_devices'), url(r'^clusters/(?P<pk>\d+)/devices/remove/$', views.ClusterRemoveDevicesView.as_view(), name='cluster_remove_devices'),
@ -48,7 +48,7 @@ urlpatterns = [
url(r'^virtual-machines/(?P<pk>\d+)/$', views.VirtualMachineView.as_view(), name='virtualmachine'), url(r'^virtual-machines/(?P<pk>\d+)/$', views.VirtualMachineView.as_view(), name='virtualmachine'),
url(r'^virtual-machines/(?P<pk>\d+)/edit/$', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'), url(r'^virtual-machines/(?P<pk>\d+)/edit/$', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'),
url(r'^virtual-machines/(?P<pk>\d+)/delete/$', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'), url(r'^virtual-machines/(?P<pk>\d+)/delete/$', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'),
url(r'^virtual-machines/(?P<pk>\d+)/changelog/$', ChangeLogView.as_view(), name='virtualmachine_changelog', kwargs={'model': VirtualMachine}), url(r'^virtual-machines/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='virtualmachine_changelog', kwargs={'model': VirtualMachine}),
url(r'^virtual-machines/(?P<virtualmachine>\d+)/services/assign/$', ServiceCreateView.as_view(), name='virtualmachine_service_assign'), url(r'^virtual-machines/(?P<virtualmachine>\d+)/services/assign/$', ServiceCreateView.as_view(), name='virtualmachine_service_assign'),
# VM interfaces # VM interfaces