mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-03-16 22:20:24 +00:00
Merge pull request #349 from brack3t/feature/history
Feature/history - Merging anyway. I'll pull out the auth link tomorrow.
This commit is contained in:
commit
5f76d84e72
10 changed files with 357 additions and 50 deletions
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-:
|
||||
"""
|
||||
WARNING: This file about to undergo major refactoring by @pydanny per Issue #99.
|
||||
WARNING: This file about to undergo major refactoring by @pydanny per
|
||||
Issue #99.
|
||||
"""
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
|
|
@ -22,7 +23,8 @@ class Admin2(object):
|
|||
It keeps a registry of all registered Models and collects the urls of their
|
||||
related ModelAdmin2 instances.
|
||||
|
||||
It also provides an index view that serves as an entry point to the admin site.
|
||||
It also provides an index view that serves as an entry point to the
|
||||
admin site.
|
||||
"""
|
||||
index_view = views.IndexView
|
||||
app_index_view = views.AppIndexView
|
||||
|
|
@ -46,7 +48,8 @@ class Admin2(object):
|
|||
If a model is already registered, this will raise ImproperlyConfigured.
|
||||
"""
|
||||
if model in self.registry:
|
||||
raise ImproperlyConfigured('%s is already registered in django-admin2' % model)
|
||||
raise ImproperlyConfigured(
|
||||
'%s is already registered in django-admin2' % model)
|
||||
if not model_admin:
|
||||
model_admin = types.ModelAdmin2
|
||||
self.registry[model] = model_admin(model, admin=self, **kwargs)
|
||||
|
|
@ -62,12 +65,14 @@ class Admin2(object):
|
|||
"""
|
||||
Deregisters the given model. Remove the model from the self.app as well
|
||||
|
||||
If the model is not already registered, this will raise ImproperlyConfigured.
|
||||
If the model is not already registered, this will raise
|
||||
ImproperlyConfigured.
|
||||
"""
|
||||
try:
|
||||
del self.registry[model]
|
||||
except KeyError:
|
||||
raise ImproperlyConfigured('%s was never registered in django-admin2' % model)
|
||||
raise ImproperlyConfigured(
|
||||
'%s was never registered in django-admin2' % model)
|
||||
|
||||
# Remove the model from the apps registry
|
||||
# Get the app label
|
||||
|
|
@ -101,7 +106,8 @@ class Admin2(object):
|
|||
for object_admin in self.registry.values():
|
||||
if object_admin.name == name:
|
||||
return object_admin
|
||||
raise ValueError(u'No object admin found with name {}'.format(repr(name)))
|
||||
raise ValueError(
|
||||
u'No object admin found with name {}'.format(repr(name)))
|
||||
|
||||
def get_index_kwargs(self):
|
||||
return {
|
||||
|
|
@ -122,7 +128,8 @@ class Admin2(object):
|
|||
}
|
||||
|
||||
def get_urls(self):
|
||||
urlpatterns = patterns('',
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(regex=r'^$',
|
||||
view=self.index_view.as_view(**self.get_index_kwargs()),
|
||||
name='dashboard'
|
||||
|
|
@ -140,18 +147,21 @@ class Admin2(object):
|
|||
name='logout'
|
||||
),
|
||||
url(regex=r'^(?P<app_label>\w+)/$',
|
||||
view=self.app_index_view.as_view(**self.get_app_index_kwargs()),
|
||||
view=self.app_index_view.as_view(
|
||||
**self.get_app_index_kwargs()),
|
||||
name='app_index'
|
||||
),
|
||||
url(regex=r'^api/v0/$',
|
||||
view=self.api_index_view.as_view(**self.get_api_index_kwargs()),
|
||||
view=self.api_index_view.as_view(
|
||||
**self.get_api_index_kwargs()),
|
||||
name='api_index'
|
||||
),
|
||||
)
|
||||
|
||||
for model, model_admin in self.registry.iteritems():
|
||||
model_options = utils.model_options(model)
|
||||
urlpatterns += patterns('',
|
||||
urlpatterns += patterns(
|
||||
'',
|
||||
url('^{}/{}/'.format(
|
||||
model_options.app_label,
|
||||
model_options.object_name.lower()),
|
||||
|
|
|
|||
|
|
@ -2,13 +2,107 @@
|
|||
""" Boilerplate for now, will serve a purpose soon! """
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.admin.util import quote
|
||||
from django.db import models
|
||||
from django.db.models import signals
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
|
||||
from . import permissions
|
||||
|
||||
|
||||
class LogEntryManager(models.Manager):
|
||||
def log_action(self, user_id, obj, action_flag, change_message=''):
|
||||
content_type_id = ContentType.objects.get_for_model(obj).id
|
||||
e = self.model(None, None, user_id, content_type_id,
|
||||
smart_text(obj.id), force_text(obj)[:200],
|
||||
action_flag, change_message)
|
||||
e.save()
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class LogEntry(models.Model):
|
||||
ADDITION = 1
|
||||
CHANGE = 2
|
||||
DELETION = 3
|
||||
|
||||
action_time = models.DateTimeField(_('action time'), auto_now=True)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL,
|
||||
related_name='log_entries')
|
||||
content_type = models.ForeignKey(ContentType, blank=True, null=True,
|
||||
related_name='log_entries')
|
||||
object_id = models.TextField(_('object id'), blank=True, null=True)
|
||||
object_repr = models.CharField(_('object repr'), max_length=200)
|
||||
action_flag = models.PositiveSmallIntegerField(_('action flag'))
|
||||
change_message = models.TextField(_('change message'), blank=True)
|
||||
|
||||
objects = LogEntryManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('log entry')
|
||||
verbose_name_plural = _('log entries')
|
||||
ordering = ('-action_time',)
|
||||
|
||||
def __repr__(self):
|
||||
return smart_text(self.action_time)
|
||||
|
||||
def __str__(self):
|
||||
if self.action_flag == self.ADDITION:
|
||||
return ugettext('Added "%(object)s".') % {
|
||||
'object': self.object_repr}
|
||||
elif self.action_flag == self.CHANGE:
|
||||
return ugettext('Changed "%(object)s" - %(changes)s') % {
|
||||
'object': self.object_repr,
|
||||
'changes': self.change_message,
|
||||
}
|
||||
elif self.action_flag == self.DELETION:
|
||||
return ugettext('Deleted "%(object)s."') % {
|
||||
'object': self.object_repr}
|
||||
|
||||
return ugettext('LogEntry Object')
|
||||
|
||||
def is_addition(self):
|
||||
return self.action_flag == self.ADDITION
|
||||
|
||||
def is_change(self):
|
||||
return self.action_flag == self.CHANGE
|
||||
|
||||
def is_deletion(self):
|
||||
return self.action_flag == self.DELETION
|
||||
|
||||
@property
|
||||
def action_type(self):
|
||||
if self.is_addition():
|
||||
return _('added')
|
||||
if self.is_change():
|
||||
return _('changed')
|
||||
if self.is_deletion():
|
||||
return _('deleted')
|
||||
return ''
|
||||
|
||||
def get_edited_object(self):
|
||||
"Returns the edited object represented by this log entry"
|
||||
return self.content_type.get_object_for_this_type(pk=self.object_id)
|
||||
|
||||
def get_admin_url(self):
|
||||
"""
|
||||
Returns the admin URL to edit the object represented by this log entry.
|
||||
This is relative to the Django admin index page.
|
||||
"""
|
||||
if self.content_type and self.object_id:
|
||||
return '{0.app_label}/{0.model}/{1}'.format(
|
||||
self.content_type,
|
||||
quote(self.object_id)
|
||||
)
|
||||
return None
|
||||
|
||||
# setup signal handlers here, since ``models.py`` will be imported by django
|
||||
# for sure if ``djadmin2`` is listed in the ``INSTALLED_APPS``.
|
||||
|
||||
signals.post_syncdb.connect(permissions.create_view_permissions,
|
||||
signals.post_syncdb.connect(
|
||||
permissions.create_view_permissions,
|
||||
dispatch_uid="django-admin2.djadmin2.permissions.create_view_permissions")
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from datetime import date, time, datetime
|
|||
from django import template
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
|
||||
from .. import utils, renderers
|
||||
from .. import utils, renderers, models
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
|
@ -136,3 +136,10 @@ def render(context, model_instance, attribute_name):
|
|||
# It must be a method instead.
|
||||
field = None
|
||||
return renderer(value, field)
|
||||
|
||||
|
||||
@register.inclusion_tag('djadmin2theme_default/includes/history.html',
|
||||
takes_context=True)
|
||||
def action_history(context):
|
||||
actions = models.LogEntry.objects.filter(user__pk=context['user'].pk)
|
||||
return {'actions': actions}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
{% if actions %}
|
||||
<ol class="unstyled">
|
||||
{% for action in actions %}
|
||||
<li>
|
||||
{% if action.is_addition %}
|
||||
<i class="added icon-plus"></i>
|
||||
{% elif action.is_change %}
|
||||
<i class="changed icon-pencil"></i>
|
||||
{% else %}
|
||||
<i class="deleted icon-minus"></i>
|
||||
{% endif %}
|
||||
{{ action }}
|
||||
<span class="muted">{{ action.content_type.model }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% else %}
|
||||
<p>None available</p>
|
||||
{% endif %}
|
||||
|
||||
|
|
@ -3,16 +3,16 @@
|
|||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="span7">
|
||||
<div class="row-fluid">
|
||||
<div class="span8">
|
||||
{% for app_label, registry in apps.items %}
|
||||
{% include 'djadmin2theme_default/includes/app_model_list.html' %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="span5">
|
||||
<div class="span4 well pull-right">
|
||||
<h4>{% trans "Recent Actions" %}</h4>
|
||||
<h5>{% trans "My Actions" %}</h5>
|
||||
TODO
|
||||
{% action_history %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
{% extends "djadmin2theme_default/base.html" %}
|
||||
{% load admin2_tags i18n %}
|
||||
|
||||
{% block title %}{% trans "History for" %} {{ object }}{% endblock title %}
|
||||
|
||||
{% block page_title %}{% trans "History for" %} {{ object }}{% endblock page_title %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<li>
|
||||
<a href="{% url "admin2:dashboard" %}">{% trans "Home" %}</a>
|
||||
<span class="divider">/</span>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url "admin2:app_index" app_label=app_label %}">{{ app_label|title }}</a>
|
||||
<span class="divider">/</span>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url view|admin2_urlname:"index" %}">{{ model_name_pluralized|title }}</a>
|
||||
<span class="divider">/</span>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url view|admin2_urlname:"detail" pk=object.pk %}">{{ object }}</a>
|
||||
<span class="divider">/</span>
|
||||
</li>
|
||||
<li class="active">{% trans "History" %}</li>
|
||||
{% endblock breadcrumbs %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p>
|
||||
{% blocktrans with object=object %}
|
||||
History for {{ object }}
|
||||
{% endblocktrans %}
|
||||
|
||||
{% if object_list %}
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Date/Time" %}</th>
|
||||
<th>{% trans "User" %}</th>
|
||||
<th>{% trans "Action" %}</th>
|
||||
<th>{% trans "Message" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in object_list %}
|
||||
<tr>
|
||||
<td>{{ log.action_time }}</td>
|
||||
<td>{{ log.user }}</td>
|
||||
<td>{{ log.action_type|capfirst }}</td>
|
||||
<td>{{ log.change_message }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No history for this object.</p>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
|
|
@ -9,7 +9,9 @@
|
|||
{% block page_title %}{% blocktrans with action=action model_name=model_name %}{{ action_name }} {{ model_name }}{% endblocktrans %}{% endblock page_title %}
|
||||
|
||||
{% block page_title_link %}
|
||||
<a href="#" class="btn btn-info">History</a>
|
||||
{% if object.pk %}
|
||||
<a href="{% url view|admin2_urlname:"history" pk=object.pk %}" class="btn btn-info pull-right">History</a>
|
||||
{% endif %}
|
||||
{% endblock page_title_link %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
|
|
@ -41,14 +43,14 @@
|
|||
{% block content %}
|
||||
|
||||
<form method="post">
|
||||
|
||||
|
||||
{% if view.model_admin.save_on_top %}
|
||||
{% include "djadmin2theme_default/includes/save_buttons.html" %}
|
||||
{% endif %}
|
||||
|
||||
<div class="row-fluid"><!-- begin main form row -->
|
||||
<div class="span12">
|
||||
|
||||
|
||||
<div class="change_form">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
|
|
@ -63,11 +65,11 @@
|
|||
|
||||
</div>
|
||||
</div><!-- end main form row -->
|
||||
|
||||
|
||||
{% if view.model_admin.save_on_bottom %}
|
||||
{% include "djadmin2theme_default/includes/save_buttons.html" %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ class ModelAdmin2(object):
|
|||
update_view = views.ModelEditFormView
|
||||
detail_view = views.ModelDetailView
|
||||
delete_view = views.ModelDeleteView
|
||||
history_view = views.ModelHistoryView
|
||||
|
||||
# API configuration
|
||||
api_serializer_class = None
|
||||
|
|
@ -151,13 +152,15 @@ class ModelAdmin2(object):
|
|||
kwargs = self.get_default_view_kwargs()
|
||||
kwargs.update({
|
||||
'inlines': self.inlines,
|
||||
'form_class': self.create_form_class if self.create_form_class else self.form_class,
|
||||
'form_class': (self.create_form_class if
|
||||
self.create_form_class else self.form_class),
|
||||
})
|
||||
return kwargs
|
||||
|
||||
def get_update_kwargs(self):
|
||||
kwargs = self.get_default_view_kwargs()
|
||||
form_class = self.update_form_class if self.update_form_class else self.form_class
|
||||
form_class = (self.update_form_class if
|
||||
self.update_form_class else self.form_class)
|
||||
if form_class is None:
|
||||
form_class = modelform_factory(self.model)
|
||||
kwargs.update({
|
||||
|
|
@ -172,8 +175,12 @@ class ModelAdmin2(object):
|
|||
def get_delete_kwargs(self):
|
||||
return self.get_default_view_kwargs()
|
||||
|
||||
def get_history_kwargs(self):
|
||||
return self.get_default_view_kwargs()
|
||||
|
||||
def get_index_url(self):
|
||||
return reverse('admin2:{}'.format(self.get_prefixed_view_name('index')))
|
||||
return reverse('admin2:{}'.format(
|
||||
self.get_prefixed_view_name('index')))
|
||||
|
||||
def get_api_list_kwargs(self):
|
||||
kwargs = self.get_default_api_view_kwargs()
|
||||
|
|
@ -186,7 +193,8 @@ class ModelAdmin2(object):
|
|||
return self.get_default_api_view_kwargs()
|
||||
|
||||
def get_urls(self):
|
||||
return patterns('',
|
||||
return patterns(
|
||||
'',
|
||||
url(
|
||||
regex=r'^$',
|
||||
view=self.index_view.as_view(**self.get_index_kwargs()),
|
||||
|
|
@ -212,10 +220,16 @@ class ModelAdmin2(object):
|
|||
view=self.delete_view.as_view(**self.get_delete_kwargs()),
|
||||
name=self.get_prefixed_view_name('delete')
|
||||
),
|
||||
url(
|
||||
regex=r'^(?P<pk>[0-9]+)/history/$',
|
||||
view=self.history_view.as_view(**self.get_history_kwargs()),
|
||||
name=self.get_prefixed_view_name('history')
|
||||
)
|
||||
)
|
||||
|
||||
def get_api_urls(self):
|
||||
return patterns('',
|
||||
return patterns(
|
||||
'',
|
||||
url(
|
||||
regex=r'^$',
|
||||
view=self.api_list_view.as_view(**self.get_api_list_kwargs()),
|
||||
|
|
@ -223,7 +237,8 @@ class ModelAdmin2(object):
|
|||
),
|
||||
url(
|
||||
regex=r'^(?P<pk>[0-9]+)/$',
|
||||
view=self.api_detail_view.as_view(**self.get_api_detail_kwargs()),
|
||||
view=self.api_detail_view.as_view(
|
||||
**self.get_api_detail_kwargs()),
|
||||
name=self.get_prefixed_view_name('api_detail'),
|
||||
),
|
||||
)
|
||||
|
|
@ -244,9 +259,9 @@ class ModelAdmin2(object):
|
|||
class_actions = getattr(cls, 'list_actions', [])
|
||||
for action in class_actions:
|
||||
actions_dict[action.__name__] = {
|
||||
'name': action.__name__,
|
||||
'description': actions.get_description(action),
|
||||
'action_callable': action
|
||||
'name': action.__name__,
|
||||
'description': actions.get_description(action),
|
||||
'action_callable': action
|
||||
}
|
||||
return actions_dict
|
||||
|
||||
|
|
@ -270,22 +285,28 @@ class Admin2Inline(extra_views.InlineFormSet):
|
|||
|
||||
|
||||
class Admin2TabularInline(Admin2Inline):
|
||||
template = os.path.join(settings.ADMIN2_THEME_DIRECTORY, 'edit_inlines/tabular.html')
|
||||
template = os.path.join(
|
||||
settings.ADMIN2_THEME_DIRECTORY, 'edit_inlines/tabular.html')
|
||||
|
||||
|
||||
class Admin2StackedInline(Admin2Inline):
|
||||
template = os.path.join(settings.ADMIN2_THEME_DIRECTORY, 'edit_inlines/stacked.html')
|
||||
template = os.path.join(
|
||||
settings.ADMIN2_THEME_DIRECTORY, 'edit_inlines/stacked.html')
|
||||
|
||||
|
||||
def immutable_admin_factory(model_admin):
|
||||
""" Provide an ImmutableAdmin to make it harder for developers to dig themselves into holes.
|
||||
See https://github.com/twoscoops/django-admin2/issues/99
|
||||
Frozen class implementation as namedtuple suggested by Audrey Roy
|
||||
"""
|
||||
Provide an ImmutableAdmin to make it harder for developers to
|
||||
dig themselves into holes.
|
||||
See https://github.com/twoscoops/django-admin2/issues/99
|
||||
Frozen class implementation as namedtuple suggested by Audrey Roy
|
||||
|
||||
Note: This won't stop developers from saving mutable objects to the result, but hopefully
|
||||
developers attempting that 'workaround/hack' will read our documentation.
|
||||
Note: This won't stop developers from saving mutable objects to
|
||||
the result, but hopefully developers attempting that
|
||||
'workaround/hack' will read our documentation.
|
||||
"""
|
||||
ImmutableAdmin = namedtuple('ImmutableAdmin',
|
||||
model_admin.model_admin_attributes,
|
||||
verbose=False)
|
||||
return ImmutableAdmin(*[getattr(model_admin, x) for x in model_admin.model_admin_attributes])
|
||||
return ImmutableAdmin(*[getattr(
|
||||
model_admin, x) for x in model_admin.model_admin_attributes])
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ from django.core.exceptions import PermissionDenied
|
|||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.forms.models import modelform_factory
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.text import get_text_list
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from braces.views import AccessMixin
|
||||
|
||||
from . import settings, permissions
|
||||
|
|
@ -43,8 +47,10 @@ class PermissionMixin(AccessMixin):
|
|||
if self.raise_exception:
|
||||
raise PermissionDenied # return a forbidden response
|
||||
else:
|
||||
return redirect_to_login(request.get_full_path(),
|
||||
self.get_login_url(), self.get_redirect_field_name())
|
||||
return redirect_to_login(
|
||||
request.get_full_path(),
|
||||
self.get_login_url(),
|
||||
self.get_redirect_field_name())
|
||||
return super(PermissionMixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
|
@ -67,7 +73,8 @@ class Admin2Mixin(PermissionMixin):
|
|||
index_path = reverse_lazy('admin2:dashboard')
|
||||
|
||||
def get_template_names(self):
|
||||
return [os.path.join(settings.ADMIN2_THEME_DIRECTORY, self.default_template_name)]
|
||||
return [os.path.join(
|
||||
settings.ADMIN2_THEME_DIRECTORY, self.default_template_name)]
|
||||
|
||||
def get_model(self):
|
||||
return self.model
|
||||
|
|
@ -139,3 +146,33 @@ class Admin2ModelFormMixin(object):
|
|||
|
||||
# default to index view
|
||||
return reverse(admin2_urlname(self, 'index'))
|
||||
|
||||
def construct_change_message(self, request, form, formsets):
|
||||
""" Construct a change message from a changed object """
|
||||
change_message = []
|
||||
if form.changed_data:
|
||||
change_message.append(
|
||||
_('Changed {0}.'.format(
|
||||
get_text_list(form.changed_data, _('and')))))
|
||||
|
||||
if formsets:
|
||||
for formset in formsets:
|
||||
for added_object in formset.new_objects:
|
||||
change_message.append(
|
||||
_('Added {0} "{1}".'.format(
|
||||
force_text(added_object._meta.verbose_name),
|
||||
force_text(added_object))))
|
||||
for changed_object, changed_fields in formset.changed_objects:
|
||||
change_message.append(
|
||||
_('Changed {0} for {1} "{2}".'.format(
|
||||
get_text_list(changed_fields, _('and')),
|
||||
force_text(changed_object._meta.verbose_name),
|
||||
force_text(changed_object))))
|
||||
for deleted_object in formset.deleted_objects:
|
||||
change_message.append(
|
||||
_('Deleted {0} "{1}".'.format(
|
||||
force_text(deleted_object._meta.verbose_name),
|
||||
force_text(deleted_object))))
|
||||
|
||||
change_message = ' '.join(change_message)
|
||||
return change_message or _('No fields changed.')
|
||||
|
|
|
|||
|
|
@ -3,25 +3,28 @@ from __future__ import division, absolute_import, unicode_literals
|
|||
|
||||
import operator
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.forms import (PasswordChangeForm,
|
||||
AdminPasswordChangeForm)
|
||||
from django.contrib.auth.views import (logout as auth_logout,
|
||||
login as auth_login)
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django.db import models
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django.views import generic
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
|
||||
import extra_views
|
||||
|
||||
|
||||
from . import permissions, utils
|
||||
from .forms import AdminAuthenticationForm
|
||||
from .models import LogEntry
|
||||
from .viewmixins import Admin2Mixin, AdminModel2Mixin, Admin2ModelFormMixin
|
||||
from .filters import build_list_filter
|
||||
|
||||
|
|
@ -81,8 +84,8 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
|||
selected_model_pks = request.POST.getlist('selected_model_pk')
|
||||
queryset = self.model.objects.filter(pk__in=selected_model_pks)
|
||||
|
||||
# If action_callable is a class subclassing from actions.BaseListAction
|
||||
# then we generate the callable object.
|
||||
# If action_callable is a class subclassing from
|
||||
# actions.BaseListAction then we generate the callable object.
|
||||
if hasattr(action_callable, "process_queryset"):
|
||||
response = action_callable.as_view(queryset=queryset)(request)
|
||||
else:
|
||||
|
|
@ -130,7 +133,8 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
|||
search_term = self.request.GET.get('q', None)
|
||||
search_use_distinct = False
|
||||
if self.model_admin.search_fields and search_term:
|
||||
queryset, search_use_distinct = self.get_search_results(queryset, search_term)
|
||||
queryset, search_use_distinct = self.get_search_results(
|
||||
queryset, search_term)
|
||||
|
||||
if self.model_admin.list_filter:
|
||||
queryset = self.build_list_filter(queryset).qs
|
||||
|
|
@ -185,7 +189,8 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
|||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
view_name = 'admin2:{}_{}_index'.format(self.app_label, self.model_name)
|
||||
view_name = 'admin2:{}_{}_index'.format(
|
||||
self.app_label, self.model_name)
|
||||
return reverse(view_name)
|
||||
|
||||
def get_actions(self):
|
||||
|
|
@ -208,7 +213,8 @@ class ModelDetailView(AdminModel2Mixin, generic.DetailView):
|
|||
permissions.ModelViewPermission)
|
||||
|
||||
|
||||
class ModelEditFormView(AdminModel2Mixin, Admin2ModelFormMixin, extra_views.UpdateWithInlinesView):
|
||||
class ModelEditFormView(AdminModel2Mixin, Admin2ModelFormMixin,
|
||||
extra_views.UpdateWithInlinesView):
|
||||
"""Context Variables
|
||||
|
||||
:model: Type of object you are editing
|
||||
|
|
@ -228,8 +234,18 @@ class ModelEditFormView(AdminModel2Mixin, Admin2ModelFormMixin, extra_views.Upda
|
|||
context['action_name'] = ugettext_lazy("Change")
|
||||
return context
|
||||
|
||||
def forms_valid(self, form, inlines):
|
||||
response = super(ModelEditFormView, self).forms_valid(form, inlines)
|
||||
LogEntry.objects.log_action(
|
||||
self.request.user.id,
|
||||
self.object,
|
||||
LogEntry.CHANGE,
|
||||
self.construct_change_message(self.request, form, inlines))
|
||||
return response
|
||||
|
||||
class ModelAddFormView(AdminModel2Mixin, Admin2ModelFormMixin, extra_views.CreateWithInlinesView):
|
||||
|
||||
class ModelAddFormView(AdminModel2Mixin, Admin2ModelFormMixin,
|
||||
extra_views.CreateWithInlinesView):
|
||||
"""Context Variables
|
||||
|
||||
:model: Type of object you are editing
|
||||
|
|
@ -249,6 +265,15 @@ class ModelAddFormView(AdminModel2Mixin, Admin2ModelFormMixin, extra_views.Creat
|
|||
context['action_name'] = ugettext_lazy("Add")
|
||||
return context
|
||||
|
||||
def forms_valid(self, form, inlines):
|
||||
response = super(ModelAddFormView, self).forms_valid(form, inlines)
|
||||
LogEntry.objects.log_action(
|
||||
self.request.user.id,
|
||||
self.object,
|
||||
LogEntry.ADDITION,
|
||||
'Object created.')
|
||||
return response
|
||||
|
||||
|
||||
class ModelDeleteView(AdminModel2Mixin, generic.DeleteView):
|
||||
"""Context Variables
|
||||
|
|
@ -279,6 +304,38 @@ class ModelDeleteView(AdminModel2Mixin, generic.DeleteView):
|
|||
})
|
||||
return context
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
LogEntry.objects.log_action(
|
||||
request.user.id,
|
||||
self.get_object(),
|
||||
LogEntry.DELETION,
|
||||
'Object deleted.')
|
||||
return super(ModelDeleteView, self).delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
class ModelHistoryView(AdminModel2Mixin, generic.ListView):
|
||||
default_template_name = "model_history.html"
|
||||
permission_classes = (
|
||||
permissions.IsStaffPermission,
|
||||
permissions.ModelChangePermission
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ModelHistoryView, self).get_context_data(**kwargs)
|
||||
context['model'] = self.get_model()
|
||||
context['object'] = self.get_object()
|
||||
return context
|
||||
|
||||
def get_object(self):
|
||||
return get_object_or_404(self.get_model(), pk=self.kwargs.get('pk'))
|
||||
|
||||
def get_queryset(self):
|
||||
content_type = ContentType.objects.get_for_model(self.get_object())
|
||||
return LogEntry.objects.filter(
|
||||
content_type=content_type,
|
||||
object_id=self.get_object().id
|
||||
)
|
||||
|
||||
|
||||
class PasswordChangeView(Admin2Mixin, generic.UpdateView):
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue