1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Implemented object add/edit/delete logging

This commit is contained in:
Jeremy Stretch
2016-05-23 14:20:42 -04:00
parent c96bfdbc18
commit cb8e0c93f2
8 changed files with 146 additions and 32 deletions

View File

@ -1,4 +1,3 @@
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import Count
from django.shortcuts import get_object_or_404, render
@ -69,8 +68,7 @@ class ProviderBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
messages.success(self.request, "Updated {} providers".format(updated_count))
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
class ProviderBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -165,8 +163,7 @@ class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
messages.success(self.request, "Updated {} circuits".format(updated_count))
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):

View File

@ -200,8 +200,7 @@ class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
messages.success(self.request, "Updated {} racks".format(updated_count))
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -305,8 +304,7 @@ class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
messages.success(self.request, "Updated {} device types".format(updated_count))
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -590,8 +588,7 @@ class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
messages.success(self.request, "Updated {} devices".format(updated_count))
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):

View File

@ -1,6 +1,6 @@
from django.contrib import admin
from .models import Graph, ExportTemplate, TopologyMap
from .models import Graph, ExportTemplate, TopologyMap, UserAction
@admin.register(Graph)
@ -19,3 +19,8 @@ class TopologyMapAdmin(admin.ModelAdmin):
prepopulated_fields = {
'slug': ['name'],
}
@admin.register(UserAction)
class UserActionAdmin(admin.ModelAdmin):
list_display = ['user', 'action', 'content_type', 'object_id', 'message']

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-05-23 18:16
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('extras', '0003_auto_20160412_1332'),
]
operations = [
migrations.CreateModel(
name='UserAction',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.DateTimeField(auto_now_add=True)),
('object_id', models.PositiveIntegerField(blank=True, null=True)),
('action', models.PositiveSmallIntegerField(choices=[(1, b'created'), (2, b'imported'), (3, b'modified'), (4, b'bulk edited'), (5, b'deleted'), (6, b'bulk deleted')])),
('message', models.TextField(blank=True)),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-time'],
},
),
]

View File

@ -1,3 +1,4 @@
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.http import HttpResponse
@ -21,6 +22,21 @@ EXPORTTEMPLATE_MODELS = [
'provider', 'circuit'
]
ACTION_CREATE = 1
ACTION_IMPORT = 2
ACTION_EDIT = 3
ACTION_BULK_EDIT = 4
ACTION_DELETE = 5
ACTION_BULK_DELETE = 6
ACTION_CHOICES = (
(ACTION_CREATE, 'created'),
(ACTION_IMPORT, 'imported'),
(ACTION_EDIT, 'modified'),
(ACTION_BULK_EDIT, 'bulk edited'),
(ACTION_DELETE, 'deleted'),
(ACTION_BULK_DELETE, 'bulk deleted')
)
class Graph(models.Model):
type = models.PositiveSmallIntegerField(choices=GRAPH_TYPE_CHOICES)
@ -93,3 +109,60 @@ class TopologyMap(models.Model):
if not self.device_patterns:
return None
return [line.strip() for line in self.device_patterns.split('\n')]
class UserActionManager(models.Manager):
# Actions affecting a single object
def log_action(self, user, obj, action, message):
self.model.objects.create(
content_type = ContentType.objects.get_for_model(obj),
object_id = obj.pk,
user = user,
action = action,
message = message,
)
def log_create(self, user, obj, message=''):
self.log_action(user, obj, ACTION_CREATE, message)
def log_edit(self, user, obj, message=''):
self.log_action(user, obj, ACTION_EDIT, message)
def log_delete(self, user, obj, message=''):
self.log_action(user, obj, ACTION_DELETE, message)
# Actions affecting multiple objects
def log_bulk_action(self, user, content_type, action, message):
self.model.objects.create(
content_type=content_type,
user=user,
action=action,
message=message,
)
def log_import(self, user, content_type, message=''):
self.log_bulk_action(user, content_type, ACTION_IMPORT, message)
def log_bulk_edit(self, user, content_type, message=''):
self.log_bulk_action(user, content_type, ACTION_BULK_EDIT, message)
def log_bulk_delete(self, user, content_type, message=''):
self.log_bulk_action(user, content_type, ACTION_BULK_DELETE, message)
class UserAction(models.Model):
"""
A record of an action (add, edit, or delete) performed on an object by a User.
"""
time = models.DateTimeField(auto_now_add=True, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(blank=True, null=True)
action = models.PositiveSmallIntegerField(choices=ACTION_CHOICES)
message = models.TextField(blank=True)
objects = UserActionManager()
class Meta:
ordering = ['-time']

View File

@ -1,7 +1,6 @@
from netaddr import IPSet
from django_tables2 import RequestConfig
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import Count
from django.shortcuts import get_object_or_404, render
@ -90,8 +89,7 @@ class VRFBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
messages.success(self.request, "Updated {} VRFs".format(updated_count))
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -198,8 +196,7 @@ class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
messages.success(self.request, "Updated {} aggregates".format(updated_count))
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -336,8 +333,7 @@ class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
messages.success(self.request, "Updated {} prefixes".format(updated_count))
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -449,8 +445,7 @@ class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
messages.success(self.request, "Updated {} IP addresses".format(updated_count))
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -519,8 +514,7 @@ class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
messages.success(self.request, "Updated {} VLANs".format(updated_count))
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):

