from django.contrib.contenttypes.generic import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.db import models
from django.db.models.query import QuerySet
from django.db.models.fields.related import ForeignObjectRel
from django.forms.models import model_to_dict
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.utils.encoding import force_str
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.translation import ugettext as _
from xadmin.layout import Field, render_field
from xadmin.plugins.actions import BaseActionView
from xadmin.plugins.inline import InlineModelAdmin
from xadmin.sites import site
from xadmin.util import unquote, quote, model_format_dict
from xadmin.views import BaseAdminPlugin, ModelAdminView, CreateAdminView, UpdateAdminView, DetailAdminView, ModelFormAdminView, DeleteAdminView, ListAdminView
from xadmin.views.base import csrf_protect_m, filter_hook
from xadmin.views.detail import DetailAdminUtil
from reversion.models import Revision, Version
from reversion.revisions import default_revision_manager, RegistrationError
from functools import partial
def _autoregister(admin, model, follow=None):
"""Registers a model with reversion, if required."""
if model._meta.proxy:
raise RegistrationError("Proxy models cannot be used with django-reversion, register the parent class instead")
if not admin.revision_manager.is_registered(model):
follow = follow or []
for parent_cls, field in model._meta.parents.items():
follow.append(field.name)
_autoregister(admin, parent_cls)
admin.revision_manager.register(
model, follow=follow, format=admin.reversion_format)
def _register_model(admin, model):
if not hasattr(admin, 'revision_manager'):
admin.revision_manager = default_revision_manager
if not hasattr(admin, 'reversion_format'):
admin.reversion_format = 'json'
if not admin.revision_manager.is_registered(model):
inline_fields = []
for inline in getattr(admin, 'inlines', []):
inline_model = inline.model
if getattr(inline, 'generic_inline', False):
ct_field = getattr(inline, 'ct_field', 'content_type')
ct_fk_field = getattr(inline, 'ct_fk_field', 'object_id')
for field in model._meta.many_to_many:
if isinstance(field, GenericRelation) and field.rel.to == inline_model and field.object_id_field_name == ct_fk_field and field.content_type_field_name == ct_field:
inline_fields.append(field.name)
_autoregister(admin, inline_model)
else:
fk_name = getattr(inline, 'fk_name', None)
if not fk_name:
for field in inline_model._meta.fields:
if isinstance(field, (models.ForeignKey, models.OneToOneField)) and issubclass(model, field.rel.to):
fk_name = field.name
_autoregister(admin, inline_model, follow=[fk_name])
if not inline_model._meta.get_field(fk_name).rel.is_hidden():
accessor = inline_model._meta.get_field(
fk_name).related.get_accessor_name()
inline_fields.append(accessor)
_autoregister(admin, model, inline_fields)
def register_models(admin_site=None):
if admin_site is None:
admin_site = site
for model, admin in admin_site._registry.items():
if getattr(admin, 'reversion_enable', False):
_register_model(admin, model)
class ReversionPlugin(BaseAdminPlugin):
# The revision manager instance used to manage revisions.
revision_manager = default_revision_manager
# The serialization format to use when registering models with reversion.
reversion_format = "json"
# Whether to ignore duplicate revision data.
ignore_duplicate_revisions = False
reversion_enable = False
def init_request(self, *args, **kwargs):
return self.reversion_enable
@property
def revision_context_manager(self):
"""The revision context manager for this VersionAdmin."""
return self.revision_manager._revision_context_manager
def get_revision_instances(self, obj):
"""Returns all the instances to be used in the object's revision."""
return [obj]
def get_revision_data(self, obj, flag):
"""Returns all the revision data to be used in the object's revision."""
return dict(
(o, self.revision_manager.get_adapter(
o.__class__).get_version_data(o, flag))
for o in self.get_revision_instances(obj)
)
def save_revision(self, obj, tag, comment):
self.revision_manager.save_revision(
self.get_revision_data(obj, tag),
user=self.user,
comment=comment,
ignore_duplicates=self.ignore_duplicate_revisions,
db=self.revision_context_manager.get_db(),
)
def do_post(self, __):
def _method():
self.revision_context_manager.set_user(self.user)
comment = ''
admin_view = self.admin_view
if isinstance(admin_view, CreateAdminView):
comment = _(u"Initial version.")
elif isinstance(admin_view, UpdateAdminView):
comment = _(u"Change version.")
elif isinstance(admin_view, RevisionView):
comment = _(u"Revert version.")
elif isinstance(admin_view, RecoverView):
comment = _(u"Rercover version.")
elif isinstance(admin_view, DeleteAdminView):
comment = _(u"Deleted %(verbose_name)s.") % {
"verbose_name": self.opts.verbose_name}
self.revision_context_manager.set_comment(comment)
return __()
return _method
def post(self, __, request, *args, **kwargs):
return self.revision_context_manager.create_revision(manage_manually=False)(self.do_post(__))()
# def save_models(self, __):
# self.revision_context_manager.create_revision(manage_manually=True)(__)()
# if self.admin_view.org_obj is None:
# self.save_revision(self.admin_view.new_obj, VERSION_ADD, _(u"Initial version."))
# else:
# self.save_revision(self.admin_view.new_obj, VERSION_CHANGE, _(u"Change version."))
# def save_related(self, __):
# self.revision_context_manager.create_revision(manage_manually=True)(__)()
# def delete_model(self, __):
# self.save_revision(self.admin_view.obj, VERSION_DELETE, \
# _(u"Deleted %(verbose_name)s.") % {"verbose_name": self.opts.verbose_name})
# self.revision_context_manager.create_revision(manage_manually=True)(__)()
# Block Views
def block_top_toolbar(self, context, nodes):
recoverlist_url = self.admin_view.model_admin_url('recoverlist')
nodes.append(mark_safe('<div class="btn-group"><a class="btn btn-default btn-sm" href="%s"><i class="fa fa-trash-o"></i> %s</a></div>' % (recoverlist_url, _(u"Recover"))))
def block_nav_toggles(self, context, nodes):
obj = getattr(
self.admin_view, 'org_obj', getattr(self.admin_view, 'obj', None))
if obj:
revisionlist_url = self.admin_view.model_admin_url(
'revisionlist', quote(obj.pk))
nodes.append(mark_safe('<a href="%s" class="navbar-toggle pull-right"><i class="fa fa-time"></i></a>' % revisionlist_url))
def block_nav_btns(self, context, nodes):
obj = getattr(
self.admin_view, 'org_obj', getattr(self.admin_view, 'obj', None))
if obj:
revisionl