Make a change to a string in django template? - django

I have a queryset in Django that contains a string field. This is a filename, something like images/photo.jpg or images/photo.20.19.22.jpg. I need to rewrite them in one particular view so that ".thumbnail" is inserted before the extension. The previous names should become images/photo.thumbnail.jpg and images/photo.20.19.22.thumbnail.jpg.
What is the best way to do this? This is part of a queryset so it will look like:
{% for record in list %}
{{ record.image }}
{% endfor %}
Now of course I would love to do this outside of my template. However, I don't see a way in which I can do that. After all, this needs to be done for every record inside my queryset. To complicate things, this record does not come directly from a modal. This record is coming from a subquery, so I don't see a way for me to change the modal itself. Should I use templatetags for this? Any other recommendations?
FYI the subquery is something like this:
>>> from django.db.models import OuterRef, Subquery
>>> newest = Comment.objects.filter(post=OuterRef('pk')).order_by('-created_at')
>>> Post.objects.annotate(image=Subquery(newest.values('image')[:1]))

A simple custom template filter could do this.
#register.filter
def add_thumbnail(image):
return image.replace('.jpg', 'thumbnail.jpg')
And in the template:
{% for record in list %}
{{ record.image|add_thumbnail }}
{% endfor %}

Related

How can I see all possible attributes of an context variables in django templates

I am trying to use double model form at once in one single view, I am using django-betterforms to merge those, It merged all fields from two model in one single form. I know I can use different class and id to separate them, But I can't extract them in template form, like
{{ form }}
it will place full form in template, I can render all field like this
{% for field in form %}
{{ field }} or anything
{% endfor %}
My question is how can I know all the possible attributes of this field like
{{ field.* }} *=anything
kind of dir(field).
This is a problem I have facing but what will be solution to find all attributes or separate two forms in two side. Basically I need to separate two model, those will save in same time with same view but in front-end those will be different.
Thanks in advance!!!
You create a custom filter:
in templatetags/my_filters.py:
from django import template
register = template.Library()
#register.filter
def getallattrs(value):
return dir(value)
in your template:
{% load my_filters %}
...
{{ field|getallattrs }}

Establish templatetag as django variable

I am attempting to filter a django queryset in a template tag, like this:
#register.simple_tag
def splice(query, person_id):
query2 = query.filter(personid=person_id)
return query2
Then, in my template, I would like to pass the newly filtered queryset into an includes html file. Here is my attempt:
{% with splice df person_id as x %}
{% include 'includes/file.html' with df=x %}
How can I execute this properly? Or does anyone have ideas how to go about this a more efficient way?
You don't need with there; a simple tag can add its data to the context directly with as.
{% splice df person_id as x %}
However, this is probably not the right approach. Instead of writing a template tag to add context for an included template, you should be using an inclusion tag, which takes care of the whole process of including the template with specific context. So:
#register.inclusion_tag('template/file.html')
def splice_include(query, person_id):
query2 = query.filter(personid=person_id)
return {'df': x}
And now you can use it directly:
{% splice_include df person_id %}
with no need for a separate include at all.
You need to rearrange how you pass the arguments. Using the Django docs provides a nice example. You can then call the templatetag from file.html.
The calling file
{% include 'includes/file.html' with df=df person_id=person_id %}
file.html
{% load my_template_tags %}
{{df|slice:person_id}}

Django pass variable context to custom filter

I need to know the HTML to pass the CONTENT of a variable into a DJANGO CUSTOM TAG
the code, for example, would be something like this:
{% for id,name in data %}
{% customTag id name %}
{% endfor %}
I need this to pass the ACTUAL CONTENTS of id, and not just "id" (the literal string)
and the answer is NOT found at https://docs.djangoproject.com/en/1.7/howto/custom-template-tags/#passing-template-variables-to-the-tag like all the previous posts say
Well somebody posted this and then removed it... but it answered my question. The answer was to use the takes_context parameter.
I used a simple tag with this flag like in the example here https://docs.djangoproject.com/en/1.7/howto/custom-template-tags/#simple-tags
#register.simple_tag(takes_context=True)
def current_time(context, format_string):
timezone = context['timezone']
return your_get_current_time_method(timezone, format_string)

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

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.