Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic order in django-mptt

I am using a django-mptt package for my comments application and I have following model for this:

class Comment(MPTTModel):
    content = models.TextField(verbose_name='Treść')
    author = models.ForeignKey(AUTH_USER_MODEL, verbose_name='Autor', blank=False, null=True)
    is_deleted = models.BooleanField(verbose_name='Komentarz usunięty', default=False,
                                     help_text='Zaznacz, aby usunąć komentarz')

    ip = models.GenericIPAddressField(default=0, verbose_name='Adres IP')

    content_type = models.ForeignKey(ContentType, verbose_name='Typ obiektu')
    object_id = models.PositiveIntegerField(verbose_name='ID obiektu')
    content_object = GenericForeignKey('content_type', 'object_id')
    parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True)
    hotness = models.FloatField(default=0)

    created_at = models.DateTimeField(auto_now_add=False, verbose_name='Data dodania')

    updated_at = models.DateTimeField(auto_now=True, verbose_name='Aktualizacja')

    class MPTTMeta:
        order_insertion_by = ('-hotness', '-created_at')

    class Meta:
        verbose_name = 'Komentarz'
        verbose_name_plural = 'Komentarze'

    def __unicode__(self):
        if len(self.content) > 50:
            return self.content[:50] + '...'
        else:
            return self.content

I would like to give user possibility to sort comment tree by hotness or creation date. Is it possible to edit order_insertion_by field from view to generate 2 types of sorting(by date, by hotness)? Thanks for your help.

like image 420
Peterek Avatar asked Jul 10 '15 13:07

Peterek


1 Answers

The Modified Preorder Tree Traversal (MPTT) is a way to retrieve a tree structure with one query using left (lft in mptt) and right (rgt) numbering as shown here http://sitepointstatic.com/graphics/sitepoint_numbering.gif.

Defining more than one order_insertion_by will do the following(according to mptts comments):

"""
    Creates a filter which matches suitable right siblings for ``node``,
    where insertion should maintain ordering according to the list of
    fields in ``order_insertion_by``.

    For example, given an ``order_insertion_by`` of
    ``['field1', 'field2', 'field3']``, the resulting filter should
    correspond to the following SQL::

       field1 > %s
       OR (field1 = %s AND field2 > %s)
       OR (field1 = %s AND field2 = %s AND field3 > %s)

"""

If I understand it correctly, order_insertion_by specifies the ordering of siblings, which represent children (not descendants) of a parent element. If you want two different orders, lft and rgt would have to change aswell, and thus it is a second tree. This is not included in mptt.

You could still do

Comment.objects.all().order_by('-hotness')

but you would lose the tree structure. It is generally not possible to maintain the tree structure and order the whole tree by something else, e.g. hotness. Imagine you have the following:

Comment1 (hotness 0)
    Comment2 (hotness 2, child of Comment1)
Comment3 (hotness 1)

which would result in

Comment2
Comment3
Comment1

It is ordered, but Comment2 is not attached to Comment1. If you want to sort using something else than defined by order_insertion_by on a sibling-level-basis, to get the following:

Comment3
Comment1
    Comment2

one might be able to write a new template tag like {% recursetree objects -hotness %} that iterates over and re-sorts children elements and returns the new tree. It still is one database query - but I cannot estimate the performance hit.

You will have to fork mptt and edit mptt_tags.py as follows:

class RecurseTreeNode(template.Node):
    def __init__(self, template_nodes, queryset_var, order_var=None):
        self.template_nodes = template_nodes
        self.queryset_var = queryset_var
        self.order_var = order_var

    def _render_node(self, context, node):
        bits = []
        context.push()

        children = node.get_children()

        if children and self.order_var is not None:
            children = children.order_by(self.order_var)            

        for child in children:
            bits.append(self._render_node(context, child))
        context['node'] = node
        context['children'] = mark_safe(''.join(bits))
        rendered = self.template_nodes.render(context)
        context.pop()
        return rendered

    def render(self, context):
        queryset = self.queryset_var.resolve(context)
        roots = cache_tree_children(queryset)
        bits = [self._render_node(context, node) for node in roots]
        return ''.join(bits)


@register.tag
def recursetree(parser, token):
    bits = token.contents.split()
    if len(bits) < 2:
        raise template.TemplateSyntaxError(_('%s tag requires a queryset') % bits[0])

    queryset_var = template.Variable(bits[1])

    if len(bits) == 3:
        order_var = bits[2]
    else:
        order_var = None

    template_nodes = parser.parse(('endrecursetree',))
    parser.delete_first_token()

    return RecurseTreeNode(template_nodes, queryset_var, order_var)
like image 200
biodiv Avatar answered Sep 21 '22 15:09

biodiv