How do I display Django ManyToMany on a template? Simple code needed - django

I am trying to display ManyToMany field on the template in reversed order.
Here is what I mean:
I managed to display ManyToMany field on template when ManyToMany field was a field in model used so for example:
<br/>{% for tag in post.tag.all %}{{ tag }}<br/>{% endfor %}
will display all of the tags(meaning categories) that the post belongs to based on this model:
class Post(models.Model):
tag = models.ManyToManyField(Tag,blank=True,null=True,related_name='tag')
Now I want something opposite - display authors of the post when ManyToMany field is in the Author model (Post model above stays the same):
class Person(models.Model):
post=models.ManyToManyField(Post,blank=True,null=True,related_name='post')
I am quite sure it has something to do with Related Object Reference ( https://docs.djangoproject.com/en/2.2/ref/models/relations/)
Just can not make it work.
I have tried the following on the template.
{% for post in posts %}
{% for author in post.person_set.all %}{{author}}<br/>{% endfor %}
{% endfor %}
Also, shall I do this kind of searches on the template like above or is it a better practice put this kind of searches in views...resourcewise.
Thanks for help.

You have a misunderstanding on what the related_name= parameter [Django-doc] does. Like the documentation says:
The name to use for the relation from the related object back to this one. (...)
So it is the name of the relation in reverse. In order to make your models "sound", you thus should name it like:
class Person(models.Model):
posts = models.ManyToManyField(Post, blank=True, null=True, related_name='authors')
It also makes sense here to use the plural, so posts instead of post.
In that case, you thus can render this with:
{% for post in posts %}
{% for author in post.authors.all %}{{author}}<br/>{% endfor %}
{% endfor %}
Note that if you want to render all the values for ManyToManyFields, you better use .prefetch_related(..) in the queryset to prefetch the Person,s otherwise rendering the template will result in a lot of extra queries.

Related

Retrieve all entries from a ForeignKey and display them in the html template in Django

I'm writing after checking different threads on similar issues with no luck whatsoever, hence I'm sharing with you my problem hoping you can give me a hint on how to solve it.
I am trying to build a keyword ranking checker and I have two models, Keyword and Ranking:
models.py
class Keyword(models.Model):
keyword = models.CharField(max_length=100)
date_added = models.DateTimeField(default=timezone.now)
market = models.CharField(max_length=100)
domain = models.CharField(max_length=100)
class Ranking(models.Model):
keyword = models.ForeignKey(Keyword, on_delete=models.CASCADE)
position = models.IntegerField()
position_date = models.DateTimeField(default=timezone.now)
Basically, user will insert a list of keywords in the DB and an API will check each month the ranking for each keyword. The ranking model will store each ranking check and relate it to the keyword.
What I'd like to do is to display in the html template the list of keywords with their rankings (all of them because I want to show historical rankings too), and here is where I'm having issues. Basically I'm not able to retrieve all the rankings for a given keyword and pass it to the html as a template tag. I only can show the complete list of keywords and that's it.
views.py
def rankings(request):
keywords = Keyword.objects.filter(market="es")
return render(request, 'rankapi/marketview.html', {'keywords': keywords})
I've tried to do it the other way around, starting from rankings, but then I cannot display keywords which have no rankings.
views.py
def rankings(request):
ranking = Ranking.objects.all()
return render(request, 'rankapi/marketview.html', {ranking':ranking})
And then in the html:
marketview.html
{% for rank in ranking %}
{{rank.position}}{{rank.position_date}} {{rank.keyword.keyword}}
{% endfor %}
But this is not showing keywords with no ranking associated to them (recently added keywords or still not checked ones).
Do you have any hint to help me solve this issue?
Thanks a lot!
Not sure what do you mean by
Basically I'm not able to retrieve all the rankings for a given keyword and pass it to the html as a template tag. I only can show the complete list of keywords and that's it.
If there is a ranking object then its must that this ranking object have an associated keyword object because in your Ranking model, you make a ForeignKey relationship with keyword and its a required=True field, django expecting its a required field implicitly i.e you can not create a ranking object without a 'keyword'.
So if you able to retrieve any 'ranking' object , then you should get associated keyword like,
{% for rank in ranking %}
{{rank.position}}{{rank.position_date}} {{rank.keyword.keyword}}{{rank.keyword.domain}}
{% endfor %}
and so on..
I've found the solution which I post here for reference if someone has similar issues:
{% for keyword in keywords %}
{{keyword.keyword}}
{% for key in keyword.ranking_set.all %}
{{key.position}}
{% endfor %}
<br>
{% endfor %}

Django filter related_name subset in templates

I have a foreign key and I'm using the related_name field like so:
class Pizza(models.Model):
...
restaurant = models.ForeignKey('Restaurant', related_name='pizzas_offered')
active = models.BooleanField(...)
...
Example from the view:
my_restaurant = get_object_or_404(Restaurant, pk=id)
In any template I can run something like my_restaurant.pizzas_offered.all to get all the pizzas belonging to a particular restaurant. However, I only want the pizzas that are active (active=True). Is there a way to retrieve this subset in the template, without having to pass a separate variable in the view? Please note that I always want to only show the active pizzas only so if I have to make a change in the model to make this happen, that is fine too.
NOTE: in the view I can simply pass my_restaurant.pizzas_offered.filter(active=True) but it returns an error when I use it in the template:
{% for details in my_restaurant.pizzas_offered.filter(active=True) %}
{{ details.name }}
{% endfor %}
It returns this error:
Could not parse the remainder: '(active=True)'
There are some reasons why I want to do this on template level and not in the view (main reason: I often loop through all the records in the database and not just one restaurant, so I can't just query for the one record). So my question is how to do this on template-level.
You need to create a Manager for your Pizza model and set the Meta.base_manager_name:
class PizzaManager(models.Manager):
def active(self):
return self.filter(status=True)
class Pizza(models.Model):
...
objects = PizzaManager()
class meta:
base_manager_name = 'objects'
Now you can use the method active in your template:
{% for details in my_restaurant.pizzas_offered.active %}
...
{% endfor %}
For more information you can read the documentation about Default and Base managers.

django filter and order foreign-foreignkey in a query

I'm looking for a solution to filter and order foreign key of the foreign key of my model.
Let's say I have:
class Song(models.Model):
author = models.CharField(max_length=200,null=False)
class Songbook(models.Model):
songs = models.ManyToManyField(Song)
I'm already able thanks to annotation to sort my songbook query by song count:
context['popular_songbooks_list'] = Songbook.objects.annotate(
favoritesongbook_count=Count('favoritesongbook')).order_by('-favoritesongbook_count')[:10]
Now, I would like to display only the 3 main author contained in each songbook and I didn't find how to do it. Is there a solution using query or do I have to do a post-processing on the query list?
I didn't find an easy way to do it inside the view by annotating the query, but thanks to this post (django regroup and than dictsort by list length) I find a way to rich my goal.
Here is the solution :
From the popular_songbooks_list I showed in my first post, I use the regroup tag in template with custom filter and then slice to 3.
{% regroup songbook.songs.all by author as author_list %}
{% for author in author_list|groups_sort_reversed|slice:":3" %}
{{ author.grouper }}
{% endfor %}
The custom tag :
#register.filter()
def groups_sort_reversed(groups):
groups.sort(key=lambda group: len(group['list']), reverse=True)
return groups
I'm not sure this is the best solution, but it work !

django best practice query foreign key

I am trying to understand the best way to structure queries in django to avoid excessive database hits.
This is similar to the question: Django best practice with foreign key queries, but involves greater 'depth' in the queries.
My situation:
models.py
class Bucket(models.Model):
categories = models.ManyToManyField('Category')
class Category(models.Model):
name = models.CharField(max_length=50)
class SubCategory(models.Model):
category = models.ForeignKey(Category)
class SubSubCategory(models.Model):
subcat = models.ForeignKey(SubCategory)
views.py
def showbucket(request, id):
bucket = Bucket.objects.prefetch_related('categories',).get(pk=id)
cats = bucket.categories.prefetch_related('subcategory_set__subsubcategory_set',)
return render_to_response('showbucket.html', locals(), context_instance=RequestContext(request))
and relevant template:
{% for c in cats %}
{{c}}
<ul>
{% for d in c.subcategory_set.all %}
<li>{{d}}</li>
<ul>
{% for e in d.subsubcategory_set.all %}
<li>{{e}}</li>
{% endfor %}
</ul>
{% endfor %}
</ul>
{% endfor %}
Despite the use of prefetch_related(), I seem to be hitting the database each time the top two for statements are evaluated, e.g. {% for c in cats %}, (at least I believe so from reading the debug_toolbar). Other ways I've tried have ended up with (C x D x E) number of database hits. Is this something inherently wrong with my use of prefetch, queries, or models? What is the best way in Django to access database objects with a "depth > 1" so-to-speak?
Use select_related() instead:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.select_related
bucket = Bucket.objects.select_related('categories',).get(id=id)
cats = bucket.categories.select_related('subcategory_set__subsubcategory_set',)
So, i found out there's a couple things going on here:
First, my current understanding on select_related vs prefetch_related:
select_related() follows foreign-key relationships, causing larger result sets but means that later use of FK won't require additional database hits. It is limited to FK and one-to-one relationships.
prefetch_related() does a separate lookup for each relationship and joins them in python, and is means to be used for many-to-many, many-to-one, and GenericRelation and GenericForeignKey.
By the book, I should be using prefetch(), as I was not 'following' the Foreign Keys.
That's what I had understood going into this, but my template seemed to be causing additional queries when evaluating the given for loops in the template, even when I added the use of {with} tags.
At first, I had thought I had discovered something similar to this issue, but I am unable to replicate when I built out my simplified example. I switched from using the debug toolbar to direct checking using the following template code (in the article Tracking SQL Queries for a Request using Django by Karen Tracey, I would link but am link-limited):
{% with sql_queries|length as qcount %}
{{ qcount }} quer{{ qcount|pluralize:"y,ies" }}
{% for qdict in sql_queries %}
{{ qdict.sql }} ({{ qdict.time }} seconds)
{% endfor %}
{% endwith %}
Using this method, I am only seeing 5 queries for using pre-fetch() (7 with debug_toolbar), and queries grow linearly when using select_related() (with +2 for debug_toolbar), which I believe is expected.
I will gladly take any other advice/tools on getting a handle on these types of issues.

django get list of distinct 'children' of ForeignKey related model (and do this in template?)

I'm making a database of released music albums
models.py
class Image(models.Model):
image = models.ImageField(....
class Album(models.Model):
title = models.CharField(....
class Release(models.Model):
album = models.ForeignKey(Album)
cover_art = models.ForeignKey(Image, blank=True, null=True, on_delete=models.SET_NULL)
In my template (at the moment I'm using generic views) I have:
{% for a in album_list %}
{% for r in a.release_set.all %}
{% if r.cover_art %}
# display cover art image
{% endif %}
{% endfor %}
{% endfor %}
The problem is that sometimes an album has been released several times with identical cover art, in which case I'd like to display the image only once, with some text listing the releases it pertains to.
I've tried:
{% for i in a.release_set.cover_art %}
{% for i in a.release_set.cover_art_set %}
{% for i in a.release_set.all.cover_art %}
{% for i in a.release_set.all.cover_art_set %}
Or in a simpler case, I'd at least like to display the images smaller if there are more than one of them.
{% if a.release_set.count > 1 %} # works but displays duplicate images
{% if a.release_set.cover_art_set.count > 1 %} # doesn't work (see above)
Is it possible to get a list of objects related by reversing this ForeignKey lookup then asking for the set of their children? The only way I can think of is by assembling some tuples/lists in the view.
I managed this with a new method on the Album model:
class Album(models.Model):
title = models.CharField(....
def distinct_cover_images(self):
"Returns the queryset of distinct images used for this album cover"
pks = self.release_set.all().values_list('cover_art__pk', flat=True)
distinct_cover_images = Images.objects.filter(pk__in=pks).distinct()
return distinct_cover_images
Then the template is much more simple:
{% for i in a.distinct_cover_images %}
Credit to #danilobargen however for his contribution to this code.
If I understood this right:
An album can have several releases
A release has only one cover
You want to loop over all covers of an album
In that case, the following should work:
{% for release in a.release_set.all %}
{{ release.cover_art.image }}
{% endfor %}
If you want to prevent listing identical covers, you can either compare the covers in the loop, or prepare a set with distinct covers in your view, so you can pass it on to the template.
# Solution using a set
context['distinct_coverimages'] = \
set([r.cover_art.image for r in album.release_set.all()])
# Solution using two queries, might perform better
pks = album.release_set.values_list('cover_art__pk', flat=True)
context['distinct_coverimages'] = models.Image.filter(pk__in=pks).distinct()
A third alternative would be creating a custom template filter for your album, to return all distinct release covers.
In any case, I recommend debugging such things in your Django shell. You can issue the shell with ./manage.py shell. If you have installed django-extensions, you can also use ./manage.py shell_plus to autoload all models. All object attributes and functions that don't require arguments (e.g. normal instance attributes or instance functions without arguments like 'string'.isalnum()) can also be used the same way (just without the parentheses) in your template.