I'm making a small site in Django with 'items' and 'characters' in a sqlite database based on the app in the 'first steps' on the documentation page. The tables (currently) follow a similar format so I want to try and make a flexible template. In my views.py, the two views are set up like so:
def characters(request):
sortby = 'lastname'
data_list = Characters.objects.order_by(sortby)
template = loader.get_template('database.html')
context = {
'data_list': data_list,
'directory': 'wiki/characters',
'sortby': sortby,
}
return HttpResponse(template.render(context, request))
def items(request):
sortby = 'name'
data_list = Clothes.objects.order_by(sortby)
template = loader.get_template('database.html')
context = {
'data_list': data_list,
'directory': 'wiki/items',
'sortby': sortby,
}
return HttpResponse(template.render(context, request))
The only difference being the sortby variable and the directory variable. The problem is, I would like to sort the characters by their lastname, and the items by their name. The issue arises in my template, specifically data.sortby:
{% if data_list %}
<ul>
{% for data in data_list %}
<li>{{data.sortby}}</li>
{% endfor %}
</ul>
{% else %}
<p>No characters,</p>
{% endif %}
If I manually reference the field I want without brackets it works fine, so I'm sure it's the string causing the issue. How do I sort by the field I specify in views.py?
Thanks in advance from a noob.
You can get an idea in this (a little outdated) answer, but the bottom line is that there is no built-in way to access attributes by name getattr()-style in django templates. So you would need to write a custom filter, as described in the django docs:
# app/templatetags/attr_tags.py
from django import template
register = template.Library()
#register.filter
def get_attribute(value, arg):
return getattr(value, arg)
Now you can do in your template:
{% load attr_tags %}
# ...
{{ data|get_attribute:sortby }}
# {{ data.sortby }} can't work because that tries to access
# an attribute named 'sortby', not an attribute with the name
# stored in the context variable of the name 'sortby'
Related
I'm passing a dictionary from my view to a template. So {"key1":"value1","key2":"value2"} is passed in and looping through key,value pairs is fine, however I've not found an elegant solution from access directly in the view from a specific key, say "key1" for example bu json.items["key1"]. I could use some if/then statements, but I'd rather do directly is there a way?
Here is looping code in the html template:
{% for key, value in json.items %}
<li>{{key}} - {{value}}</li>
{% endfor %}
The Django template language supports looking up dictionary keys as follows:
{{ json.key1 }}
See the template docs on variables and lookups.
The template language does not provide a way to display json[key], where key is a variable. You can write a template filter to do this, as suggested in the answers to this Stack Overflow question.
As #Alasdair suggests, you can use a template filter.
In your templatetags directory, create the following file dict_key.py:
from django.template.defaultfilters import register
#register.filter(name='dict_key')
def dict_key(d, k):
'''Returns the given key from a dictionary.'''
return d[k]
Then, in your HTML, you can write:
{% for k in json.items %}
<li>{{ k }} - {{ json.items|dict_key:k }}</li>
{% endfor %}
For example, to send the below dictionary
dict = {'name':'myname','number':'mynumber'}
views :
return render(request, self.template_name, {'dict': dict})
To render the value in html template:
<p>{{ dict.name }}</p>
It prints 'myname'
To overcome this problem you could try something like this:
def get_context_data(self, **kwargs):
context['cart'] = []
cart = Cart()
cart.name = book.name
cart.author = book.author.name
cart.publisher = book.publisher.name
cart.price = 123
cart.discount = 12
cart.total = 100
context['cart'].append(cart)
return context
class Cart(object):
"""
Cart Template class
This is a magic class, having attributes
name, author, publisher, price, discount, total, image
You can add other attributes on the fly
"""
pass
By this way you can access your cart something like this:
{% for item in cart %}
<div class="jumbotron">
<div>
<img src="{{item.image}}" />
<div class="book_name"> <b>{{item.name}}</b></div>
<div class="book_by"><i>{{item.author}}</i></div>
<span>Rs. {{item.price}}</span> <i>{{item.discount}}% OFF </i>
<b>Rs. {{item.total}}</b>
{% endfor %}
I want to create a template tag that passes in a dictionary of objects
models.py
class Notification(models.Models):
name = models.CharField()
..........
template_tag.py
created a template tag that gets all the objects that I want to display
from django import template
register = template.Library()
#register.simple_tag
def notifications():
context = {'notifications':Notification.objects.order_by('-date')[:4]}
return context
The later initiate a forloop that displays the objects
{% load template_tag %}
{% for obj in notifications %}
{{ obj.name }}
{% endfor %}
Hope you get the idea.....
Like in python:
{% for key, value in notifications.items %}
{{key}} - {{value}}
{% endfor %}
As simple as that!
After doing some research I have found that django as something called "inclusion_tag"
https://docs.djangoproject.com/en/3.2/howto/custom-template-tags/#inclusion-tags
create an html file and call it "notifications.html"
#register.inclusion_tag('notifications.html')
def notifications(total = 5):
context = {'notifications':Notification.objects.order_by('-date')[:total]}
return context
(Django , Python) I have created a list of book objects and it is being passed as context in my views.py along with the current session. On my template, i was to check if the books in that list are stored in the session, and if they are i want to access some info relating to that book within that session. how do i access the books in the session dynamically? is there a way?
i know i can access them by using "request.session.name" (where "name" is the same of the space in the session it is stored)
There are several book titles saved in the session, the way they are saved are as follows (in a function under views.py)
request.session["random book title"] = "random dollar price"
i want to access that "random dollar price" dynamically in a template.
this is the block of code in the template
{% for book in book_list %}
{% if book.title in request.session %}
{{ request.session.??? }}
{% endif %}
{% endfor %}
Thank you in advance!
You can make a custom template tag to look up by attribute like here
Performing a getattr() style lookup in a django template:
# app/templatetags/getattribute.py
import re
from django import template
from django.conf import settings
numeric_test = re.compile("^\d+$")
register = template.Library()
def getattribute(value, arg):
"""Gets an attribute of an object dynamically from a string name"""
if hasattr(value, str(arg)):
return getattr(value, arg)
elif hasattr(value, 'has_key') and value.has_key(arg):
return value[arg]
elif numeric_test.match(str(arg)) and len(value) > int(arg):
return value[int(arg)]
else:
return settings.TEMPLATE_STRING_IF_INVALID
register.filter('getattribute', getattribute)
Now change your template to
{% load getattribute %}
{% for book in book_list %}
{% if book.title in request.session %}
{{ request.session|getattribute:book.title }}
{% endif %}
{% endfor %}
This is a basic custom template tag example:
Django - Simple custom template tag example
and docs:
https://docs.djangoproject.com/en/1.11/howto/custom-template-tags/
From what I remember from my django days should work
You can put session data in a dictionary and send this data to target template when you want to render it in view function.
def some_function(request):
context={
'data':sessionData #put session data here
}
return render(request,"pass/to/template.html",context)
Now you can access 'data' in your template.html
I think you should just send a list of book names from your view instead of a queryset so when you are crosschecking with session you use the title directly instead.
{% for book in book_list %}
{% if book in request.session %}
{{ request.session.book }}
{% endif %}
{% endfor %}
I have created a template tag and trying to loop through the results from the template tag
but I don't get any results
tags.py
from django import template
from loadprograms import dbcontext
register = template.Library()
#register.simple_tag
def get_category():
x = dbcontext.DBContext()
results = x.executequery("Select name from Categories")
categories = [each[0] for each in results]
return categories
template code
{% load category_tags %}
{% get_category %}
{% for each in get_category %}
{{ each }}
{% endfor %}
The {% get_category %} prints all the categories without any issues but the for loop stmt
that loop through the results does not work
What could be the problem?
To make this change in your tag, you'll have to set a variable in the context, but if your objective is to have a list of categories available in templates, just like you would have passed it in from the view - then you need to write a template context processor, which will allow all views to have this variable in their context.
A template context processor is just a method that adds to the request context, by returning a dictionary. Think of it like a view function, that just returns a context.
from .models import Categories
def cat_names(request):
return {'category_names': Category.objects.values_list('name', flat=True)}
To activate this context processor, you have to do a few things:
Add the above code to a file called template_processors.py in the same place as your models.py and views.py.
In your settings.py, add the fully-qualified name of the method to the TEMPLATE_CONTEXT_PROCESSORS setting, making sure you don't override the defaults. To do this easily, import the default settings first, then add to it:
from django.conf.default_settings import TEMPLATE_CONTEXT_PROCESSORS as TCP
TEMPLATE_CONTEXT_PROCESSORS = TCP + ('yourapp.template_processors.cat_names',)
Use the render shortcut, which will make sure the context is correctly passed.
In your views, you can now just do this:
from django.shortcuts import render
def home(request):
return render(request, 'home.html')
In your home.html, you can now do:
{% for name in category_names %}
{{ name }}
{% endfor %}
I've developed a custom i18n system in Jinja2 based on the following filter (simplified):
#contextfilter
def render(context, value):
"""
Renders the filtered value as a string template, using the context
and environment of the caller template.
"""
mini_template = _environment.from_string(value)
return mini_template.render(context)
This allows me, for example, to create the following context:
context = {
'user': {
'name': 'Joel',
'locale': 'es'
}
'greetings': {
'en': 'Hi {{user.name}}!',
'es': '¡Hola {{user.name}}!'
}
}
And use it like this in my templates:
{{ greetings[user.locale]|render() }}
That works perfectly.
Now imagine that I've an array of users instead of a single one. I was doing the following in Django Templates, but it doesn't work in Jinja2 because the variable 'user' is not in the context:
{% for user in list_of_users %}
{{ greetings[user.locale]|render() }}
{% endfor %}
Is there anything I could do to add the new variable (user) to the context I use at the contextfilter? I need to add both its name and value if I want it to work.
Thank you very much for your help.
Ok, I've fixed it using kwargs (although it's more verbose than its equivalente in Django templates).
Filter:
#contextfilter
def render(context, value, **kwargs):
"""
Renders the filtered value as a string template, using the context
and environment of the caller template.
"""
if kwargs:
kwargs.update(context)
ctx = kwargs
else:
ctx = context
#we render the string as its own template
mini_template = _environment.from_string(value)
return mini_template.render(ctx)
Usage:
{% for user in list_of_users %}
{{ greetings[user.locale]|render(user=user) }}
{% endfor %}