I have added a custom field for my model (status). But I want to use a custom query for children:
my template tag is:
def get_top_menu(context):
item = Item.objects.all()
try:
item = Item.objects.filter(position__position='top')
except ObjectDoesNotExist:
item = Item.objects.none()
return {
'nodes': item,
}
and template:
<ul class="root">
{% recursetree nodes %}
<li>
{{ node.name }}
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
as doc
How can I using a custom query for children?
Take a look at the MPTTModel instance methods docs (under Models and Managers section). There is a get_children() method which creates a QuerySet containing the immediate children of the model instance, in tree order. The benefit of using this method over the reverse relation provided by the ORM to the instance’s children is that a database query can be avoided in the case where the instance is a leaf node (it has no children).
Related
I have a model which looks like this:
class Category(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField()
parent = models.ForeignKey(
'categories.Category',
null=True,
blank=True,
on_delete=models.CASCADE,
related_name='categories'
)
basically, in the parent field, it references itself. If a parent is set to None, it's the root category.
I use it to build a hierarchy of categories.
What would be the most efficient way to:
fetch all the objects through the hierarchy
display them in a template?
For some reason, select_related does not seem to lead to performance improvements here.
I also found this: How to recursively query in django efficiently?
But had a really hard time applying it to my example, because I still don't really understand what's going on. This was my result:
WITH RECURSIVE hierarchy(slug, parent_id) AS (
SELECT slug, parent_id
FROM categories_category
WHERE parent_id = '18000'
UNION ALL
SELECT sm.slug, sm.parent_id
FROM categories_category AS sm, hierarchy AS h
WHERE sm.parent_id = h.slug
)
SELECT * FROM hierarchy
Would appreciate any help.
Thanks!
One possible solution can be using https://django-mptt.readthedocs.io/en/latest/overview.html#what-is-django-mptt
MPTT is a technique for storing hierarchical data in a database. The
aim is to make retrieval operations very efficient.
The trade-off for this efficiency is that performing inserts and moving items around the tree is more involved, as there’s some extra work required to keep the tree structure in a good state at all times.
from django.db import models
from mptt.models import MPTTModel, TreeForeignKey
class Category(MPTTModel):
name = models.CharField(max_length=50)
slug = models.SlugField()
parent = TreeForeignKey(
'self',
null=True,
blank=True,
on_delete=models.CASCADE,
related_name='children'
)
class MPTTMeta:
order_insertion_by = ['name']
You can use the django-mptt template tag as this:
{% load mptt_tags %}
<ul>
{% recursetree categories %}
<li>
{{ node.name }}
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
There is a tutorial and more information int library docs.
I had the same problem and ended up creating the following function that hits the database once, then sorts out the heirarchy and returns a dict:
def get_category_tree():
categories = Category.objects.order_by('name')
itemtree = {}
# Add 'children' attribute to each category; populate dict
for category in categories:
category.children = {}
itemtree[category.pk] = category
# Add categories to 'children'
for key,value in itemtree.items():
if value.parent_id:
itemtree[value.parent_id].children[key] = value
# Return top-level items
return {k:v for k,v in itemtree.items() if not v.parent_id}
Each value of the returned dict is a top-level Category object which has a children attribute.
You can render it in the template by looping through the dict values. The following example will handle three levels of heirarchy:
<ul>
{% for level1 in category_tree.values %}
<li>
{{ level1.name }}
{% if level1.children %}
<ul>
{for level2 in level1.children.values %}
<li>{{ level2.name }}
{% if level2.children %}
<ul>
{for level3 in level2.children.values %}
<li>{{ level3.name }}</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
If you need to render many levels of the heirarchy, you could consider using template recursion. Have a read of the following question and answers to determine if that might be suitable: Represent a tree of objects in Django template
<ul>
{% for staff in staff_list %}
<li>{{ staff|title }}<br>
{% for person in movie.staff.all %}
{{ person }}<br>
<img src="{{ person.photo.url }}"></li>
{% endfor %}
{% endfor %}
</ul>
I iterate over the list "staff_list" and want to take from the model "movie" the field that is in the variable "staff", but orm django calls the field "staff", but I do not have such a field, and therefore nothing is returned. How to call the data that is in the "staff" variable and not call the "staff" field
Another solution could be to build the template dataset in view and then loop it in template.
Datalogic should be happening in view and template should focus on rendering.
A dictionary with person and related movies should be build in view.
I have two very simple classes for products and photos. I would like to present products on the main page of my store along with photos related to a foreign key. But I do not know how to do this, I found many answers to get this in the view using a join like 'prefetch_related' but my ID changes for each case. So how do you present your photos for each product? are there any Django tags about which I do not know?
my models.py
class Product(models.Model)
name = models.CharField(max_length=10)
class Image(models.Model)
image = models.ImageField()
name_related = models.ForeignKay(Product, on_delate=models.CASCADE)
views.py
def home(request):
product_list = Product.objects.all()[:12]
#img = ??
context = {'product_list': product_list,
}
return render(request, 'home.html', context)
home.html
{% for product in product_list %}
{{ product.name }}
<!-- {{ product.imge }} ' element presenting the first photo for each 'product' model'???-->
{% endfor %}
Any help will be appreciated.
Since a foreign relation has been established you can iterate through the related models from the parent model.
The related models are already accessible from the parent model without doing anything explicit.
In your template you can do this:
{% for product in product_list %}
{{ product.name }}
{% for product_image in product.image.all %}
<!-- Iterate through this products related images -->
{{ product_image.image.url }}
{% endfor %}
{% endfor %}
For performance reasons, you will want to prefetch the relations, or else for every product you will do an additional query, so, in your view you will want to add this:
product_list = Product.objects.all().prefetch_related('image')[:12]
Just do
{% for product in product_list %}
{{ product.name }}
{% for image in product.image.all %}
<!-- {{ image.image.url }} -->?
{% endfor %}
{% endfor %}
We have the following model structure:
Parent (name, slug, country, children[ManyToMany on Child])
Child
(name, country, children[ManyToMany on Grandchild])
Grandchild (name,
country)
Now, in my view, I want to make sure we are only dealing with data for the current country (country=kwargs["country"]) at all levels. Now, I have been using the following (obfuscated code) in order to filter the children. This allows me to simply reference "children" in the template in order to access the filtered child records, however I'm having trouble determining a best-practice solution for applying this same filtering at the grandchild level.
Below is an example of what is working for filtering children, but how do I perform this same filtering within the template when looping through the grandchildren? I don't want a front-end developer to have to understand the data structure - ideally, I would want them to be able to loop through children, and within that loop, loop through child.grandchildren, which will already be filtered.
View class:
class ParentView(DetailView):
model = Parent
def get_object(self):
return self.model.objects.filter(slug=self.kwargs["slug"], country=self.kwargs["country"])
def get_context_data(self, **kwargs):
context = super(ParentView, self).get_context_data(**kwargs)
context["children"] = self.object.children.filter(country=self.country)
return context
Template sample:
{% for child in children %}
<li>{{child.name}}
<ul>
{# This list of grandchildren is NOT filtered #}
{% for grandchild in child.children%}
<li>{{grandchild.name}}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
Please let me know if I can provide additional information.
In the template:
{% for child in children %}
<li>{{ child.name }}
<ul>
{% for grandchild in child.children.all %}
<li>{{ grandchild.name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
I have a queryset in a view limiting on an m2m field. The query appears to work correctly and gets the right parent objects, but when I loop through the parent.object_set.all in the template I see m2m objects that were technically not included in the initial query (or shouldn't be anyway), but now included because they are part of parent's child m2m object_set.
To try put this in a more generic example...I'm completely making up parents and children:
class Parent(models.Model):
name = models.CharField(max_length=42)
children = models.ManyToManyField(Child)
class Child(models.Model):
...
# no, I'm not serious on this as a data structure, but for the example....
male = models.BooleanField()
female = models.BooleanField()
class ParentListView(ListView):
...
def get_queryset(self):
...
parents = Parent.objects.filter(children__male = True)
In the template.
{% for parent in parents %}
{{ parent.name }}
{% for child in parent.child_set.all %}
{% comment %}
oops, I got all the children of this parent....I only want those that were male from the original queryset. I need the child_set to only be those that met the criteria in that first queryset. How do I accomplish this?
{% endcomment %}
{% endfor %}
{% endfor %}
You can't provide filter arguments with the Django template language.
In Django 1.7+, you can use prefetch_related with a custom Prefetch object when defining the queryset in the view.
def get_queryset(self):
...
parents = Parent.objects.filter(
children__male=True
).prefetch_related(
Prefetch('children', queryset=Child.objects.filter(male=True), to_attr='male_children')
)
Note that in the example above, you probably need to use distinct() to prevent duplicated parents for each male child.
We have used the to_attr argument as recommended in the docs. In the template, we then loop through the male children.
{% for parent in parents %}
{{ parent.name }}
{% for child in parent.male_children %}
{{ child }}
{% endfor %}
{% endfor %}