Determine variable type within django template - django

I have a variable that I'm pulling into a table that sometimes is a date and sometimes is a string. If the variable is a date, I want to change the formatting:
<td>{{ action.extra_column|date:"M d" }}</td>
But if it is a string, I just want to display it as is:
<td>{{ action.extra_column }}</td>
If I try to format it and it is a string, I get no output for the variable.
How can I determine the type so that I can adjust my rendering based on type.

You could also implement a general template filter as an equivalent to the type() builtin:
# app/templatetags/util.py
from django import template
register = template.Library()
#register.filter
def get_type(value):
return type(value)
# template.html
{% load util %}
{% if extra_column|get_type == 'str' %}
String
{% elif extra_column|get_type == 'datetime.date' %}
Date
{% else %}
Oh no!
{% endif %}
I think Ignacio and Dirk are right, however. Can't you just have two keys (you say "array", but I assume you mean "dictionary" from the fact that the items have names) called date and detail?
# views.py
...
actions = [{
'some_property': 'some_value'
'date': None,
'detail': 'details'
},
{
'some_property': 'some_value'
'date': datetime.date.today(),
'detail': None
}]
...
# template.html
{% for action in actions %}
<td>{% if action.date %}{{ action.date|date:"M d" }}{% endif %}{{ action.detail }}</td>
{% endfor %}
# output
<td>details</td>
<td>Aug 19</td>