View File

@ -213,8 +213,7 @@ class SecretBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
messages.success(self.request, "Updated {} secrets".format(updated_count))
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
class SecretBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):

View File

@ -13,7 +13,7 @@ from django.utils.decorators import method_decorator
from django.utils.http import is_safe_url
from django.views.generic import View
from extras.models import ExportTemplate
from extras.models import ExportTemplate, UserAction
from .error_handlers import handle_protectederror
from .forms import ConfirmationForm
@ -116,6 +116,7 @@ class ObjectEditView(View):
obj = form.save(commit=False)
obj_created = not obj.pk
obj.save()
msg = 'Created ' if obj_created else 'Modified '
msg += self.model._meta.verbose_name
if hasattr(obj, 'get_absolute_url'):
@ -123,6 +124,11 @@ class ObjectEditView(View):
else:
msg += ' {}'.format(obj)
messages.success(request, msg)
if obj_created:
UserAction.objects.log_create(request.user, obj, msg)
else:
UserAction.objects.log_edit(request.user, obj, msg)
if '_addanother' in request.POST:
return redirect(request.path)
elif self.success_url:
@ -169,7 +175,9 @@ class ObjectDeleteView(View):
if form.is_valid():
try:
obj.delete()
messages.success(request, 'Deleted {} {}'.format(self.model._meta.verbose_name, obj))
msg = 'Deleted {} {}'.format(self.model._meta.verbose_name, obj)
messages.success(request, msg)
UserAction.objects.log_delete(request.user, obj, msg)
return redirect(self.redirect_url)
except ProtectedError, e:
handle_protectederror(obj, request, e)
@ -208,7 +216,9 @@ class BulkImportView(View):
new_objs.append(obj)
obj_table = self.table(new_objs)
messages.success(request, "Imported {} objects".format(len(new_objs)))
msg = 'Imported {} objects'.format(len(new_objs))
messages.success(request, msg)
UserAction.objects.log_import(request.user, ContentType.objects.get_for_model(new_objs[0]), msg)
return render(request, "import_success.html", {
'table': obj_table,
@ -247,9 +257,12 @@ class BulkEditView(View):
form = self.form(request.POST)
if form.is_valid():
pk_list = [obj.pk for obj in form.cleaned_data['pk']]
self.update_objects(pk_list, form)
if not form.errors:
return redirect(redirect_url)
updated_count = self.update_objects(pk_list, form)
msg = 'Updated {} {}'.format(updated_count, self.cls._meta.verbose_name_plural)
messages.success(self.request, msg)
UserAction.objects.log_bulk_edit(request.user, ContentType.objects.get_for_model(self.cls), msg)
return redirect(redirect_url)
else:
form = self.form(initial={'pk': request.POST.getlist('pk')})
@ -306,7 +319,9 @@ class BulkDeleteView(View):
handle_protectederror(list(objects_to_delete), request, e)
return redirect(redirect_url)
messages.success(request, "Deleted {} {}".format(deleted_count, self.cls._meta.verbose_name_plural))
msg = 'Deleted {} {}'.format(deleted_count, self.cls._meta.verbose_name_plural)
messages.success(request, msg)
UserAction.objects.log_bulk_delete(request.user, ContentType.objects.get_for_model(self.cls), msg)
return redirect(redirect_url)
else: