public
Description: utilities for implementing a modified pre-order traversal tree in django
Homepage: http://code.google.com/p/django-mptt/
Clone URL: git://github.com/brosner/django-mptt.git
Implemented insertion ordering based on multiple fields - the 
order_insertion_by argument to mptt.register must now be a list or other 
iterable of field names

git-svn-id: http://django-mptt.googlecode.com/svn/trunk@100 
7eea2103-5a42-0410-ac78-6383eb04bdbf
jonathan.buchanan (author)
Thu Jan 24 18:58:56 -0800 2008
commit  684a503f7df0f84a23dc7b55e24a495c8a3e59a1
tree    4aef179c225fae4823778774a858783a7a71c548
parent  418566dab0cb1657ccdfef1f475ba0e9740b7344
...
7
8
9
 
 
 
 
 
 
 
10
11
12
...
7
8
9
10
11
12
13
14
15
16
17
18
19
0
@@ -7,6 +7,13 @@ Django MPTT CHANGELOG
0
 Trunk Changes
0
 =============
0
 
0
+Fri 25th Jan, 2008
0
+------------------
0
+
0
+* BACKWARDS-INCOMPATIBLE CHANGE - the ``order_insertion_by`` argument
0
+ to ``mptt.register`` must now be a list or other iterable of field
0
+ names.
0
+
0
 Tue 22nd Jan, 2008
0
 ------------------
0
 
...
83
84
85
86
87
88
 
 
 
 
 
 
89
90
91
...
106
107
108
109
 
110
111
112
...
83
84
85
 
 
 
86
87
88
89
90
91
92
93
94
...
109
110
111
 
112
113
114
115
0
@@ -83,9 +83,12 @@ exist, they will be added to the model dynamically:
0
    used to work with trees of model instances. Defaults to ``'tree'``.
0
 
0
 ``order_insertion_by``
0
- The name of a field which should define ordering when new tree nodes
0
- are being inserted or existing nodes are being reparented. Defaults
0
- to ``None``.
0
+ A list of field names which should define ordering when new tree
0
+ nodes are being inserted or existing nodes are being reparented, with
0
+ the most significant ordering field name first. Defaults to ``[]``.
0
+
0
+ It is assumed that any field identified as defining ordering will
0
+ never be ``NULL`` in the database.
0
 
0
    Note that this will require an extra database query to determine
0
    where nodes should be positioned when they are being saved. This
0
@@ -106,7 +109,7 @@ arguments which specify fields and the tree manager attribute::
0
        name = models.CharField(max_length=50, unique=True)
0
        parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
0
 
0
- mptt.register(Genre, order_insertion_by='name')
0
+ mptt.register(Genre, order_insertion_by=['name'])
0
 
0
 
0
 Model instance methods added by Django MPTT
...
2
3
4
 
 
 
5
6
7
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
10
11
12
13
 
 
 
14
15
16
...
19
20
21
22
23
 
 
24
25
 
26
27
 
28
29
30
 
31
32
 
33
34
35
36
 
 
37
38
39
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 
 
39
40
41
42
43
44
...
47
48
49
 
 
50
51
52
 
53
54
 
55
56
57
 
58
59
 
60
61
62
 
 
63
64
65
66
67
0
@@ -2,15 +2,43 @@
0
 Signal receiving functions which handle Modified Preorder Tree Traversal
0
 related logic when model instances are about to be saved or deleted.
0
 """
0
+import operator
0
+
0
+from django.db.models.query import Q
0
 from django.utils.translation import ugettext as _
0
 
0
 __all__ = ('pre_save', 'pre_delete')
0
 
0
+def _insertion_target_filters(node, order_insertion_by):
0
+ """
0
+ Creates a filter which matches suitable right siblings for ``node``,
0
+ where insertion should maintain ordering according to the list of
0
+ fields in ``order_insertion_by``.
0
+
0
+ For example, given an ``order_insertion_by`` of
0
+ ``['field1', 'field2', 'field3']``, the resulting filter should
0
+ correspond to the following SQL::
0
+
0
+ field1 > %s
0
+ OR (field1 = %s AND field2 > %s)
0
+ OR (field1 = %s AND field2 = %s AND field3 > %s)
0
+
0
+ """
0
+ fields = []
0
+ filters = []
0
+ for field in order_insertion_by:
0
+ value = getattr(node, field)
0
+ filters.append(reduce(operator.and_, [Q(**{f: v}) for f, v in fields] +
0
+ [Q(**{'%s__gt' % field: value})]))
0
+ fields.append((field, value))
0
+ return reduce(operator.or_, filters)
0
+
0
 def _get_ordered_insertion_target(node, parent):
0
     """
0
     Attempts to retrieve a suitable right sibling for ``node``
0
- underneath ``parent`` so that ordering by the field specified by
0
- the node's class' ``order_insertion_by`` field is maintained.
0
+ underneath ``parent`` (which may be ``None`` in the case of root
0
+ nodes) so that ordering by the fields specified by the node's class'
0
+ ``order_insertion_by`` option is maintained.
0
 
0
     Returns ``None`` if no suitable sibling can be found.
0
     """
0
@@ -19,21 +47,21 @@ def _get_ordered_insertion_target(node, parent):
0
     # the node will always be its last child.
0
     if parent is None or parent.get_descendant_count() > 0:
0
         opts = node._meta
0
- filters = {'%s__gt' % opts.order_insertion_by: getattr(node, opts.order_insertion_by)}
0
- order_by = [opts.order_insertion_by]
0
+ order_by = opts.order_insertion_by[:]
0
+ filters = _insertion_target_filters(node, order_by)
0
         if parent:
0
- filters[opts.parent_attr] = parent
0
+ filters = filters & Q(**{opts.parent_attr: parent})
0
             # Fall back on tree ordering if multiple child nodes have
0
- # the same name.
0
+ # the same values.
0
             order_by.append(opts.left_attr)
0
         else:
0
- filters['%s__isnull' % opts.parent_attr] = True
0
+ filters = filters & Q(**{'%s__isnull' % opts.parent_attr: True})
0
             # Fall back on tree id ordering if multiple root nodes have
0
- # the same name.
0
+ # the same values.
0
             order_by.append(opts.tree_id_attr)
0
         try:
0
- right_sibling = node._default_manager.filter(
0
- **filters).order_by(*order_by)[0]
0
+ right_sibling = \
0
+ node._default_manager.filter(filters).order_by(*order_by)[0]
0
         except IndexError:
0
             # No suitable right sibling could be found
0
             pass
...
19
20
21
 
 
 
 
 
 
 
 
 
22
23
24
...
35
36
37
 
38
39
40
 
41
...
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
44
45
46
47
48
49
 
50
51
0
@@ -19,6 +19,15 @@ class Genre(models.Model):
0
 class Insert(models.Model):
0
     parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
0
 
0
+class MultiOrder(models.Model):
0
+ name = models.CharField(max_length=50)
0
+ size = models.PositiveIntegerField()
0
+ date = models.DateField()
0
+ parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
0
+
0
+ def __unicode__(self):
0
+ return self.name
0
+
0
 class Node(models.Model):
0
     parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
0
 
0
@@ -35,7 +44,8 @@ class Tree(models.Model):
0
 mptt.register(Category)
0
 mptt.register(Genre)
0
 mptt.register(Insert)
0
+mptt.register(MultiOrder, order_insertion_by=['name', 'size', 'date'])
0
 mptt.register(Node, left_attr='does', right_attr='zis', level_attr='madness',
0
               tree_id_attr='work')
0
-mptt.register(OrderedInsertion, order_insertion_by='name')
0
+mptt.register(OrderedInsertion, order_insertion_by=['name'])
0
 mptt.register(Tree)
...
1
 
2
3
 
4
5
6
...
1407
1408
1409
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1410
1411
1412
...
1
2
3
 
4
5
6
7
...
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
0
@@ -1,6 +1,7 @@
0
 r"""
0
+>>> from datetime import date
0
 >>> from mptt.exceptions import InvalidMove
0
->>> from mptt.tests.models import Category, Genre, Insert, Node, OrderedInsertion, Tree
0
+>>> from mptt.tests.models import Category, Genre, Insert, MultiOrder, Node, OrderedInsertion, Tree
0
 
0
 >>> def print_tree_details(nodes):
0
 ... opts = nodes[0]._meta
0
@@ -1407,6 +1408,76 @@ ValueError: Cannot insert a node which has already been saved.
0
 2 - 2 0 1 2
0
 3 - 3 0 1 4
0
 1 3 3 1 2 3
0
+
0
+# Insertion of positioned nodes, multiple ordering criteria ###################
0
+>>> r1 = MultiOrder.objects.create(name='fff', size=20, date=date(2008, 1, 1))
0
+
0
+# Root nodes - ordering by subsequent fields
0
+>>> r2 = MultiOrder.objects.create(name='fff', size=10, date=date(2009, 1, 1))
0
+>>> print_tree_details(MultiOrder.tree.all())
0
+2 - 1 0 1 2
0
+1 - 2 0 1 2
0
+
0
+>>> r3 = MultiOrder.objects.create(name='fff', size=20, date=date(2007, 1, 1))
0
+>>> print_tree_details(MultiOrder.tree.all())
0
+2 - 1 0 1 2
0
+3 - 2 0 1 2
0
+1 - 3 0 1 2
0
+
0
+>>> r4 = MultiOrder.objects.create(name='fff', size=20, date=date(2008, 1, 1))
0
+>>> print_tree_details(MultiOrder.tree.all())
0
+2 - 1 0 1 2
0
+3 - 2 0 1 2
0
+1 - 3 0 1 2
0
+4 - 4 0 1 2
0
+
0
+>>> r5 = MultiOrder.objects.create(name='fff', size=20, date=date(2007, 1, 1))
0
+>>> print_tree_details(MultiOrder.tree.all())
0
+2 - 1 0 1 2
0
+3 - 2 0 1 2
0
+5 - 3 0 1 2
0
+1 - 4 0 1 2
0
+4 - 5 0 1 2
0
+
0
+>>> r6 = MultiOrder.objects.create(name='aaa', size=999, date=date(2010, 1, 1))
0
+>>> print_tree_details(MultiOrder.tree.all())
0
+6 - 1 0 1 2
0
+2 - 2 0 1 2
0
+3 - 3 0 1 2
0
+5 - 4 0 1 2
0
+1 - 5 0 1 2
0
+4 - 6 0 1 2
0
+
0
+# Child nodes
0
+>>> r1 = MultiOrder.objects.get(pk=r1.pk)
0
+>>> c1 = MultiOrder.objects.create(parent=r1, name='hhh', size=10, date=date(2009, 1, 1))
0
+>>> print_tree_details(MultiOrder.tree.filter(tree_id=r1.tree_id))
0
+1 - 5 0 1 4
0
+7 1 5 1 2 3
0
+
0
+>>> r1 = MultiOrder.objects.get(pk=r1.pk)
0
+>>> c2 = MultiOrder.objects.create(parent=r1, name='hhh', size=20, date=date(2008, 1, 1))
0
+>>> print_tree_details(MultiOrder.tree.filter(tree_id=r1.tree_id))
0
+1 - 5 0 1 6
0
+7 1 5 1 2 3
0
+8 1 5 1 4 5
0
+
0
+>>> r1 = MultiOrder.objects.get(pk=r1.pk)
0
+>>> c3 = MultiOrder.objects.create(parent=r1, name='hhh', size=15, date=date(2008, 1, 1))
0
+>>> print_tree_details(MultiOrder.tree.filter(tree_id=r1.tree_id))
0
+1 - 5 0 1 8
0
+7 1 5 1 2 3
0
+9 1 5 1 4 5
0
+8 1 5 1 6 7
0
+
0
+>>> r1 = MultiOrder.objects.get(pk=r1.pk)
0
+>>> c4 = MultiOrder.objects.create(parent=r1, name='hhh', size=15, date=date(2008, 1, 1))
0
+>>> print_tree_details(MultiOrder.tree.filter(tree_id=r1.tree_id))
0
+1 - 5 0 1 10
0
+7 1 5 1 2 3
0
+9 1 5 1 4 5
0
+10 1 5 1 6 7
0
+8 1 5 1 8 9
0
 """
0
 
0
 # TODO Fixtures won't work with Django MPTT unless the pre_save signal

Comments

    No one has commented yet.