Initial commit
This commit is contained in:
21
env/lib/python3.10/site-packages/treebeard/__init__.py
vendored
Normal file
21
env/lib/python3.10/site-packages/treebeard/__init__.py
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
See PEP 386 (https://www.python.org/dev/peps/pep-0386/)
|
||||
|
||||
Release logic:
|
||||
1. Remove ".devX" from __version__ (below)
|
||||
2. git add treebeard/__init__.py
|
||||
3. git commit -m 'Bump to <version>'
|
||||
4. git tag <version>
|
||||
5. git push
|
||||
6. ensure that all tests pass on Github Actions
|
||||
7. git push --tags
|
||||
8. pip install --upgrade pip wheel twine
|
||||
9. python setup.py clean --all
|
||||
9. python setup.py sdist bdist_wheel
|
||||
10. twine upload dist/*
|
||||
11. bump the version, append ".dev0" to __version__
|
||||
12. git add treebeard/__init__.py
|
||||
13. git commit -m 'Start with <version>'
|
||||
14. git push
|
||||
"""
|
||||
__version__ = '4.7.1'
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/admin.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/admin.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/al_tree.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/al_tree.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/exceptions.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/exceptions.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/forms.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/forms.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/models.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/models.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/mp_tree.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/mp_tree.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/ns_tree.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/ns_tree.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/numconv.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/__pycache__/numconv.cpython-310.pyc
vendored
Normal file
Binary file not shown.
127
env/lib/python3.10/site-packages/treebeard/admin.py
vendored
Normal file
127
env/lib/python3.10/site-packages/treebeard/admin.py
vendored
Normal file
@@ -0,0 +1,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))
|
||||
405
env/lib/python3.10/site-packages/treebeard/al_tree.py
vendored
Normal file
405
env/lib/python3.10/site-packages/treebeard/al_tree.py
vendored
Normal file
@@ -0,0 +1,405 @@
|
||||
"""Adjacency List"""
|
||||
|
||||
from django.core import serializers
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_noop as _
|
||||
from treebeard.exceptions import InvalidMoveToDescendant, NodeAlreadySaved
|
||||
from treebeard.models import Node
|
||||
|
||||
|
||||
def get_result_class(cls):
|
||||
"""
|
||||
For the given model class, determine what class we should use for the
|
||||
nodes returned by its tree methods (such as get_children).
|
||||
|
||||
Usually this will be trivially the same as the initial model class,
|
||||
but there are special cases when model inheritance is in use:
|
||||
|
||||
* If the model extends another via multi-table inheritance, we need to
|
||||
use whichever ancestor originally implemented the tree behaviour (i.e.
|
||||
the one which defines the 'parent' field). We can't use the
|
||||
subclass, because it's not guaranteed that the other nodes reachable
|
||||
from the current one will be instances of the same subclass.
|
||||
|
||||
* If the model is a proxy model, the returned nodes should also use
|
||||
the proxy class.
|
||||
"""
|
||||
base_class = cls._meta.get_field('parent').model
|
||||
if cls._meta.proxy_for_model == base_class:
|
||||
return cls
|
||||
else:
|
||||
return base_class
|
||||
|
||||
|
||||
class AL_NodeManager(models.Manager):
|
||||
"""Custom manager for nodes in an Adjacency List tree."""
|
||||
def get_queryset(self):
|
||||
"""Sets the custom queryset as the default."""
|
||||
if self.model.node_order_by:
|
||||
order_by = ['parent'] + list(self.model.node_order_by)
|
||||
else:
|
||||
order_by = ['parent', 'sib_order']
|
||||
return super().get_queryset().order_by(*order_by)
|
||||
|
||||
|
||||
class AL_Node(Node):
|
||||
"""Abstract model to create your own Adjacency List Trees."""
|
||||
|
||||
objects = AL_NodeManager()
|
||||
node_order_by = None
|
||||
|
||||
@classmethod
|
||||
def add_root(cls, **kwargs):
|
||||
"""Adds a root node to the tree."""
|
||||
|
||||
if len(kwargs) == 1 and 'instance' in kwargs:
|
||||
# adding the passed (unsaved) instance to the tree
|
||||
newobj = kwargs['instance']
|
||||
if not newobj._state.adding:
|
||||
raise NodeAlreadySaved("Attempted to add a tree node that is "\
|
||||
"already in the database")
|
||||
else:
|
||||
newobj = cls(**kwargs)
|
||||
|
||||
newobj._cached_depth = 1
|
||||
if not cls.node_order_by:
|
||||
try:
|
||||
max = get_result_class(cls).objects.filter(
|
||||
parent__isnull=True).order_by(
|
||||
'sib_order').reverse()[0].sib_order
|
||||
except IndexError:
|
||||
max = 0
|
||||
newobj.sib_order = max + 1
|
||||
newobj.save()
|
||||
return newobj
|
||||
|
||||
@classmethod
|
||||
def get_root_nodes(cls):
|
||||
""":returns: A queryset containing the root nodes in the tree."""
|
||||
return get_result_class(cls).objects.filter(parent__isnull=True)
|
||||
|
||||
def get_depth(self, update=False):
|
||||
"""
|
||||
:returns: the depth (level) of the node
|
||||
Caches the result in the object itself to help in loops.
|
||||
|
||||
:param update: Updates the cached value.
|
||||
"""
|
||||
|
||||
if self.parent_id is None:
|
||||
return 1
|
||||
|
||||
try:
|
||||
if update:
|
||||
del self._cached_depth
|
||||
else:
|
||||
return self._cached_depth
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
depth = 0
|
||||
node = self
|
||||
while node:
|
||||
node = node.parent
|
||||
depth += 1
|
||||
self._cached_depth = depth
|
||||
return depth
|
||||
|
||||
def get_children(self):
|
||||
""":returns: A queryset of all the node's children"""
|
||||
return get_result_class(self.__class__).objects.filter(parent=self)
|
||||
|
||||
def get_parent(self, update=False):
|
||||
""":returns: the parent node of the current node object."""
|
||||
if self._meta.proxy_for_model:
|
||||
# the current node is a proxy model; the returned parent
|
||||
# should be the same proxy model, so we need to explicitly
|
||||
# fetch it as an instance of that model rather than simply
|
||||
# following the 'parent' relation
|
||||
if self.parent_id is None:
|
||||
return None
|
||||
else:
|
||||
return self.__class__.objects.get(pk=self.parent_id)
|
||||
else:
|
||||
return self.parent
|
||||
|
||||
def get_ancestors(self):
|
||||
"""
|
||||
:returns: A *list* containing the current node object's ancestors,
|
||||
starting by the root node and descending to the parent.
|
||||
"""
|
||||
ancestors = []
|
||||
if self._meta.proxy_for_model:
|
||||
# the current node is a proxy model; our result set
|
||||
# should use the same proxy model, so we need to
|
||||
# explicitly fetch instances of that model
|
||||
# when following the 'parent' relation
|
||||
cls = self.__class__
|
||||
node = self
|
||||
while node.parent_id:
|
||||
node = cls.objects.get(pk=node.parent_id)
|
||||
ancestors.insert(0, node)
|
||||
else:
|
||||
node = self.parent
|
||||
while node:
|
||||
ancestors.insert(0, node)
|
||||
node = node.parent
|
||||
return ancestors
|
||||
|
||||
def get_root(self):
|
||||
""":returns: the root node for the current node object."""
|
||||
ancestors = self.get_ancestors()
|
||||
if ancestors:
|
||||
return ancestors[0]
|
||||
return self
|
||||
|
||||
def is_descendant_of(self, node):
|
||||
"""
|
||||
:returns: ``True`` if the node if a descendant of another node given
|
||||
as an argument, else, returns ``False``
|
||||
"""
|
||||
return self.pk in [obj.pk for obj in node.get_descendants()]
|
||||
|
||||
@classmethod
|
||||
def dump_bulk(cls, parent=None, keep_ids=True):
|
||||
"""Dumps a tree branch to a python data structure."""
|
||||
|
||||
serializable_cls = cls._get_serializable_model()
|
||||
if (
|
||||
parent and serializable_cls != cls and
|
||||
parent.__class__ != serializable_cls
|
||||
):
|
||||
parent = serializable_cls.objects.get(pk=parent.pk)
|
||||
|
||||
# a list of nodes: not really a queryset, but it works
|
||||
objs = serializable_cls.get_tree(parent)
|
||||
|
||||
ret, lnk = [], {}
|
||||
pk_field = cls._meta.pk.attname
|
||||
for node, pyobj in zip(objs, serializers.serialize('python', objs)):
|
||||
depth = node.get_depth()
|
||||
# django's serializer stores the attributes in 'fields'
|
||||
fields = pyobj['fields']
|
||||
del fields['parent']
|
||||
|
||||
# non-sorted trees have this
|
||||
if 'sib_order' in fields:
|
||||
del fields['sib_order']
|
||||
|
||||
if pk_field in fields:
|
||||
del fields[pk_field]
|
||||
|
||||
newobj = {'data': fields}
|
||||
if keep_ids:
|
||||
newobj[pk_field] = pyobj['pk']
|
||||
|
||||
if (not parent and depth == 1) or\
|
||||
(parent and depth == parent.get_depth()):
|
||||
ret.append(newobj)
|
||||
else:
|
||||
parentobj = lnk[node.parent_id]
|
||||
if 'children' not in parentobj:
|
||||
parentobj['children'] = []
|
||||
parentobj['children'].append(newobj)
|
||||
lnk[node.pk] = newobj
|
||||
return ret
|
||||
|
||||
def add_child(self, **kwargs):
|
||||
"""Adds a child to the node."""
|
||||
cls = get_result_class(self.__class__)
|
||||
|
||||
if len(kwargs) == 1 and 'instance' in kwargs:
|
||||
# adding the passed (unsaved) instance to the tree
|
||||
newobj = kwargs['instance']
|
||||
if not newobj._state.adding:
|
||||
raise NodeAlreadySaved("Attempted to add a tree node that is "\
|
||||
"already in the database")
|
||||
else:
|
||||
newobj = cls(**kwargs)
|
||||
|
||||
try:
|
||||
newobj._cached_depth = self._cached_depth + 1
|
||||
except AttributeError:
|
||||
pass
|
||||
if not cls.node_order_by:
|
||||
try:
|
||||
max = cls.objects.filter(parent=self).reverse(
|
||||
)[0].sib_order
|
||||
except IndexError:
|
||||
max = 0
|
||||
newobj.sib_order = max + 1
|
||||
newobj.parent = self
|
||||
newobj.save()
|
||||
return newobj
|
||||
|
||||
@classmethod
|
||||
def _get_tree_recursively(cls, results, parent, depth):
|
||||
if parent:
|
||||
nodes = parent.get_children()
|
||||
else:
|
||||
nodes = cls.get_root_nodes()
|
||||
for node in nodes:
|
||||
node._cached_depth = depth
|
||||
results.append(node)
|
||||
cls._get_tree_recursively(results, node, depth + 1)
|
||||
|
||||
@classmethod
|
||||
def get_tree(cls, parent=None):
|
||||
"""
|
||||
:returns: A list of nodes ordered as DFS, including the parent. If
|
||||
no parent is given, the entire tree is returned.
|
||||
"""
|
||||
if parent:
|
||||
depth = parent.get_depth() + 1
|
||||
results = [parent]
|
||||
else:
|
||||
depth = 1
|
||||
results = []
|
||||
cls._get_tree_recursively(results, parent, depth)
|
||||
return results
|
||||
|
||||
def get_descendants(self):
|
||||
"""
|
||||
:returns: A *list* of all the node's descendants, doesn't
|
||||
include the node itself
|
||||
"""
|
||||
return self.__class__.get_tree(parent=self)[1:]
|
||||
|
||||
def get_descendant_count(self):
|
||||
""":returns: the number of descendants of a nodee"""
|
||||
return len(self.get_descendants())
|
||||
|
||||
def get_siblings(self):
|
||||
"""
|
||||
:returns: A queryset of all the node's siblings, including the node
|
||||
itself.
|
||||
"""
|
||||
if self.parent:
|
||||
return get_result_class(self.__class__).objects.filter(
|
||||
parent=self.parent)
|
||||
return self.__class__.get_root_nodes()
|
||||
|
||||
def add_sibling(self, pos=None, **kwargs):
|
||||
"""Adds a new node as a sibling to the current node object."""
|
||||
pos = self._prepare_pos_var_for_add_sibling(pos)
|
||||
|
||||
if len(kwargs) == 1 and 'instance' in kwargs:
|
||||
# adding the passed (unsaved) instance to the tree
|
||||
newobj = kwargs['instance']
|
||||
if not newobj._state.adding:
|
||||
raise NodeAlreadySaved("Attempted to add a tree node that is "\
|
||||
"already in the database")
|
||||
else:
|
||||
# creating a new object
|
||||
newobj = get_result_class(self.__class__)(**kwargs)
|
||||
|
||||
if not self.node_order_by:
|
||||
newobj.sib_order = self.__class__._get_new_sibling_order(pos,
|
||||
self)
|
||||
newobj.parent_id = self.parent_id
|
||||
newobj.save()
|
||||
return newobj
|
||||
|
||||
@classmethod
|
||||
def _is_target_pos_the_last_sibling(cls, pos, target):
|
||||
return pos == 'last-sibling' or (
|
||||
pos == 'right' and target == target.get_last_sibling())
|
||||
|
||||
@classmethod
|
||||
def _make_hole_in_db(cls, min, target_node):
|
||||
qset = get_result_class(cls).objects.filter(sib_order__gte=min)
|
||||
if target_node.is_root():
|
||||
qset = qset.filter(parent__isnull=True)
|
||||
else:
|
||||
qset = qset.filter(parent=target_node.parent)
|
||||
qset.update(sib_order=models.F('sib_order') + 1)
|
||||
|
||||
@classmethod
|
||||
def _make_hole_and_get_sibling_order(cls, pos, target_node):
|
||||
siblings = target_node.get_siblings()
|
||||
siblings = {
|
||||
'left': siblings.filter(sib_order__gte=target_node.sib_order),
|
||||
'right': siblings.filter(sib_order__gt=target_node.sib_order),
|
||||
'first-sibling': siblings
|
||||
}[pos]
|
||||
sib_order = {
|
||||
'left': target_node.sib_order,
|
||||
'right': target_node.sib_order + 1,
|
||||
'first-sibling': 1
|
||||
}[pos]
|
||||
try:
|
||||
min = siblings.order_by('sib_order')[0].sib_order
|
||||
except IndexError:
|
||||
min = 0
|
||||
if min:
|
||||
cls._make_hole_in_db(min, target_node)
|
||||
return sib_order
|
||||
|
||||
@classmethod
|
||||
def _get_new_sibling_order(cls, pos, target_node):
|
||||
if cls._is_target_pos_the_last_sibling(pos, target_node):
|
||||
sib_order = target_node.get_last_sibling().sib_order + 1
|
||||
else:
|
||||
sib_order = cls._make_hole_and_get_sibling_order(pos, target_node)
|
||||
return sib_order
|
||||
|
||||
def move(self, target, pos=None):
|
||||
"""
|
||||
Moves the current node and all it's descendants to a new position
|
||||
relative to another node.
|
||||
"""
|
||||
|
||||
pos = self._prepare_pos_var_for_move(pos)
|
||||
|
||||
sib_order = None
|
||||
parent = None
|
||||
|
||||
if pos in ('first-child', 'last-child', 'sorted-child'):
|
||||
# moving to a child
|
||||
if not target.is_leaf():
|
||||
target = target.get_last_child()
|
||||
pos = {'first-child': 'first-sibling',
|
||||
'last-child': 'last-sibling',
|
||||
'sorted-child': 'sorted-sibling'}[pos]
|
||||
else:
|
||||
parent = target
|
||||
if pos == 'sorted-child':
|
||||
pos = 'sorted-sibling'
|
||||
else:
|
||||
pos = 'first-sibling'
|
||||
sib_order = 1
|
||||
|
||||
if target.is_descendant_of(self):
|
||||
raise InvalidMoveToDescendant(
|
||||
_("Can't move node to a descendant."))
|
||||
|
||||
if self == target and (
|
||||
(pos == 'left') or
|
||||
(pos in ('right', 'last-sibling') and
|
||||
target == target.get_last_sibling()) or
|
||||
(pos == 'first-sibling' and
|
||||
target == target.get_first_sibling())):
|
||||
# special cases, not actually moving the node so no need to UPDATE
|
||||
return
|
||||
|
||||
if pos == 'sorted-sibling':
|
||||
if parent:
|
||||
self.parent = parent
|
||||
else:
|
||||
self.parent = target.parent
|
||||
else:
|
||||
if sib_order:
|
||||
self.sib_order = sib_order
|
||||
else:
|
||||
self.sib_order = self.__class__._get_new_sibling_order(pos,
|
||||
target)
|
||||
if parent:
|
||||
self.parent = parent
|
||||
else:
|
||||
self.parent = target.parent
|
||||
|
||||
self.save()
|
||||
|
||||
class Meta:
|
||||
"""Abstract model."""
|
||||
abstract = True
|
||||
31
env/lib/python3.10/site-packages/treebeard/exceptions.py
vendored
Normal file
31
env/lib/python3.10/site-packages/treebeard/exceptions.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
"""Treebeard exceptions"""
|
||||
|
||||
|
||||
class InvalidPosition(Exception):
|
||||
"""Raised when passing an invalid pos value"""
|
||||
|
||||
|
||||
class InvalidMoveToDescendant(Exception):
|
||||
"""Raised when attempting to move a node to one of it's descendants."""
|
||||
|
||||
|
||||
class NodeAlreadySaved(Exception):
|
||||
"""
|
||||
Raised when attempting to add a node which is already saved to the
|
||||
database.
|
||||
"""
|
||||
|
||||
|
||||
class MissingNodeOrderBy(Exception):
|
||||
"""
|
||||
Raised when an operation needs a missing
|
||||
:attr:`~treebeard.MP_Node.node_order_by` attribute
|
||||
"""
|
||||
|
||||
|
||||
class PathOverflow(Exception):
|
||||
"""
|
||||
Raised when trying to add or move a node to a position where no more nodes
|
||||
can be added (see :attr:`~treebeard.MP_Node.path` and
|
||||
:attr:`~treebeard.MP_Node.alphabet` for more info)
|
||||
"""
|
||||
226
env/lib/python3.10/site-packages/treebeard/forms.py
vendored
Normal file
226
env/lib/python3.10/site-packages/treebeard/forms.py
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
"""Forms for treebeard."""
|
||||
|
||||
from django import forms
|
||||
from django.db.models.query import QuerySet
|
||||
from django.forms.models import ErrorList
|
||||
from django.forms.models import modelform_factory as django_modelform_factory
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from treebeard.al_tree import AL_Node
|
||||
from treebeard.mp_tree import MP_Node
|
||||
from treebeard.ns_tree import NS_Node
|
||||
|
||||
|
||||
class MoveNodeForm(forms.ModelForm):
|
||||
"""
|
||||
Form to handle moving a node in a tree.
|
||||
|
||||
Handles sorted/unsorted trees.
|
||||
|
||||
It adds two fields to the form:
|
||||
|
||||
- Relative to: The target node where the current node will
|
||||
be moved to.
|
||||
- Position: The position relative to the target node that
|
||||
will be used to move the node. These can be:
|
||||
|
||||
- For sorted trees: ``Child of`` and ``Sibling of``
|
||||
- For unsorted trees: ``First child of``, ``Before`` and
|
||||
``After``
|
||||
|
||||
.. warning::
|
||||
|
||||
Subclassing :py:class:`MoveNodeForm` directly is
|
||||
discouraged, since special care is needed to handle
|
||||
excluded fields, and these change depending on the
|
||||
tree type.
|
||||
|
||||
It is recommended that the :py:func:`movenodeform_factory`
|
||||
function is used instead.
|
||||
|
||||
"""
|
||||
|
||||
__position_choices_sorted = (
|
||||
('sorted-child', _('Child of')),
|
||||
('sorted-sibling', _('Sibling of')),
|
||||
)
|
||||
|
||||
__position_choices_unsorted = (
|
||||
('first-child', _('First child of')),
|
||||
('left', _('Before')),
|
||||
('right', _('After')),
|
||||
)
|
||||
|
||||
_position = forms.ChoiceField(required=True, label=_("Position"))
|
||||
|
||||
_ref_node_id = forms.ChoiceField(required=False, label=_("Relative to"))
|
||||
|
||||
def _get_position_ref_node(self, instance):
|
||||
if self.is_sorted:
|
||||
position = 'sorted-child'
|
||||
node_parent = instance.get_parent()
|
||||
if node_parent:
|
||||
ref_node_id = node_parent.pk
|
||||
else:
|
||||
ref_node_id = ''
|
||||
else:
|
||||
prev_sibling = instance.get_prev_sibling()
|
||||
if prev_sibling:
|
||||
position = 'right'
|
||||
ref_node_id = prev_sibling.pk
|
||||
else:
|
||||
position = 'first-child'
|
||||
if instance.is_root():
|
||||
ref_node_id = ''
|
||||
else:
|
||||
ref_node_id = instance.get_parent().pk
|
||||
return {'_ref_node_id': ref_node_id,
|
||||
'_position': position}
|
||||
|
||||
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
|
||||
initial=None, error_class=ErrorList, label_suffix=':',
|
||||
empty_permitted=False, instance=None, **kwargs):
|
||||
opts = self._meta
|
||||
if opts.model is None:
|
||||
raise ValueError('ModelForm has no model class specified')
|
||||
|
||||
# update the '_position' field choices
|
||||
self.is_sorted = getattr(opts.model, 'node_order_by', False)
|
||||
if self.is_sorted:
|
||||
choices_sort_mode = self.__class__.__position_choices_sorted
|
||||
else:
|
||||
choices_sort_mode = self.__class__.__position_choices_unsorted
|
||||
self.declared_fields['_position'].choices = choices_sort_mode
|
||||
|
||||
# update the '_ref_node_id' choices
|
||||
choices = self.mk_dropdown_tree(opts.model, for_node=instance)
|
||||
self.declared_fields['_ref_node_id'].choices = choices
|
||||
# use the formfield `to_python` method to coerse the field for custom ids
|
||||
pkFormField = opts.model._meta.pk.formfield()
|
||||
self.declared_fields['_ref_node_id'].coerce = pkFormField.to_python if pkFormField else int
|
||||
|
||||
# put initial data for these fields into a map, update the map with
|
||||
# initial data, and pass this new map to the parent constructor as
|
||||
# initial data
|
||||
if instance is None:
|
||||
initial_ = {}
|
||||
else:
|
||||
initial_ = self._get_position_ref_node(instance)
|
||||
|
||||
if initial is not None:
|
||||
initial_.update(initial)
|
||||
|
||||
super().__init__(
|
||||
data=data, files=files, auto_id=auto_id, prefix=prefix,
|
||||
initial=initial_, error_class=error_class,
|
||||
label_suffix=label_suffix, empty_permitted=empty_permitted,
|
||||
instance=instance, **kwargs)
|
||||
|
||||
def _clean_cleaned_data(self):
|
||||
""" delete auxilary fields not belonging to node model """
|
||||
reference_node_id = None
|
||||
|
||||
if '_ref_node_id' in self.cleaned_data:
|
||||
if self.cleaned_data['_ref_node_id'] != '0':
|
||||
reference_node_id = self.cleaned_data['_ref_node_id']
|
||||
if reference_node_id.isdigit():
|
||||
reference_node_id = int(reference_node_id)
|
||||
del self.cleaned_data['_ref_node_id']
|
||||
|
||||
position_type = self.cleaned_data['_position']
|
||||
del self.cleaned_data['_position']
|
||||
|
||||
return position_type, reference_node_id
|
||||
|
||||
def save(self, commit=True):
|
||||
position_type, reference_node_id = self._clean_cleaned_data()
|
||||
|
||||
if self.instance._state.adding:
|
||||
if reference_node_id:
|
||||
reference_node = self._meta.model.objects.get(
|
||||
pk=reference_node_id)
|
||||
self.instance = reference_node.add_child(instance=self.instance)
|
||||
self.instance.move(reference_node, pos=position_type)
|
||||
else:
|
||||
self.instance = self._meta.model.add_root(instance=self.instance)
|
||||
else:
|
||||
self.instance.save()
|
||||
if reference_node_id:
|
||||
reference_node = self._meta.model.objects.get(
|
||||
pk=reference_node_id)
|
||||
self.instance.move(reference_node, pos=position_type)
|
||||
else:
|
||||
if self.is_sorted:
|
||||
pos = 'sorted-sibling'
|
||||
else:
|
||||
pos = 'first-sibling'
|
||||
self.instance.move(self._meta.model.get_first_root_node(), pos)
|
||||
# Reload the instance
|
||||
self.instance.refresh_from_db()
|
||||
super().save(commit=commit)
|
||||
return self.instance
|
||||
|
||||
@staticmethod
|
||||
def is_loop_safe(for_node, possible_parent):
|
||||
if for_node is not None:
|
||||
return not (
|
||||
possible_parent == for_node
|
||||
) or (possible_parent.is_descendant_of(for_node))
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def mk_indent(level):
|
||||
return ' ' * (level - 1)
|
||||
|
||||
@classmethod
|
||||
def add_subtree(cls, for_node, node, options):
|
||||
""" Recursively build options tree. """
|
||||
if cls.is_loop_safe(for_node, node):
|
||||
for item, _ in node.get_annotated_list(node):
|
||||
options.append((item.pk, mark_safe(cls.mk_indent(item.get_depth()) + escape(item))))
|
||||
|
||||
@classmethod
|
||||
def mk_dropdown_tree(cls, model, for_node=None):
|
||||
""" Creates a tree-like list of choices """
|
||||
|
||||
options = [(None, _('-- root --'))]
|
||||
for node in model.get_root_nodes():
|
||||
cls.add_subtree(for_node, node, options)
|
||||
return options
|
||||
|
||||
|
||||
def movenodeform_factory(model, form=MoveNodeForm, fields=None, exclude=None,
|
||||
formfield_callback=None, widgets=None):
|
||||
"""Dynamically build a MoveNodeForm subclass with the proper Meta.
|
||||
|
||||
:param Node model:
|
||||
|
||||
The subclass of :py:class:`Node` that will be handled
|
||||
by the form.
|
||||
|
||||
:param form:
|
||||
|
||||
The form class that will be used as a base. By
|
||||
default, :py:class:`MoveNodeForm` will be used.
|
||||
|
||||
:return: A :py:class:`MoveNodeForm` subclass
|
||||
"""
|
||||
_exclude = _get_exclude_for_model(model, exclude)
|
||||
return django_modelform_factory(
|
||||
model, form, fields, _exclude, formfield_callback, widgets)
|
||||
|
||||
|
||||
def _get_exclude_for_model(model, exclude):
|
||||
if exclude:
|
||||
_exclude = tuple(exclude)
|
||||
else:
|
||||
_exclude = ()
|
||||
if issubclass(model, AL_Node):
|
||||
_exclude += ('sib_order', 'parent')
|
||||
elif issubclass(model, MP_Node):
|
||||
_exclude += ('depth', 'numchild', 'path')
|
||||
elif issubclass(model, NS_Node):
|
||||
_exclude += ('depth', 'lft', 'rgt', 'tree_id')
|
||||
return _exclude
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/de/LC_MESSAGES/django.mo
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/locale/de/LC_MESSAGES/django.mo
vendored
Normal file
Binary file not shown.
86
env/lib/python3.10/site-packages/treebeard/locale/de/LC_MESSAGES/django.po
vendored
Normal file
86
env/lib/python3.10/site-packages/treebeard/locale/de/LC_MESSAGES/django.po
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 20:56+0000\n"
|
||||
"PO-Revision-Date: 2018-06-15 23:09+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
|
||||
#: treebeard/admin.py:106
|
||||
#, python-format
|
||||
msgid "Exception raised while moving node: %s"
|
||||
msgstr "Ausnahmefehler in folgendem Element: %s"
|
||||
|
||||
#: treebeard/admin.py:110
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as child of \"%(other)s\""
|
||||
msgstr "Element \"%(node)s\" positioniert unterhalb von \"%(other)s\""
|
||||
|
||||
#: treebeard/admin.py:112
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as sibling of \"%(other)s\""
|
||||
msgstr "Element \"%(node)s\" positioniert gleichauf mit \"%(other)s\""
|
||||
|
||||
#: treebeard/al_tree.py:373 treebeard/mp_tree.py:474 treebeard/ns_tree.py:363
|
||||
msgid "Can't move node to a descendant."
|
||||
msgstr "Kann Element nicht in ein eigenes Unter-Element verschieben"
|
||||
|
||||
#: treebeard/forms.py:46
|
||||
msgid "Child of"
|
||||
msgstr "als Unterkategorie von"
|
||||
|
||||
#: treebeard/forms.py:47
|
||||
msgid "Sibling of"
|
||||
msgstr "auf gleicher Ebene wie"
|
||||
|
||||
#: treebeard/forms.py:51
|
||||
msgid "First child of"
|
||||
msgstr "Als erste Unterkategorie von"
|
||||
|
||||
#: treebeard/forms.py:52
|
||||
msgid "Before"
|
||||
msgstr "Vor"
|
||||
|
||||
#: treebeard/forms.py:53
|
||||
msgid "After"
|
||||
msgstr "Nach"
|
||||
|
||||
#: treebeard/forms.py:56
|
||||
msgid "Position"
|
||||
msgstr "Position"
|
||||
|
||||
#: treebeard/forms.py:60
|
||||
msgid "Relative to"
|
||||
msgstr "relativ zu"
|
||||
|
||||
#: treebeard/forms.py:189
|
||||
msgid "-- root --"
|
||||
msgstr "-- Basiskategorie --"
|
||||
|
||||
#: treebeard/mp_tree.py:382
|
||||
msgid ""
|
||||
"The new node is too deep in the tree, try increasing the path.max_length "
|
||||
"property and UPDATE your database"
|
||||
msgstr ""
|
||||
"Das neue Element ist zu tief positioniert. Versuche path.max_length zu "
|
||||
"erhöhen und aktualisiere die Datenbank"
|
||||
|
||||
#: treebeard/mp_tree.py:1114
|
||||
#, python-format
|
||||
msgid "Path Overflow from: '%s'"
|
||||
msgstr "Pfad Überlauf von: '%s'"
|
||||
|
||||
#: treebeard/templatetags/admin_tree.py:249
|
||||
msgid "Return to ordered tree"
|
||||
msgstr "Zurück zur geordneten Baumansicht"
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/de/LC_MESSAGES/djangojs.mo
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/locale/de/LC_MESSAGES/djangojs.mo
vendored
Normal file
Binary file not shown.
30
env/lib/python3.10/site-packages/treebeard/locale/de/LC_MESSAGES/djangojs.po
vendored
Normal file
30
env/lib/python3.10/site-packages/treebeard/locale/de/LC_MESSAGES/djangojs.po
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 20:56+0000\n"
|
||||
"PO-Revision-Date: 2018-06-15 23:10+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
|
||||
#: treebeard/static/treebeard/treebeard-admin.js:158
|
||||
msgid "Abort"
|
||||
msgstr "Abbruch"
|
||||
|
||||
#: treebeard/static/treebeard/treebeard-admin.js:180
|
||||
msgid "As Sibling"
|
||||
msgstr "Als Geschwister-Element"
|
||||
|
||||
#: treebeard/static/treebeard/treebeard-admin.js:198
|
||||
msgid "As child"
|
||||
msgstr "Als Kind-Element"
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/es/LC_MESSAGES/django.mo
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/locale/es/LC_MESSAGES/django.mo
vendored
Normal file
Binary file not shown.
78
env/lib/python3.10/site-packages/treebeard/locale/es/LC_MESSAGES/django.po
vendored
Normal file
78
env/lib/python3.10/site-packages/treebeard/locale/es/LC_MESSAGES/django.po
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-07-18 17:36+0200\n"
|
||||
"PO-Revision-Date: 2010-05-03 23:40-0500\n"
|
||||
"Last-Translator: Gustavo Picon <tabo@tabo.pe>\n"
|
||||
"Language-Team: Spanish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: admin.py:113
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as child of \"%(other)s\""
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:119
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as sibling of \"%(other)s\""
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:129
|
||||
#, python-format
|
||||
msgid "Exception raised while moving node: %s"
|
||||
msgstr ""
|
||||
|
||||
#: al_tree.py:319 mp_tree.py:641 ns_tree.py:308
|
||||
msgid "Can't move node to a descendant."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:17
|
||||
msgid "Child of"
|
||||
msgstr "Hijo de"
|
||||
|
||||
#: forms.py:18
|
||||
msgid "Sibling of"
|
||||
msgstr "Hermano de"
|
||||
|
||||
#: forms.py:22
|
||||
msgid "First child of"
|
||||
msgstr "Primer hijo de"
|
||||
|
||||
#: forms.py:23
|
||||
msgid "Before"
|
||||
msgstr "Antes"
|
||||
|
||||
#: forms.py:24
|
||||
msgid "After"
|
||||
msgstr "Después"
|
||||
|
||||
#: forms.py:27
|
||||
msgid "Position"
|
||||
msgstr "Posición"
|
||||
|
||||
#: forms.py:31
|
||||
msgid "Relative to"
|
||||
msgstr "Relativo a"
|
||||
|
||||
#: forms.py:81
|
||||
msgid "-- root --"
|
||||
msgstr "-- raíz --"
|
||||
|
||||
#: mp_tree.py:521
|
||||
msgid ""
|
||||
"The new node is too deep in the tree, try increasing the path.max_length "
|
||||
"property and UPDATE your database"
|
||||
msgstr ""
|
||||
|
||||
#: mp_tree.py:702
|
||||
#, python-format
|
||||
msgid "Path Overflow from: '%s'"
|
||||
msgstr ""
|
||||
|
||||
#: templatetags/admin_tree.py:148
|
||||
msgid "Return to ordered tree"
|
||||
msgstr ""
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/es/LC_MESSAGES/djangojs.mo
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/locale/es/LC_MESSAGES/djangojs.mo
vendored
Normal file
Binary file not shown.
24
env/lib/python3.10/site-packages/treebeard/locale/es/LC_MESSAGES/djangojs.po
vendored
Normal file
24
env/lib/python3.10/site-packages/treebeard/locale/es/LC_MESSAGES/djangojs.po
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-07-18 14:12+0200\n"
|
||||
"PO-Revision-Date: 2011-07-18 14:12+0200\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:157
|
||||
msgid "Abort"
|
||||
msgstr ""
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:172
|
||||
msgid "As Sibling"
|
||||
msgstr ""
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:190
|
||||
msgid "As child"
|
||||
msgstr ""
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/fr/LC_MESSAGES/django.mo
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/locale/fr/LC_MESSAGES/django.mo
vendored
Normal file
Binary file not shown.
86
env/lib/python3.10/site-packages/treebeard/locale/fr/LC_MESSAGES/django.po
vendored
Normal file
86
env/lib/python3.10/site-packages/treebeard/locale/fr/LC_MESSAGES/django.po
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
# treebeard translation in french.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the treebeard package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 20:56+0000\n"
|
||||
"PO-Revision-Date: 2018-06-15 23:09+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
|
||||
#: treebeard/admin.py:106
|
||||
#, python-format
|
||||
msgid "Exception raised while moving node: %s"
|
||||
msgstr "Une expetion est survenue pendant le placement de l'élément: %s"
|
||||
|
||||
#: treebeard/admin.py:110
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as child of \"%(other)s\""
|
||||
msgstr "Élément \"%(node)s\" déplacé en temps qu'enfant de \"%(other)s\""
|
||||
|
||||
#: treebeard/admin.py:112
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as sibling of \"%(other)s\""
|
||||
msgstr "Élément \"%(node)s\" déplacé au même niveau que \"%(other)s\""
|
||||
|
||||
#: treebeard/al_tree.py:373 treebeard/mp_tree.py:474 treebeard/ns_tree.py:363
|
||||
msgid "Can't move node to a descendant."
|
||||
msgstr "L'élément ne peux être déplacé vers un de ces décendant."
|
||||
|
||||
#: treebeard/forms.py:46
|
||||
msgid "Child of"
|
||||
msgstr "Enfant de"
|
||||
|
||||
#: treebeard/forms.py:47
|
||||
msgid "Sibling of"
|
||||
msgstr "Au même niveau que"
|
||||
|
||||
#: treebeard/forms.py:51
|
||||
msgid "First child of"
|
||||
msgstr "Premier enfant de"
|
||||
|
||||
#: treebeard/forms.py:52
|
||||
msgid "Before"
|
||||
msgstr "Avant"
|
||||
|
||||
#: treebeard/forms.py:53
|
||||
msgid "After"
|
||||
msgstr "Après"
|
||||
|
||||
#: treebeard/forms.py:56
|
||||
msgid "Position"
|
||||
msgstr "Position"
|
||||
|
||||
#: treebeard/forms.py:60
|
||||
msgid "Relative to"
|
||||
msgstr "Relative à"
|
||||
|
||||
#: treebeard/forms.py:189
|
||||
msgid "-- root --"
|
||||
msgstr "-- racine --"
|
||||
|
||||
#: treebeard/mp_tree.py:382
|
||||
msgid ""
|
||||
"The new node is too deep in the tree, try increasing the path.max_length "
|
||||
"property and UPDATE your database"
|
||||
msgstr ""
|
||||
"L'élément est trop profond dans l'arbre, essayez d'augmenter la propriété path.max_length "
|
||||
"et de mêtre à jour vore base de donnée"
|
||||
|
||||
#: treebeard/mp_tree.py:1114
|
||||
#, python-format
|
||||
msgid "Path Overflow from: '%s'"
|
||||
msgstr "Chemin trop long de: '%s'"
|
||||
|
||||
#: treebeard/templatetags/admin_tree.py:249
|
||||
msgid "Return to ordered tree"
|
||||
msgstr "Retour à l'arbre trié"
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/fr/LC_MESSAGES/djangojs.mo
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/locale/fr/LC_MESSAGES/djangojs.mo
vendored
Normal file
Binary file not shown.
30
env/lib/python3.10/site-packages/treebeard/locale/fr/LC_MESSAGES/djangojs.po
vendored
Normal file
30
env/lib/python3.10/site-packages/treebeard/locale/fr/LC_MESSAGES/djangojs.po
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 20:56+0000\n"
|
||||
"PO-Revision-Date: 2018-06-15 23:10+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
|
||||
#: treebeard/static/treebeard/treebeard-admin.js:158
|
||||
msgid "Abort"
|
||||
msgstr "Interrompre"
|
||||
|
||||
#: treebeard/static/treebeard/treebeard-admin.js:180
|
||||
msgid "As Sibling"
|
||||
msgstr "Au même niveau"
|
||||
|
||||
#: treebeard/static/treebeard/treebeard-admin.js:198
|
||||
msgid "As child"
|
||||
msgstr "En tant qu'enfant"
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/hu/LC_MESSAGES/django.mo
vendored
Executable file
BIN
env/lib/python3.10/site-packages/treebeard/locale/hu/LC_MESSAGES/django.mo
vendored
Executable file
Binary file not shown.
86
env/lib/python3.10/site-packages/treebeard/locale/hu/LC_MESSAGES/django.po
vendored
Executable file
86
env/lib/python3.10/site-packages/treebeard/locale/hu/LC_MESSAGES/django.po
vendored
Executable file
@@ -0,0 +1,86 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 20:56+0000\n"
|
||||
"PO-Revision-Date: 2018-06-15 23:09+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
|
||||
#: treebeard/admin.py:106
|
||||
#, python-format
|
||||
msgid "Exception raised while moving node: %s"
|
||||
msgstr "Hiba lépett fel a csomópont mozgatása közben: %s"
|
||||
|
||||
#: treebeard/admin.py:110
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as child of \"%(other)s\""
|
||||
msgstr "A(z) \"%(node)s\" csomópont a(z) \"%(other)s\" csomópont alá került."
|
||||
|
||||
#: treebeard/admin.py:112
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as sibling of \"%(other)s\""
|
||||
msgstr "A(z) \"%(node)s\" csomópont a(z) \"%(other)s\" testvére lett."
|
||||
|
||||
#: treebeard/al_tree.py:373 treebeard/mp_tree.py:474 treebeard/ns_tree.py:363
|
||||
msgid "Can't move node to a descendant."
|
||||
msgstr "Nem lehet egy csomópontot egy leszármazottja alá mozgatni."
|
||||
|
||||
#: treebeard/forms.py:46
|
||||
msgid "Child of"
|
||||
msgstr "Gyermeke ennek"
|
||||
|
||||
#: treebeard/forms.py:47
|
||||
msgid "Sibling of"
|
||||
msgstr "Testvére ennek"
|
||||
|
||||
#: treebeard/forms.py:51
|
||||
msgid "First child of"
|
||||
msgstr "Első gyermeke ennek"
|
||||
|
||||
#: treebeard/forms.py:52
|
||||
msgid "Before"
|
||||
msgstr "Előtte"
|
||||
|
||||
#: treebeard/forms.py:53
|
||||
msgid "After"
|
||||
msgstr "Utána"
|
||||
|
||||
#: treebeard/forms.py:56
|
||||
msgid "Position"
|
||||
msgstr "Pozíció"
|
||||
|
||||
#: treebeard/forms.py:60
|
||||
msgid "Relative to"
|
||||
msgstr "Relatív ehhez"
|
||||
|
||||
#: treebeard/forms.py:189
|
||||
msgid "-- root --"
|
||||
msgstr "-- Gyökérkategória --"
|
||||
|
||||
#: treebeard/mp_tree.py:382
|
||||
msgid ""
|
||||
"The new node is too deep in the tree, try increasing the path.max_length "
|
||||
"property and UPDATE your database"
|
||||
msgstr ""
|
||||
"Az új csomópont túl mélyen van a fában, próbáljuk meg megnövelni a "
|
||||
"path.max_length értékét és frissítsük az adatbázist."
|
||||
|
||||
#: treebeard/mp_tree.py:1114
|
||||
#, python-format
|
||||
msgid "Path Overflow from: '%s'"
|
||||
msgstr "Útvonal túlfolyás innen: '%s'"
|
||||
|
||||
#: treebeard/templatetags/admin_tree.py:249
|
||||
msgid "Return to ordered tree"
|
||||
msgstr "Vissza a rendezett fához"
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/hu/LC_MESSAGES/djangojs.mo
vendored
Executable file
BIN
env/lib/python3.10/site-packages/treebeard/locale/hu/LC_MESSAGES/djangojs.mo
vendored
Executable file
Binary file not shown.
30
env/lib/python3.10/site-packages/treebeard/locale/hu/LC_MESSAGES/djangojs.po
vendored
Executable file
30
env/lib/python3.10/site-packages/treebeard/locale/hu/LC_MESSAGES/djangojs.po
vendored
Executable file
@@ -0,0 +1,30 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 20:56+0000\n"
|
||||
"PO-Revision-Date: 2018-06-15 23:10+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
|
||||
#: treebeard/static/treebeard/treebeard-admin.js:158
|
||||
msgid "Abort"
|
||||
msgstr "Megszakít"
|
||||
|
||||
#: treebeard/static/treebeard/treebeard-admin.js:180
|
||||
msgid "As Sibling"
|
||||
msgstr "Testvérként"
|
||||
|
||||
#: treebeard/static/treebeard/treebeard-admin.js:198
|
||||
msgid "As child"
|
||||
msgstr "Gyermekként"
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/nl/LC_MESSAGES/django.mo
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/locale/nl/LC_MESSAGES/django.mo
vendored
Normal file
Binary file not shown.
80
env/lib/python3.10/site-packages/treebeard/locale/nl/LC_MESSAGES/django.po
vendored
Normal file
80
env/lib/python3.10/site-packages/treebeard/locale/nl/LC_MESSAGES/django.po
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-07-18 17:36+0200\n"
|
||||
"PO-Revision-Date: 2011-07-18 14:11+0200\n"
|
||||
"Last-Translator: Jaap Roes <jaap@eight.nl>\n"
|
||||
"Language-Team: Dutch\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: admin.py:113
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as child of \"%(other)s\""
|
||||
msgstr "\"%(node)s\" is nu onderdeel van \"%(other)s\""
|
||||
|
||||
#: admin.py:119
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as sibling of \"%(other)s\""
|
||||
msgstr "\"%(node)s\" staat nu voor \"%(other)s\""
|
||||
|
||||
#: admin.py:129
|
||||
#, python-format
|
||||
msgid "Exception raised while moving node: %s"
|
||||
msgstr "Fatale fout tijdens het verplaatsen: %s"
|
||||
|
||||
#: al_tree.py:319 mp_tree.py:641 ns_tree.py:308
|
||||
msgid "Can't move node to a descendant."
|
||||
msgstr "Kan node niet naar eigen subnode verplaatsen"
|
||||
|
||||
#: forms.py:17
|
||||
msgid "Child of"
|
||||
msgstr "Onderdeel"
|
||||
|
||||
#: forms.py:18
|
||||
msgid "Sibling of"
|
||||
msgstr "Naast"
|
||||
|
||||
#: forms.py:22
|
||||
msgid "First child of"
|
||||
msgstr "1e onderdeel"
|
||||
|
||||
#: forms.py:23
|
||||
msgid "Before"
|
||||
msgstr "Voor"
|
||||
|
||||
#: forms.py:24
|
||||
msgid "After"
|
||||
msgstr "Na"
|
||||
|
||||
#: forms.py:27
|
||||
msgid "Position"
|
||||
msgstr "Positie"
|
||||
|
||||
#: forms.py:31
|
||||
msgid "Relative to"
|
||||
msgstr "Ten opzichte van"
|
||||
|
||||
#: forms.py:81
|
||||
msgid "-- root --"
|
||||
msgstr "-- hoofdniveau --"
|
||||
|
||||
#: mp_tree.py:521
|
||||
msgid ""
|
||||
"The new node is too deep in the tree, try increasing the path.max_length "
|
||||
"property and UPDATE your database"
|
||||
msgstr ""
|
||||
"De nieuwe node bevindt zich te diep in de boom. Verhoog de path.max_lenght "
|
||||
"waarde en UPDATE de database."
|
||||
|
||||
#: mp_tree.py:702
|
||||
#, python-format
|
||||
msgid "Path Overflow from: '%s'"
|
||||
msgstr "Path overflow van: '%s'"
|
||||
|
||||
#: templatetags/admin_tree.py:148
|
||||
msgid "Return to ordered tree"
|
||||
msgstr "Als gesorteerde boom"
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/nl/LC_MESSAGES/djangojs.mo
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/locale/nl/LC_MESSAGES/djangojs.mo
vendored
Normal file
Binary file not shown.
24
env/lib/python3.10/site-packages/treebeard/locale/nl/LC_MESSAGES/djangojs.po
vendored
Normal file
24
env/lib/python3.10/site-packages/treebeard/locale/nl/LC_MESSAGES/djangojs.po
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-07-18 14:12+0200\n"
|
||||
"PO-Revision-Date: 2011-07-18 14:12+0200\n"
|
||||
"Last-Translator: Jaap Roes <jaap@eight.nl>\n"
|
||||
"Language-Team: Dutch\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:157
|
||||
msgid "Abort"
|
||||
msgstr "Annuleren"
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:172
|
||||
msgid "As Sibling"
|
||||
msgstr "Als naastliggend onderdeel"
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:190
|
||||
msgid "As child"
|
||||
msgstr "Als subonderdeel"
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/pl/LC_MESSAGES/django.mo
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/locale/pl/LC_MESSAGES/django.mo
vendored
Normal file
Binary file not shown.
44
env/lib/python3.10/site-packages/treebeard/locale/pl/LC_MESSAGES/django.po
vendored
Normal file
44
env/lib/python3.10/site-packages/treebeard/locale/pl/LC_MESSAGES/django.po
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-05-03 23:53-0500\n"
|
||||
"PO-Revision-Date: 2010-05-03 23:40-0500\n"
|
||||
"Last-Translator: Bartosz Turkot <bartosz.turkot@blueservices.pl>\n"
|
||||
"Language-Team: Polish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: forms.py:16
|
||||
msgid "Child of"
|
||||
msgstr "Dziecko kategorii"
|
||||
|
||||
#: forms.py:17
|
||||
msgid "Sibling of"
|
||||
msgstr "Sąsiad kategorii"
|
||||
|
||||
#: forms.py:21
|
||||
msgid "First child of"
|
||||
msgstr "Pierwsze dziecko kategorii"
|
||||
|
||||
#: forms.py:22
|
||||
msgid "Before"
|
||||
msgstr "Przed"
|
||||
|
||||
#: forms.py:23
|
||||
msgid "After"
|
||||
msgstr "Za"
|
||||
|
||||
#: forms.py:26
|
||||
msgid "Position"
|
||||
msgstr "Pozycja"
|
||||
|
||||
#: forms.py:30
|
||||
msgid "Relative to"
|
||||
msgstr "Względem"
|
||||
|
||||
#: forms.py:80
|
||||
msgid "-- root --"
|
||||
msgstr "-- kategoria główna --"
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/ru/LC_MESSAGES/django.mo
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/locale/ru/LC_MESSAGES/django.mo
vendored
Normal file
Binary file not shown.
79
env/lib/python3.10/site-packages/treebeard/locale/ru/LC_MESSAGES/django.po
vendored
Normal file
79
env/lib/python3.10/site-packages/treebeard/locale/ru/LC_MESSAGES/django.po
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-07-18 17:36+0200\n"
|
||||
"PO-Revision-Date: 2009-04-10 18:37+0400\n"
|
||||
"Last-Translator: chembervint <chembervint@gmail.com>\n"
|
||||
"Language-Team: Russian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%"
|
||||
"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
|
||||
#: admin.py:113
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as child of \"%(other)s\""
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:119
|
||||
#, python-format
|
||||
msgid "Moved node \"%(node)s\" as sibling of \"%(other)s\""
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:129
|
||||
#, python-format
|
||||
msgid "Exception raised while moving node: %s"
|
||||
msgstr ""
|
||||
|
||||
#: al_tree.py:319 mp_tree.py:641 ns_tree.py:308
|
||||
msgid "Can't move node to a descendant."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:17
|
||||
msgid "Child of"
|
||||
msgstr "Вложенный"
|
||||
|
||||
#: forms.py:18
|
||||
msgid "Sibling of"
|
||||
msgstr "Соседний к"
|
||||
|
||||
#: forms.py:22
|
||||
msgid "First child of"
|
||||
msgstr "Первый вложенный"
|
||||
|
||||
#: forms.py:23
|
||||
msgid "Before"
|
||||
msgstr "До"
|
||||
|
||||
#: forms.py:24
|
||||
msgid "After"
|
||||
msgstr "После"
|
||||
|
||||
#: forms.py:27
|
||||
msgid "Position"
|
||||
msgstr "Позиция"
|
||||
|
||||
#: forms.py:31
|
||||
msgid "Relative to"
|
||||
msgstr "Относительно"
|
||||
|
||||
#: forms.py:81
|
||||
msgid "-- root --"
|
||||
msgstr "-- корень --"
|
||||
|
||||
#: mp_tree.py:521
|
||||
msgid ""
|
||||
"The new node is too deep in the tree, try increasing the path.max_length "
|
||||
"property and UPDATE your database"
|
||||
msgstr ""
|
||||
|
||||
#: mp_tree.py:702
|
||||
#, python-format
|
||||
msgid "Path Overflow from: '%s'"
|
||||
msgstr ""
|
||||
|
||||
#: templatetags/admin_tree.py:148
|
||||
msgid "Return to ordered tree"
|
||||
msgstr ""
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/locale/ru/LC_MESSAGES/djangojs.mo
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/locale/ru/LC_MESSAGES/djangojs.mo
vendored
Normal file
Binary file not shown.
25
env/lib/python3.10/site-packages/treebeard/locale/ru/LC_MESSAGES/djangojs.po
vendored
Normal file
25
env/lib/python3.10/site-packages/treebeard/locale/ru/LC_MESSAGES/djangojs.po
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django-treebeard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-07-18 14:12+0200\n"
|
||||
"PO-Revision-Date: 2011-07-18 14:12+0200\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%"
|
||||
"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:157
|
||||
msgid "Abort"
|
||||
msgstr ""
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:172
|
||||
msgid "As Sibling"
|
||||
msgstr ""
|
||||
|
||||
#: static/treebeard/treebeard-admin.js:190
|
||||
msgid "As child"
|
||||
msgstr ""
|
||||
657
env/lib/python3.10/site-packages/treebeard/models.py
vendored
Normal file
657
env/lib/python3.10/site-packages/treebeard/models.py
vendored
Normal file
@@ -0,0 +1,657 @@
|
||||
"""Models and base API"""
|
||||
|
||||
import operator
|
||||
from functools import reduce
|
||||
|
||||
from django.db.models import Q
|
||||
from django.db import models, router, connections
|
||||
|
||||
from treebeard.exceptions import InvalidPosition, MissingNodeOrderBy
|
||||
|
||||
|
||||
class Node(models.Model):
|
||||
"""Node class"""
|
||||
|
||||
_db_connection = None
|
||||
|
||||
@classmethod
|
||||
def add_root(cls, **kwargs): # pragma: no cover
|
||||
"""
|
||||
Adds a root node to the tree. The new root node will be the new
|
||||
rightmost root node. If you want to insert a root node at a specific
|
||||
position, use :meth:`add_sibling` in an already existing root node
|
||||
instead.
|
||||
|
||||
:param `**kwargs`: object creation data that will be passed to the
|
||||
inherited Node model
|
||||
:param instance: Instead of passing object creation data, you can
|
||||
pass an already-constructed (but not yet saved) model instance to
|
||||
be inserted into the tree.
|
||||
|
||||
:returns: the created node object. It will be save()d by this method.
|
||||
|
||||
:raise NodeAlreadySaved: when the passed ``instance`` already exists
|
||||
in the database
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def get_foreign_keys(cls):
|
||||
"""Get foreign keys and models they refer to, so we can pre-process
|
||||
the data for load_bulk
|
||||
"""
|
||||
foreign_keys = {}
|
||||
for field in cls._meta.fields:
|
||||
if (
|
||||
field.get_internal_type() == 'ForeignKey' and
|
||||
field.name != 'parent'
|
||||
):
|
||||
foreign_keys[field.name] = field.remote_field.model
|
||||
return foreign_keys
|
||||
|
||||
@classmethod
|
||||
def _process_foreign_keys(cls, foreign_keys, node_data):
|
||||
"""For each foreign key try to load the actual object so load_bulk
|
||||
doesn't fail trying to load an int where django expects a
|
||||
model instance
|
||||
"""
|
||||
for key in foreign_keys.keys():
|
||||
if key in node_data:
|
||||
node_data[key] = foreign_keys[key].objects.get(
|
||||
pk=node_data[key])
|
||||
|
||||
@classmethod
|
||||
def load_bulk(cls, bulk_data, parent=None, keep_ids=False):
|
||||
"""
|
||||
Loads a list/dictionary structure to the tree.
|
||||
|
||||
|
||||
:param bulk_data:
|
||||
|
||||
The data that will be loaded, the structure is a list of
|
||||
dictionaries with 2 keys:
|
||||
|
||||
- ``data``: will store arguments that will be passed for object
|
||||
creation, and
|
||||
|
||||
- ``children``: a list of dictionaries, each one has it's own
|
||||
``data`` and ``children`` keys (a recursive structure)
|
||||
|
||||
|
||||
:param parent:
|
||||
|
||||
The node that will receive the structure as children, if not
|
||||
specified the first level of the structure will be loaded as root
|
||||
nodes
|
||||
|
||||
|
||||
:param keep_ids:
|
||||
|
||||
If enabled, loads the nodes with the same primary keys that are
|
||||
given in the structure. Will error if there are nodes without
|
||||
primary key info or if the primary keys are already used.
|
||||
|
||||
|
||||
:returns: A list of the added node ids.
|
||||
"""
|
||||
|
||||
# tree, iterative preorder
|
||||
added = []
|
||||
# stack of nodes to analyze
|
||||
stack = [(parent, node) for node in bulk_data[::-1]]
|
||||
foreign_keys = cls.get_foreign_keys()
|
||||
pk_field = cls._meta.pk.attname
|
||||
|
||||
while stack:
|
||||
parent, node_struct = stack.pop()
|
||||
# shallow copy of the data structure so it doesn't persist...
|
||||
node_data = node_struct['data'].copy()
|
||||
cls._process_foreign_keys(foreign_keys, node_data)
|
||||
if keep_ids:
|
||||
node_data[pk_field] = node_struct[pk_field]
|
||||
if parent:
|
||||
node_obj = parent.add_child(**node_data)
|
||||
else:
|
||||
node_obj = cls.add_root(**node_data)
|
||||
added.append(node_obj.pk)
|
||||
if 'children' in node_struct:
|
||||
# extending the stack with the current node as the parent of
|
||||
# the new nodes
|
||||
stack.extend([
|
||||
(node_obj, node)
|
||||
for node in node_struct['children'][::-1]
|
||||
])
|
||||
return added
|
||||
|
||||
@classmethod
|
||||
def dump_bulk(cls, parent=None, keep_ids=True): # pragma: no cover
|
||||
"""
|
||||
Dumps a tree branch to a python data structure.
|
||||
|
||||
:param parent:
|
||||
|
||||
The node whose descendants will be dumped. The node itself will be
|
||||
included in the dump. If not given, the entire tree will be dumped.
|
||||
|
||||
:param keep_ids:
|
||||
|
||||
Stores the pk value (primary key) of every node. Enabled by
|
||||
default.
|
||||
|
||||
:returns: A python data structure, described with detail in
|
||||
:meth:`load_bulk`
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def get_root_nodes(cls): # pragma: no cover
|
||||
""":returns: A queryset containing the root nodes in the tree."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def get_first_root_node(cls):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The first root node in the tree or ``None`` if it is empty.
|
||||
"""
|
||||
try:
|
||||
return cls.get_root_nodes()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_last_root_node(cls):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The last root node in the tree or ``None`` if it is empty.
|
||||
"""
|
||||
try:
|
||||
return cls.get_root_nodes().reverse()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def find_problems(cls): # pragma: no cover
|
||||
"""Checks for problems in the tree structure."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def fix_tree(cls): # pragma: no cover
|
||||
"""
|
||||
Solves problems that can appear when transactions are not used and
|
||||
a piece of code breaks, leaving the tree in an inconsistent state.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def get_tree(cls, parent=None):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
A list of nodes ordered as DFS, including the parent. If
|
||||
no parent is given, the entire tree is returned.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def get_descendants_group_count(cls, parent=None):
|
||||
"""
|
||||
Helper for a very common case: get a group of siblings and the number
|
||||
of *descendants* (not only children) in every sibling.
|
||||
|
||||
:param parent:
|
||||
|
||||
The parent of the siblings to return. If no parent is given, the
|
||||
root nodes will be returned.
|
||||
|
||||
:returns:
|
||||
|
||||
A `list` (**NOT** a Queryset) of node objects with an extra
|
||||
attribute: `descendants_count`.
|
||||
"""
|
||||
if parent is None:
|
||||
qset = cls.get_root_nodes()
|
||||
else:
|
||||
qset = parent.get_children()
|
||||
nodes = list(qset)
|
||||
for node in nodes:
|
||||
node.descendants_count = node.get_descendant_count()
|
||||
return nodes
|
||||
|
||||
def get_depth(self): # pragma: no cover
|
||||
""":returns: the depth (level) of the node"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_siblings(self): # pragma: no cover
|
||||
"""
|
||||
:returns:
|
||||
|
||||
A queryset of all the node's siblings, including the node
|
||||
itself.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_children(self): # pragma: no cover
|
||||
""":returns: A queryset of all the node's children"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_children_count(self):
|
||||
""":returns: The number of the node's children"""
|
||||
return self.get_children().count()
|
||||
|
||||
def get_descendants(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
A queryset of all the node's descendants, doesn't
|
||||
include the node itself (some subclasses may return a list).
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_descendant_count(self):
|
||||
""":returns: the number of descendants of a node."""
|
||||
return self.get_descendants().count()
|
||||
|
||||
def get_first_child(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The leftmost node's child, or None if it has no children.
|
||||
"""
|
||||
try:
|
||||
return self.get_children()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def get_last_child(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The rightmost node's child, or None if it has no children.
|
||||
"""
|
||||
try:
|
||||
return self.get_children().reverse()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def get_first_sibling(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The leftmost node's sibling, can return the node itself if
|
||||
it was the leftmost sibling.
|
||||
"""
|
||||
return self.get_siblings()[0]
|
||||
|
||||
def get_last_sibling(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The rightmost node's sibling, can return the node itself if
|
||||
it was the rightmost sibling.
|
||||
"""
|
||||
return self.get_siblings().reverse()[0]
|
||||
|
||||
def get_prev_sibling(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The previous node's sibling, or None if it was the leftmost
|
||||
sibling.
|
||||
"""
|
||||
siblings = self.get_siblings()
|
||||
ids = [obj.pk for obj in siblings]
|
||||
if self.pk in ids:
|
||||
idx = ids.index(self.pk)
|
||||
if idx > 0:
|
||||
return siblings[idx - 1]
|
||||
|
||||
def get_next_sibling(self):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
The next node's sibling, or None if it was the rightmost
|
||||
sibling.
|
||||
"""
|
||||
siblings = self.get_siblings()
|
||||
ids = [obj.pk for obj in siblings]
|
||||
if self.pk in ids:
|
||||
idx = ids.index(self.pk)
|
||||
if idx < len(siblings) - 1:
|
||||
return siblings[idx + 1]
|
||||
|
||||
def is_sibling_of(self, node):
|
||||
"""
|
||||
:returns: ``True`` if the node is a sibling of another node given as an
|
||||
argument, else, returns ``False``
|
||||
|
||||
:param node:
|
||||
|
||||
The node that will be checked as a sibling
|
||||
"""
|
||||
return self.get_siblings().filter(pk=node.pk).exists()
|
||||
|
||||
def is_child_of(self, node):
|
||||
"""
|
||||
:returns: ``True`` if the node is a child of another node given as an
|
||||
argument, else, returns ``False``
|
||||
|
||||
:param node:
|
||||
|
||||
The node that will be checked as a parent
|
||||
"""
|
||||
return node.get_children().filter(pk=self.pk).exists()
|
||||
|
||||
def is_descendant_of(self, node): # pragma: no cover
|
||||
"""
|
||||
:returns: ``True`` if the node is a descendant of another node given
|
||||
as an argument, else, returns ``False``
|
||||
|
||||
:param node:
|
||||
|
||||
The node that will be checked as an ancestor
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def add_child(self, **kwargs): # pragma: no cover
|
||||
"""
|
||||
Adds a child to the node. The new node will be the new rightmost
|
||||
child. If you want to insert a node at a specific position,
|
||||
use the :meth:`add_sibling` method of an already existing
|
||||
child node instead.
|
||||
|
||||
:param `**kwargs`:
|
||||
|
||||
Object creation data that will be passed to the inherited Node
|
||||
model
|
||||
:param instance: Instead of passing object creation data, you can
|
||||
pass an already-constructed (but not yet saved) model instance to
|
||||
be inserted into the tree.
|
||||
|
||||
:returns: The created node object. It will be save()d by this method.
|
||||
|
||||
:raise NodeAlreadySaved: when the passed ``instance`` already exists
|
||||
in the database
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def add_sibling(self, pos=None, **kwargs): # pragma: no cover
|
||||
"""
|
||||
Adds a new node as a sibling to the current node object.
|
||||
|
||||
|
||||
:param pos:
|
||||
The position, relative to the current node object, where the
|
||||
new node will be inserted, can be one of:
|
||||
|
||||
- ``first-sibling``: the new node will be the new leftmost sibling
|
||||
- ``left``: the new node will take the node's place, which will be
|
||||
moved to the right 1 position
|
||||
- ``right``: the new node will be inserted at the right of the node
|
||||
- ``last-sibling``: the new node will be the new rightmost sibling
|
||||
- ``sorted-sibling``: the new node will be at the right position
|
||||
according to the value of node_order_by
|
||||
|
||||
:param `**kwargs`:
|
||||
|
||||
Object creation data that will be passed to the inherited
|
||||
Node model
|
||||
:param instance: Instead of passing object creation data, you can
|
||||
pass an already-constructed (but not yet saved) model instance to
|
||||
be inserted into the tree.
|
||||
|
||||
:returns:
|
||||
|
||||
The created node object. It will be saved by this method.
|
||||
|
||||
:raise InvalidPosition: when passing an invalid ``pos`` parm
|
||||
:raise InvalidPosition: when :attr:`node_order_by` is enabled and the
|
||||
``pos`` parm wasn't ``sorted-sibling``
|
||||
:raise MissingNodeOrderBy: when passing ``sorted-sibling`` as ``pos``
|
||||
and the :attr:`node_order_by` attribute is missing
|
||||
:raise NodeAlreadySaved: when the passed ``instance`` already exists
|
||||
in the database
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_root(self): # pragma: no cover
|
||||
""":returns: the root node for the current node object."""
|
||||
raise NotImplementedError
|
||||
|
||||
def is_root(self):
|
||||
""":returns: True if the node is a root node (else, returns False)"""
|
||||
return self.get_root().pk == self.pk
|
||||
|
||||
def is_leaf(self):
|
||||
""":returns: True if the node is a leaf node (else, returns False)"""
|
||||
return not self.get_children().exists()
|
||||
|
||||
def get_ancestors(self): # pragma: no cover
|
||||
"""
|
||||
:returns:
|
||||
|
||||
A queryset containing the current node object's ancestors,
|
||||
starting by the root node and descending to the parent.
|
||||
(some subclasses may return a list)
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_parent(self, update=False): # pragma: no cover
|
||||
"""
|
||||
:returns: the parent node of the current node object.
|
||||
Caches the result in the object itself to help in loops.
|
||||
|
||||
:param update: Updates the cached value.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def move(self, target, pos=None): # pragma: no cover
|
||||
"""
|
||||
Moves the current node and all it's descendants to a new position
|
||||
relative to another node.
|
||||
|
||||
:param target:
|
||||
|
||||
The node that will be used as a relative child/sibling when moving
|
||||
|
||||
:param pos:
|
||||
|
||||
The position, relative to the target node, where the
|
||||
current node object will be moved to, can be one of:
|
||||
|
||||
- ``first-child``: the node will be the new leftmost child of the
|
||||
``target`` node
|
||||
- ``last-child``: the node will be the new rightmost child of the
|
||||
``target`` node
|
||||
- ``sorted-child``: the new node will be moved as a child of the
|
||||
``target`` node according to the value of :attr:`node_order_by`
|
||||
- ``first-sibling``: the node will be the new leftmost sibling of
|
||||
the ``target`` node
|
||||
- ``left``: the node will take the ``target`` node's place, which
|
||||
will be moved to the right 1 position
|
||||
- ``right``: the node will be moved to the right of the ``target``
|
||||
node
|
||||
- ``last-sibling``: the node will be the new rightmost sibling of
|
||||
the ``target`` node
|
||||
- ``sorted-sibling``: the new node will be moved as a sibling of
|
||||
the ``target`` node according to the value of
|
||||
:attr:`node_order_by`
|
||||
|
||||
.. note::
|
||||
|
||||
If no ``pos`` is given the library will use ``last-sibling``,
|
||||
or ``sorted-sibling`` if :attr:`node_order_by` is enabled.
|
||||
|
||||
:returns: None
|
||||
|
||||
:raise InvalidPosition: when passing an invalid ``pos`` parm
|
||||
:raise InvalidPosition: when :attr:`node_order_by` is enabled and the
|
||||
``pos`` parm wasn't ``sorted-sibling`` or ``sorted-child``
|
||||
:raise InvalidMoveToDescendant: when trying to move a node to one of
|
||||
it's own descendants
|
||||
:raise PathOverflow: when the library can't make room for the
|
||||
node's new position
|
||||
:raise MissingNodeOrderBy: when passing ``sorted-sibling`` or
|
||||
``sorted-child`` as ``pos`` and the :attr:`node_order_by`
|
||||
attribute is missing
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""Removes a node and all it's descendants."""
|
||||
return self.__class__.objects.filter(pk=self.pk).delete(*args, **kwargs)
|
||||
|
||||
delete.alters_data = True
|
||||
delete.queryset_only = True
|
||||
|
||||
def _prepare_pos_var(self, pos, method_name, valid_pos, valid_sorted_pos):
|
||||
if pos is None:
|
||||
if self.node_order_by:
|
||||
pos = 'sorted-sibling'
|
||||
else:
|
||||
pos = 'last-sibling'
|
||||
if pos not in valid_pos:
|
||||
raise InvalidPosition('Invalid relative position: %s' % (pos, ))
|
||||
if self.node_order_by and pos not in valid_sorted_pos:
|
||||
raise InvalidPosition(
|
||||
'Must use %s in %s when node_order_by is enabled' % (
|
||||
' or '.join(valid_sorted_pos), method_name))
|
||||
if pos in valid_sorted_pos and not self.node_order_by:
|
||||
raise MissingNodeOrderBy('Missing node_order_by attribute.')
|
||||
return pos
|
||||
|
||||
_valid_pos_for_add_sibling = ('first-sibling', 'left', 'right',
|
||||
'last-sibling', 'sorted-sibling')
|
||||
_valid_pos_for_sorted_add_sibling = ('sorted-sibling',)
|
||||
|
||||
def _prepare_pos_var_for_add_sibling(self, pos):
|
||||
return self._prepare_pos_var(
|
||||
pos,
|
||||
'add_sibling',
|
||||
self._valid_pos_for_add_sibling,
|
||||
self._valid_pos_for_sorted_add_sibling)
|
||||
|
||||
_valid_pos_for_move = _valid_pos_for_add_sibling + (
|
||||
'first-child', 'last-child', 'sorted-child')
|
||||
_valid_pos_for_sorted_move = _valid_pos_for_sorted_add_sibling + (
|
||||
'sorted-child',)
|
||||
|
||||
def _prepare_pos_var_for_move(self, pos):
|
||||
return self._prepare_pos_var(
|
||||
pos,
|
||||
'move',
|
||||
self._valid_pos_for_move,
|
||||
self._valid_pos_for_sorted_move)
|
||||
|
||||
def get_sorted_pos_queryset(self, siblings, newobj):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
A queryset of the nodes that must be moved to the right.
|
||||
Called only for Node models with :attr:`node_order_by`
|
||||
|
||||
This function is based on _insertion_target_filters from django-mptt
|
||||
(BSD licensed) by Jonathan Buchanan:
|
||||
https://github.com/django-mptt/django-mptt/blob/0.3.0/mptt/signals.py
|
||||
"""
|
||||
|
||||
fields, filters = [], []
|
||||
for field in self.node_order_by:
|
||||
value = getattr(newobj, field)
|
||||
filters.append(
|
||||
Q(
|
||||
*[Q(**{f: v}) for f, v in fields] +
|
||||
[Q(**{'%s__gt' % field: value})]
|
||||
)
|
||||
)
|
||||
fields.append((field, value))
|
||||
return siblings.filter(reduce(operator.or_, filters))
|
||||
|
||||
@classmethod
|
||||
def get_annotated_list_qs(cls, qs):
|
||||
"""
|
||||
Gets an annotated list from a queryset.
|
||||
"""
|
||||
result, info = [], {}
|
||||
start_depth, prev_depth = (None, None)
|
||||
for node in qs:
|
||||
depth = node.get_depth()
|
||||
if start_depth is None:
|
||||
start_depth = depth
|
||||
open = (depth and (prev_depth is None or depth > prev_depth))
|
||||
if prev_depth is not None and depth < prev_depth:
|
||||
info['close'] = list(range(0, prev_depth - depth))
|
||||
info = {'open': open, 'close': [], 'level': depth - start_depth}
|
||||
result.append((node, info,))
|
||||
prev_depth = depth
|
||||
if start_depth and start_depth > 0:
|
||||
info['close'] = list(range(0, prev_depth - start_depth + 1))
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_annotated_list(cls, parent=None, max_depth=None):
|
||||
"""
|
||||
Gets an annotated list from a tree branch.
|
||||
|
||||
:param parent:
|
||||
|
||||
The node whose descendants will be annotated. The node itself
|
||||
will be included in the list. If not given, the entire tree
|
||||
will be annotated.
|
||||
|
||||
:param max_depth:
|
||||
|
||||
Optionally limit to specified depth
|
||||
"""
|
||||
|
||||
result, info = [], {}
|
||||
start_depth, prev_depth = (None, None)
|
||||
qs = cls.get_tree(parent)
|
||||
if max_depth:
|
||||
qs = qs.filter(depth__lte=max_depth)
|
||||
return cls.get_annotated_list_qs(qs)
|
||||
|
||||
@classmethod
|
||||
def _get_serializable_model(cls):
|
||||
"""
|
||||
Returns a model with a valid _meta.local_fields (serializable).
|
||||
|
||||
Basically, this means the original model, not a proxied model.
|
||||
|
||||
(this is a workaround for a bug in django)
|
||||
"""
|
||||
current_class = cls
|
||||
while current_class._meta.proxy:
|
||||
current_class = current_class._meta.proxy_for_model
|
||||
return current_class
|
||||
|
||||
@classmethod
|
||||
def _get_database_connection(cls, action):
|
||||
return {
|
||||
'read': connections[router.db_for_read(cls)],
|
||||
'write': connections[router.db_for_write(cls)]
|
||||
}[action]
|
||||
|
||||
@classmethod
|
||||
def get_database_vendor(cls, action):
|
||||
"""
|
||||
returns the supported database vendor used by a treebeard model when
|
||||
performing read (select) or write (update, insert, delete) operations.
|
||||
|
||||
:param action:
|
||||
|
||||
`read` or `write`
|
||||
|
||||
:returns: postgresql, mysql or sqlite
|
||||
"""
|
||||
return cls._get_database_connection(action).vendor
|
||||
|
||||
@classmethod
|
||||
def _get_database_cursor(cls, action):
|
||||
return cls._get_database_connection(action).cursor()
|
||||
|
||||
class Meta:
|
||||
"""Abstract model."""
|
||||
abstract = True
|
||||
1221
env/lib/python3.10/site-packages/treebeard/mp_tree.py
vendored
Normal file
1221
env/lib/python3.10/site-packages/treebeard/mp_tree.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
692
env/lib/python3.10/site-packages/treebeard/ns_tree.py
vendored
Normal file
692
env/lib/python3.10/site-packages/treebeard/ns_tree.py
vendored
Normal file
@@ -0,0 +1,692 @@
|
||||
"""Nested Sets"""
|
||||
|
||||
import operator
|
||||
from functools import reduce
|
||||
|
||||
from django.core import serializers
|
||||
from django.db import connection, models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_noop as _
|
||||
|
||||
from treebeard.exceptions import InvalidMoveToDescendant, NodeAlreadySaved
|
||||
from treebeard.models import Node
|
||||
|
||||
|
||||
def get_result_class(cls):
|
||||
"""
|
||||
For the given model class, determine what class we should use for the
|
||||
nodes returned by its tree methods (such as get_children).
|
||||
|
||||
Usually this will be trivially the same as the initial model class,
|
||||
but there are special cases when model inheritance is in use:
|
||||
|
||||
* If the model extends another via multi-table inheritance, we need to
|
||||
use whichever ancestor originally implemented the tree behaviour (i.e.
|
||||
the one which defines the 'lft'/'rgt' fields). We can't use the
|
||||
subclass, because it's not guaranteed that the other nodes reachable
|
||||
from the current one will be instances of the same subclass.
|
||||
|
||||
* If the model is a proxy model, the returned nodes should also use
|
||||
the proxy class.
|
||||
"""
|
||||
base_class = cls._meta.get_field('lft').model
|
||||
if cls._meta.proxy_for_model == base_class:
|
||||
return cls
|
||||
else:
|
||||
return base_class
|
||||
|
||||
|
||||
def merge_deleted_counters(c1, c2):
|
||||
"""
|
||||
Merge return values from Django's Queryset.delete() method.
|
||||
"""
|
||||
object_counts = {
|
||||
key: c1[1].get(key, 0) + c2[1].get(key, 0)
|
||||
for key in set(c1[1]) | set(c2[1])
|
||||
}
|
||||
return (c1[0] + c2[0], object_counts)
|
||||
|
||||
|
||||
class NS_NodeQuerySet(models.query.QuerySet):
|
||||
"""
|
||||
Custom queryset for the tree node manager.
|
||||
|
||||
Needed only for the customized delete method.
|
||||
"""
|
||||
|
||||
def delete(self, *args, removed_ranges=None, deleted_counter=None, **kwargs):
|
||||
"""
|
||||
Custom delete method, will remove all descendant nodes to ensure a
|
||||
consistent tree (no orphans)
|
||||
|
||||
:returns: tuple of the number of objects deleted and a dictionary
|
||||
with the number of deletions per object type
|
||||
"""
|
||||
model = get_result_class(self.model)
|
||||
|
||||
if deleted_counter is None:
|
||||
deleted_counter = (0, {})
|
||||
|
||||
if removed_ranges is not None:
|
||||
# we already know the children, let's call the default django
|
||||
# delete method and let it handle the removal of the user's
|
||||
# foreign keys...
|
||||
result = super().delete(*args, **kwargs)
|
||||
deleted_counter = merge_deleted_counters(deleted_counter, result)
|
||||
cursor = model._get_database_cursor('write')
|
||||
|
||||
# Now closing the gap (Celko's trees book, page 62)
|
||||
# We do this for every gap that was left in the tree when the nodes
|
||||
# were removed. If many nodes were removed, we're going to update
|
||||
# the same nodes over and over again. This would be probably
|
||||
# cheaper precalculating the gapsize per intervals, or just do a
|
||||
# complete reordering of the tree (uses COUNT)...
|
||||
for tree_id, drop_lft, drop_rgt in sorted(removed_ranges,
|
||||
reverse=True):
|
||||
sql, params = model._get_close_gap_sql(drop_lft, drop_rgt,
|
||||
tree_id)
|
||||
cursor.execute(sql, params)
|
||||
else:
|
||||
# we'll have to manually run through all the nodes that are going
|
||||
# to be deleted and remove nodes from the list if an ancestor is
|
||||
# already getting removed, since that would be redundant
|
||||
removed = {}
|
||||
for node in self.order_by('tree_id', 'lft'):
|
||||
found = False
|
||||
for rid, rnode in removed.items():
|
||||
if node.is_descendant_of(rnode):
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
removed[node.pk] = node
|
||||
|
||||
# ok, got the minimal list of nodes to remove...
|
||||
# we must also remove their descendants
|
||||
toremove = []
|
||||
ranges = []
|
||||
for id, node in removed.items():
|
||||
toremove.append(Q(lft__range=(node.lft, node.rgt)) &
|
||||
Q(tree_id=node.tree_id))
|
||||
ranges.append((node.tree_id, node.lft, node.rgt))
|
||||
if toremove:
|
||||
deleted_counter = model.objects.filter(
|
||||
reduce(operator.or_,
|
||||
toremove)
|
||||
).delete(removed_ranges=ranges, deleted_counter=deleted_counter)
|
||||
return deleted_counter
|
||||
|
||||
delete.alters_data = True
|
||||
delete.queryset_only = True
|
||||
|
||||
|
||||
class NS_NodeManager(models.Manager):
|
||||
"""Custom manager for nodes in a Nested Sets tree."""
|
||||
|
||||
def get_queryset(self):
|
||||
"""Sets the custom queryset as the default."""
|
||||
return NS_NodeQuerySet(self.model).order_by('tree_id', 'lft')
|
||||
|
||||
|
||||
class NS_Node(Node):
|
||||
"""Abstract model to create your own Nested Sets Trees."""
|
||||
node_order_by = []
|
||||
|
||||
lft = models.PositiveIntegerField(db_index=True)
|
||||
rgt = models.PositiveIntegerField(db_index=True)
|
||||
tree_id = models.PositiveIntegerField(db_index=True)
|
||||
depth = models.PositiveIntegerField(db_index=True)
|
||||
|
||||
objects = NS_NodeManager()
|
||||
|
||||
@classmethod
|
||||
def add_root(cls, **kwargs):
|
||||
"""Adds a root node to the tree."""
|
||||
|
||||
# do we have a root node already?
|
||||
last_root = cls.get_last_root_node()
|
||||
|
||||
if last_root and last_root.node_order_by:
|
||||
# there are root nodes and node_order_by has been set
|
||||
# delegate sorted insertion to add_sibling
|
||||
return last_root.add_sibling('sorted-sibling', **kwargs)
|
||||
|
||||
if last_root:
|
||||
# adding the new root node as the last one
|
||||
newtree_id = last_root.tree_id + 1
|
||||
else:
|
||||
# adding the first root node
|
||||
newtree_id = 1
|
||||
|
||||
if len(kwargs) == 1 and 'instance' in kwargs:
|
||||
# adding the passed (unsaved) instance to the tree
|
||||
newobj = kwargs['instance']
|
||||
if not newobj._state.adding:
|
||||
raise NodeAlreadySaved("Attempted to add a tree node that is "\
|
||||
"already in the database")
|
||||
else:
|
||||
# creating the new object
|
||||
newobj = get_result_class(cls)(**kwargs)
|
||||
|
||||
newobj.depth = 1
|
||||
newobj.tree_id = newtree_id
|
||||
newobj.lft = 1
|
||||
newobj.rgt = 2
|
||||
# saving the instance before returning it
|
||||
newobj.save()
|
||||
return newobj
|
||||
|
||||
@classmethod
|
||||
def _move_right(cls, tree_id, rgt, lftmove=False, incdec=2):
|
||||
if lftmove:
|
||||
lftop = '>='
|
||||
else:
|
||||
lftop = '>'
|
||||
sql = 'UPDATE %(table)s '\
|
||||
' SET lft = CASE WHEN lft %(lftop)s %(parent_rgt)d '\
|
||||
' THEN lft %(incdec)+d '\
|
||||
' ELSE lft END, '\
|
||||
' rgt = CASE WHEN rgt >= %(parent_rgt)d '\
|
||||
' THEN rgt %(incdec)+d '\
|
||||
' ELSE rgt END '\
|
||||
' WHERE rgt >= %(parent_rgt)d AND '\
|
||||
' tree_id = %(tree_id)s' % {
|
||||
'table': connection.ops.quote_name(
|
||||
get_result_class(cls)._meta.db_table),
|
||||
'parent_rgt': rgt,
|
||||
'tree_id': tree_id,
|
||||
'lftop': lftop,
|
||||
'incdec': incdec}
|
||||
return sql, []
|
||||
|
||||
@classmethod
|
||||
def _move_tree_right(cls, tree_id):
|
||||
sql = 'UPDATE %(table)s '\
|
||||
' SET tree_id = tree_id+1 '\
|
||||
' WHERE tree_id >= %(tree_id)d' % {
|
||||
'table': connection.ops.quote_name(
|
||||
get_result_class(cls)._meta.db_table),
|
||||
'tree_id': tree_id}
|
||||
return sql, []
|
||||
|
||||
def add_child(self, **kwargs):
|
||||
"""Adds a child to the node."""
|
||||
if not self.is_leaf():
|
||||
# there are child nodes, delegate insertion to add_sibling
|
||||
if self.node_order_by:
|
||||
pos = 'sorted-sibling'
|
||||
else:
|
||||
pos = 'last-sibling'
|
||||
last_child = self.get_last_child()
|
||||
last_child._cached_parent_obj = self
|
||||
return last_child.add_sibling(pos, **kwargs)
|
||||
|
||||
# we're adding the first child of this node
|
||||
sql, params = self.__class__._move_right(self.tree_id,
|
||||
self.rgt, False, 2)
|
||||
|
||||
if len(kwargs) == 1 and 'instance' in kwargs:
|
||||
# adding the passed (unsaved) instance to the tree
|
||||
newobj = kwargs['instance']
|
||||
if not newobj._state.adding:
|
||||
raise NodeAlreadySaved("Attempted to add a tree node that is "\
|
||||
"already in the database")
|
||||
else:
|
||||
# creating a new object
|
||||
newobj = get_result_class(self.__class__)(**kwargs)
|
||||
|
||||
newobj.tree_id = self.tree_id
|
||||
newobj.depth = self.depth + 1
|
||||
newobj.lft = self.lft + 1
|
||||
newobj.rgt = self.lft + 2
|
||||
|
||||
# this is just to update the cache
|
||||
self.rgt += 2
|
||||
|
||||
newobj._cached_parent_obj = self
|
||||
|
||||
cursor = self._get_database_cursor('write')
|
||||
cursor.execute(sql, params)
|
||||
|
||||
# saving the instance before returning it
|
||||
newobj.save()
|
||||
|
||||
return newobj
|
||||
|
||||
def add_sibling(self, pos=None, **kwargs):
|
||||
"""Adds a new node as a sibling to the current node object."""
|
||||
|
||||
pos = self._prepare_pos_var_for_add_sibling(pos)
|
||||
|
||||
if len(kwargs) == 1 and 'instance' in kwargs:
|
||||
# adding the passed (unsaved) instance to the tree
|
||||
newobj = kwargs['instance']
|
||||
if not newobj._state.adding:
|
||||
raise NodeAlreadySaved("Attempted to add a tree node that is "\
|
||||
"already in the database")
|
||||
else:
|
||||
# creating a new object
|
||||
newobj = get_result_class(self.__class__)(**kwargs)
|
||||
|
||||
newobj.depth = self.depth
|
||||
|
||||
sql = None
|
||||
target = self
|
||||
|
||||
if target.is_root():
|
||||
newobj.lft = 1
|
||||
newobj.rgt = 2
|
||||
if pos == 'sorted-sibling':
|
||||
siblings = list(target.get_sorted_pos_queryset(
|
||||
target.get_siblings(), newobj))
|
||||
if siblings:
|
||||
pos = 'left'
|
||||
target = siblings[0]
|
||||
else:
|
||||
pos = 'last-sibling'
|
||||
|
||||
last_root = target.__class__.get_last_root_node()
|
||||
if (
|
||||
(pos == 'last-sibling') or
|
||||
(pos == 'right' and target == last_root)
|
||||
):
|
||||
newobj.tree_id = last_root.tree_id + 1
|
||||
else:
|
||||
newpos = {'first-sibling': 1,
|
||||
'left': target.tree_id,
|
||||
'right': target.tree_id + 1}[pos]
|
||||
sql, params = target.__class__._move_tree_right(newpos)
|
||||
|
||||
newobj.tree_id = newpos
|
||||
else:
|
||||
newobj.tree_id = target.tree_id
|
||||
|
||||
if pos == 'sorted-sibling':
|
||||
siblings = list(target.get_sorted_pos_queryset(
|
||||
target.get_siblings(), newobj))
|
||||
if siblings:
|
||||
pos = 'left'
|
||||
target = siblings[0]
|
||||
else:
|
||||
pos = 'last-sibling'
|
||||
|
||||
if pos in ('left', 'right', 'first-sibling'):
|
||||
siblings = list(target.get_siblings())
|
||||
|
||||
if pos == 'right':
|
||||
if target == siblings[-1]:
|
||||
pos = 'last-sibling'
|
||||
else:
|
||||
pos = 'left'
|
||||
found = False
|
||||
for node in siblings:
|
||||
if found:
|
||||
target = node
|
||||
break
|
||||
elif node == target:
|
||||
found = True
|
||||
if pos == 'left':
|
||||
if target == siblings[0]:
|
||||
pos = 'first-sibling'
|
||||
if pos == 'first-sibling':
|
||||
target = siblings[0]
|
||||
|
||||
move_right = self.__class__._move_right
|
||||
|
||||
if pos == 'last-sibling':
|
||||
newpos = target.get_parent().rgt
|
||||
sql, params = move_right(target.tree_id, newpos, False, 2)
|
||||
elif pos == 'first-sibling':
|
||||
newpos = target.lft
|
||||
sql, params = move_right(target.tree_id, newpos - 1, False, 2)
|
||||
elif pos == 'left':
|
||||
newpos = target.lft
|
||||
sql, params = move_right(target.tree_id, newpos, True, 2)
|
||||
|
||||
newobj.lft = newpos
|
||||
newobj.rgt = newpos + 1
|
||||
|
||||
# saving the instance before returning it
|
||||
if sql:
|
||||
cursor = self._get_database_cursor('write')
|
||||
cursor.execute(sql, params)
|
||||
newobj.save()
|
||||
|
||||
return newobj
|
||||
|
||||
def move(self, target, pos=None):
|
||||
"""
|
||||
Moves the current node and all it's descendants to a new position
|
||||
relative to another node.
|
||||
"""
|
||||
|
||||
pos = self._prepare_pos_var_for_move(pos)
|
||||
cls = get_result_class(self.__class__)
|
||||
|
||||
parent = None
|
||||
|
||||
if pos in ('first-child', 'last-child', 'sorted-child'):
|
||||
# moving to a child
|
||||
if target.is_leaf():
|
||||
parent = target
|
||||
pos = 'last-child'
|
||||
else:
|
||||
target = target.get_last_child()
|
||||
pos = {'first-child': 'first-sibling',
|
||||
'last-child': 'last-sibling',
|
||||
'sorted-child': 'sorted-sibling'}[pos]
|
||||
|
||||
if target.is_descendant_of(self):
|
||||
raise InvalidMoveToDescendant(
|
||||
_("Can't move node to a descendant."))
|
||||
|
||||
if self == target and (
|
||||
(pos == 'left') or
|
||||
(pos in ('right', 'last-sibling') and
|
||||
target == target.get_last_sibling()) or
|
||||
(pos == 'first-sibling' and
|
||||
target == target.get_first_sibling())):
|
||||
# special cases, not actually moving the node so no need to UPDATE
|
||||
return
|
||||
|
||||
if pos == 'sorted-sibling':
|
||||
siblings = list(target.get_sorted_pos_queryset(
|
||||
target.get_siblings(), self))
|
||||
if siblings:
|
||||
pos = 'left'
|
||||
target = siblings[0]
|
||||
else:
|
||||
pos = 'last-sibling'
|
||||
if pos in ('left', 'right', 'first-sibling'):
|
||||
siblings = list(target.get_siblings())
|
||||
|
||||
if pos == 'right':
|
||||
if target == siblings[-1]:
|
||||
pos = 'last-sibling'
|
||||
else:
|
||||
pos = 'left'
|
||||
found = False
|
||||
for node in siblings:
|
||||
if found:
|
||||
target = node
|
||||
break
|
||||
elif node == target:
|
||||
found = True
|
||||
if pos == 'left':
|
||||
if target == siblings[0]:
|
||||
pos = 'first-sibling'
|
||||
if pos == 'first-sibling':
|
||||
target = siblings[0]
|
||||
|
||||
# ok let's move this
|
||||
cursor = self._get_database_cursor('write')
|
||||
move_right = cls._move_right
|
||||
gap = self.rgt - self.lft + 1
|
||||
sql = None
|
||||
target_tree = target.tree_id
|
||||
|
||||
# first make a hole
|
||||
if pos == 'last-child':
|
||||
newpos = parent.rgt
|
||||
sql, params = move_right(target.tree_id, newpos, False, gap)
|
||||
elif target.is_root():
|
||||
newpos = 1
|
||||
if pos == 'last-sibling':
|
||||
target_tree = target.get_siblings().reverse()[0].tree_id + 1
|
||||
elif pos == 'first-sibling':
|
||||
target_tree = 1
|
||||
sql, params = cls._move_tree_right(1)
|
||||
elif pos == 'left':
|
||||
sql, params = cls._move_tree_right(target.tree_id)
|
||||
else:
|
||||
if pos == 'last-sibling':
|
||||
newpos = target.get_parent().rgt
|
||||
sql, params = move_right(target.tree_id, newpos, False, gap)
|
||||
elif pos == 'first-sibling':
|
||||
newpos = target.lft
|
||||
sql, params = move_right(target.tree_id,
|
||||
newpos - 1, False, gap)
|
||||
elif pos == 'left':
|
||||
newpos = target.lft
|
||||
sql, params = move_right(target.tree_id, newpos, True, gap)
|
||||
|
||||
if sql:
|
||||
cursor.execute(sql, params)
|
||||
|
||||
# we reload 'self' because lft/rgt may have changed
|
||||
|
||||
fromobj = cls.objects.get(pk=self.pk)
|
||||
|
||||
depthdiff = target.depth - fromobj.depth
|
||||
if parent:
|
||||
depthdiff += 1
|
||||
|
||||
# move the tree to the hole
|
||||
sql = "UPDATE %(table)s "\
|
||||
" SET tree_id = %(target_tree)d, "\
|
||||
" lft = lft + %(jump)d , "\
|
||||
" rgt = rgt + %(jump)d , "\
|
||||
" depth = depth + %(depthdiff)d "\
|
||||
" WHERE tree_id = %(from_tree)d AND "\
|
||||
" lft BETWEEN %(fromlft)d AND %(fromrgt)d" % {
|
||||
'table': connection.ops.quote_name(cls._meta.db_table),
|
||||
'from_tree': fromobj.tree_id,
|
||||
'target_tree': target_tree,
|
||||
'jump': newpos - fromobj.lft,
|
||||
'depthdiff': depthdiff,
|
||||
'fromlft': fromobj.lft,
|
||||
'fromrgt': fromobj.rgt}
|
||||
cursor.execute(sql, [])
|
||||
|
||||
# close the gap
|
||||
sql, params = cls._get_close_gap_sql(fromobj.lft,
|
||||
fromobj.rgt, fromobj.tree_id)
|
||||
cursor.execute(sql, params)
|
||||
|
||||
@classmethod
|
||||
def _get_close_gap_sql(cls, drop_lft, drop_rgt, tree_id):
|
||||
sql = 'UPDATE %(table)s '\
|
||||
' SET lft = CASE '\
|
||||
' WHEN lft > %(drop_lft)d '\
|
||||
' THEN lft - %(gapsize)d '\
|
||||
' ELSE lft END, '\
|
||||
' rgt = CASE '\
|
||||
' WHEN rgt > %(drop_lft)d '\
|
||||
' THEN rgt - %(gapsize)d '\
|
||||
' ELSE rgt END '\
|
||||
' WHERE (lft > %(drop_lft)d '\
|
||||
' OR rgt > %(drop_lft)d) AND '\
|
||||
' tree_id=%(tree_id)d' % {
|
||||
'table': connection.ops.quote_name(
|
||||
get_result_class(cls)._meta.db_table),
|
||||
'gapsize': drop_rgt - drop_lft + 1,
|
||||
'drop_lft': drop_lft,
|
||||
'tree_id': tree_id}
|
||||
return sql, []
|
||||
|
||||
@classmethod
|
||||
def load_bulk(cls, bulk_data, parent=None, keep_ids=False):
|
||||
"""Loads a list/dictionary structure to the tree."""
|
||||
|
||||
cls = get_result_class(cls)
|
||||
|
||||
# tree, iterative preorder
|
||||
added = []
|
||||
if parent:
|
||||
parent_id = parent.pk
|
||||
else:
|
||||
parent_id = None
|
||||
# stack of nodes to analyze
|
||||
stack = [(parent_id, node) for node in bulk_data[::-1]]
|
||||
foreign_keys = cls.get_foreign_keys()
|
||||
pk_field = cls._meta.pk.attname
|
||||
while stack:
|
||||
parent_id, node_struct = stack.pop()
|
||||
# shallow copy of the data structure so it doesn't persist...
|
||||
node_data = node_struct['data'].copy()
|
||||
cls._process_foreign_keys(foreign_keys, node_data)
|
||||
if keep_ids:
|
||||
node_data[pk_field] = node_struct[pk_field]
|
||||
if parent_id:
|
||||
parent = cls.objects.get(pk=parent_id)
|
||||
node_obj = parent.add_child(**node_data)
|
||||
else:
|
||||
node_obj = cls.add_root(**node_data)
|
||||
added.append(node_obj.pk)
|
||||
if 'children' in node_struct:
|
||||
# extending the stack with the current node as the parent of
|
||||
# the new nodes
|
||||
stack.extend([
|
||||
(node_obj.pk, node)
|
||||
for node in node_struct['children'][::-1]
|
||||
])
|
||||
return added
|
||||
|
||||
def get_children(self):
|
||||
""":returns: A queryset of all the node's children"""
|
||||
return self.get_descendants().filter(depth=self.depth + 1)
|
||||
|
||||
def get_depth(self):
|
||||
""":returns: the depth (level) of the node"""
|
||||
return self.depth
|
||||
|
||||
def is_leaf(self):
|
||||
""":returns: True if the node is a leaf node (else, returns False)"""
|
||||
return self.rgt - self.lft == 1
|
||||
|
||||
def get_root(self):
|
||||
""":returns: the root node for the current node object."""
|
||||
if self.lft == 1:
|
||||
return self
|
||||
return get_result_class(self.__class__).objects.get(
|
||||
tree_id=self.tree_id, lft=1)
|
||||
|
||||
def is_root(self):
|
||||
""":returns: True if the node is a root node (else, returns False)"""
|
||||
return self.lft == 1
|
||||
|
||||
def get_siblings(self):
|
||||
"""
|
||||
:returns: A queryset of all the node's siblings, including the node
|
||||
itself.
|
||||
"""
|
||||
if self.lft == 1:
|
||||
return self.get_root_nodes()
|
||||
return self.get_parent(True).get_children()
|
||||
|
||||
@classmethod
|
||||
def dump_bulk(cls, parent=None, keep_ids=True):
|
||||
"""Dumps a tree branch to a python data structure."""
|
||||
qset = cls._get_serializable_model().get_tree(parent)
|
||||
ret, lnk = [], {}
|
||||
pk_field = cls._meta.pk.attname
|
||||
for pyobj in qset:
|
||||
serobj = serializers.serialize('python', [pyobj])[0]
|
||||
# django's serializer stores the attributes in 'fields'
|
||||
fields = serobj['fields']
|
||||
depth = fields['depth']
|
||||
# this will be useless in load_bulk
|
||||
del fields['lft']
|
||||
del fields['rgt']
|
||||
del fields['depth']
|
||||
del fields['tree_id']
|
||||
if pk_field in fields:
|
||||
# this happens immediately after a load_bulk
|
||||
del fields[pk_field]
|
||||
|
||||
newobj = {'data': fields}
|
||||
if keep_ids:
|
||||
newobj[pk_field] = serobj['pk']
|
||||
|
||||
if (not parent and depth == 1) or\
|
||||
(parent and depth == parent.depth):
|
||||
ret.append(newobj)
|
||||
else:
|
||||
parentobj = pyobj.get_parent()
|
||||
parentser = lnk[parentobj.pk]
|
||||
if 'children' not in parentser:
|
||||
parentser['children'] = []
|
||||
parentser['children'].append(newobj)
|
||||
lnk[pyobj.pk] = newobj
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def get_tree(cls, parent=None):
|
||||
"""
|
||||
:returns:
|
||||
|
||||
A *queryset* of nodes ordered as DFS, including the parent.
|
||||
If no parent is given, all trees are returned.
|
||||
"""
|
||||
cls = get_result_class(cls)
|
||||
|
||||
if parent is None:
|
||||
# return the entire tree
|
||||
return cls.objects.all()
|
||||
if parent.is_leaf():
|
||||
return cls.objects.filter(pk=parent.pk)
|
||||
return cls.objects.filter(
|
||||
tree_id=parent.tree_id,
|
||||
lft__range=(parent.lft, parent.rgt - 1))
|
||||
|
||||
def get_descendants(self):
|
||||
"""
|
||||
:returns: A queryset of all the node's descendants as DFS, doesn't
|
||||
include the node itself
|
||||
"""
|
||||
if self.is_leaf():
|
||||
return get_result_class(self.__class__).objects.none()
|
||||
return self.__class__.get_tree(self).exclude(pk=self.pk)
|
||||
|
||||
def get_descendant_count(self):
|
||||
""":returns: the number of descendants of a node."""
|
||||
return (self.rgt - self.lft - 1) / 2
|
||||
|
||||
def get_ancestors(self):
|
||||
"""
|
||||
:returns: A queryset containing the current node object's ancestors,
|
||||
starting by the root node and descending to the parent.
|
||||
"""
|
||||
if self.is_root():
|
||||
return get_result_class(self.__class__).objects.none()
|
||||
return get_result_class(self.__class__).objects.filter(
|
||||
tree_id=self.tree_id,
|
||||
lft__lt=self.lft,
|
||||
rgt__gt=self.rgt)
|
||||
|
||||
def is_descendant_of(self, node):
|
||||
"""
|
||||
:returns: ``True`` if the node if a descendant of another node given
|
||||
as an argument, else, returns ``False``
|
||||
"""
|
||||
return (
|
||||
self.tree_id == node.tree_id and
|
||||
self.lft > node.lft and
|
||||
self.rgt < node.rgt
|
||||
)
|
||||
|
||||
def get_parent(self, update=False):
|
||||
"""
|
||||
:returns: the parent node of the current node object.
|
||||
Caches the result in the object itself to help in loops.
|
||||
"""
|
||||
if self.is_root():
|
||||
return
|
||||
try:
|
||||
if update:
|
||||
del self._cached_parent_obj
|
||||
else:
|
||||
return self._cached_parent_obj
|
||||
except AttributeError:
|
||||
pass
|
||||
# parent = our most direct ancestor
|
||||
self._cached_parent_obj = self.get_ancestors().reverse()[0]
|
||||
return self._cached_parent_obj
|
||||
|
||||
@classmethod
|
||||
def get_root_nodes(cls):
|
||||
""":returns: A queryset containing the root nodes in the tree."""
|
||||
return get_result_class(cls).objects.filter(lft=1)
|
||||
|
||||
class Meta:
|
||||
"""Abstract model."""
|
||||
abstract = True
|
||||
115
env/lib/python3.10/site-packages/treebeard/numconv.py
vendored
Normal file
115
env/lib/python3.10/site-packages/treebeard/numconv.py
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
"""Convert strings to numbers and numbers to strings.
|
||||
|
||||
Gustavo Picon
|
||||
https://tabo.pe/projects/numconv/
|
||||
|
||||
"""
|
||||
|
||||
|
||||
__version__ = '2.1.1'
|
||||
|
||||
# from april fool's rfc 1924
|
||||
BASE85 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' \
|
||||
'!#$%&()*+-;<=>?@^_`{|}~'
|
||||
|
||||
# rfc4648 alphabets
|
||||
BASE16 = BASE85[:16]
|
||||
BASE32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
BASE32HEX = BASE85[:32]
|
||||
BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
BASE64URL = BASE64[:62] + '-_'
|
||||
|
||||
# http://en.wikipedia.org/wiki/Base_62 useful for url shorteners
|
||||
BASE62 = BASE85[:62]
|
||||
|
||||
|
||||
class NumConv(object):
|
||||
"""Class to create converter objects.
|
||||
|
||||
:param radix: The base that will be used in the conversions.
|
||||
The default value is 10 for decimal conversions.
|
||||
:param alphabet: A string that will be used as a encoding alphabet.
|
||||
The length of the alphabet can be longer than the radix. In this
|
||||
case the alphabet will be internally truncated.
|
||||
|
||||
The default value is :data:`numconv.BASE85`
|
||||
|
||||
:raise TypeError: when *radix* isn't an integer
|
||||
:raise ValueError: when *radix* is invalid
|
||||
:raise ValueError: when *alphabet* has duplicated characters
|
||||
"""
|
||||
|
||||
def __init__(self, radix=10, alphabet=BASE85):
|
||||
"""basic validation and cached_map storage"""
|
||||
if int(radix) != radix:
|
||||
raise TypeError('radix must be an integer')
|
||||
if not 2 <= radix <= len(alphabet):
|
||||
raise ValueError('radix must be >= 2 and <= %d' % (
|
||||
len(alphabet), ))
|
||||
self.radix = radix
|
||||
self.alphabet = alphabet
|
||||
self.cached_map = dict(zip(self.alphabet, range(len(self.alphabet))))
|
||||
if len(self.cached_map) != len(self.alphabet):
|
||||
raise ValueError("duplicate characters found in '%s'" % (
|
||||
self.alphabet, ))
|
||||
|
||||
def int2str(self, num):
|
||||
"""Converts an integer into a string.
|
||||
|
||||
:param num: A numeric value to be converted to another base as a
|
||||
string.
|
||||
|
||||
:rtype: string
|
||||
|
||||
:raise TypeError: when *num* isn't an integer
|
||||
:raise ValueError: when *num* isn't positive
|
||||
"""
|
||||
if int(num) != num:
|
||||
raise TypeError('number must be an integer')
|
||||
if num < 0:
|
||||
raise ValueError('number must be positive')
|
||||
radix, alphabet = self.radix, self.alphabet
|
||||
if radix in (8, 10, 16) and \
|
||||
alphabet[:radix].lower() == BASE85[:radix].lower():
|
||||
return ({8: '%o', 10: '%d', 16: '%x'}[radix] % num).upper()
|
||||
ret = ''
|
||||
while True:
|
||||
ret = alphabet[num % radix] + ret
|
||||
if num < radix:
|
||||
break
|
||||
num //= radix
|
||||
return ret
|
||||
|
||||
def str2int(self, num):
|
||||
"""Converts a string into an integer.
|
||||
|
||||
If possible, the built-in python conversion will be used for speed
|
||||
purposes.
|
||||
|
||||
:param num: A string that will be converted to an integer.
|
||||
|
||||
:rtype: integer
|
||||
|
||||
:raise ValueError: when *num* is invalid
|
||||
"""
|
||||
radix, alphabet = self.radix, self.alphabet
|
||||
if radix <= 36 and alphabet[:radix].lower() == BASE85[:radix].lower():
|
||||
return int(num, radix)
|
||||
ret = 0
|
||||
lalphabet = alphabet[:radix]
|
||||
for char in num:
|
||||
if char not in lalphabet:
|
||||
raise ValueError("invalid literal for radix2int() with radix "
|
||||
"%d: '%s'" % (radix, num))
|
||||
ret = ret * radix + self.cached_map[char]
|
||||
return ret
|
||||
|
||||
|
||||
def int2str(num, radix=10, alphabet=BASE85):
|
||||
"""helper function for quick base conversions from integers to strings"""
|
||||
return NumConv(radix, alphabet).int2str(num)
|
||||
|
||||
|
||||
def str2int(num, radix=10, alphabet=BASE85):
|
||||
"""helper function for quick base conversions from strings to integers"""
|
||||
return NumConv(radix, alphabet).str2int(num)
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/static/treebeard/expand-collapse.png
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/static/treebeard/expand-collapse.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1020 B |
83
env/lib/python3.10/site-packages/treebeard/static/treebeard/treebeard-admin.css
vendored
Normal file
83
env/lib/python3.10/site-packages/treebeard/static/treebeard/treebeard-admin.css
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
/* Treebeard Admin */
|
||||
|
||||
#roots {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#roots li {
|
||||
list-style: none;
|
||||
padding: 5px !important;
|
||||
line-height: 13px;
|
||||
border-bottom: 1px solid #EEE;
|
||||
}
|
||||
|
||||
#roots li a {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#roots li input {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.oder-grabber {
|
||||
width: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.drag-handler span {
|
||||
width: 16px;
|
||||
background: transparent url(expand-collapse.png) no-repeat left -48px;
|
||||
height: 16px;
|
||||
margin: 0 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.drag-handler span.active {
|
||||
background: transparent url(expand-collapse.png) no-repeat left -32px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: 10px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
text-indent: -999px;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
background: transparent url(expand-collapse.png) no-repeat left -16px;
|
||||
}
|
||||
|
||||
.expanded {
|
||||
background: transparent url(expand-collapse.png) no-repeat left 0;
|
||||
}
|
||||
|
||||
#drag_line {
|
||||
border-top: 5px solid #A0A;
|
||||
background: #A0A;
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#drag_line span {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100px;
|
||||
background: #FFD;
|
||||
color: #000;
|
||||
left: 100px;
|
||||
text-align: center;
|
||||
border: 1px solid #000;
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
/*tr:target { I'm handling the highlight with js to have more control
|
||||
background-color: #FF0;
|
||||
}*/
|
||||
314
env/lib/python3.10/site-packages/treebeard/static/treebeard/treebeard-admin.js
vendored
Normal file
314
env/lib/python3.10/site-packages/treebeard/static/treebeard/treebeard-admin.js
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
(function ($) {
|
||||
// Ok, let's do eeet
|
||||
|
||||
ACTIVE_NODE_BG_COLOR = '#B7D7E8';
|
||||
RECENTLY_MOVED_COLOR = '#FFFF00';
|
||||
RECENTLY_MOVED_FADEOUT = '#FFFFFF';
|
||||
ABORT_COLOR = '#EECCCC';
|
||||
DRAG_LINE_COLOR = '#AA00AA';
|
||||
MOVE_NODE_ENDPOINT = 'move/';
|
||||
|
||||
RECENTLY_FADE_DURATION = 2000;
|
||||
|
||||
CSRF_TOKEN = document.currentScript.dataset.csrftoken;
|
||||
|
||||
// Add jQuery util for disabling selection
|
||||
// Originally taken from jquery-ui (where it is deprecated)
|
||||
// https://api.jqueryui.com/disableSelection/
|
||||
$.fn.extend( {
|
||||
disableSelection: ( function() {
|
||||
var eventType = "onselectstart" in document.createElement( "div" ) ? "selectstart" : "mousedown";
|
||||
return function() {
|
||||
return this.on( eventType + ".ui-disableSelection", function( event ) {
|
||||
event.preventDefault();
|
||||
} );
|
||||
};
|
||||
} )(),
|
||||
|
||||
enableSelection: function() {
|
||||
return this.off( ".ui-disableSelection" );
|
||||
}
|
||||
} );
|
||||
|
||||
// This is the basic Node class, which handles UI tree operations for each 'row'
|
||||
var Node = function (elem) {
|
||||
var $elem = $(elem);
|
||||
var node_id = $elem.attr('node');
|
||||
var parent_id = $elem.attr('parent');
|
||||
var level = parseInt($elem.attr('level'));
|
||||
var children_num = parseInt($elem.attr('children-num'));
|
||||
return {
|
||||
elem: elem,
|
||||
$elem: $elem,
|
||||
node_id: node_id,
|
||||
parent_id: parent_id,
|
||||
level: level,
|
||||
has_children: function () {
|
||||
return children_num > 0;
|
||||
},
|
||||
node_name: function () {
|
||||
// Returns the text of the node
|
||||
return $elem.find('th a:not(.collapse)').text();
|
||||
},
|
||||
is_collapsed: function () {
|
||||
return $elem.find('a.collapse').hasClass('collapsed');
|
||||
},
|
||||
children: function () {
|
||||
return $('tr[parent=' + node_id + ']');
|
||||
},
|
||||
collapse: function () {
|
||||
// For each children, hide it's children and so on...
|
||||
$.each(this.children(),function () {
|
||||
var node = new Node(this);
|
||||
node.collapse();
|
||||
}).hide();
|
||||
// Swicth class to set the property expand/collapse icon
|
||||
$elem.find('a.collapse').removeClass('expanded').addClass('collapsed');
|
||||
},
|
||||
parent_node: function () {
|
||||
// Returns a Node object of the parent
|
||||
return new Node($('tr[node=' + parent_id + ']', $elem.parent())[0]);
|
||||
},
|
||||
expand: function () {
|
||||
// Display each kid (will display in collapsed state)
|
||||
this.children().show();
|
||||
// Swicth class to set the property expand/collapse icon
|
||||
$elem.find('a.collapse').removeClass('collapsed').addClass('expanded');
|
||||
|
||||
},
|
||||
toggle: function () {
|
||||
if (this.is_collapsed()) {
|
||||
this.expand();
|
||||
} else {
|
||||
this.collapse();
|
||||
}
|
||||
},
|
||||
clone: function () {
|
||||
return $elem.clone();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
$(document).ajaxSend(function (event, xhr, settings) {
|
||||
if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
|
||||
// Only send the token to relative URLs i.e. locally.
|
||||
xhr.setRequestHeader("X-CSRFToken", CSRF_TOKEN);
|
||||
}
|
||||
});
|
||||
|
||||
// Don't activate drag or collapse if GET filters are set on the page
|
||||
if ($('#has-filters').val() === "1") {
|
||||
return;
|
||||
}
|
||||
|
||||
$body = $('body');
|
||||
|
||||
// Activate all rows for drag & drop
|
||||
// then bind mouse down event
|
||||
$('td.drag-handler span').addClass('active').bind('mousedown', function (evt) {
|
||||
$ghost = $('<div id="ghost"></div>');
|
||||
$drag_line = $('<div id="drag_line"><span></span></div>');
|
||||
$ghost.appendTo($body);
|
||||
$drag_line.appendTo($body);
|
||||
|
||||
var stop_drag = function () {
|
||||
$ghost.remove();
|
||||
$drag_line.remove();
|
||||
$body.enableSelection().unbind('mousemove').unbind('mouseup');
|
||||
node.elem.removeAttribute('style');
|
||||
};
|
||||
|
||||
// Create a clone create the illusion that we're moving the node
|
||||
var node = new Node($(this).closest('tr')[0]);
|
||||
cloned_node = node.clone();
|
||||
node.$elem.css({
|
||||
'background': ACTIVE_NODE_BG_COLOR
|
||||
});
|
||||
|
||||
$targetRow = null;
|
||||
as_child = false;
|
||||
|
||||
// Now make the new clone move with the mouse
|
||||
$body.disableSelection().bind('mousemove',function (evt2) {
|
||||
$ghost.html(cloned_node).css({ // from FeinCMS :P
|
||||
'opacity': .8,
|
||||
'position': 'absolute',
|
||||
'top': evt2.pageY,
|
||||
'left': evt2.pageX - 30,
|
||||
'width': 600
|
||||
});
|
||||
// Iterate through all rows and see where am I moving so I can place
|
||||
// the drag line accordingly
|
||||
rowHeight = node.$elem.height();
|
||||
$('tr', node.$elem.parent()).each(function (index, element) {
|
||||
$row = $(element);
|
||||
rtop = $row.offset().top;
|
||||
// The tooltip will display whether I'm dropping the element as
|
||||
// child or sibling
|
||||
$tooltip = $drag_line.find('span');
|
||||
$tooltip.css({
|
||||
'left': node.$elem.width() - $tooltip.width(),
|
||||
'height': rowHeight,
|
||||
});
|
||||
node_top = node.$elem.offset().top;
|
||||
// Check if you are dragging over the same node
|
||||
if (evt2.pageY >= node_top && evt2.pageY <= node_top + rowHeight) {
|
||||
$targetRow = null;
|
||||
$tooltip.text(gettext('Abort'));
|
||||
$drag_line.css({
|
||||
'top': node_top,
|
||||
'height': rowHeight,
|
||||
'borderWidth': 0,
|
||||
'opacity': 0.8,
|
||||
'backgroundColor': ABORT_COLOR
|
||||
});
|
||||
} else
|
||||
// Check if mouse is over this row
|
||||
if (evt2.pageY >= rtop && evt2.pageY <= rtop + rowHeight / 2) {
|
||||
// The mouse is positioned on the top half of a $row
|
||||
$targetRow = $row;
|
||||
as_child = false;
|
||||
$drag_line.css({
|
||||
'left': node.$elem.offset().left,
|
||||
'width': node.$elem.width(),
|
||||
'top': rtop,
|
||||
'borderWidth': '5px',
|
||||
'height': 0,
|
||||
'opacity': 1
|
||||
});
|
||||
$tooltip.text(gettext('As Sibling'));
|
||||
} else if (evt2.pageY >= rtop + rowHeight / 2 && evt2.pageY <= rtop + rowHeight) {
|
||||
// The mouse is positioned on the bottom half of a row
|
||||
$targetRow = $row;
|
||||
target_node = new Node($targetRow[0]);
|
||||
if (target_node.is_collapsed()) {
|
||||
target_node.expand();
|
||||
}
|
||||
as_child = true;
|
||||
$drag_line.css({
|
||||
'top': rtop,
|
||||
'left': node.$elem.offset().left,
|
||||
'height': rowHeight,
|
||||
'opacity': 0.4,
|
||||
'width': node.$elem.width(),
|
||||
'borderWidth': 0,
|
||||
'backgroundColor': DRAG_LINE_COLOR
|
||||
});
|
||||
$tooltip.text(gettext('As child'));
|
||||
}
|
||||
});
|
||||
}).bind('mouseup',function () {
|
||||
if ($targetRow !== null) {
|
||||
target_node = new Node($targetRow[0]);
|
||||
if (target_node.node_id !== node.node_id) {
|
||||
/*alert('Insert node ' + node.node_name() + ' as child of: '
|
||||
+ target_node.parent_node().node_name() + '\n and sibling of: '
|
||||
+ target_node.node_name());*/
|
||||
// Call $.ajax so we can handle the error
|
||||
// On Drop, make an XHR call to perform the node move
|
||||
$.ajax({
|
||||
url: MOVE_NODE_ENDPOINT,
|
||||
type: 'POST',
|
||||
data: {
|
||||
node_id: node.node_id,
|
||||
parent_id: target_node.parent_id,
|
||||
sibling_id: target_node.node_id,
|
||||
as_child: as_child ? 1 : 0
|
||||
},
|
||||
complete: function (req, status) {
|
||||
// http://stackoverflow.com/questions/1439895/add-a-hash-with-javascript-to-url-without-scrolling-page/1439910#1439910
|
||||
node.$elem.remove();
|
||||
window.location.hash = 'node-' + node.node_id;
|
||||
window.location.reload();
|
||||
},
|
||||
error: function (req, status, error) {
|
||||
// On error (!200) also reload to display
|
||||
// the message
|
||||
node.$elem.remove();
|
||||
window.location.hash = 'node-' + node.node_id;
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
stop_drag();
|
||||
}).bind('keyup', function (kbevt) {
|
||||
// Cancel drag on escape
|
||||
if (kbevt.keyCode === 27) {
|
||||
stop_drag();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('a.collapse').click(function () {
|
||||
var node = new Node($(this).closest('tr')[0]); // send the DOM node, not jQ
|
||||
node.toggle();
|
||||
return false;
|
||||
});
|
||||
var hash = window.location.hash;
|
||||
// This is a hack, the actual element's id ends in '-id' but the url's hash
|
||||
// doesn't, I'm doing this to avoid scrolling the page... is that a good thing?
|
||||
if (hash) {
|
||||
$(hash + '-id').animate({
|
||||
backgroundColor: RECENTLY_MOVED_COLOR
|
||||
}, RECENTLY_FADE_DURATION, function () {
|
||||
$(this).animate({
|
||||
backgroundColor: RECENTLY_MOVED_FADEOUT
|
||||
}, RECENTLY_FADE_DURATION, function () {
|
||||
this.removeAttribute('style');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
})(django.jQuery);
|
||||
|
||||
// http://stackoverflow.com/questions/190560/jquery-animate-backgroundcolor/2302005#2302005
|
||||
(function (d) {
|
||||
d.each(["backgroundColor", "borderBottomColor", "borderLeftColor", "borderRightColor", "borderTopColor", "color", "outlineColor"], function (f, e) {
|
||||
d.fx.step[e] = function (g) {
|
||||
if (!g.colorInit) {
|
||||
g.start = c(g.elem, e);
|
||||
g.end = b(g.end);
|
||||
g.colorInit = true
|
||||
}
|
||||
g.elem.style[e] = "rgb(" + [Math.max(Math.min(parseInt((g.pos * (g.end[0] - g.start[0])) + g.start[0]), 255), 0), Math.max(Math.min(parseInt((g.pos * (g.end[1] - g.start[1])) + g.start[1]), 255), 0), Math.max(Math.min(parseInt((g.pos * (g.end[2] - g.start[2])) + g.start[2]), 255), 0)].join(",") + ")"
|
||||
}
|
||||
});
|
||||
function b(f) {
|
||||
var e;
|
||||
if (f && f.constructor == Array && f.length == 3) {
|
||||
return f
|
||||
}
|
||||
if (e = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(f)) {
|
||||
return[parseInt(e[1]), parseInt(e[2]), parseInt(e[3])]
|
||||
}
|
||||
if (e = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(f)) {
|
||||
return[parseFloat(e[1]) * 2.55, parseFloat(e[2]) * 2.55, parseFloat(e[3]) * 2.55]
|
||||
}
|
||||
if (e = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(f)) {
|
||||
return[parseInt(e[1], 16), parseInt(e[2], 16), parseInt(e[3], 16)]
|
||||
}
|
||||
if (e = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(f)) {
|
||||
return[parseInt(e[1] + e[1], 16), parseInt(e[2] + e[2], 16), parseInt(e[3] + e[3], 16)]
|
||||
}
|
||||
if (e = /rgba\(0, 0, 0, 0\)/.exec(f)) {
|
||||
return a.transparent
|
||||
}
|
||||
return a[d.trim(f).toLowerCase()]
|
||||
}
|
||||
|
||||
function c(g, e) {
|
||||
var f;
|
||||
do {
|
||||
f = d.css(g, e);
|
||||
if (f != "" && f != "transparent" || d.nodeName(g, "body")) {
|
||||
break
|
||||
}
|
||||
e = "backgroundColor"
|
||||
} while (g = g.parentNode);
|
||||
return b(f)
|
||||
}
|
||||
|
||||
var a = {aqua: [0, 255, 255], azure: [240, 255, 255], beige: [245, 245, 220], black: [0, 0, 0], blue: [0, 0, 255], brown: [165, 42, 42], cyan: [0, 255, 255], darkblue: [0, 0, 139], darkcyan: [0, 139, 139], darkgrey: [169, 169, 169], darkgreen: [0, 100, 0], darkkhaki: [189, 183, 107], darkmagenta: [139, 0, 139], darkolivegreen: [85, 107, 47], darkorange: [255, 140, 0], darkorchid: [153, 50, 204], darkred: [139, 0, 0], darksalmon: [233, 150, 122], darkviolet: [148, 0, 211], fuchsia: [255, 0, 255], gold: [255, 215, 0], green: [0, 128, 0], indigo: [75, 0, 130], khaki: [240, 230, 140], lightblue: [173, 216, 230], lightcyan: [224, 255, 255], lightgreen: [144, 238, 144], lightgrey: [211, 211, 211], lightpink: [255, 182, 193], lightyellow: [255, 255, 224], lime: [0, 255, 0], magenta: [255, 0, 255], maroon: [128, 0, 0], navy: [0, 0, 128], olive: [128, 128, 0], orange: [255, 165, 0], pink: [255, 192, 203], purple: [128, 0, 128], violet: [128, 0, 128], red: [255, 0, 0], silver: [192, 192, 192], white: [255, 255, 255], yellow: [255, 255, 0], transparent: [255, 255, 255]}
|
||||
})(django.jQuery);
|
||||
24
env/lib/python3.10/site-packages/treebeard/templates/admin/tree_change_list.html
vendored
Normal file
24
env/lib/python3.10/site-packages/treebeard/templates/admin/tree_change_list.html
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{# Used for MP and NS trees #}
|
||||
{% extends "admin/change_list.html" %}
|
||||
{% load admin_list admin_tree static %}
|
||||
|
||||
{% block extrastyle %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'treebeard/treebeard-admin.css' %}" />
|
||||
{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
<script src="{% url 'admin:jsi18n' %}"></script>
|
||||
<script data-csrftoken="{{ csrf_token }}" src="{% static 'treebeard/treebeard-admin.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block result_list %}
|
||||
{% if action_form and actions_on_top and cl.full_result_count %}
|
||||
{% admin_actions %}
|
||||
{% endif %}
|
||||
{% result_tree cl request %}
|
||||
{% if action_form and actions_on_bottom and cl.full_result_count %}
|
||||
{% admin_actions %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
37
env/lib/python3.10/site-packages/treebeard/templates/admin/tree_change_list_results.html
vendored
Normal file
37
env/lib/python3.10/site-packages/treebeard/templates/admin/tree_change_list_results.html
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
{% if result_hidden_fields %}
|
||||
<div class="hiddenfields"> {# DIV for HTML validation #}
|
||||
{% for item in result_hidden_fields %}{{ item }}{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if results %}
|
||||
<div class="results">
|
||||
<table cellspacing="0" id="result_list">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for header in result_headers %}
|
||||
<th{{ header.class_attrib }}>
|
||||
{% if header.sortable %}<a href="{{ header.url }}"
|
||||
{% if header.tooltip %}title="{{ header.tooltip }}"{% endif %}>{% endif %}
|
||||
{{ header.text|capfirst }}
|
||||
{% if header.sortable %}</a>{% endif %}</th>{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for node_id, parent_id, node_level, children_num, result in results %}
|
||||
<tr id="node-{{ node_id }}-id" class="{% cycle 'row1' 'row2' %}"
|
||||
level="{{ node_level }}" children-num="{{ children_num }}"
|
||||
parent="{{ parent_id }}" node="{{ node_id }}">
|
||||
{% for item in result %}
|
||||
{% if forloop.counter == 1 %}
|
||||
{% for spacer in item.depth %}<span class="grab">
|
||||
</span>{% endfor %}
|
||||
{% endif %}
|
||||
{{ item }}
|
||||
{% endfor %}</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<input type="hidden" id="has-filters" value="{{ filtered|yesno:"1,0" }}"/>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
21
env/lib/python3.10/site-packages/treebeard/templates/admin/tree_list.html
vendored
Normal file
21
env/lib/python3.10/site-packages/treebeard/templates/admin/tree_list.html
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{# Used for AL trees #}
|
||||
{% extends "admin/change_list.html" %}
|
||||
{% load admin_list admin_tree_list i18n %}
|
||||
|
||||
{% block extrastyle %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block result_list %}
|
||||
{% if action_form and actions_on_top and cl.full_result_count %}
|
||||
{% admin_actions %}
|
||||
{% endif %}
|
||||
{% result_tree cl request %}
|
||||
{% if action_form and actions_on_bottom and cl.full_result_count %}
|
||||
{% admin_actions %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
7
env/lib/python3.10/site-packages/treebeard/templates/admin/tree_list_results.html
vendored
Normal file
7
env/lib/python3.10/site-packages/treebeard/templates/admin/tree_list_results.html
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{% if results %}
|
||||
<ul>
|
||||
{% for result in results %}
|
||||
<li class="{% cycle 'row1' 'row2' %}">{{ result }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
11
env/lib/python3.10/site-packages/treebeard/templatetags/__init__.py
vendored
Normal file
11
env/lib/python3.10/site-packages/treebeard/templatetags/__init__.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.template import Variable, VariableDoesNotExist
|
||||
|
||||
|
||||
action_form_var = Variable('action_form')
|
||||
|
||||
|
||||
def needs_checkboxes(context):
|
||||
try:
|
||||
return action_form_var.resolve(context) is not None
|
||||
except VariableDoesNotExist:
|
||||
return False
|
||||
BIN
env/lib/python3.10/site-packages/treebeard/templatetags/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/templatetags/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/templatetags/__pycache__/admin_tree.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/templatetags/__pycache__/admin_tree.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/templatetags/__pycache__/admin_tree_list.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/templatetags/__pycache__/admin_tree_list.cpython-310.pyc
vendored
Normal file
Binary file not shown.
215
env/lib/python3.10/site-packages/treebeard/templatetags/admin_tree.py
vendored
Normal file
215
env/lib/python3.10/site-packages/treebeard/templatetags/admin_tree.py
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
"""
|
||||
Templatetags for django-treebeard to add drag and drop capabilities to the
|
||||
nodes change list - @jjdelc
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.admin.templatetags.admin_list import (
|
||||
result_headers, result_hidden_fields)
|
||||
from django.contrib.admin.utils import (
|
||||
lookup_field, display_for_field, display_for_value)
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.template import Library
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.html import conditional_escape, format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from treebeard.templatetags import needs_checkboxes
|
||||
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
def get_result_and_row_class(cl, field_name, result):
|
||||
empty_value_display = cl.model_admin.get_empty_value_display()
|
||||
row_classes = ['field-%s' % field_name]
|
||||
try:
|
||||
f, attr, value = lookup_field(field_name, result, cl.model_admin)
|
||||
except ObjectDoesNotExist:
|
||||
result_repr = empty_value_display
|
||||
else:
|
||||
empty_value_display = getattr(attr, 'empty_value_display', empty_value_display)
|
||||
if f is None:
|
||||
if field_name == 'action_checkbox':
|
||||
row_classes = ['action-checkbox']
|
||||
allow_tags = getattr(attr, 'allow_tags', False)
|
||||
boolean = getattr(attr, 'boolean', False)
|
||||
result_repr = display_for_value(value, empty_value_display, boolean)
|
||||
# Strip HTML tags in the resulting text, except if the
|
||||
# function has an "allow_tags" attribute set to True.
|
||||
# WARNING: this will be deprecated in Django 2.0
|
||||
if allow_tags:
|
||||
result_repr = mark_safe(result_repr)
|
||||
if isinstance(value, (datetime.date, datetime.time)):
|
||||
row_classes.append('nowrap')
|
||||
else:
|
||||
if isinstance(getattr(f, 'remote_field'), models.ManyToOneRel):
|
||||
field_val = getattr(result, f.name)
|
||||
if field_val is None:
|
||||
result_repr = empty_value_display
|
||||
else:
|
||||
result_repr = field_val
|
||||
else:
|
||||
result_repr = display_for_field(value, f, empty_value_display)
|
||||
if isinstance(f, (models.DateField, models.TimeField,
|
||||
models.ForeignKey)):
|
||||
row_classes.append('nowrap')
|
||||
if force_str(result_repr) == '':
|
||||
result_repr = mark_safe(' ')
|
||||
row_class = mark_safe(' class="%s"' % ' '.join(row_classes))
|
||||
return result_repr, row_class
|
||||
|
||||
|
||||
def get_spacer(first, result):
|
||||
if first:
|
||||
spacer = '<span class="spacer"> </span>' * (
|
||||
result.get_depth() - 1)
|
||||
else:
|
||||
spacer = ''
|
||||
|
||||
return spacer
|
||||
|
||||
|
||||
def get_collapse(result):
|
||||
if result.get_children_count():
|
||||
collapse = ('<a href="#" title="" class="collapse expanded">'
|
||||
'-</a>')
|
||||
else:
|
||||
collapse = '<span class="collapse"> </span>'
|
||||
|
||||
return collapse
|
||||
|
||||
|
||||
def get_drag_handler(first):
|
||||
drag_handler = ''
|
||||
if first:
|
||||
drag_handler = ('<td class="drag-handler">'
|
||||
'<span> </span></td>')
|
||||
return drag_handler
|
||||
|
||||
|
||||
def items_for_result(cl, result, form):
|
||||
"""
|
||||
Generates the actual list of data.
|
||||
|
||||
@jjdelc:
|
||||
This has been shamelessly copied from original
|
||||
django.contrib.admin.templatetags.admin_list.items_for_result
|
||||
in order to alter the dispay for the first element
|
||||
"""
|
||||
first = True
|
||||
pk = cl.lookup_opts.pk.attname
|
||||
for field_name in cl.list_display:
|
||||
result_repr, row_class = get_result_and_row_class(cl, field_name,
|
||||
result)
|
||||
# If list_display_links not defined, add the link tag to the
|
||||
# first field
|
||||
if (first and not cl.list_display_links) or \
|
||||
field_name in cl.list_display_links:
|
||||
table_tag = {True: 'th', False: 'td'}[first]
|
||||
# This spacer indents the nodes based on their depth
|
||||
spacer = get_spacer(first, result)
|
||||
# This shows a collapse or expand link for nodes with childs
|
||||
collapse = get_collapse(result)
|
||||
# Add a <td/> before the first col to show the drag handler
|
||||
drag_handler = get_drag_handler(first)
|
||||
first = False
|
||||
url = cl.url_for_result(result)
|
||||
# Convert the pk to something that can be used in Javascript.
|
||||
# Problem cases are long ints (23L) and non-ASCII strings.
|
||||
if cl.to_field:
|
||||
attr = str(cl.to_field)
|
||||
else:
|
||||
attr = pk
|
||||
value = result.serializable_value(attr)
|
||||
result_id = "'%s'" % force_str(value)
|
||||
onclickstr = (
|
||||
' onclick="opener.dismissRelatedLookupPopup(window, %s);'
|
||||
' return false;"')
|
||||
yield mark_safe(
|
||||
'%s<%s%s>%s %s <a href="%s"%s>%s</a></%s>' % (
|
||||
drag_handler, table_tag, row_class, spacer, collapse, url,
|
||||
(cl.is_popup and onclickstr % result_id or ''),
|
||||
conditional_escape(result_repr), table_tag))
|
||||
else:
|
||||
# By default the fields come from ModelAdmin.list_editable, but if
|
||||
# we pull the fields out of the form instead of list_editable
|
||||
# custom admins can provide fields on a per request basis
|
||||
if (
|
||||
form and
|
||||
field_name in form.fields and
|
||||
not (
|
||||
field_name == cl.model._meta.pk.name and
|
||||
form[cl.model._meta.pk.name].is_hidden
|
||||
)
|
||||
):
|
||||
bf = form[field_name]
|
||||
result_repr = mark_safe(force_str(bf.errors) + force_str(bf))
|
||||
yield format_html('<td{0}>{1}</td>', row_class, result_repr)
|
||||
if form and not form[cl.model._meta.pk.name].is_hidden:
|
||||
yield format_html('<td>{0}</td>', force_str(form[cl.model._meta.pk.name]))
|
||||
|
||||
|
||||
def get_parent_id(node):
|
||||
"""Return the node's parent id or 0 if node is a root node."""
|
||||
if node.is_root():
|
||||
return 0
|
||||
return node.get_parent().pk
|
||||
|
||||
|
||||
def results(cl):
|
||||
if cl.formset:
|
||||
for res, form in zip(cl.result_list, cl.formset.forms):
|
||||
yield (res.pk, get_parent_id(res), res.get_depth(),
|
||||
res.get_children_count(),
|
||||
list(items_for_result(cl, res, form)))
|
||||
else:
|
||||
for res in cl.result_list:
|
||||
yield (res.pk, get_parent_id(res), res.get_depth(),
|
||||
res.get_children_count(),
|
||||
list(items_for_result(cl, res, None)))
|
||||
|
||||
|
||||
def check_empty_dict(GET_dict):
|
||||
"""
|
||||
Returns True if the GET query string contains on values, but it can contain
|
||||
empty keys.
|
||||
This is better than doing not bool(request.GET) as an empty key will return
|
||||
True
|
||||
"""
|
||||
empty = True
|
||||
for k, v in GET_dict.items():
|
||||
# Don't disable on p(age) or 'all' GET param
|
||||
if v and k != 'p' and k != 'all':
|
||||
empty = False
|
||||
return empty
|
||||
|
||||
|
||||
@register.inclusion_tag(
|
||||
'admin/tree_change_list_results.html', takes_context=True)
|
||||
def result_tree(context, cl, request):
|
||||
"""
|
||||
Added 'filtered' param, so the template's js knows whether the results have
|
||||
been affected by a GET param or not. Only when the results are not filtered
|
||||
you can drag and sort the tree
|
||||
"""
|
||||
|
||||
# Here I'm adding an extra col on pos 2 for the drag handlers
|
||||
headers = list(result_headers(cl))
|
||||
headers.insert(1 if needs_checkboxes(context) else 0, {
|
||||
'text': '+',
|
||||
'sortable': True,
|
||||
'url': request.path,
|
||||
'tooltip': _('Return to ordered tree'),
|
||||
'class_attrib': mark_safe(' class="oder-grabber"')
|
||||
})
|
||||
return {
|
||||
'filtered': not check_empty_dict(request.GET),
|
||||
'result_hidden_fields': list(result_hidden_fields(cl)),
|
||||
'result_headers': headers,
|
||||
'results': list(results(cl)),
|
||||
}
|
||||
47
env/lib/python3.10/site-packages/treebeard/templatetags/admin_tree_list.py
vendored
Normal file
47
env/lib/python3.10/site-packages/treebeard/templatetags/admin_tree_list.py
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
from django.template import Library
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.contrib.admin.options import TO_FIELD_VAR
|
||||
|
||||
from treebeard.templatetags import needs_checkboxes
|
||||
|
||||
|
||||
register = Library()
|
||||
CHECKBOX_TMPL = ('<input type="checkbox" class="action-select" value="{}" '
|
||||
'name="_selected_action" />')
|
||||
|
||||
|
||||
def _line(context, node, request):
|
||||
pk_field = node._meta.model._meta.pk.attname
|
||||
if TO_FIELD_VAR in request.GET and request.GET[TO_FIELD_VAR] == pk_field:
|
||||
raw_id_fields = format_html("""
|
||||
onclick="opener.dismissRelatedLookupPopup(window, '{}'); return false;"
|
||||
""", node.pk)
|
||||
else:
|
||||
raw_id_fields = ''
|
||||
output = ''
|
||||
if needs_checkboxes(context):
|
||||
output += format_html(CHECKBOX_TMPL, node.pk)
|
||||
return output + format_html(
|
||||
'<a href="{}/" {}>{}</a>',
|
||||
node.pk, mark_safe(raw_id_fields), str(node))
|
||||
|
||||
|
||||
def _subtree(context, node, request):
|
||||
tree = ''
|
||||
for subnode in node.get_children():
|
||||
tree += format_html(
|
||||
'<li>{}</li>',
|
||||
mark_safe(_subtree(context, subnode, request)))
|
||||
if tree:
|
||||
tree = format_html('<ul>{}</ul>', mark_safe(tree))
|
||||
return _line(context, node, request) + tree
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def result_tree(context, cl, request):
|
||||
tree = ''
|
||||
for root_node in cl.model.get_root_nodes():
|
||||
tree += format_html(
|
||||
'<li>{}</li>', mark_safe(_subtree(context, root_node, request)))
|
||||
return format_html("<ul>{}</ul>", mark_safe(tree))
|
||||
0
env/lib/python3.10/site-packages/treebeard/tests/__init__.py
vendored
Normal file
0
env/lib/python3.10/site-packages/treebeard/tests/__init__.py
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/admin.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/admin.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/conftest.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/conftest.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/manage.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/manage.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/models.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/models.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/settings.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/settings.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/test_migrations.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/test_migrations.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/test_treebeard.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/test_treebeard.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/urls.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/__pycache__/urls.cpython-310.pyc
vendored
Normal file
Binary file not shown.
18
env/lib/python3.10/site-packages/treebeard/tests/admin.py
vendored
Normal file
18
env/lib/python3.10/site-packages/treebeard/tests/admin.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import itertools
|
||||
|
||||
from django.contrib import admin
|
||||
from treebeard.admin import admin_factory
|
||||
from treebeard.forms import movenodeform_factory
|
||||
|
||||
from treebeard.tests.models import BASE_MODELS, UNICODE_MODELS, DEP_MODELS
|
||||
|
||||
|
||||
def register(admin_site, model):
|
||||
form_class = movenodeform_factory(model)
|
||||
admin_class = admin_factory(form_class)
|
||||
admin_site.register(model, admin_class)
|
||||
|
||||
|
||||
def register_all(admin_site=admin.site):
|
||||
for model in itertools.chain(BASE_MODELS, UNICODE_MODELS, DEP_MODELS):
|
||||
register(admin_site, model)
|
||||
15
env/lib/python3.10/site-packages/treebeard/tests/conftest.py
vendored
Normal file
15
env/lib/python3.10/site-packages/treebeard/tests/conftest.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Pytest configuration file
|
||||
"""
|
||||
import os
|
||||
|
||||
os.environ["DJANGO_SETTINGS_MODULE"] = "treebeard.tests.settings"
|
||||
|
||||
import django
|
||||
|
||||
|
||||
def pytest_report_header(config):
|
||||
return "Django: " + django.get_version()
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
django.setup()
|
||||
10
env/lib/python3.10/site-packages/treebeard/tests/manage.py
vendored
Normal file
10
env/lib/python3.10/site-packages/treebeard/tests/manage.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "treebeard.tests.settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
381
env/lib/python3.10/site-packages/treebeard/tests/migrations/0001_initial.py
vendored
Normal file
381
env/lib/python3.10/site-packages/treebeard/tests/migrations/0001_initial.py
vendored
Normal file
@@ -0,0 +1,381 @@
|
||||
# Generated by Django 3.1.2 on 2021-02-24 20:44
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AL_TestNode',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('sib_order', models.PositiveIntegerField()),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children_set', to='tests.al_testnode')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestNode',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('path', models.CharField(max_length=255, unique=True)),
|
||||
('depth', models.PositiveIntegerField()),
|
||||
('numchild', models.PositiveIntegerField(default=0)),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestNodeAlphabet',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('path', models.CharField(max_length=255, unique=True)),
|
||||
('depth', models.PositiveIntegerField()),
|
||||
('numchild', models.PositiveIntegerField(default=0)),
|
||||
('numval', models.IntegerField()),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestNodeCustomId',
|
||||
fields=[
|
||||
('path', models.CharField(max_length=255, unique=True)),
|
||||
('depth', models.PositiveIntegerField()),
|
||||
('numchild', models.PositiveIntegerField(default=0)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestNodeShortPath',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('path', models.CharField(max_length=4, unique=True)),
|
||||
('depth', models.PositiveIntegerField()),
|
||||
('numchild', models.PositiveIntegerField(default=0)),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestNodeSmallStep',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('path', models.CharField(max_length=255, unique=True)),
|
||||
('depth', models.PositiveIntegerField()),
|
||||
('numchild', models.PositiveIntegerField(default=0)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestNodeSorted',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('path', models.CharField(max_length=255, unique=True)),
|
||||
('depth', models.PositiveIntegerField()),
|
||||
('numchild', models.PositiveIntegerField(default=0)),
|
||||
('val1', models.IntegerField()),
|
||||
('val2', models.IntegerField()),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestNodeSortedAutoNow',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('path', models.CharField(max_length=255, unique=True)),
|
||||
('depth', models.PositiveIntegerField()),
|
||||
('numchild', models.PositiveIntegerField(default=0)),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestNodeUuid',
|
||||
fields=[
|
||||
('path', models.CharField(max_length=255, unique=True)),
|
||||
('depth', models.PositiveIntegerField()),
|
||||
('numchild', models.PositiveIntegerField(default=0)),
|
||||
('custom_id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False)),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestSortedNodeShortPath',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('path', models.CharField(max_length=4, unique=True)),
|
||||
('depth', models.PositiveIntegerField()),
|
||||
('numchild', models.PositiveIntegerField(default=0)),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_UnicodeNode',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('path', models.CharField(max_length=255, unique=True)),
|
||||
('depth', models.PositiveIntegerField()),
|
||||
('numchild', models.PositiveIntegerField(default=0)),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NS_TestNode',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('lft', models.PositiveIntegerField(db_index=True)),
|
||||
('rgt', models.PositiveIntegerField(db_index=True)),
|
||||
('tree_id', models.PositiveIntegerField(db_index=True)),
|
||||
('depth', models.PositiveIntegerField(db_index=True)),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NS_TestNodeSorted',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('lft', models.PositiveIntegerField(db_index=True)),
|
||||
('rgt', models.PositiveIntegerField(db_index=True)),
|
||||
('tree_id', models.PositiveIntegerField(db_index=True)),
|
||||
('depth', models.PositiveIntegerField(db_index=True)),
|
||||
('val1', models.IntegerField()),
|
||||
('val2', models.IntegerField()),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NS_UnicodetNode',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('lft', models.PositiveIntegerField(db_index=True)),
|
||||
('rgt', models.PositiveIntegerField(db_index=True)),
|
||||
('tree_id', models.PositiveIntegerField(db_index=True)),
|
||||
('depth', models.PositiveIntegerField(db_index=True)),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RelatedModel',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AL_TestNodeInherited',
|
||||
fields=[
|
||||
('al_testnode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.al_testnode')),
|
||||
('extra_desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('tests.al_testnode',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestNodeInherited',
|
||||
fields=[
|
||||
('mp_testnode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.mp_testnode')),
|
||||
('extra_desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('tests.mp_testnode',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NS_TestNodeInherited',
|
||||
fields=[
|
||||
('ns_testnode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.ns_testnode')),
|
||||
('extra_desc', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('tests.ns_testnode',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NS_TestNodeSomeDep',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('node', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.ns_testnode')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NS_TestNodeRelated',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('lft', models.PositiveIntegerField(db_index=True)),
|
||||
('rgt', models.PositiveIntegerField(db_index=True)),
|
||||
('tree_id', models.PositiveIntegerField(db_index=True)),
|
||||
('depth', models.PositiveIntegerField(db_index=True)),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
('related', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.relatedmodel')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestNodeSomeDep',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('node', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.mp_testnode')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestNodeRelated',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('path', models.CharField(max_length=255, unique=True)),
|
||||
('depth', models.PositiveIntegerField()),
|
||||
('numchild', models.PositiveIntegerField(default=0)),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
('related', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.relatedmodel')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestManyToManyWithUser',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('path', models.CharField(max_length=255, unique=True)),
|
||||
('depth', models.PositiveIntegerField()),
|
||||
('numchild', models.PositiveIntegerField(default=0)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('users', models.ManyToManyField(to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AL_UnicodeNode',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('sib_order', models.PositiveIntegerField()),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children_set', to='tests.al_unicodenode')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AL_TestNodeSorted',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('val1', models.IntegerField()),
|
||||
('val2', models.IntegerField()),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children_set', to='tests.al_testnodesorted')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AL_TestNodeSomeDep',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('node', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.al_testnode')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AL_TestNodeRelated',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('sib_order', models.PositiveIntegerField()),
|
||||
('desc', models.CharField(max_length=255)),
|
||||
('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children_set', to='tests.al_testnoderelated')),
|
||||
('related', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tests.relatedmodel')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AL_TestNode_Proxy',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('tests.al_testnode',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MP_TestNode_Proxy',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('tests.mp_testnode',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NS_TestNode_Proxy',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('tests.ns_testnode',),
|
||||
),
|
||||
]
|
||||
0
env/lib/python3.10/site-packages/treebeard/tests/migrations/__init__.py
vendored
Normal file
0
env/lib/python3.10/site-packages/treebeard/tests/migrations/__init__.py
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/migrations/__pycache__/0001_initial.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/migrations/__pycache__/0001_initial.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/treebeard/tests/migrations/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/treebeard/tests/migrations/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
Binary file not shown.
299
env/lib/python3.10/site-packages/treebeard/tests/models.py
vendored
Normal file
299
env/lib/python3.10/site-packages/treebeard/tests/models.py
vendored
Normal file
@@ -0,0 +1,299 @@
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from treebeard.mp_tree import MP_Node
|
||||
from treebeard.al_tree import AL_Node
|
||||
from treebeard.ns_tree import NS_Node
|
||||
|
||||
|
||||
class RelatedModel(models.Model):
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self):
|
||||
return self.desc
|
||||
|
||||
|
||||
class MP_TestNode(MP_Node):
|
||||
steplen = 3
|
||||
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class MP_UnicodeNode(MP_Node):
|
||||
steplen = 3
|
||||
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return self.desc
|
||||
|
||||
|
||||
class MP_TestNodeSomeDep(models.Model):
|
||||
node = models.ForeignKey(MP_TestNode, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeRelated(MP_Node):
|
||||
steplen = 3
|
||||
|
||||
desc = models.CharField(max_length=255)
|
||||
related = models.ForeignKey(RelatedModel, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeInherited(MP_TestNode):
|
||||
extra_desc = models.CharField(max_length=255)
|
||||
|
||||
|
||||
class MP_TestNodeCustomId(MP_Node):
|
||||
steplen = 3
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class NS_TestNode(NS_Node):
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class NS_UnicodetNode(NS_Node):
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return self.desc
|
||||
|
||||
|
||||
class NS_TestNodeSomeDep(models.Model):
|
||||
node = models.ForeignKey(NS_TestNode, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class NS_TestNodeRelated(NS_Node):
|
||||
desc = models.CharField(max_length=255)
|
||||
related = models.ForeignKey(RelatedModel, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class NS_TestNodeInherited(NS_TestNode):
|
||||
extra_desc = models.CharField(max_length=255)
|
||||
|
||||
|
||||
class AL_TestNode(AL_Node):
|
||||
parent = models.ForeignKey(
|
||||
"self",
|
||||
related_name="children_set",
|
||||
null=True,
|
||||
db_index=True,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
sib_order = models.PositiveIntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class AL_UnicodeNode(AL_Node):
|
||||
parent = models.ForeignKey(
|
||||
"self",
|
||||
related_name="children_set",
|
||||
null=True,
|
||||
db_index=True,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
sib_order = models.PositiveIntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return self.desc
|
||||
|
||||
|
||||
class AL_TestNodeSomeDep(models.Model):
|
||||
node = models.ForeignKey(AL_TestNode, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class AL_TestNodeRelated(AL_Node):
|
||||
parent = models.ForeignKey(
|
||||
"self",
|
||||
related_name="children_set",
|
||||
null=True,
|
||||
db_index=True,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
sib_order = models.PositiveIntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
related = models.ForeignKey(RelatedModel, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class AL_TestNodeInherited(AL_TestNode):
|
||||
extra_desc = models.CharField(max_length=255)
|
||||
|
||||
|
||||
class MP_TestNodeSorted(MP_Node):
|
||||
steplen = 1
|
||||
node_order_by = ["val1", "val2", "desc"]
|
||||
val1 = models.IntegerField()
|
||||
val2 = models.IntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class NS_TestNodeSorted(NS_Node):
|
||||
node_order_by = ["val1", "val2", "desc"]
|
||||
val1 = models.IntegerField()
|
||||
val2 = models.IntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class AL_TestNodeSorted(AL_Node):
|
||||
parent = models.ForeignKey(
|
||||
"self",
|
||||
related_name="children_set",
|
||||
null=True,
|
||||
db_index=True,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
node_order_by = ["val1", "val2", "desc"]
|
||||
val1 = models.IntegerField()
|
||||
val2 = models.IntegerField()
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeAlphabet(MP_Node):
|
||||
steplen = 2
|
||||
|
||||
numval = models.IntegerField()
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeSmallStep(MP_Node):
|
||||
steplen = 1
|
||||
alphabet = "0123456789"
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeSortedAutoNow(MP_Node):
|
||||
desc = models.CharField(max_length=255)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
node_order_by = ["created"]
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeShortPath(MP_Node):
|
||||
steplen = 1
|
||||
alphabet = "012345678"
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
class MP_TestNodeUuid(MP_Node):
|
||||
steplen = 1
|
||||
custom_id = models.UUIDField(primary_key=True, default=uuid.uuid1, editable=False)
|
||||
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %s" % self.pk
|
||||
|
||||
|
||||
# This is how you change the default fields defined in a Django abstract class
|
||||
# (in this case, MP_Node), since Django doesn't allow overriding fields, only
|
||||
# mehods and attributes
|
||||
MP_TestNodeShortPath._meta.get_field("path").max_length = 4
|
||||
|
||||
|
||||
class MP_TestNode_Proxy(MP_TestNode):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
|
||||
class NS_TestNode_Proxy(NS_TestNode):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
|
||||
class AL_TestNode_Proxy(AL_TestNode):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
|
||||
class MP_TestSortedNodeShortPath(MP_Node):
|
||||
steplen = 1
|
||||
alphabet = "012345678"
|
||||
desc = models.CharField(max_length=255)
|
||||
|
||||
node_order_by = ["desc"]
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return "Node %d" % self.pk
|
||||
|
||||
|
||||
MP_TestSortedNodeShortPath._meta.get_field("path").max_length = 4
|
||||
|
||||
|
||||
class MP_TestManyToManyWithUser(MP_Node):
|
||||
name = models.CharField(max_length=255)
|
||||
users = models.ManyToManyField(User)
|
||||
|
||||
|
||||
BASE_MODELS = (
|
||||
AL_TestNode,
|
||||
MP_TestNode,
|
||||
NS_TestNode,
|
||||
MP_TestNodeUuid,
|
||||
MP_TestNodeCustomId,
|
||||
)
|
||||
PROXY_MODELS = AL_TestNode_Proxy, MP_TestNode_Proxy, NS_TestNode_Proxy
|
||||
SORTED_MODELS = AL_TestNodeSorted, MP_TestNodeSorted, NS_TestNodeSorted
|
||||
DEP_MODELS = AL_TestNodeSomeDep, MP_TestNodeSomeDep, NS_TestNodeSomeDep
|
||||
MP_SHORTPATH_MODELS = MP_TestNodeShortPath, MP_TestSortedNodeShortPath
|
||||
RELATED_MODELS = AL_TestNodeRelated, MP_TestNodeRelated, NS_TestNodeRelated
|
||||
UNICODE_MODELS = AL_UnicodeNode, MP_UnicodeNode, NS_UnicodetNode
|
||||
INHERITED_MODELS = (AL_TestNodeInherited, MP_TestNodeInherited, NS_TestNodeInherited)
|
||||
|
||||
|
||||
def empty_models_tables(models):
|
||||
for model in models:
|
||||
model.objects.all().delete()
|
||||
92
env/lib/python3.10/site-packages/treebeard/tests/settings.py
vendored
Normal file
92
env/lib/python3.10/site-packages/treebeard/tests/settings.py
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Django settings for testing treebeard
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def get_db_conf():
|
||||
"""
|
||||
Configures database according to the DATABASE_ENGINE environment
|
||||
variable. Defaults to SQlite.
|
||||
|
||||
This method is used to run tests against different database backends.
|
||||
"""
|
||||
database_engine = os.environ.get('DATABASE_ENGINE', 'sqlite')
|
||||
if database_engine == 'sqlite':
|
||||
return {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ':memory:'
|
||||
}
|
||||
elif database_engine == 'psql':
|
||||
return {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'treebeard',
|
||||
'USER': os.environ.get('DATABASE_USER_POSTGRES', 'treebeard'),
|
||||
'PASSWORD': os.environ.get('DATABASE_PASSWORD', ''),
|
||||
'HOST': os.environ.get('DATABASE_HOST', 'localhost'),
|
||||
'PORT': os.environ.get('DATABASE_PORT_POSTGRES', ''),
|
||||
}
|
||||
elif database_engine == "mysql":
|
||||
return {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'treebeard',
|
||||
'USER': os.environ.get('DATABASE_USER_MYSQL', 'treebeard'),
|
||||
'PASSWORD': os.environ.get('DATABASE_PASSWORD', ''),
|
||||
'HOST': os.environ.get('DATABASE_HOST', 'localhost'),
|
||||
'PORT': os.environ.get('DATABASE_PORT_MYSQL', ''),
|
||||
}
|
||||
elif database_engine == "mssql":
|
||||
return {
|
||||
'ENGINE': 'mssql',
|
||||
'NAME': 'master',
|
||||
'USER': 'sa',
|
||||
'PASSWORD': 'Password12!',
|
||||
'HOST': '(local)\\SQL2019',
|
||||
'PORT': '',
|
||||
'OPTIONS': {
|
||||
'driver': 'SQL Server Native Client 11.0',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
DATABASES = {'default': get_db_conf()}
|
||||
SECRET_KEY = '7r33b34rd'
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.messages',
|
||||
'treebeard',
|
||||
'treebeard.tests'
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware'
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'treebeard.tests.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.i18n',
|
||||
'django.template.context_processors.media',
|
||||
'django.template.context_processors.static',
|
||||
'django.template.context_processors.tz',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
54
env/lib/python3.10/site-packages/treebeard/tests/test_migrations.py
vendored
Normal file
54
env/lib/python3.10/site-packages/treebeard/tests/test_migrations.py
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Check that all changes to Treebeard models have had migrations created in our test app. If there
|
||||
are outstanding model changes that need migrations, fail the tests.
|
||||
|
||||
This module is taken from https://github.com/wagtail/wagtail/blob/master/wagtail/core/tests/test_migrations.py.
|
||||
"""
|
||||
|
||||
from django.apps import apps
|
||||
from django.db.migrations.autodetector import MigrationAutodetector
|
||||
from django.db.migrations.loader import MigrationLoader
|
||||
from django.db.migrations.questioner import MigrationQuestioner
|
||||
from django.db.migrations.state import ProjectState
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
class TestForMigrations(TestCase):
|
||||
def test__migrations(self):
|
||||
app_labels = set(app.label for app in apps.get_app_configs() if app.name.startswith('tests.'))
|
||||
for app_label in app_labels:
|
||||
apps.get_app_config(app_label.split('.')[-1])
|
||||
loader = MigrationLoader(None, ignore_no_migrations=True)
|
||||
|
||||
conflicts = dict(
|
||||
(app_label, conflict)
|
||||
for app_label, conflict in loader.detect_conflicts().items()
|
||||
if app_label in app_labels
|
||||
)
|
||||
|
||||
if conflicts:
|
||||
name_str = "; ".join("%s in %s" % (", ".join(names), app) for app, names in conflicts.items())
|
||||
self.fail("Conflicting migrations detected (%s)." % name_str)
|
||||
|
||||
autodetector = MigrationAutodetector(
|
||||
loader.project_state(),
|
||||
ProjectState.from_apps(apps),
|
||||
MigrationQuestioner(specified_apps=app_labels, dry_run=True),
|
||||
)
|
||||
|
||||
changes = autodetector.changes(
|
||||
graph=loader.graph,
|
||||
trim_to_apps=app_labels or None,
|
||||
convert_apps=app_labels or None,
|
||||
)
|
||||
|
||||
if changes:
|
||||
migrations = '\n'.join((
|
||||
' {migration}\n{changes}'.format(
|
||||
migration=migration,
|
||||
changes='\n'.join(' {0}'.format(operation.describe())
|
||||
for operation in migration.operations))
|
||||
for (_, migrations) in changes.items()
|
||||
for migration in migrations))
|
||||
|
||||
self.fail('Model changes with no migrations detected:\n%s' % migrations)
|
||||
3102
env/lib/python3.10/site-packages/treebeard/tests/test_treebeard.py
vendored
Normal file
3102
env/lib/python3.10/site-packages/treebeard/tests/test_treebeard.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
8
env/lib/python3.10/site-packages/treebeard/tests/urls.py
vendored
Normal file
8
env/lib/python3.10/site-packages/treebeard/tests/urls.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
]
|
||||
Reference in New Issue
Block a user