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

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

View File

@@ -3,12 +3,16 @@ from __future__ import unicode_literals
from collections import OrderedDict
from django import forms
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from taggit.models import Tag
from utilities.forms import 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 .models import CustomField, CustomFieldValue, ImageAttachment
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,
OBJECTCHANGE_ACTION_CHOICES,
)
from .models import CustomField, CustomFieldValue, ImageAttachment, ObjectChange
#
@@ -189,3 +193,38 @@ class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = ImageAttachment
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.models import ContentType
from django.contrib.postgres.fields import JSONField
from django.core.urlresolvers import reverse
from django.core.validators import ValidationError
from django.db import models
from django.db.models import Q
@@ -704,15 +705,18 @@ class ObjectChange(models.Model):
editable=False
)
serializer = 'extras.api.serializers.ObjectChangeSerializer'
csv_headers = ['time', 'user', 'request_id', 'action', 'content_type', 'object_id', 'object_repr', 'object_data']
class Meta:
ordering = ['-time']
def __str__(self):
attribution = 'by {}'.format(self.user_name) if self.user_name else '(no attribution)'
return '{} {} {}'.format(
return '{} {} {} by {}'.format(
self.content_type,
self.object_repr,
self.get_action_display().lower(),
attribution
self.user_name
)
def save(self, *args, **kwargs):
@@ -724,6 +728,21 @@ class ObjectChange(models.Model):
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
def object_data_pretty(self):
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 utilities.tables import BaseTable, ToggleColumn
from .models import ObjectChange
TAG_ACTIONS = """
{% if perms.taggit.change_tag %}
@@ -14,6 +15,24 @@ TAG_ACTIONS = """
{% 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):
pk = ToggleColumn()
@@ -26,3 +45,21 @@ class TagTable(BaseTable):
class Meta(BaseTable.Meta):
model = Tag
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>[^/]+\.[^/]+)/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.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 .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
#
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):
@@ -68,7 +96,16 @@ class ChangeLogView(View):
# Gather all changes for this object
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
base_template = '{}/{}.html'.format(model._meta.app_label, model._meta.model_name)
@@ -79,9 +116,9 @@ class ChangeLogView(View):
base_template = '_base.html'
object_var = 'obj'
return render(request, 'extras/changelog.html', {
return render(request, 'extras/object_changelog.html', {
object_var: obj,
'changes': changes,
'objectchanges_table': objectchanges_table,
'base_template': base_template,
'active_tab': 'changelog',
})