[Python-modules-commits] [python-django-mptt] 01/04: Import python-django-mptt_0.8.3.orig.tar.gz

Brian May bam at moszumanska.debian.org
Wed Apr 6 03:50:05 UTC 2016


This is an automated email from the git hooks/post-receive script.

bam pushed a commit to branch master
in repository python-django-mptt.

commit 20a2efe130927acb6e52aa406666d3c76d36e836
Author: Brian May <bam at debian.org>
Date:   Wed Apr 6 13:35:22 2016 +1000

    Import python-django-mptt_0.8.3.orig.tar.gz
---
 INSTALL                                         |   8 +-
 MANIFEST.in                                     |   1 +
 PKG-INFO                                        |   5 +-
 README.rst                                      |   9 +-
 django_mptt.egg-info/PKG-INFO                   |   5 +-
 django_mptt.egg-info/SOURCES.txt                |  12 +
 django_mptt.egg-info/requires.txt               |   2 +-
 django_mptt.egg-info/top_level.txt              |   1 +
 docs/Makefile                                   |   6 -
 docs/admin.rst                                  | 111 ++++++-
 mptt/__init__.py                                |   3 +-
 mptt/admin.py                                   | 193 ++++++++++-
 mptt/fields.py                                  |   5 +-
 mptt/managers.py                                |  11 +-
 mptt/models.py                                  |  14 +-
 mptt/static/mptt/arrow-move.png                 | Bin 0 -> 457 bytes
 mptt/static/mptt/disclosure-down.png            | Bin 0 -> 496 bytes
 mptt/static/mptt/disclosure-right.png           | Bin 0 -> 464 bytes
 mptt/static/mptt/draggable-admin.css            |  52 +++
 mptt/static/mptt/draggable-admin.js             | 422 ++++++++++++++++++++++++
 mptt/templatetags/mptt_admin.py                 |   8 +-
 setup.cfg                                       |   2 +-
 setup.py                                        |  77 ++---
 tests/__init__.pyc                              | Bin 226 -> 210 bytes
 tests/__pycache__/__init__.cpython-35.pyc       | Bin 0 -> 194 bytes
 tests/__pycache__/settings.cpython-35.pyc       | Bin 0 -> 1525 bytes
 tests/myapp/__pycache__/__init__.cpython-35.pyc | Bin 0 -> 200 bytes
 tests/myapp/__pycache__/admin.cpython-35.pyc    | Bin 0 -> 568 bytes
 tests/myapp/__pycache__/models.cpython-35.pyc   | Bin 0 -> 12127 bytes
 tests/myapp/__pycache__/tests.cpython-35.pyc    | Bin 0 -> 65077 bytes
 tests/myapp/__pycache__/urls.cpython-35.pyc     | Bin 0 -> 349 bytes
 tests/myapp/admin.py                            |   5 +-
 tests/myapp/admin.pyc                           | Bin 632 -> 733 bytes
 tests/myapp/tests.py                            | 108 +++++-
 tests/myapp/tests.pyc                           | Bin 79289 -> 81653 bytes
 tests/settings.py                               |   1 +
 tests/settings.pyc                              | Bin 1597 -> 2156 bytes
 37 files changed, 973 insertions(+), 88 deletions(-)

diff --git a/INSTALL b/INSTALL
index db7ab38..40a3d92 100644
--- a/INSTALL
+++ b/INSTALL
@@ -9,8 +9,8 @@ somewhere on your PYTHONPATH, or symlink to it from somewhere on your
 PYTHONPATH; this is useful if you're working from a git checkout.
 
 Requires:
- - Python 2.6 or newer
- - Django 1.4.2 or newer
+ - Python 2.7 or newer
+ - Django 1.8 or newer
 
-You can obtain Python from http://www.python.org/ and Django from
-http://www.djangoproject.com/
+You can obtain Python from https://www.python.org/ and Django from
+https://www.djangoproject.com/
diff --git a/MANIFEST.in b/MANIFEST.in
index 8fcf272..0cc5997 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -8,3 +8,4 @@ recursive-include mptt *.json
 recursive-include tests *
 recursive-include mptt/templates *
 recursive-include mptt/locale *
+recursive-include mptt/static *
diff --git a/PKG-INFO b/PKG-INFO
index 77d7b08..a24fcc9 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,12 @@
 Metadata-Version: 1.1
 Name: django-mptt
-Version: 0.8.0
+Version: 0.8.3
 Summary: Utilities for implementing Modified Preorder Tree Traversal
         with your Django Models and working with trees of Model instances.
 Home-page: http://github.com/django-mptt/django-mptt
 Author: Craig de Stigter
 Author-email: craig.ds at gmail.com
