Django Annotations - More efficient way? - django

In a class Based ListView I want to annotate each Post with the number of comments (this method works)
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['approved_comments'] = Comment.objects.values('post_id').annotate(c_count=Count('post_id')).filter(approved=True)
return context
in my Template I do the following (which feels really inefficient)
{% for comment in approved_comments %}
{% if post.id == comment.post_id %}
{{comment.c_count}}
{% endif %}
{% endfor %}
This gets me the result I want but I'm struggling to find a better way to deliver this since this seems really redundant.
I was trying to find a SO question that already deals with this but haven't found one so if you could point me to the link that would be helpful.
Thanks in advance :)

This is a quadratic JOIN, you should not do that. You can simply annotate the Posts with the number of approved comments in the view:
from django.db.models import Count, Q
from django.views.generic import ListView
class MyListView(ListView):
model = Post
queryset = Post.objects.annotate(
napproved_comments=Count('comment', filter=Q(comment__approved=True))
)
and then render this in the template with:
{{ post.napproved_comments }}

Related

Manual Django Form Display

I want to manually render the form in my template, but what I'm trying is not yielding the expected result, and it is not apparent as to why.
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['field1'] = forms.BooleanField()
self.fields['field2'] = forms.BooleanField()
systems = System.objects.all()
for i in range(len(systems)):
self.fields['s' + str(i)] = forms.BooleanField()
self.fields['field3'] = forms.BooleanField()
self.initial_fields = [self.fields['field1'], self.fields['field2']]
now when I do this in my template:
{% for field in form.visible_fields %}
{{ field }}
{% endfor %}
it returns what you would expect...after looking up the method visible_fields it simply returns a list of fields. So in theory, if I create my own list of fields as with self.initial_fields, the form generator should render the following same as above:
{% for field in form.initial_fields %}
{{ field }}
{% endfor %}
but instead I get this output in my template:
<django.forms.fields.BooleanField object at 0x000001242F51E588>
<django.forms.fields.BooleanField object at 0x000001242F51E400>
I'm assuming I'm missing some initialization of the field itself? I don't understand why one works and the other doesn't. Anyone know?
You need to get the bound field object and not the field itself. It's not really a clean way of doing so, but if you are looking to hack around it, you should do like so,
...
self.initial_fields = [self.fields['field1'].get_bound_field(self, 'field1'),
self.fields['field2'].get_bound_field(self, 'field2')]
...
Hope this helps!

How to Pull info from another model within a DetailsView template (without ForeignKey)?

I am trying to pull data from two models within a DetailsView template (in Django). There is of course a primary model (eg. Articles) associated with the view, which is easy to access. However, I want to access data from a model (eg. Terms). I do not want to use ForeignKey because I will be using many 'Terms' in each 'Article,' and since ForeignKey will allow me to link to only row in the Terms model, I will have to set-up mutiple ForeignKey fields, which can get messy fast.
I was thinking that this could be accomplished using get_context_data or templatetags, but haven't have had any luck yet. Any thoughts?
From Django Documentation you can add any queryset or context value you like to call in your template context like book_list below will list all books and it doesn't has to be linked with any other models..
#views.py
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetail, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
#yourtemplate.html
{% for book in book_list %}
{% if book %}
{{ book.title }}
{% endif %}
{% empty %}
No book_list found.
{% endfor %}

Generate PDF with Django Class Based Views

I currently have a Library Loan template implemented using Class Based Views. The implementation is probably something that we have seen before:
In urls.py:
#PK is the member ID
url(r'^library/generate_loan_slip/(?P<pk>(\d+))/(?P<loan>(\d+))/'
,LoanSlipDetailView.as_view()
, name='library_app_generate_loan_slip'),
In views.py:
class LoanSlipDetailView(DetailView):
model = LibraryMember
loan = None
template_name = 'loan-slip.html'
def get_context_data(self, **kwargs):
context['loan'] = self.loan
context['member'] = member
return context
def get_object(self):
member = self.model.objects.get(pk=self.kwargs['pk'])
self.loan = LibraryLoan.objects.get(pk=self.kwargs['loan'], for_member=member)
return member
And in the HTML, loan-slip.html, the html with all the required variables in {{}} and controls using {% if %} ... {% else %} ... {% endif %} and {% for ... %} ... {% endfor %}.
Right now, I want the response to generate a PDF view. So far, I have checked out reportlab and they implemented it for function based views. However, is there an implementation for Class Based Views?
You can just override get method of DetailView and write reportlab logic over there.