Like Ignacio Vazquez-Abrams pointed out in the first comment, that's not really a great way to code your logic. I would ensure that your variable has a certain type. That could be solved through an additional variable you add to the context or an object that holds the data and something that describes the type of data.
If you want to stick to your logic, a possible approach would be to write your own template filter (let's call it date_or_string). The filter could subclass the builtin date filter with the format parameter being optional. In case the parameter is passed it works like the normal date filter, without the parameter it simply returns the string. In a more complex scenario the filter could also do some type checking. Just an idea, i wouldn't actually put that kind of logic into the template.

I know I'm way behind on this (by three years) but I just got here looking to do something similar and came up with what I think is a decent solution.
Just add a function to your models like get_model_type and have it return something you'd expect from each model like so:
class MyModelOne(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
first_name = models.CharField(max_length=255)
def get_model_type(self):
return "my_model_one"
class MyModelTwo(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
other_field = models.CharField(max_length=255)
def get_model_type(self):
return "my_model_two"
Then in your template you can easily just call that function:
{% if model.get_model_type == 'my_model_one' %}
<p>Model One</p>
{% elif model.get_model_type == 'my_model_two' %}
<p>Model Two</p>
{% endif %}

Late to the party, but I just had this problem. The solution I went for is duck-typing, so:
{% if action.extra_column.year %}
{{ action.extra_column|date:"M y" }}
{% else %}
{{ action.extra_column }}
{% endif %}
Could you argue that this is definitely not the right way to do it? Probably. Will it get the job done without writing your own template filter and having even more code to maintain? Absolutely.

That's my approach:
#register.filter
def get_type(value):
""" It returns variable type as a pure string name """
return type(value).__name__

You can try this to recognize String vs List type:
{%if v_1.0.1|length == 0%}
<!--STR-->
{{v_1}}
{%else%}
<!--List-->
{{v_1.0}}
{%endif%}

Related

Django: If value exists in database, pass it as a variable from view to template

I am new to coding and new to Django. I searched stackoverflow for my question but didn't find what I was looking for:
What I am trying to do is check if certain values are in my database and if yes, pass it as a variable to the template. The values will be items of a dropdown menu.
If I have for example a database with bicycle1 to bicycleN I'd like to check if the value of the attribute "handlebar" of each database-object matches a certain manufacturer. If yes, pass it to the template so it can appear in the dropdown menu to later filter the results.
First I thought I should check in the template itself and thought about something like this:
bicyle_list.html
<ul id='dropdown1' class='dropdown-content'>
{% for bicyle in bicycles %}
{% with manufacturerA=False %}
{% if manufacturerA == False and bicycle.handlebar == "manufacturerA" %}
<li>ManufacturerA</li>
{% manufacturerA=True %}
{% endif %}
{% endwith %}
{% endfor %}
But as I understand the template should only contain rendering logic. (Besides, I`d have to use boolean variables in the for-loop, because a manufacturer should only appear once in the dropdown menu even if there are several bicycles with that certain handlebar. Changing the value of variables in a template seems quite complicated to me.)
In the views.py I thought about this but don't know how to work with conditions and the rendering-method:
views.py
bicycles = Bicycle.objects.all()
for bicycle in bicyles:
if bicycle.handlebar == "manufacturerA":
manufacturerA= "manufacturerA"
if bicycle.handlebar == "manufacturerB":
manufacturerB= "manufacturerB"
#if variable manufacturerA exists, pass it to the template – if not, not! Same with manufacturerB
#But how to include in return render(..)?
return render(request, 'shop/bicycle_list.html', {'bicycles': bicycles})
Do you have any idea how to pass optional variables?
You can obtain a list of distinct manufacturers with:
def some_view(request):
manufacturers = Bicycle.objects.values_list('handlebar', flat=True).distinct()
return render(request, 'shop/bicycle_list.html', {'manufacturers': manufacturers})
<ul id='dropdown1' class='dropdown-content'>
{% for manufacturer in manufacturers %}
<li>{{ manufacturer }}</li>
{% endfor %}
</ul>
You however might want to make a model for the manufacturer, and work with a many-to-one relationship [Django-doc].

Why won't Django template recognize list from object field?

I have an object field that stores a list. The Django template is treating the list like a string, it iterates over each character, not each list value.
Tried saving the object field in various ways, "['1','2']" and "1,2". Tried the "|make_list" filter.
models.py
class SpecialField(models.Model):
name = models.CharField(max_length=200,blank=True,null=True)
description = models.CharField(max_length=200,blank=True,null=True)
value_options = models.CharField(max_length=200,blank=True,null=True)
# value_options might be "['1','2']" or "red, green, blue"
views.py
object_field_list= SpecialField.objects.all()
context = {
'object_field_list': object_field_list,
}
return render(request, 'app1/template-detail.html', context)
template
{% for object_field in object_field_list%}
{% for list_value in object_field.value_options %}
<option>{{ list_value }}</option>
{% endfor %}
{% endfor %}
I was hoping for:
<option>1</option>
<option>2</option>
But I am getting:
<option>[</option>
<option>'</option>
<option>1</option>
<option>'</option>
<option>,</option>
<option>'</option>
<option>2</option>
<option>'</option>
<option>]</option>
You are wanting to show data from a model, so let's suppose you have a model
class SpecialField(models.Model):
name=models.CharField(max_length=101)
price = models.IntegerField(default=0)
Given the way you've set in in context you can show this in the template with
{% for obj in object_field_list %}
{{ obj.name}} - {{ obj.price }}
{% endfor %}
Obviously, you need to amend for your model
Tweak on your suggestion...it needs one more level of looping. Still can't get it to work.
models.py
class SpecialField(models.Model):
name=models.CharField(max_length=101)
price = models.IntegerField(default=0)
custom_list = models.CharField(max_length=200)
template
{% for obj in object_field_list %}
{{ obj.name}} - {{ obj.price }}
{% for list_value in obj.custom_list %}
{{ list_value }}
# this is where it's breaking for me
{% endfor %}
{% endfor %}
context = {
'object_field_list': SpecialField.objects.values_list('value_options', flat=True),
}
Should get you what you actually want to loop over.
EDIT: Sorry, I missed the fact you are storing a string rather than using an ArrayField or similar. The problem from your updated answer is the data you have isn't consistent. If it were simply all comma-separated values you could do:
object_field_list = [value_list.split(',') for value_list in SpecialField.objects.values_list('value_options', flat=True)]
but you will need some way of normalizing the data you're storing in value_options. How does the data get into the database? If it's via Django, you can apply some kind of cleaning method on the form or API endpoint that accepts the data.
Tried saving the object field in various ways, "['1','2']" and "1,2". Tried the "|make_list" filter.
If you have complete control over the incoming data, you would be better off normalizing the data: rather than storing a single value_options entry on SpecialField, you would remove that field and add a second model, e.g., SpecialFieldOption like
class SpecialFieldOption(models.Model):
name = models.CharField(max_length=200, blank=False)
field = models.ForeignKey(SpecialField, related_name='options')
# now you can do
SpecialField.objects.get(pk=1).options.all()
to loop over the list of all options for a given SpecialField.
This was the handiest solution...define a new list using split. https://stackoverflow.com/a/8318915/9268133. Thanks for everyone's help!

Formatting date in django template

My template renders the tag {{ test.date}} in the following format -
2015-12-15T23:55:33.422679
When I try to format it using django's built in template tag date, it doesn't display anything.
Variations I've tried:
{{ test.date|date:"SHORT_DATE_FORMAT" }}
{{ test.date|date:"D d M Y" }}
models.py:
class Human(models.Model):
name = models.CharField(max_length=50,default='',blank=False)
class Test(models.Model):
human = models.ForeignKey(Human)
date = models.DateTimeField(default=datetime.now)
views.py:
def list(request):
h = Human.objects.all()
s=[]
for her in h:
t = h.test_set.all()
s.extend(t)
context = RequestContext(request, {'test_list': s,})
return render_to_response('template.html', context)
I am then using it in template like this:
{% for test in test_list %}
{{test.date}}
{% endfor %}
What am I missing?
Answering an OLD post... but, the answer doesn't seem (to me) to be answering the original question - which was WHY isn't the Django inline template date formatting working...
The answer is (I believe) that the author was trying to output to his page something like:
"This is my date:{{test.date|date:"D d M Y"}}."
The problem, if this is truly what was being tried, is that the double quotes don't work in this situation. You need to do the following instead:
"This is my date:{{test.date|date:'D d M Y'}}."
Note the single quotes...
I'm not sure what you want from this logic, but I think you can use this:
def list(request):
test = Test.objects.all()
return render(request, 'template.html', {'test':test})
and in template:
{% for t in test %}
{% t.date %}
{% endfor %}
if you want display human, just add in cycle {% t.human.name %}

Django template - dynamic variable name

Good Afternoon,
How can I use a variable variable name in Django templates?
I have a custom auth system using context, has_perm checks to see if the user has access to the specified section.
deptauth is a variable with a restriction group name i.e SectionAdmin. I think has.perm is actually checking for 'deptauth' instead of the variable value SectionAdmin as I would like.
{%if has_perm.deptauth %}
How can I do that? has_perm.{{depauth}} or something along those lines?
EDIT - Updated code
{% with arg_value="authval" %}
{% lookup has_perm "admintest" %}
{% endwith %}
{%if has_perm.authval %}
window.location = './portal/tickets/admin/add/{{dept}}/'+val;
{% else %}
window.location = './portal/tickets/add/{{dept}}/'+val;
{%endif%}
has_perm isn't an object.. it's in my context processor (permchecker):
class permchecker(object):
def __init__(self, request):
self.request = request
pass
def __getitem__(self, perm_name):
return check_perm(self.request, perm_name)
You're best off writing your own custom template tag for that. It's not difficult to do, and normal for this kind of situation.
I have not tested this, but something along these lines should work. Remember to handle errors properly!
def lookup(object, property):
return getattr(object, property)()
register.simple_tag(lookup)
If you're trying to get a property rather than execute a method, remove those ().
and use it:
{% lookup has_perm "depauth" %}
Note that has_perm is a variable, and "depauth" is a string value. this will pass the string for lookup, i.e. get has_perm.depauth.
You can call it with a variable:
{% with arg_value="depauth_other_value" %}
{% lookup has_perm arg_value %}
{% endwith %}
which means that the value of the variable will be used to look it up, i.e. has_perm.depauth_other_value'.
You can try like this,
{{ dict|key:key_name }}
Filter:
def key(d, key_name):
return d[key_name]
key = register.filter('key', key)
More information, django ticket

Django templates: verbose version of a choice

I have a model:
from django.db import models
CHOICES = (
('s', 'Glorious spam'),
('e', 'Fabulous eggs'),
)
class MealOrder(models.Model):
meal = models.CharField(max_length=8, choices=CHOICES)
I have a form:
from django.forms import ModelForm
class MealOrderForm(ModelForm):
class Meta:
model = MealOrder
And I want to use formtools.preview. The default template prints the short version of the choice ('e' instead of 'Fabulous eggs'), becuase it uses
{% for field in form %}
<tr>
<th>{{ field.label }}:</th>
<td>{{ field.data }}</td>
</tr>
{% endfor %}.
I'd like a template as general as the mentioned, but printing 'Fabulous eggs' instead.
[as I had doubts where's the real question, I bolded it for all of us :)]
I know how to get the verbose version of a choice in a way that is itself ugly:
{{ form.meal.field.choices.1.1 }}
The real pain is I need to get the selected choice, and the only way coming to my mind is iterating through choices and checking {% ifequals currentChoice.0 choiceField.data %}, which is even uglier.
Can it be done easily? Or it needs some template-tag programming? Shouldn't that be available in django already?
In Django templates you can use the "get_FOO_display()" method, that will return the readable alias for the field, where 'FOO' is the name of the field.
Note: in case the standard FormPreview templates are not using it, then you can always provide your own templates for that form, which will contain something like {{ form.get_meal_display }}.
The best solution for your problem is to use helper functions.
If the choices are stored in the variable CHOICES and the model field storing the selected choice is 'choices' then you can directly use
{{ x.get_choices_display }}
in your template. Here, x is the model instance.
Hope it helps.
My apologies if this answer is redundant with any listed above, but it appears this one hasn't been offered yet, and it seems fairly clean. Here's how I've solved this:
from django.db import models
class Scoop(models.Model):
FLAVOR_CHOICES = [
('c', 'Chocolate'),
('v', 'Vanilla'),
]
flavor = models.CharField(choices=FLAVOR_CHOICES)
def flavor_verbose(self):
return dict(Scoop.FLAVOR_CHOCIES)[self.flavor]
My view passes a Scoop to the template (note: not Scoop.values()), and the template contains:
{{ scoop.flavor_verbose }}
Basing on Noah's reply, here's a version immune to fields without choices:
#annoyances/templatetags/data_verbose.py
from django import template
register = template.Library()
#register.filter
def data_verbose(boundField):
"""
Returns field's data or it's verbose version
for a field with choices defined.
Usage::
{% load data_verbose %}
{{form.some_field|data_verbose}}
"""
data = boundField.data
field = boundField.field
return hasattr(field, 'choices') and dict(field.choices).get(data,'') or data
I'm not sure wether it's ok to use a filter for such purpose. If anybody has a better solution, I'll be glad to see it :) Thank you Noah!
We can extend the filter solution by Noah to be more universal in dealing with data and field types:
<table>
{% for item in query %}
<tr>
{% for field in fields %}
<td>{{item|human_readable:field}}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
Here's the code:
#app_name/templatetags/custom_tags.py
def human_readable(value, arg):
if hasattr(value, 'get_' + str(arg) + '_display'):
return getattr(value, 'get_%s_display' % arg)()
elif hasattr(value, str(arg)):
if callable(getattr(value, str(arg))):
return getattr(value, arg)()
else:
return getattr(value, arg)
else:
try:
return value[arg]
except KeyError:
return settings.TEMPLATE_STRING_IF_INVALID
register.filter('human_readable', human_readable)
I don't think there's any built-in way to do that. A filter might do the trick, though:
#register.filter(name='display')
def display_value(bf):
"""Returns the display value of a BoundField"""
return dict(bf.field.choices).get(bf.data, '')
Then you can do:
{% for field in form %}
<tr>
<th>{{ field.label }}:</th>
<td>{{ field.data|display }}</td>
</tr>
{% endfor %}
You have Model.get_FOO_display() where FOO is the name of the field that has choices.
In your template do this :
{{ scoop.get_flavor_display }}
Add to your models.py one simple function:
def get_display(key, list):
d = dict(list)
if key in d:
return d[key]
return None
Now, you can get verbose value of choice fields like that:
class MealOrder(models.Model):
meal = models.CharField(max_length=8, choices=CHOICES)
def meal_verbose(self):
return get_display(self.meal, CHOICES)
Upd.: I'm not sure, is that solution “pythonic” and “django-way” enough or not, but it works. :)
The extended-extended version of Noah's and Ivan's solution. Also fixed Noah's solution for Django 3.1, as ModelChoiceIteratorValue is now unhashable.
#register.filter
def display_value(value: Any, arg: str = None) -> str:
"""Returns the display value of a BoundField or other form fields"""
if not arg: # attempt to auto-parse
# Returning regular field's value
if not hasattr(value.field, 'choices'): return value.value()
# Display select value for BoundField / Multiselect field
# This is used to get_..._display() for a read-only form-field
# which is not rendered as Input, but instead as text
return list(value.field.choices)[value.value()][1]
# usage: {{ field|display_value:<arg> }}
if hasattr(value, 'get_' + str(arg) + '_display'):
return getattr(value, 'get_%s_display' % arg)()
elif hasattr(value, str(arg)):
if callable(getattr(value, str(arg))):
return getattr(value, arg)()
return getattr(value, arg)
return value.get(arg) or ''
<select class="form-select">
{% for key, value in form.meal.field.choices %}
{% if form.meal.value == key %}
<option value="{{ form.key }}" selected>{{ value }}</option>
{% else %}
<option value="{{ key }}">{{ value }}</option>
{% endif %}
{% endfor %}
</select>
Hey what about that way?
in models.py
class MealOrder(models.Model):
CHOICES = (
('s', 'Glorious spam'),
('e', 'Fabulous eggs'),
)
meal = models.CharField(max_length=8, choices=CHOICES)
meal_value = models.CharField(max_length=1, blank=True, null=True,
editable=False)
def save(self, *args, **kwargs):
if self.meal == "s":
self.meal_value = "Glorious spam"
elif self.meal == "e":
self.meal_value = "Fabulous eggs"
super(MealOrder, self).save(*args, **kwargs)
in views.py
from .models import MealOrder
def meal_order(request):
meals = MealOrder.objects.all()
return render(request, "meals.html", {
"meals": meals,
})
in meals.html
{% for meal in meals %}
{{meal.meal_value }}
{%endfor%}