-License: UNKNOWN
+License: MIT License
 Description: UNKNOWN
 Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.2
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Utilities
diff --git a/README.rst b/README.rst
index 021800e..737a0ee 100644
--- a/README.rst
+++ b/README.rst
@@ -39,7 +39,7 @@ details about how the technique itself works:
 What is ``django-mptt``?
 ========================
 
-``django-mptt`` is a reusable Django app which aims to make it easy for you 
+``django-mptt`` is a reusable Django app which aims to make it easy for you
 to use MPTT with your own Django models.
 
 It takes care of the details of managing a database table as a tree
@@ -49,7 +49,7 @@ Requirements
 ------------
 
 * Python 2.7 or 3.2+
-* A supported version of django (currently 1.8+)
+* A supported version of Django (currently 1.8+)
 
 Feature overview
 ----------------
@@ -72,7 +72,7 @@ Feature overview
 
 * A ``TreeManager`` manager is added to all registered models. This provides
   methods to:
-  
+
   * move nodes around a tree, or into a different tree
   * insert a node anywhere in a tree
   * rebuild the MPTT fields for the tree (useful when you do bulk updates
@@ -83,3 +83,6 @@ Feature overview
 * Utility functions for tree models.
 
 * Template tags and filters for rendering trees.
+
+* Admin classes for visualizing and modifying trees in Django's administration
+  interface.
diff --git a/django_mptt.egg-info/PKG-INFO b/django_mptt.egg-info/PKG-INFO
index 77d7b08..a24fcc9 100644
--- a/django_mptt.egg-info/PKG-INFO
+++ b/django_mptt.egg-info/PKG-INFO
@@ -1,12 +1,12 @@
 Metadata-Version: 1.1
 Name: django-mptt
-Version: 0.8.0
+Version: 0.8.3
 Summary: Utilities for implementing Modified Preorder Tree Traversal
         with your Django Models and working with trees of Model instances.
 Home-page: http://github.com/django-mptt/django-mptt
 Author: Craig de Stigter
 Author-email: craig.ds at gmail.com
-License: UNKNOWN
+License: MIT License
 Description: UNKNOWN
 Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.2
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Utilities
diff --git a/django_mptt.egg-info/SOURCES.txt b/django_mptt.egg-info/SOURCES.txt
index d366748..675959f 100644
--- a/django_mptt.egg-info/SOURCES.txt
+++ b/django_mptt.egg-info/SOURCES.txt
@@ -64,6 +64,11 @@ mptt/locale/pt_BR/LC_MESSAGES/django.mo
 mptt/locale/pt_BR/LC_MESSAGES/django.po
 mptt/locale/ru/LC_MESSAGES/django.mo
 mptt/locale/ru/LC_MESSAGES/django.po
+mptt/static/mptt/arrow-move.png
+mptt/static/mptt/disclosure-down.png
+mptt/static/mptt/disclosure-right.png
+mptt/static/mptt/draggable-admin.css
+mptt/static/mptt/draggable-admin.js
 mptt/templates/admin/grappelli_mptt_change_list.html
 mptt/templates/admin/grappelli_mptt_change_list_results.html
 mptt/templates/admin/mptt_change_list.html
@@ -83,9 +88,11 @@ tests/settings.pyc
 tests/__pycache__/__init__.cpython-32.pyc
 tests/__pycache__/__init__.cpython-33.pyc
 tests/__pycache__/__init__.cpython-34.pyc
+tests/__pycache__/__init__.cpython-35.pyc
 tests/__pycache__/settings.cpython-32.pyc
 tests/__pycache__/settings.cpython-33.pyc
 tests/__pycache__/settings.cpython-34.pyc
+tests/__pycache__/settings.cpython-35.pyc
 tests/myapp/__init__.py
 tests/myapp/__init__.pyc
 tests/myapp/admin.py
@@ -100,14 +107,19 @@ tests/myapp/urls.pyc
 tests/myapp/__pycache__/__init__.cpython-32.pyc
 tests/myapp/__pycache__/__init__.cpython-33.pyc
 tests/myapp/__pycache__/__init__.cpython-34.pyc
+tests/myapp/__pycache__/__init__.cpython-35.pyc
 tests/myapp/__pycache__/admin.cpython-34.pyc
+tests/myapp/__pycache__/admin.cpython-35.pyc
 tests/myapp/__pycache__/models.cpython-32.pyc
 tests/myapp/__pycache__/models.cpython-33.pyc
 tests/myapp/__pycache__/models.cpython-34.pyc
+tests/myapp/__pycache__/models.cpython-35.pyc
 tests/myapp/__pycache__/tests.cpython-32.pyc
 tests/myapp/__pycache__/tests.cpython-33.pyc
 tests/myapp/__pycache__/tests.cpython-34.pyc
+tests/myapp/__pycache__/tests.cpython-35.pyc
 tests/myapp/__pycache__/urls.cpython-34.pyc
+tests/myapp/__pycache__/urls.cpython-35.pyc
 tests/myapp/fixtures/categories.json
 tests/myapp/fixtures/genres.json
 tests/myapp/fixtures/items.json
diff --git a/django_mptt.egg-info/requires.txt b/django_mptt.egg-info/requires.txt
index 363a653..531dd9b 100644
--- a/django_mptt.egg-info/requires.txt
+++ b/django_mptt.egg-info/requires.txt
@@ -1 +1 @@
-Django>=1.8
\ No newline at end of file
+Django>=1.8
diff --git a/django_mptt.egg-info/top_level.txt b/django_mptt.egg-info/top_level.txt
index 7c545e2..5f89f8d 100644
--- a/django_mptt.egg-info/top_level.txt
+++ b/django_mptt.egg-info/top_level.txt
@@ -1 +1,2 @@
 mptt
+tests
diff --git a/docs/Makefile b/docs/Makefile
index 1a7e91c..876c7a0 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -39,12 +39,6 @@ clean:
 html:
 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
 	@echo
-	@echo "renaming sphinx dirs so they're okay for gh-pages"
-	sed -i -s -e 's/"_static\//"static\//g' $(BUILDDIR)/html/*.html
-	mv $(BUILDDIR)/html/_static/ $(BUILDDIR)/html/static
-	sed -i -s -e 's/"_sources\//"sources\//g' $(BUILDDIR)/html/*.html
-	mv $(BUILDDIR)/html/_sources/ $(BUILDDIR)/html/sources
-	@echo
 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
 
 dirhtml:
diff --git a/docs/admin.rst b/docs/admin.rst
index a4b003f..8f5027b 100644
--- a/docs/admin.rst
+++ b/docs/admin.rst
@@ -3,11 +3,16 @@ Admin classes
 =============
 
 ``mptt.admin.MPTTModelAdmin``
------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 This is a bare-bones tree admin. All it does is enforce ordering, and indent the nodes
 in the tree to make a pretty tree list view.
 
+.. image:: mpttmodeladmin-genres.png
+    :align: center
+    :width: 26.21cm
+    :alt: MPTTModelAdmin screenshot
+
 Usage::
 
     from django.contrib import admin
@@ -42,3 +47,107 @@ to your MPTTModelAdmin::
     class CustomMPTTModelAdmin(MPTTModelAdmin):
         mptt_indent_field = "some_node_field"
     # …
+
+
+``mptt.admin.DraggableMPTTAdmin``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 0.8.1
+
+.. image:: draggablempttadmin-genres.png
+    :align: center
+    :width: 26.39cm
+    :alt: DraggableMPTTAdmin screenshot
+
+This is a tree admin based on FeinCMS_ offering drag-drop functionality for
+moving nodes::
+
+    from django.contrib import admin
+    from mptt.admin import DraggableMPTTAdmin
+    from myproject.myapp.models import Node
+
+    admin.site.register(
+        Node,
+        DraggableMPTTAdmin,
+        list_display=(
+            'tree_actions',
+            'indented_title',
+            # ...more fields if you feel like it...
+        ),
+        list_display_links=(
+            'indented_title',
+        ),
+    )
+
+
+.. note::
+
+   Supported browsers include all recent versions of Firefox, Chrome,
+   Safari and Internet Explorer (9 or better).
+
+.. warning::
+
+   Does not work well with big trees (more than a few hundred nodes, or trees
+   deeper than 10 levels). Patches implementing lazy-loading of deep trees
+   are very much appreciated.
+
+
+It is recommended that ``tree_actions`` is the first value passed to
+``list_display``; this also requires you to specify ``list_display_links``
+because ``tree_actions`` cannot be used as the object link field.
+
+``indented_title`` does nothing but return the indented self-description
+of nodes, ``20px`` per level (or the value of ``mptt_level_indent``,
+see below.)
+
+``list_per_page`` is set to 2000 by default (which effectively disables
+pagination for most trees).
+
+
+Replacing ``indented_title``
+----------------------------
+
+If you want to replace the ``indented_title`` method with your own, we
+recommend using the following code::
+
+    from django.utils.html import format_html
+
+    class MyDraggableMPTTAdmin(DraggableMPTTAdmin):
+        list_display = ('tree_actions', 'something')
+        list_display_links = ('something',)
+
+        def something(self, instance):
+            return format_html(
+                '<div style="text-indent:{}px">{}</div>',
+                instance._mpttfield('level') * self.mptt_level_indent,
+                item.name,  # Or whatever you want to put here
+            )
+        something.short_description = _('something nice')
+
+For changing the indentation per node, look below. Simply replacing
+``indented_title`` is insufficient because the indentation also needs
+to be communicated to the JavaScript code.
+
+
+Overriding admin templates per app or model
+-------------------------------------------
+
+``DraggableMPTTAdmin`` uses the stock admin changelist template with some CSS
+and JavaScript on top, so simply follow the official guide for
+`overriding admin templates`_.
+
+
+Changing the indentation of nodes
+---------------------------------
+
+Simply set ``mptt_level_indent`` to a different pixel value (defaults
+to ``20``)::
+
+    # ...
+    class MyDraggableMPTTAdmin(DraggableMPTTAdmin):
+        mptt_level_indent = 50
+    # ...
+
+
+.. _overriding admin templates: https://docs.djangoproject.com/en/1.9/ref/contrib/admin/#overriding-admin-templates
+.. _FeinCMS: https://github.com/feincms/feincms/
diff --git a/mptt/__init__.py b/mptt/__init__.py
index 87ec9ae..f3a8209 100644
--- a/mptt/__init__.py
+++ b/mptt/__init__.py
@@ -1,6 +1,7 @@
 from __future__ import unicode_literals
 
-VERSION = (0, 8, 0)
+VERSION = (0, 8, 3)
+__version__ = '.'.join(str(v) for v in VERSION)
 
 
 def register(*args, **kwargs):
diff --git a/mptt/admin.py b/mptt/admin.py
index f90413a..730a146 100644
--- a/mptt/admin.py
+++ b/mptt/admin.py
@@ -1,15 +1,23 @@
 from __future__ import unicode_literals
 
+import json
+
+from django import http
 from django.conf import settings
 from django.contrib.admin.actions import delete_selected
 from django.contrib.admin.options import ModelAdmin
+from django.db import IntegrityError, transaction
+from django.forms.utils import flatatt
+from django.templatetags.static import static
 from django.utils.encoding import force_text
-from django.utils.translation import ugettext as _
+from django.utils.html import format_html, mark_safe
+from django.utils.translation import ugettext as _, ugettext_lazy
 
+from mptt.exceptions import InvalidMove
 from mptt.forms import MPTTAdminForm, TreeNodeChoiceField
 from mptt.models import MPTTModel, TreeForeignKey
 
-__all__ = ('MPTTModelAdmin', 'MPTTAdminForm')
+__all__ = ('MPTTModelAdmin', 'MPTTAdminForm', 'DraggableMPTTAdmin')
 IS_GRAPPELLI_INSTALLED = 'grappelli' in settings.INSTALLED_APPS
 
 
@@ -84,3 +92,184 @@ class MPTTModelAdmin(ModelAdmin):
                 'delete_selected',
                 _('Delete selected %(verbose_name_plural)s'))
         return actions
+
+
+class JS(object):
+    """
+    Use this to insert a script tag via ``forms.Media`` containing additional
+    attributes (such as ``id`` and ``data-*`` for CSP-compatible data
+    injection.)::
+
+        media.add_js([
+            JS('asset.js', {
+                'id': 'asset-script',
+                'data-the-answer': '"42"',
+            }),
+        ])
+
+    The rendered media tag (via ``{{ media.js }}`` or ``{{ media }}`` will
+    now contain a script tag as follows, without line breaks::
+
+        <script type="text/javascript" src="/static/asset.js"
+            data-answer=""42"" id="asset-script"></script>
+
+    The attributes are automatically escaped. The data attributes may now be
+    accessed inside ``asset.js``::
+
+        var answer = document.querySelector('#asset-script').dataset.answer;
+    """
+    def __init__(self, js, attrs):
+        self.js = js
+        self.attrs = attrs
+
+    def startswith(self, _):
+        # Masquerade as absolute path so that we are returned as-is.
+        return True
+
+    def __html__(self):
+        return format_html(
+            '{}"{}',
+            static(self.js),
+            mark_safe(flatatt(self.attrs)),
+        ).rstrip('"')
+
+
+class DraggableMPTTAdmin(MPTTModelAdmin):
+    """
+    The ``DraggableMPTTAdmin`` modifies the standard Django administration
+    change list to a drag-drop enabled interface.
+    """
+
+    change_list_template = None  # Back to default
+    list_per_page = 2000  # This will take a really long time to load.
+    list_display = ('tree_actions', 'indented_title')  # Sane defaults.
+    list_display_links = ('indented_title',)  # Sane defaults.
+    mptt_level_indent = 20
+
+    def tree_actions(self, item):
+        try:
+            url = item.get_absolute_url()
+        except Exception:  # Nevermind.
+            url = ''
+
+        return format_html(
+            '<div class="drag-handle"></div>'
+            '<div class="tree-node" data-pk="{}" data-level="{}"'
+            ' data-url="{}"></div>',
+            item.pk,
+            item._mpttfield('level'),
+            url,
+        )
+    tree_actions.short_description = ''
+
+    def indented_title(self, item):
+        """
+        Generate a short title for an object, indent it depending on
+        the object's depth in the hierarchy.
+        """
+        return format_html(
+            '<div style="text-indent:{}px">{}</div>',
+            item._mpttfield('level') * self.mptt_level_indent,
+            item,
+        )
+    indented_title.short_description = ugettext_lazy('title')
+
+    def changelist_view(self, request, *args, **kwargs):
+        if request.is_ajax() and request.POST.get('cmd') == 'move_node':
+            return self._move_node(request)
+
+        response = super(DraggableMPTTAdmin, self).changelist_view(
+            request, *args, **kwargs)
+
+        try:
+            response.context_data['media'].add_css({'all': (
+                'mptt/draggable-admin.css',
+            )})
+            response.context_data['media'].add_js((
+                JS('mptt/draggable-admin.js', {
+                    'id': 'draggable-admin-context',
+                    'data-context': json.dumps(self._tree_context(request)),
+                }),
+            ),)
+        except (AttributeError, KeyError):
+            # Not meant for us if there is no context_data attribute (no
+            # TemplateResponse) or no media in the context.
+            pass
+
+        return response
+
+    @transaction.atomic
+    def _move_node(self, request):
+        position = request.POST.get('position')
+        if position not in ('last-child', 'left', 'right'):
+            self.message_user(request, _('Did not understand moving instruction.'))
+            return http.HttpResponse('FAIL, unknown instruction.')
+
+        queryset = self.get_queryset(request)
+        try:
+            cut_item = queryset.get(pk=request.POST.get('cut_item'))
+            pasted_on = queryset.get(pk=request.POST.get('pasted_on'))
+        except (self.model.DoesNotExist, TypeError, ValueError):
+            self.message_user(request, _('Objects have disappeared, try again.'))
+            return http.HttpResponse('FAIL, invalid objects.')
+
+        if not self.has_change_permission(request, cut_item):
+            self.message_user(request, _('No permission'))
+            return http.HttpResponse('FAIL, no permission.')
+
+        try:
+            self.model._tree_manager.move_node(cut_item, pasted_on, position)
+        except InvalidMove as e:
+            self.message_user(request, '%s' % e)
+            return http.HttpResponse('FAIL, invalid move.')
+        except IntegrityError as e:
+            self.message_user(request, _('Database error: %s') % e)
+            raise
+
+        self.message_user(
+            request,
+            _('%s has been successfully moved.') % cut_item)
+        return http.HttpResponse('OK, moved.')
+
+    def _tree_context(self, request):
+        opts = self.model._meta
+
+        return {
+            'storageName': 'tree_%s_%s_collapsed' % (opts.app_label, opts.model_name),
+            'treeStructure': self._build_tree_structure(self.get_queryset(request)),
+            'levelIndent': self.mptt_level_indent,
+            'messages': {
+                'before': _('move node before node'),
+                'child': _('move node to child position'),
+                'after': _('move node after node'),
+                'collapseTree': _('Collapse tree'),
+                'expandTree': _('Expand tree'),
+            },
+        }
+
+    def _build_tree_structure(self, queryset):
+        """
+        Build an in-memory representation of the item tree, trying to keep
+        database accesses down to a minimum. The returned dictionary looks like
+        this (as json dump):
+
+            {"6": [7, 8, 10]
+             "7": [12],
+             ...
+             }
+
+        Leaves are not included in the dictionary.
+        """
+        all_nodes = {}
+
+        mptt_opts = self.model._mptt_meta
+        items = queryset.values_list(
+            'pk',
+            '%s_id' % mptt_opts.parent_attr,
+        )
+        for p_id, parent_id in items:
+            all_nodes.setdefault(
+                str(parent_id) if parent_id else 0,
+                [],
+            ).append(p_id)
+        return all_nodes
diff --git a/mptt/fields.py b/mptt/fields.py
index 69b809a..f7a4ec3 100644
--- a/mptt/fields.py
+++ b/mptt/fields.py
@@ -3,13 +3,14 @@ Model fields for working with trees.
 """
 from __future__ import unicode_literals
 
-__all__ = ('TreeForeignKey', 'TreeOneToOneField', 'TreeManyToManyField')
-
 from django.db import models
 from django.conf import settings
 from mptt.forms import TreeNodeChoiceField, TreeNodeMultipleChoiceField
 
 
+__all__ = ('TreeForeignKey', 'TreeOneToOneField', 'TreeManyToManyField')
+
+
 class TreeForeignKey(models.ForeignKey):
     """
     Extends the foreign key, but uses mptt's ``TreeNodeChoiceField`` as
diff --git a/mptt/managers.py b/mptt/managers.py
index bac96ad..657624e 100644
--- a/mptt/managers.py
+++ b/mptt/managers.py
@@ -5,7 +5,6 @@ from __future__ import unicode_literals
 import contextlib
 from itertools import groupby
 
-import django
 from django.db import models, connections, router
 from django.db.models import F, ManyToManyField, Max, Q
 from django.utils.translation import ugettext as _
@@ -80,11 +79,11 @@ class TreeManager(models.Manager.from_queryset(TreeQuerySet)):
         """
         Ensures that this manager always returns nodes in tree order.
         """
-        if django.VERSION < (1, 7):
-            qs = TreeQuerySet(self.model, using=self._db)
-        else:
-            qs = super(TreeManager, self).get_queryset(*args, **kwargs)
-        return qs.order_by(self.tree_id_attr, self.left_attr)
+        return super(TreeManager, self).get_queryset(
+            *args, **kwargs
+        ).order_by(
+            self.tree_id_attr, self.left_attr
+        )
 
     def _get_queryset_relatives(self, queryset, direction, include_self):
         """
diff --git a/mptt/models.py b/mptt/models.py
index 0d9b8b0..0f84009 100644
--- a/mptt/models.py
+++ b/mptt/models.py
@@ -269,8 +269,8 @@ class MPTTModelBase(ModelBase):
         else:
             bases = [base for base in cls.mro() if issubclass(base, MPTTModel)]
         for base in bases:
-            if (not (base._meta.abstract or base._meta.proxy)
-                    and base._tree_manager.tree_model is base):
+            if (not (base._meta.abstract or base._meta.proxy) and
+                    base._tree_manager.tree_model is base):
                 cls._mptt_tracking_base = base
                 break
         if cls is cls._mptt_tracking_base:
@@ -754,8 +754,8 @@ class MPTTModel(six.with_metaclass(MPTTModelBase, models.Model)):
             right = getattr(self, opts.right_attr)
 
             return (
-                left > getattr(other, opts.left_attr)
-                and right < getattr(other, opts.right_attr))
+                left > getattr(other, opts.left_attr) and
+                right < getattr(other, opts.right_attr))
 
     @raise_if_unsaved
     def is_ancestor_of(self, other, include_self=False):
@@ -902,9 +902,9 @@ class MPTTModel(six.with_metaclass(MPTTModelBase, models.Model)):
                         # we need to update the parent.rght so things like
                         # get_children and get_descendant_count work correctly.
                         update_cached_parent = (
-                            getattr(self, opts.tree_id_attr) != getattr(parent, opts.tree_id_attr)
-                            or getattr(self, opts.left_attr) < getattr(parent, opts.left_attr)
-                            or getattr(self, opts.right_attr) > getattr(parent, opts.right_attr))
+                            getattr(self, opts.tree_id_attr) != getattr(parent, opts.tree_id_attr) or  # noqa
+                            getattr(self, opts.left_attr) < getattr(parent, opts.left_attr) or
+                            getattr(self, opts.right_attr) > getattr(parent, opts.right_attr))
 
                     if right_sibling:
                         self._tree_manager._move_node(
diff --git a/mptt/static/mptt/arrow-move.png b/mptt/static/mptt/arrow-move.png
new file mode 100644
index 0000000..d528679
Binary files /dev/null and b/mptt/static/mptt/arrow-move.png differ
diff --git a/mptt/static/mptt/disclosure-down.png b/mptt/static/mptt/disclosure-down.png
new file mode 100644
index 0000000..5f21757
Binary files /dev/null and b/mptt/static/mptt/disclosure-down.png differ
diff --git a/mptt/static/mptt/disclosure-right.png b/mptt/static/mptt/disclosure-right.png
new file mode 100644
index 0000000..50741ef
Binary files /dev/null and b/mptt/static/mptt/disclosure-right.png differ
diff --git a/mptt/static/mptt/draggable-admin.css b/mptt/static/mptt/draggable-admin.css
new file mode 100644
index 0000000..936daad
--- /dev/null
+++ b/mptt/static/mptt/draggable-admin.css
@@ -0,0 +1,52 @@
+.field-tree_actions {
+  width: 50px;
+  padding: 2px;
+}
+
+.field-tree_actions > div {
+  display: inline-block;
+  vertical-align: middle;
+  background-repeat: no-repeat;
+  width: 18px;
+  height: 18px;
+  margin: 7px 2px 0 0;
+}
+
+.tree-node { cursor: pointer; }
+.tree-node.children { background-image: url(disclosure-down.png); }
+.tree-node.closed { background-image: url(disclosure-right.png); }
+
+.drag-handle { background-image: url(arrow-move.png); cursor: move; }
+
+/* focus */
+#result_list tbody tr:focus {
+  background-color: #ffffcc !important; outline: 0px; }
+
+#drag-line {
+  position: absolute;
+  height: 3px;
+  font-size: 0px;
+  background-color: #417690;
+}
+
+#drag-line:before {
+  content: ' ';
+  display: block;
+  position: absolute;
+  height: 10px;
+  width: 10px;
+  background: #417690;
+  margin: -3px 0 0 0;
+  border-radius: 9px;
+}
+
+#drag-line span {
+    display: block;
+    position: absolute;
+    left: 15px;
+    bottom: 0;
+    width: 300px;
+    height: 18px;
+    color: #417690;
+    font-size: 12px;
+}
diff --git a/mptt/static/mptt/draggable-admin.js b/mptt/static/mptt/draggable-admin.js
new file mode 100644
index 0000000..ec141ad
--- /dev/null
+++ b/mptt/static/mptt/draggable-admin.js
@@ -0,0 +1,422 @@
+/* global django */
+
+// IE<9 lacks Array.prototype.indexOf
+if (!Array.prototype.indexOf) {
+    Array.prototype.indexOf = function(needle) {
+        for (var i=0, l=this.length; i<l; ++i) {
+            if (this[i] === needle) return i;
+        }
+        return -1;
+    };
+}
+
+// https://github.com/jquery/jquery-ui/blob/master/ui/disable-selection.js
+django.jQuery.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');
+    }
+});
+
+
+django.jQuery(function($){
+    // We are not on a changelist it seems.
+    if (!document.getElementById('result_list')) return;
+
+    var DraggableMPTTAdmin = null;
+
+    function isExpandedNode(id) {
+        return DraggableMPTTAdmin.collapsedNodes.indexOf(id) == -1;
+    }
+
+    function markNodeAsExpanded(id) {
+        // remove itemId from array of collapsed nodes
+        var idx = DraggableMPTTAdmin.collapsedNodes.indexOf(id);
+        if(idx >= 0)
+            DraggableMPTTAdmin.collapsedNodes.splice(idx, 1);
+    }
+
+    function markNodeAsCollapsed(id) {
+        if(isExpandedNode(id))
+            DraggableMPTTAdmin.collapsedNodes.push(id);
+    }
+
+    function treeNode(pk) {
+        return $('.tree-node[data-pk="' + pk + '"]');
+    }
+
+    // toggle children
+    function doToggle(id, show) {
+        var children = DraggableMPTTAdmin.treeStructure[id] || [];
+        for (var i=0; i<children.length; ++i) {
+            var childId = children[i];
+            if(show) {
+                treeNode(childId).closest('tr').show();
+                // only reveal children if current node is not collapsed
+                if(isExpandedNode(childId)) {
+                    doToggle(childId, show);
+                }
+            } else {
+                treeNode(childId).closest('tr').hide();
+                // always recursively hide children
+                doToggle(childId, show);
+            }
+        }
+    }
+
+    function rowLevel($row) {
+        try {
+            return $row.find('.tree-node').data('level') || 0;
+        } catch (e) {
+            return 0;
+        }
+    }
+
+    /* Thanks, Django */
+    function getCookie(name) {
+        var cookieValue = null;
+        if (document.cookie && document.cookie != '') {
+            var cookies = document.cookie.split(';');
+            for (var i = 0; i < cookies.length; i++) {
+                var cookie = $.trim(cookies[i]);
+                // Does this cookie string begin with the name we want?
+                if (cookie.substring(0, name.length + 1) == (name + '=')) {
+                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+                    break;
+                }
+            }
+        }
+        return cookieValue;
+    }
+
+    /*
+     * FeinCMS Drag-n-drop tree reordering.
+     * Based upon code by bright4 for Radiant CMS, rewritten for
+     * FeinCMS by Bjorn Post.
+     *
+     * September 2010
+     */
+    $.extend($.fn.feinTree = function() {
+        $.each(DraggableMPTTAdmin.treeStructure, function(key, value) {
+          treeNode(key).addClass('children');
+        });
+
+        $('div.drag-handle').bind('mousedown', function(event) {
+            var BEFORE = 'before';
+            var AFTER = 'after';
+            var CHILD = 'child';
+            var CHILD_PAD = DraggableMPTTAdmin.levelIndent;
+            var originalRow = $(event.target).closest('tr');
+            var rowHeight = originalRow.height();
+            var moveTo = new Object();
+            var resultListWidth = $('#result_list').width();
+
+            $('body').addClass('dragging').disableSelection().bind('mousemove', function(event) {
+                // Remove focus
+                originalRow.blur();
+
+                // attach dragged item to mouse
+                var cloned = originalRow.html();
+                if($('#ghost').length == 0) {
+                    $('<div id="ghost"></div>').appendTo('body');
+                }
+                $('#ghost').html(cloned).css({
+                    'opacity': .8,
+                    'position': 'absolute',
+                    'top': event.pageY,
+                    'left': event.pageX - 30,
+                    'width': 600
+                });
+
+                // check on edge of screen
+                if(event.pageY+100 > $(window).height()+$(window).scrollTop()) {
+                    $('html,body').stop().animate({scrollTop: $(window).scrollTop()+250 }, 500);
+                } else if(event.pageY-50 < $(window).scrollTop()) {
+                    $('html,body').stop().animate({scrollTop: $(window).scrollTop()-250 }, 500);
+                }
+
+                // check if drag-line element already exists, else append
+                if($('#drag-line').length < 1) {
+                    $('body').append('<div id="drag-line"><span></span></div>');
+                }
+
+                // loop trough all rows
+                $('tr', originalRow.parent()).each(function(index, el) {
+                    var element = $(el),
+                        top = element.offset().top,
+                        next;
+
+                    // check if mouse is over a row
+                    if (event.pageY >= top && event.pageY < top + rowHeight) {
+                        var targetRow = null,
+                            targetLoc = null,
+                            elementLevel = rowLevel(element);
+
+                        if (event.pageY >= top && event.pageY < top + rowHeight / 3) {
+                            targetRow = element;
+                            targetLoc = BEFORE;
+                        } else if (event.pageY >= top + rowHeight / 3 && event.pageY < top + rowHeight * 2 / 3) {
+                            next = element.next();
+                            // there's no point in allowing adding children when there are some already
+                            // better move the items to the correct place right away
+                            if (!next.length || rowLevel(next) <= elementLevel) {
+                                targetRow = element;
+                                targetLoc = CHILD;
+                            }
+                        } else if (event.pageY >= top + rowHeight * 2 / 3 && event.pageY < top + rowHeight) {
+                            next = element.next();
+                            if (!next.length || rowLevel(next) <= elementLevel) {
+                                targetRow = element;
+                                targetLoc = AFTER;
+                            }
+                        }
+
+                        if(targetRow) {
+
+                            // Positioning relative to cell containing the link
+                            var offset = targetRow.find('th').offset();
+                            var left = offset.left
+                                + rowLevel(targetRow) * CHILD_PAD
+                                + (targetLoc == CHILD ? CHILD_PAD : 0)
+                                + 5; // Center of the circle aligns with start of link text (cell padding!)
+
+                            $('#drag-line').css({
+                                'width': resultListWidth - left,
+                                'left': left,
+                                'top': offset.top + (targetLoc == BEFORE ? 0 : rowHeight)
+                            }).find('span').text(DraggableMPTTAdmin.messages[targetLoc] || '');
+
+                            // Store the found row and options
+                            moveTo.hovering = element;
+                            moveTo.relativeTo = targetRow;
+                            moveTo.side = targetLoc;
+
+                            return true;
+                        }
+                    }
+                });
+            });
+
+            $('body').keydown(function(event) {
+                if (event.which == '27') {
+                    $('#drag-line').remove();
+                    $('#ghost').remove();
+                    $('body').removeClass('dragging').enableSelection().unbind('mousemove').unbind('mouseup');
+                    event.preventDefault();
+                }
+            });
+
+            $('body').bind('mouseup', function() {
+                if(moveTo.relativeTo) {
+                    var cutItem = originalRow.find('.tree-node').data('pk');
+                    var pastedOn = moveTo.relativeTo.find('.tree-node').data('pk');
+
+                    // get out early if items are the same
+                    if(cutItem != pastedOn) {
+                        var isParent = (
+                           rowLevel(moveTo.relativeTo.next()) >
+                           rowLevel(moveTo.relativeTo));
+
+                        var position = '';
+
+                        // determine position
+                        if(moveTo.side == CHILD && !isParent) {
+                            position = 'last-child';
+                        } else if (moveTo.side == BEFORE) {
... 530 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-django-mptt.git



More information about the Python-modules-commits mailing list