0

I have next models in my Django project.

models.py:

class Document(models.Model):
    name = models.TextField(blank=True, null=True)

    def get_children(self):
        return [dc.child for dc in DocumentClosure.objects.filter(parent=self)]

class DocumentClosure(models.Model):
    parent = models.ForeignKey(
        Document,
        on_delete=models.CASCADE,
        related_name="closures_as_parent",
        db_column='parent_id',
        blank=True,
        null=True
    )

    child = models.ForeignKey(
        Document,
        on_delete=models.CASCADE,
        related_name="closures_as_child",
        db_column='child_id',
        blank=True,
        null=True
    )

    level = models.IntegerField(
        default=0,
        blank=True,
        null=True
    )

Next models create 2 table in database with "Closure Table" architecture. DocumentClosure store information about ancestors and descendants. I want to show tree in template as it make django-mptt application.

I started with next code. Now I need to rewrite get_children() method based on my current models. Can someone help me with this method?!

template:

<ul>
    {% recursetree documents %}
        <li>
            {{ node.name }}
            <ul class="children">
                {{ children }}
            </ul>
        </li>
    {% endrecursetree %}
</ul>

custom_tag.py:

from django import template

register = template.Library()

@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])
    template_nodes = parser.parse(('endrecursetree',))
    parser.delete_first_token()
    return RecurseTreeNode(template_nodes, queryset_var)


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

    def _render_node(self, context, node):
        bits = []
        context.push()
        for child in node.get_children():  # get_children() initialized in models.py file | rewrite this method
            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 = queryset
        bits = [self._render_node(context, node) for node in roots]
        return ''.join(bits)

EDIT: After python manage.py runserver command in console I see the endless repetitive queries to the database. But after some time it raise error:

