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

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.

Related

Check and clear filters with django-filter

I am using django-filter to filter a ListView and would like to display a "Clear all filters" link if any filters are applied.
Due to the generic nature of the filtering system I haven't yet found a straightforward way to achieve this.
The only thing I came up with so far is to return the regular queryset in the get_queryset method of the view if a "clear" flag is present in the request, however this doesn't actually clear the filters - it just returns all the data.
Does anyone have a solution/idea for this?
Update: Solution
After Jerin's comment I decided to solve this problem in 2 separate parts:
has filter:
I check if any of the fields I defined in my filter class are in the request. My solution looks a bit different as I'm using class based views so I abstracted it away in a mixin but if you're using simple views like here, you could just do:
def product_list(request):
f = ProductFilter(request.GET, queryset=Product.objects.all())
has_filter = any(field in request.GET for field in
set(f.get_fields()))
return render(request, 'my_app/template.html', {
'filter': f,
'has_filter': has_filter
})
clear all filters:
A simple redirect to your list view:
{% if has_filter %}
{% trans 'Clear all filters' %}
{% endif %}
Here is the mixup version of the answer (combination of mine and Chris)
You could place a Clear all filters button and that will redirect to your default ListView (/host/end/point/).
But some non-filter parameters (such as pagination or something else) may occur in URL. So the better option is, check for any filter fields in URL and if so, display the filter clearing link
The opted solution is,
def product_list(request):
f = ProductFilter(request.GET, queryset=Product.objects.all())
has_filter = any(field in request.GET for field in set(f.get_fields()))
return render(request, 'my_app/template.html', {
'filter': f,
'has_filter': has_filter
})
and in template,
{% if has_filter %}
{% trans 'Clear all filters' %}
{% endif %}
Just make a button and point to the base search field.
<a class="btn btn-warning" href="{% url 'App:FilterView' %}">Reset</a>
If your FilterSet instance is available on the template you can check for filter.is_bound like this:
{% if filter.is_bound %}
Clear filters
{% endif %}
If you are using the FilterMixin or the FilterView, your FilterSet instance will be available as filter to the template as above.
I like this simple solution, however when I attempt to use it the current filter parameters are some how getting appended to the url even though it's the base url in the anchor.
So hovering over the button my link (determined using {% url 'app:view' %} shows
localhost/app/view correctly
However when clicking the button the url in browser has the parameters appended
localhost/app/view/?filter1=val1&filter2=val2 etc.
Is django caching something? Is browser (Chrome) caching? Can I force something in the anchor to not use them?
Answered my own, but for anyone else passing by:
I had the anchor on a button within a the filter form, although it was not a submit button moving it outside the form gave the desired result.

How do I look in a list of items, find the user and see if it exists in a different table and change the template as a result?

I have a simple follow/following setup running.
When a user (request.user) see's an object she likes, she can click the follow button and follow that user.
When she returns I want that button on the object to now not be enabled cause she is already following that user.
What is happening is that in the background, the follower/followee record is being made. The object in question has the id of the followee. I just can't figure out how to add that representation to the object_list.
In REST I would add a field to the serializer and that would take care of it. I could then evaluate the truthiness of the new field.
Any ideas on how to accomplish this?
You should do it as a separate query and make the test in the template.
view:
def objects_list(request):
...
return render(request, "the_template_path.html", {
'objects': ObjectName.objects.all()[0:100],
'followed_object_ids': ObjectName.objects.filter(follower=request.user).values_list('id', flat=True)
})
template:
{% for object in objects %}
{% if object.id in followed_object_ids %}
...
{% else %}
...
{% endif %}
{% endfor %}
That will get you started. Obviously you don't want to use generic names like object.
It would then probably be better to move the followed_object_ids query in a middleware so you don't always do it (really useful if you have featured objects widgets everywhere for instance).

reusing views inside other views in Django

I have a few components on my website that appear on many pages. DRY in mind, I would like to factor them out in separate snippets that I can include in the views that need them.
If it were only static items, then an {% include "snippet.html" %} would be the perfect solution. How can achieve a similar thing for snippets that include forms (and hence logic in the view) or require calculations before being displayed? Also, I would like to be able to nest the snippets several levels deep.
I know I can put simple logic in the template, using {% if ... %} ... {% endif %} blocks, but this turns into horrible spagetthi very soon and I want to keep the business logic separated from the presentation logic.
I am imagining a pattern as follows (here with oversimplified business logic):
def view1(request):
"Display some data"
total = get_total_vote_count()
return render(request, 'snippet1.html', {'total': total})
def view2(request, pk):
"Display some data about the article with primary key pk."
votes = get_votes_for_article(pk)
render1 = view1(request)
return render(request, 'snippet2.html', {'votes': votes, 'render1': render1})
def view3(request, pk):
"Display article pk and some additional data from view1 and view2":
article = get_object_or_404(Article, pk=pk)
render2 = view2(request, pk)
return render(request,
'article.html',
{'article': article, 'render2': render2},
)
with the templates something like:
# in snippet1.html:
{{ total }}
# in snippet2.html:
<p>Votes for this article: {{ votes }} out of {{ render1 }} total votes.</p>
# in page.html:
{% extends "base.html" %}
{% block "content" %}
<h1>article.title</h1>
<p>article.text</p>
<small>{{ render2 }}</small>
{% end block "content" %}
Note that there will be more views that will use view1 and view2 (e.g. an overview of the votes for all articles); that is why I have factored them out in separate functions.
How can I make this work?
Or is there a better trick in the Django toolbox to make this work without repeating view1 and view2 every time I want to use the same snippets in other views?
This is what custom template tags - specifically, inclusion tags - are for: rendering a template fragment with its own context.
what about django middleware, you can use middleware for this case. view1 and view2 repeating every time right ?. then attach the render1 and render2 to your request.
Middleware is a framework of hooks into Django’s request/response processing. It’s a light, low-level “plugin” system for globally altering Django’s input or output.
https://docs.djangoproject.com/en/dev/topics/http/middleware/

Get filtered object from a ForeignKey relation in django template

I am having a problem with djangos design choice not to allow model filtering in templates. Actually, I do understand its sense and I do not really want to break it, but currently I cannot see what's the best or usual method to circumvent my situation.
I am having a model Task with a foreign key user_solutions to another model Solution. Now I am iterating over all Tasks and if the user already has a solution for this task, I want to display both a tick and the link to his solution. Somewhat like this:
{% for task in tasks %}
{{ task.title }}
{% if task.user_solutions.filter(author=author).count() > 0 %}
Tick!
{{ task.user_solutions.get(author=author).get_absolute_url }}
{% endif %}
{% endfor %}
Yes, it looks cruel querying the database two times for the same information and django template does not accept it like this (correctly).
However, the other approaches to not seem to work either:
I cannot add a method Task.get_current_user_solution(), because in the model I do not know which user is logged in
I cannot add a method Task.get_user_solution(user), because I cannot pass arguments through the template
I cannot query information in the view and save it into a dictionary current_users_solutions (with Task.id as index), because in the template, I cannot use combined variables to access dictionaries (and the index to access it would of course be task.id)
So what else is there I can do? From the linked article I can only see that I could add a new template tag to allow querying from the template, but as said, I actually would like to follow djangos design principle if possible.
The Django way to do this is to create a custom template tag that accepts a user parameter and filters the queryset appropriately. It's just a couple of lines of code.
Django isn't dogmatic about "no logic in templates" (dogmaticism is frowned on in Python generally, aka "practicality beats purity"). It doesn't provide the ability to do that sort of thing natively in the template language, but that's why it has custom template tags at all: if your design requires it, and the simplest way to do it would be to query from the template, then that's what you should do.
You can add whatever you want to your tasks while in the view, so, in views.py, you could do something like this:
# in views.py
for task in tasks:
if task.user_solutions.filter(author=author).count() > 0:
task.is_this_users = True
task.url = task.user_solutions.get(author=author).get.absolute_url
and then in your template:
{% for task in tasks %}
{{ task.title }}
{% if task.is_this_users %}
Tick!
{{ task.url }}
{% endif %}
{% endfor %}
You can use django template tags like this:
templatetags.py
#register.inclusion_tag("template.html")
def task_def(request):
task = user_solutions.filter(author=author).count()
if task >0:
task.is_this_users = True
task_url = task.user_solutions.get(author=author).get.absolute_url
return {'task_url': task_url}
in the template file (.html)
{% load templatetags %}
and now you can use your return result here like you want
{% for element in task_url %}

Displaying page content using django-page-cms

I would like to display some content located in my models in some of my template pages.
I am using django-page cms
In the documentation views are not used to display content. Instead ready made template tags are used.
http://packages.python.org/django-page-cms/display-content.html
I do not understand a word of this. Please Bear with me I am new.
All I want to do is display some info located in my models inside a template this manner..
{% if latest_news_list %}
{% for news in latest_news_list %}
<li><h3>{{ news.title }}</h3></li>
<li><p>{{ news.body }}</p></li>
{% endfor %}
Since views are not used I cannot use if latest_news_list.
I need to somehow get my models to display in the templates using django-page cms and NOT regular views. The documentation states to use some kind of template tag for this.
Could somebody please explain to me how to do this.
And a clear concise explanation of the following ready-made template tags would also be appreciated...
* get_content
* show_content
* get_page
* show_absolute_url
taken from.http://packages.python.org/django-page-cms/display-content.html
I need to display info contained using the following models in the manner I have highlighted above. Thank you very much for your help. my models are as follows.
class Body(models.Model):
type = models.ForeignKey(Content)
title = models.CharField(max_length=100)
published = models.DateTimeField(default=datetime.now)
body = tinymce_models.HTMLField("Main content")
As I have stated I am very new to this please make explanations as simple as possible.
The template tags you mentioned are supposed to display content coming from the cms. If you want to include data coming from your app, you should see this sectionlink text.
def extra_context():
from myapp.models import Body
items = Body.object.all()
return {'items': items}
PAGE_EXTRA_CONTEXT = extra_context
{% if items %}
<ul>
{% for item in items %}
<li>{{ item.title }} </li>
{% endfor %}
<ul>
{% endif %}
Or, if you want to use your app's view, see this.