Django ListView: taking a time difference in all items

I'm rather new to Class Based Views, so this is probably obvious, but any tips are appreciated. I want to display "time left" for each item on a list. That is if I have 10 objects, each should display in the template the number of days, hours, mn left until a deadline arrives. Here's my attempt:
model.py
class Law(models.Model):
deadline = models.DateTimeField(_(u'The Deadline'),)
name = ..
more_stuff = ..
views.py
class LawList(ListView):
model = Law
context_object_name = 'law'
template_name = 'template.html'
template.html
{% for l in law %}
<h3>{{ l.deadline }} - {{l.name }} </h3>
{{l.more_stuff}}
{% endfor %}
all good up to here. However I would like to have {{l.time-left}} instead of {{l.deadline}}. Is there a way for the view to calculate this and pass it to the template?
I thought of adding a get_context_data to the 'LawList' view, but I don't know how to do so for every item in my list. Below is what works for a single item.
# views.py, below the section above
def get_context_data(self, **kwargs):
context = super(LawList, self).get_context_data(**kwargs)
context['time_left'] = Law.objects.all()[0].deadline - timezone.now()
but I'm a little stuck. Thanks!
have a look at the timeuntil template tag

loop in Django template: how to control the loop iterator?

I'm using Django to show a list of posts. Each post has a 'is_public' field, so if one post's 'is_public' equals to False, it should not be shown to the user. Also, I want to show a fixed number of posts in one page, but this number can be changing depending on views.
I decided to crop the queryset in template as a few views are using the same template, generating it in the view means a lot of repeated codes.
If written in python, it should look like this:
i=number_of_posts_to_show_in_one_page
while i:
if qs[i].is_public == True:
#show qs[i] to the page
i--
As the django template does not support while loop and for loop seems hard to control, is there a way of achieving this? Or should I do it in another way?(One idea is to crop the qs before looping)Thanks!
Update:
I've written this template tag to pre-process the queryset:
#register.simple_tag(takes_context=True)
def pre_process_list(context,list,numbers):
#if not user.has_perm('admin'):
context['result_list']=list.filter(is_public=True, is_removed=False)[0:numbers]
#else:
#context['result_list']=list[0:numbers]
return ''
Before using for loop in the template, I'll pass the queryset to this templage tag, and use a simple for loop to show its result.
If in the future I want to show non-public posts to admins(which is not decided yet), I can write in some logic like the commented ones, and have them styled differently in the template.
{% for post in posts %}
{% if post.is_public %}
{{ post }}
{% endif %}
{% endfor %}
Though this would be a perfect use case for a manager.
You could write a simple manager that filters public posts.
class PublicPostManager(models.Manager):
def get_query_set(self):
return super(PublicPostManager, self).get_query_set().filter(is_public=True)
Then you would add it to your Post Class:
class Post(models.Model):
...
public = PublicPostManager()
Then you could pass post.public.all() as public_posts to your template and simplify your loop:
{% for post in public_posts %}
{{ post }}
{% endfor %}
#arie has a good approach with the manager, but you can easily do the same without writing a manager:
# View
posts = Post.objects.filter(is_public=True) # or use the manager
# Now, you can either limit the number of posts you send
# posts = posts[:5] (only show five in the view)
return render_to_response('foo.html',{'posts':posts})
# Template
# Or you can do the limits in your template itself:
{% for post in posts|slice:":5" %}
{{ post }}
{% endfor %}
See the slice filter on more information.
However, since this is a common operation, with django 1.3 you can use class based views to automate most of this.