1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
|
"""Django admin support for treebeard"""
import sys
from django.conf import settings
from django.contrib import admin, messages
from django.http import HttpResponse, HttpResponseBadRequest
from django.urls import path
from django.utils.translation import gettext_lazy as _
from django.utils.encoding import force_str
from treebeard.exceptions import (InvalidPosition, MissingNodeOrderBy,
InvalidMoveToDescendant, PathOverflow)
from treebeard.al_tree import AL_Node
class TreeAdmin(admin.ModelAdmin):
"""Django Admin class for treebeard."""
change_list_template = 'admin/tree_change_list.html'
def get_queryset(self, request):
if issubclass(self.model, AL_Node):
# AL Trees return a list instead of a QuerySet for .get_tree()
# So we're returning the regular .get_queryset cause we will use
# the old admin
return super().get_queryset(request)
else:
return self.model.get_tree()
def changelist_view(self, request, extra_context=None):
if issubclass(self.model, AL_Node):
# For AL trees, use the old admin display
self.change_list_template = 'admin/tree_list.html'
if extra_context is None:
extra_context = {}
request_context = any(
map(
lambda tmpl:
tmpl.get('BACKEND', None) == 'django.template.backends.django.DjangoTemplates' and
tmpl.get('APP_DIRS', False) and
'django.template.context_processors.request' in tmpl.get('OPTIONS', {}).get('context_processors', []),
settings.TEMPLATES
)
)
lacks_request = ('request' not in extra_context and not request_context)
if lacks_request:
extra_context['request'] = request
return super().changelist_view(request, extra_context)
def get_urls(self):
"""
Adds a url to move nodes to this admin
"""
urls = super().get_urls()
from django.views.i18n import JavaScriptCatalog
jsi18n_url = path('jsi18n/',
JavaScriptCatalog.as_view(packages=['treebeard']),
name='javascript-catalog'
)
new_urls = [
path('move/', self.admin_site.admin_view(self.move_node), ),
jsi18n_url,
]
return new_urls + urls
def get_node(self, node_id):
return self.model.objects.get(pk=node_id)
def try_to_move_node(self, as_child, node, pos, request, target):
try:
node.move(target, pos=pos)
# Call the save method on the (reloaded) node in order to trigger
# possible signal handlers etc.
node = self.get_node(node.pk)
node.save()
except (MissingNodeOrderBy, PathOverflow, InvalidMoveToDescendant,
InvalidPosition):
e = sys.exc_info()[1]
# An error was raised while trying to move the node, then set an
# error message and return 400, this will cause a reload on the
# client to show the message
messages.error(request,
_('Exception raised while moving node: %s') % _(
force_str(e)))
return HttpResponseBadRequest('Exception raised during move')
if as_child:
msg = _('Moved node "%(node)s" as child of "%(other)s"')
else:
msg = _('Moved node "%(node)s" as sibling of "%(other)s"')
messages.info(request, msg % {'node': node, 'other': target})
return HttpResponse('OK')
def move_node(self, request):
try:
node_id = request.POST['node_id']
target_id = request.POST['sibling_id']
as_child = bool(int(request.POST.get('as_child', 0)))
except (KeyError, ValueError):
# Some parameters were missing return a BadRequest
return HttpResponseBadRequest('Malformed POST params')
node = self.get_node(node_id)
target = self.get_node(target_id)
is_sorted = True if node.node_order_by else False
pos = {
(True, True): 'sorted-child',
(True, False): 'last-child',
(False, True): 'sorted-sibling',
(False, False): 'left',
}[as_child, is_sorted]
return self.try_to_move_node(as_child, node, pos, request, target)
def admin_factory(form_class):
"""Dynamically build a TreeAdmin subclass for the given form class.
:param form_class:
:return: A TreeAdmin subclass.
"""
return type(
form_class.__name__ + 'Admin',
(TreeAdmin,),
dict(form=form_class))
|