I have the following models:
class Thread(models.Model):
name = models.CharField(max_length=50)
category = models.ForeignKey(Category)
class Post(models.Model):
thread = models.ForeignKey(Thread)
datetime = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User)
This is for my forum system.
I need to fetch all the Threads of category X and I need to have a few custom attributes like datetime and pk of the last post to the thread, for example:
threads = Thread.objects.filter(category=x)
last_post = Post.objects.filter(thread=threads[0]).order_by('-pk')[0]
Then I want to transfer everything to the template so that I get:
{{ t.pk }} as thread_pk
{{ t.last_post_pk }} as last_post_pk
And so on.
To get last post for every thread you should use aggregation:
Post.objects.filter(thread__in=threads).values('thread').annotate(last_id=Max('id')).order_by()
This will get you last_id from every thread in threads.
Since Django doesn't do joining like you would expect with SQL joins (see my comment for further explanation), I would solve it in the view (or perhaps a method added to the Thread class). Consider the following example view:
def index(request):
res = []
for t in Thread.objects.filter(category__name='x'):
res.append((t, Post.objects.filter(thread=t).order_by('-datetime')[0]))
# Optionally sort by datetime here:
# res.sort(key=lambda x: x[1].datetime, reverse=True)
return render_to_response('index.html', {'result': res})
You could then use it in the template as follows:
{% for r in res %}
Thread {{ r.0.name }}, last post by {{ r.1.author.name }} on {{ r.1.datetime }}.
{% endfor %}
# This would give something like this:
# Thread mythread, last post by john on Oct. 21, 2011, 1:52 a.m..
By creating a tuple of Threads and Posts, you don't really join them, but do make them easily available in the template.
Related
I'm fairly new to Django. I have a database with Events. I want to display the name of the Organisation where org_id is the foreign key in the events model. My approach was to load in all the objects and then do some iterating through them but I get no output on the website. I feel like its got to do something with the templating language
models.py
class Organisation(models.Model):
org_id=AutoSlugField(unique=True)
name = models.CharField(max_length=200)
email=models.EmailField(max_length = 250)
class Event(models.Model):
event_id = AutoSlugField(unique=True)
name = models.CharField(max_length=100)
date = models.DateField()
event_category = models.CharField(max_length=50)
duration= models.IntegerField()
org_id = models.ForeignKey(Organisation,on_delete=models.CASCADE)
maxparticipants= models.IntegerField()
teacher_id=models.ForeignKey(User,on_delete=models.CASCADE)
relevant snippet from event_details.html
<h3>Hosting Body</h3>
{% for org in orgs%}
{%if org.org_id == event.org_id %}
<p>{{org.name}}</p>
{%endif%}
{%endfor%}
<h3>Date</h3>
<p>{{event.date}}</p>
views.py
def event_details(request,pk):
event=Event.objects.get(event_id=pk)
orgs=Organisation.objects.all()
context={'event':event,'orgs':orgs}
return render(request,'event_details.html',context)
You can render the relevant organization with:
<h3>Hosting Body</h3>
<p>{{ event.org_id.name }}</p>
<h3>Date</h3>
<p>{{ event.date }}</p>
You can boost efficiency by fetching the Event and Organisation data in the same query with:
from django.shortcuts import get_object_or_404
def event_details(request, pk):
event = get_object_or_404(Event.objects.select_related('org_id'), event_id=pk)
context = {'event':event}
Note: It is often better to use get_object_or_404(…) [Django-doc],
then to use .get(…) [Django-doc] directly. In case the object does not exists,
for example because the user altered the URL themselves, the get_object_or_404(…) will result in returning a HTTP 404 Not Found response, whereas using
.get(…) will result in a HTTP 500 Server Error.
Note: Normally one does not add a suffix _id to a ForeignKey field, since Django
will automatically add a "twin" field with an _id suffix. Therefore it should
be org, instead of org_id.
in your template change it to:
<h3>Hosting Body</h3>
{% for org in orgs%}
{%if org.org_id == event.org_id.org_id %}
<p>{{org.name}}</p>
{%endif%}
{%endfor%}
<h3>Date</h3>
<p>{{event.date}}</p>
Let's say I have two related models, one of the two with a datetime field.
(Author & Book with a pub_date).
I want to display a list of authors and the latest book each of them has written.
I made a method on the Author model:
def get_latest_book(self):
return self.books.all().latest('pub_date')
That is working, but it's very inefficent when it comes to be rendered on a template:
views.py:
class AuthorListView(ListView):
model = Author
template_name = 'author_list.html'
def get_queryset(self):
return self.model.objects.filter(owner=self.request.user).order_by('name').prefetch_related('books')
author_list.html:
...
{% for author in author_list %}
Name: {{author.name}} - Latest publication: {{author.get_latest_book}}
{% endfor %}
...
This is generating a large number of queries like:
SELECT ••• FROM `app_book` WHERE `app_book`.`author_id` = 374 ORDER BY `app_book`.`pub_date` DESC LIMIT 1
36 similar queries.
for each Author I have in the database!
This results in huge loading times for the book list.
How can I print on the template a list of Authors with their latest book in an efficient way?
An easy way to boost efficiency is with a Prefetch object [Django-doc]:
from django.db.models import Prefetch
class AuthorListView(ListView):
model = Author
template_name = 'author_list.html'
def get_queryset(self):
return self.model.objects.filter(
owner=self.request.user
).prefetch_related(
Prefetch('books', Book.objects.order_by('-pub_date'), to_attr='books_ordered')
).order_by('name')
and then render this with:
{% for author in author_list %}
Name: {{author.name}} - Latest publication: {{ author.books_ordered.0 }}
{% endfor %}
This however will load all related books in memory, but in a single query.
My code looks like this:
models.py
class Tag(models.Model):
name = models.CharField(max_length=42)
class Post(models.Model):
user = models.ForeignKey(User, related_name='post')
#...various fields...
tags = models.ManyToManyField(Tag, null=True)
views.py
posts = Post.objects.all().values('id', 'user', 'title')
tags_dict = {}
for post in posts: # Iteration? Why?
p = Post.objects.get(pk=[post['id']]) # one extra query? Why?
tags_dict[post['id']] = p.tags.all()
How am I supposed to create a dictionary with tags for each Post object with minimum set of queries? Is it possible to avoid iterating, too?
Yes you will need a loop. But you can save one extra query in each iteration, you don't need to get post object to get all its tags. You can directly query on Tag model to get tags related to post id:
for post in posts:
tags_dict[post['id']] = Tag.objects.filter(post__id=post['id'])
Or use Dict Comprehension for efficiency:
tags_dict = {post['id']: Tag.objects.filter(post__id=post['id']) for post in posts}
If you have Django version >= 1.4 and don't really need a dictionary, but need to cut down the count of queries, you can use this method like this:
posts = Post.objects.all().only('id', 'user', 'title').prefetch_related('tags')
It seems to execute only 2 queries (one for Post and another for Tag with INNER JOIN).
And then you can access post.tags.all without extra queries, because tags was already prefetched.
{% for post in posts %}
{% for tag in post.tags.all %}
{{ tag.name }}
{% endfor %}
{% endfor %}
I have a Contact class that can have many Conversations. But each conversation can belong to a single Contact.
So its a One-To-Many relationship.
class Contact(models.Model):
first_name = models.CharField()
last_name = models.CharField()
class Conversation(models.Model):
contact = models.ForeignKey(Contact)
notes = models.TextField()
def __unicode__(self):
return self.notes
Now when I pass in the contacts to the template, I would like to have one field that shows the last conversation for the contact.
contacts= profile.company.contact_set.all()[:10]
calls = []
for c in contacts:
calls.append(max(c.conversation_set.all()))
And I pass them in like this:
vars = {'contacts' : contacts, 'calls': calls}
variables = RequestContext(request, vars)
return render_to_response('main_page.html', variables)
In the template I came up with this magic:
{% for idx, val in contacts %}
<tr>
<td>...
</td>
<td>
{% if calls %}
{{ calls.idx }}
{% endif %}</td>
</tr>
{% endfor %}
This doesn't show anything for calls. But if I replaced calls.idx with calls.0 I get the first one displayed.
What am I doing wrong? Beside the fact that it could be done probably much easier than that. :) I am open for better ways of doing it.
You can't do this sort of indirect lookup in the template language - calls.idx will always refer to an attribute idx belonging to calls.
However I think a better solution is to add the call value directly onto the contact object - don't forget that in Python objects are dynamic, so you can annotate anything you like onto them.
for c in contacts:
c.max_call = max(c.conversation_set.all())
As an aside, does that max really work? I'm not sure what it would be doing the comparison based on. An alternative is to define get_latest_by in your Conversation model, then you can avoid this loop altogether and just call {{ contact.conversation_set.get_latest }} in your template for each contact through the loop.
(Note this will still be pretty inefficient, there are various ways of getting the whole set of max conversations in one go, but it will do for now.)
class Contact(models.Model):
# your code here
#property
def last_conversation(self):
try:
return self.conversation_set.order_by("-some_date_or_order_field")[0]
except IndexError:
return None
Then you don't have to care about this in your view and just need to call "c.last_contact" in your template.
I'm making a very basic poll app. It's similar to the one in the Django tutorial but I chose to break out the vote counting aspect into its own model (the tutorial just adds a vote count field alongside each answer). Here's my models:
class PollQuestion(models.Model):
question = models.CharField(max_length=75)
class PollAnswer(models.Model):
poll = models.ForeignKey('PollQuestion')
answer = models.CharField(max_length=75)
class PollVote(models.Model):
poll = models.ForeignKey('PollQuestion')
answer = models.ForeignKey('PollAnswer')
date_voted = models.DateTimeField(auto_now_add=True)
user_ip = models.CharField(max_length=75)
I'm trying to show all of the vote counts for a given poll. Here's my view code:
from django.db.models import Count
poll_votes = PollVote.objects.select_related('PollAnswer').filter(poll=poll_id).annotate(num_votes=Count('answer__id'))
When I output the results of this query I just get a single row per vote (eg I see about 40 'answers' for my poll, each one representing a vote for one of the 5 actual PollAnswers). If I look at the queries Django makes, it runs something like this for every vote in the poll:
SELECT `poll_answers`.`id`, `poll_answers`.`poll_id`, `poll_answers`.`answer`
FROM `poll_answers`
WHERE `poll_answers`.`id` = 101
Can anyone poke me in the right direction here? I get the feeling this should be easy.
EDIT: here's my template code, for completeness.
<ul>
{% for vote in votes %}
{{ vote.answer }} ({{ votes.num_votes }})<br />
{% endfor %}
</ul>
Try:
poll_votes = PollVote.objects.filter(poll=poll_id).annotate(num_votes=Count('answer__id'))
or:
poll_votes = PollVote.objects.values('poll', 'answer__answer').filter(poll=poll_id).annotate(num_votes=Count('answer__id'))
Relevant docs:
Django offical docs
Never mind, fixed it myself after finding a tutorial which uses the same sort of models as me.
Essentially the fix was in the view:
p = get_object_or_404(PollQuestion, pk=poll_id)
choices = p.pollanswer_set.all()
And in the template:
{% for choice in choices %}
<p class="resultsList">{{choice.answer}} - {{choice.pollvote_set.count}}</p>
{% endfor %}