0

I got a tree that have several levels. Bucky at thenewboston said not to break the first rule of coding - Never repeat the code.

Well, here I am breaking this rule. Andcanät figure out how to avoid this.

My plan is also to be able to individually collapse each branch. But first I want to populate the tree without typing out ten repeated code. This is how I print out my tree.

{% block side-menu %}
    <div id="main-menu" role="navigation">
        <div id="main-menu-inner">
            {% load mptt_tags %}
            <ul class="navigation">
                {% for a in nodes %}
                    {% if a.item_parent is None %}
                        <li>
                            <i class="menu-icon glyphicon glyphicon-chevron-right"></i>
                            <span class="mm-text">{{ a.item_title }} - {{  a.get_descendant_count }}</span>
                            {% if a.get_descendant_count > 0 %}
                                {% for b in a.get_children %}
                                    {% if b.get_previous_sibling is none %}<ul class="children">{% endif %}
                                        {% if a.item_title == b.item_parent|stringformat:"s" %}
                                            <li>
                                                <i class="menu-icon glyphicon glyphicon-chevron-right"></i>
                                                <span class="mm-text">{{ b.item_title }} - {{  b.get_descendant_count }}</span>

                                                {% if b.get_descendant_count > 0 %}
                                                    {% for c in b.get_children %}
                                                        {% if c.get_previous_sibling is none %}<ul class="children">{% endif %}
                                                            <li>
                                                                <i class="menu-icon glyphicon glyphicon-chevron-right"></i>
                                                                <span class="mm-text">{{ c.item_title }} - {{  c.get_descendant_count }}</span>
                                                            </li>
                                                        {% if c.get_next_sibling is none %}</ul>{% endif %}
                                                    {% endfor %}
                                                {% endif %}
                                            </li>

                                        {% endif %}
                                    {% if b.get_next_sibling is none %}</ul>{% endif %}
                                {% endfor %}

                            {% endif %}
                        </li>
                    {% endif %}
                {% endfor %}
            </ul>
        </div>
    </div>
{% endblock %}

And this is my simple model

from django.db import models
from mptt.models import MPTTModel, TreeForeignKey

class Item(MPTTModel):
    item_title = models.CharField(max_length=250)
    item_parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True)


    class MPTTMeta:
        order_insertion_by = ['item_title']
        parent_attr = 'item_parent'

    def __str__(self):
        return self.item_title
sumpen
  • 503
  • 6
  • 19
  • You're probably going to have to move logic into Python and use recursion if you want to eliminate duplicate code. See the first answer / comments here for a neat idea: https://stackoverflow.com/questions/32044/how-can-i-render-a-tree-structure-recursive-using-a-django-template – whp May 26 '17 at 23:09
  • @whp Hi, thanks for more info on this. I have made a workaround with ajax. Now I'm sending root items from the view to template. When I click the root item I do a Ajax request to fetch children. In the view I send children to a template the generates the HTML. Then I, in the same view, sends as HttpResponse and inject into a div. This feels so wrong doing this way. But I have not enough knowledge of Django to do it otherwise. It's really hard finding examples. People are usually just answering "do it like this" and no code samples. Which makes it really hard, for a newbie, to learn. – sumpen May 27 '17 at 09:37

1 Answers1

0

I have made a solution by myself. I have no idea if this is a correct way of doing it. Or if the performance is decreased. Or security is bad. But it works.

I will go through the whole process, for all you newbies like me out there.As you can see from my question, I have the app Django-mptt installed. This is required to build up the tree.

First there will be a requesting (from browser) to page. This request is translated in URLS.PY. In URLS the request gets channeled to the correct view in VIEWS.PY. In the view, you make some logic that will be passed to a template. This template is what will be shown to the user.

So first. If you request the url: http://www.WEBSITE.se/items/

URLS.PY will see if you stated what to do if they request item/

urls.py

from django.conf.urls import url

urlpatterns = [
    url(r'^item/$', views.item, name='item'),
]

This means the url request item/ gets sent to the VIEWS.PY and the view item. This in the code states that: views.item

views.py

from django.shortcuts import render
from item.models import Item

    def item(request):
        all_root = Item.objects.filter(item_parent__isnull=True)

        context = {
            'nodes': all_root,
        }
        return render(request, 'item/index.html', context)

Here I'm doing a database search for all rows that have the column item_parent to nothing. The root for each branch does not have a parent. I put the object in a dictionary here called 'context'. I set the name to 'nodes'. This will be the name I can call the object with in the template. Then I render the template with context as a resource for it. As you see, the template is 'item/index.html'.

index.html

{% load mptt_tags %}
            <ul>
                {% for a in nodes %}

                        <li>
                            <i id="glyph_{{ a.id }}"></i>
                            <span class="mm-text" id="{{ a.id }}">{{ a.item_title }}</span>

                            <div style="display:none;" id="tree_{{ a.id }}" class=""></div>
                        </li>

                {% endfor %}
            </ul>

This is all for displaying the root items. If you are wondering about the database. Then this can be viewed in my question. You see the model there.

So, now I want to be able to expand all root with the children. Therefor I have included Jquery and use Ajax.

So I'm adding this script in the template.

index.html

<script>
$(document).on("click",".mm-text", function(){
        console.log("Click Parent: "+ this.id);
        var parent_id = this.id;        
            $.ajax({
                type: 'POST',
                url: '/item/children/',
                data: {parent_id : parent_id, csrfmiddlewaretoken: '{{ csrf_token }}' },
                beforeSend: function() {
                    // If you want to anything before sending request
                    },
                success: function(response) {
                    $('#tree_'+ parent_id).html(response);
                    $('#tree_'+ parent_id).toggle("slow");                    
                },
                error: function(xhr, status, error) {
                    console.log("error");
                    // show error message
                }
            });
         }
    });
</script>

The Ajax request is calling /item/children/ therefor I have to add this in urls.py. Otherwise it does not know where to redirect.

so add this so it will look like this

urls.py

 from django.conf.urls import url

    urlpatterns = [
        url(r'^item/$', views.item, name='item'),
        url(r'^item/children/$', views.get_children, name='get_children'),
    ]

You also need to make a view that the urls is redirecting to. So add this in

views.py

def get_children(request):
    p_id = request.POST['parent_id']

    context = {
        'child_list':  Item.objects.filter(item_parent_id=p_id),
    }
    return_str = render_to_string('item/ax_get_children.html', context)
    return HttpResponse(return_str)

This first makes a render of a template called 'ax_get_children.html'. Then send this html as HttpResponse back to our 'index.html' template.

So lets take a look at

ax_get_children.html

{% load mptt_tags %}
            <ul>
                {% for a in child_list %}

                        <li>
                            <i id="glyph_{{ a.id }}"></i>
                            <span class="mm-text" id="{{ a.id }}">{{ a.item_title }}  </span>                            
                            <div  style="display:none;" id="tree_{{ a.id }}" class=""></div>
                        </li>

                {% endfor %}
            </ul>

That's it. Again. I have no idea if this i a correct approach. That's for someone who actually knows Django to evaluate. I would say it's probably not a good solution. For now, I don't know better. Atleast it works.

sumpen
  • 503
  • 6
  • 19