Django : How to use select_related for a OneToOneField? - django

I have created a OneToOneField(parent) in Child model with related_name='children'. In my views, I used select_related to get the queryset. But in my page the list of children associated to a parent shows empty.
Models.py:
class Parent(models.Model):
item = models.CharField(max_length=20)
class Child(models.Model):
parent = models.OneToOneField(Parent, unique = True, related_name = 'children')
price = models.IntegerField()
views.py:
def live_prices(request):
parent_queryset = Parent.objects.all().select_related('children')
return render(request, 'live_prices.html', 'parent_queryset' : parent_queryset)
Template:
{% for parent in parent_queryset %}
{% child in parent.children.all %}
{{ child.price }}
{% endfor %}
{% endfor %}

It's a one to one field, so you simply access parent.children (because you have related_name='children') instead of looping through parent.children.all().
Since there is only one child, I would remove the related_name='children', then you will access parent.child instead of parent.children. You don't need unique=True for a one-to-one field either.
parent = models.OneToOneField(Parent)
Then, in your template:
{% for parent in parent_queryset %}
{{ parent.child.price }}
{% endfor %}
Note that using select_related does not change the way you access the objects in the template, it just reduces the number of SQL queries.

Related

Django PostgreSQL – Efficiently fetch recursive category structure

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

Django manytomany field filter list

I am printing a list of beers matching some filters, and the bars where each is on tap. These are in a manytomany relationship. I need to filter this list of bars to only show those in a given state.
I can achieve this using if statements in the template, but then am unable to format the list to use commas with an 'and' before the final item (like https://stackoverflow.com/a/3649002/6180992), as I do not know the length of the list.
I have thought of three ways this might be possible, but cannot get any to work:
filtering the bars related field as well as the beers in the views
assembling the list in the template before looping through again to print it
filtering the bars related field in the template
Here are the relevant sections of code:
models.py
class Bar(models.Model):
bar = models.CharField(max_length=200, default='FinshnPig')
state = models.CharField(max_length=200,default='NY')
def __str__(self):
return self.bar
class Meta:
ordering = ('bar','region')
class Tap(models.Model):
bar = models.ManyToManyField(Bar,default='FinshnPig')
brewery = models.CharField(max_length=200)
beer = models.CharField(max_length=200)
state = models.CharField(max_length=200, default='NY')
def __str__(self):
return self.beer
views.py
f = TapFilter(request.GET, queryset=Tap.objects.filter(state="VIC"))
template:
{% for tap in filter %}
<li>
<b>{{ tap.beer }}</b>
<em>{{ tap.brewery }}</em>
#{% for bar in tap.bar.all %}{% if bar.state == "VIC" %}{{ bar.bar }}</b>{% endif %}{% include "taplists/comma.html" %}{% endfor %}
</li>
{% endfor %}
You can use prefetch for querying only the related bars before sending it to template, like so:
prefetch = Prefetch(
'bar',
queryset=Bar.objects.filter(state=CHOSEN_STATE),
to_attr='selected_states'
)
filter = Tap.objects.filter(state=CHOSEN_STATE).prefetch_related(prefetch)
Now inside your template use the custom attribute you assigned:
{% for tap in filter %}
# Now selected_bars only contains the bars in the state you want
{% for bar in tap.selected_bars.all %}
...
{% endfor %}
{% endfor %}
Additional info on Django docs for prefetch_related
Note: Using prefetch for ManyToMany relations will also increase the performance, as there won't be as many database lookups.

how reverse query ManytoMany Django

I have these two models and I want to display the project with a specific task in my page. These are my models:
class Project (models.Model):
name = models.CharField(verbose_name="Project name", max_length=25)
tasks = models.ManyToManyField(Task, verbose_name="tasksInProject", blank=True,
related_name="projects+")
class Task(models.Model):
name = models.CharField(verbose_name="Task", max_length=50)
I call this view:
class TaskToProjectFilterView(DetailView):
model = Task
template_name = "vivs/task_filter.html"
context_object_name = "task_filter"
And this is my html template:
<h4>filter : {{ task_filter }} </h4>
<h4>projects :
{% for element in task_filter.projects.all %}
{{ element }}
{% endfor %}
</h4>
This code displays the {{ task_filter }} but not the list of {{ task_filter.projects.all }}.
Can you help me? I don't understand my mistake. Thanks!
As schwobaseggl stated, remove the + sign and it should work as expected, using:
{% for element in task_filter.projects.all %}
From the Django Docs:
If you’d prefer Django not to create a backwards relation, set related_name to '+' or end it with '+'. For example, this will ensure that the User model won’t have a backwards relation to this model:

How can I limit what m2m items are displayed in django template-side?

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 %}

Display name of many to many relation in template

I try to display the names of fields from a Model in my template.
This works fine with any type of fields except ManyToManyField.
I use this function in my models.py to return all fields.
def get_all_fields(self):
"""Returns a list of all field names on the instance."""
fields = []
# only display fields with values and skip some fields entirely
if f.editable and value and f.name not in ('id','lastname','firstname') :
fields.append(
{
'label':f.verbose_name,
'name':f.name,
'value':value,
})
return fields
In my template I use this loop to display all fields:
{% for f in modelname.get_all_fields %}
<td>{{f.label|capfirst}}</td><td>{{f.value|escape|urlize|linebreaks}}</td>
{% endfor %}
As mentioned before, this works fine with all fields except ManyToManyFields.
For example one of my M2M relations looks like this:
family = models.ManyToManyField('family', related_name="family", null=True, blank=True)
I'd be thankful for every hint that helps solving this.
Regards
Conrad
Try to specify verbose_name argument for ManytoManyfield
family = models.ManyToManyField('family',verbose_name=u'trampampam', related_name="family", null=True, blank=True)
You write {{f.value|escape|urlize|linebreaks}}, which displays the value of the field. However, the value of a M2M relation is a set of object instances and you need to iterate again over the set (if that is the result you want):
{% load m2m_filter %}
{% for f in modelname.get_all_fields %}
<td>{{f.label|capfirst}}</td>
<td>
{% if f.value|is_m2m %}
{% for object in f.value.objects.all %}
{{ object|escape|urlize|linebreaks }}
{% endfor %}
{% else %}
{{f.value|escape|urlize|linebreaks}}
{% endif %}
</td>
{% endfor %}
and you also have to create the filter
m2m_filter.py
from django import template
from django.db import models
register = template.Library()
def is_m2m(value):
return type(value) == models.ManyToManyField *
register.filter('is_m2m', is_m2m)
* I guess, it's a different type; just check that