Traceback (most recent call last):
File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/core/handlers/base.py", line 217, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/core/handlers/base.py", line 215, in _get_response
    response = response.render()
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/response.py", line 107, in render
    self.content = self.rendered_content
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/response.py", line 84, in rendered_content
    content = template.render(context, self._request)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/backends/django.py", line 66, in render
    return self.template.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 207, in render
    return self._render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 199, in _render
    return self.nodelist.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 990, in render
    bit = node.render_annotated(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 957, in render_annotated
    return self.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/loader_tags.py", line 177, in render
    return compiled_parent._render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 199, in _render
    return self.nodelist.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 990, in render
    bit = node.render_annotated(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 957, in render_annotated
    return self.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/loader_tags.py", line 177, in render
    return compiled_parent._render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 199, in _render
    return self.nodelist.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 990, in render
    bit = node.render_annotated(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 957, in render_annotated
    return self.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/loader_tags.py", line 72, in render
    result = block.nodelist.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 990, in render
    bit = node.render_annotated(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 957, in render_annotated
    return self.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/loader_tags.py", line 72, in render
    result = block.nodelist.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 990, in render
    bit = node.render_annotated(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 957, in render_annotated
    return self.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/loader_tags.py", line 216, in render
    return template.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 209, in render
    return self._render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 199, in _render
    return self.nodelist.render(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 990, in render
    bit = node.render_annotated(context)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/template/base.py", line 957, in render_annotated
    return self.render(context)
  File "/Applications/Projects/web/project/documents/templatetags/documents_tags.py", line 41, in render
    bits = [self._render_node(context, node) for node in roots]
  File "/Applications/Projects/web/project/documents/templatetags/documents_tags.py", line 31, in _render_node
    bits.append(self._render_node(context, child))
File "/Applications/Projects/web/project/documents/models.py", line 117, in get_children
    return [dc.child for dc in DocumentClosure.objects.filter(parent=self)]
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/query.py", line 250, in __iter__
    self._fetch_all()
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/query.py", line 1118, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/query.py", line 53, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 871, in execute_sql
    sql, params = self.as_sql()
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/backends/oracle/compiler.py", line 21, in as_sql
    with_col_aliases=with_col_aliases,
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 436, in as_sql
    where, w_params = self.compile(self.where) if self.where is not None else ("", [])
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 373, in compile
    sql, params = node.as_sql(self, self.connection)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/sql/where.py", line 79, in as_sql
    sql, params = compiler.compile(child)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 373, in compile
    sql, params = node.as_sql(self, self.connection)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/fields/related_lookups.py", line 127, in as_sql
    return super(RelatedLookupMixin, self).as_sql(compiler, connection)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/lookups.py", line 169, in as_sql
    lhs_sql, params = self.process_lhs(compiler, connection)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/lookups.py", line 162, in process_lhs
    db_type = self.lhs.output_field.db_type(connection=connection)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/fields/related.py", line 991, in db_type
    return self.target_field.rel_db_type(connection=connection)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 951, in rel_db_type
    return IntegerField().db_type(connection=connection)
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 166, in __init__
    if isinstance(choices, collections.Iterator):
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/bin/../lib/python2.7/abc.py", line 141, in __instancecheck__
    subtype in cls._abc_negative_cache):
  File "/Users/nurzhan_nogerbek/Virtualenvs/py2714/bin/../lib/python2.7/_weakrefset.py", line 75, in __contains__
    return wr in self.data
RuntimeError: maximum recursion depth exceeded in cmp

Lets say I have such tree. Example:

A
|
 - B
   |
    - C

document table:

id | parent_id | name
1  |           | A
2  | 1         | B
3  | 2         | C

Finally document_closure table:

id | parent_id | child_id | level
1  | 1         | 1        | 0
2  | 2         | 2        | 0
3  | 3         | 3        | 0
4  | 1         | 2        | 1
5  | 2         | 3        | 1
6  | 1         | 3        | 2
Nurzhan Nogerbek
  • 4,806
  • 16
  • 87
  • 193
  • Why don't you actually use django-mptt? That has a [`recursetree` template tag](https://django-mptt.github.io/django-mptt/templates.html#recursetree) already. And it has support for get_children. – Daniel Roseman Jan 08 '18 at 09:16
  • I expected that someone will ask it. I wrote it in my post. I tried to make `"Closure Table"` architecture. `django-mptt` is great application but it use other architecture. – Nurzhan Nogerbek Jan 08 '18 at 09:40
  • You just copied whole the code from django-mptt and give us a task... "get_children() initialized in models.py file | rewrite this method" - you should actually use your closure architecture and write it on your own - it is not that complicated! – dahrens Jan 08 '18 at 10:40
  • Hello, my friend! :) Well, I wrote it in my post. My code based on `django-mptt`. Main login here is the same in custom_tag.py file, I just remove cache in `render` method which mptt use. Now my main aim is to rewrite `get_children` method. I dont understand how it works. In the same time I understand how mptt tag file works. I know that its not easy task. For thats why I ask some ideas about realisation of get_children method. – Nurzhan Nogerbek Jan 08 '18 at 11:22
  • Why do you have `parent_id` on Document? And if you need it - why don't you use it? Did you post whole the code of your models or just relevant parts? And may you tell something about your motivation to use the closure table approach - look like a common tree with parent reference in children might be sufficient?. – dahrens Jan 10 '18 at 09:37

1 Answers1

0

Select all DocumentClosure objects that have your Document as parent and return their children.

Return a list with their linked child Document like this:

class Document(models.Model):
    name = models.TextField(blank=True, null=True)

    def get_children(self):
        return [dc.child for dc in DocumentClosure.objects.filter(parent=self)]

You should consider renaming the related_name of both ForeignKey relations on DocumentClosure - they create properties with confusing names. Document.parents returns a Queryset of DocumentClosure objects where the given Document is the parent. One would expect it to include the parents of the current document. Maybe something like closures_as_parent and closures_as_child fits better.

dahrens
  • 3,879
  • 1
  • 20
  • 38
  • Hello, my friend! :) As you advised I changed `related_name` of both `ForeignKey` relations on `DocumentClosure`. In my `views.py` file I use `context['documents'] = Document.objects.filter(***)`. Also I add your code in `models.py` file. When I run server something strange happens. In console I see the endless repetitive queries to the database. Finally after some time (15-20 seconds) it raise error: `RuntimeError: maximum recursion depth exceeded in cmp`. I add to my post full Traceback. Can you check it pls?! – Nurzhan Nogerbek Jan 09 '18 at 04:55
  • May you please add some numbers regarding your trees? max_depth, node_count, average_count_per_lvl - just to see whether this approach is worth debugging. Additional - have you tried using the models and `get_children()` in a django shell? First quick guess - try to set `related_name='+'` to prevent the backward relation at all. – dahrens Jan 09 '18 at 10:54
  • Hello @dahrens I add simple example with some real data of my tables at the end of my post. Can you check it pls. I set `related_name='+'` to both ForeignKey but problem is steal the same. Do you have any ideas, my friend? – Nurzhan Nogerbek Jan 10 '18 at 04:56