Django templates: loop through and print all available properties of an object? - django

I have a database object called manor_stats, with around 30 fields. For most rows, most of these fields will be null.
In my template, I'd like to loop through all the fields in the row, and print info for only the fields that aren't null.
For example, there's a field called "name": I'd like to print <li>Name: {{ manor_stats.name }}</li> in the template ONLY for those objects where the field isn't null. Ideally I'd like to pull in "Name: " from somewhere automatically too, rather than specifying it.
I know I could use {% if manor_stats.name %} to check whether each field is null, but I don't want to do that 30 times for all the fields.
Here's what I have in views.py:
manor_stats = Manors.objects.get(idx=id)
return render_to_response('place.html', { 'place' : place, 'manor_stats' : manor_stats }, context_instance = RequestContext(request))
And then in place.html, I'd like to have something that works approximately like this (pseudocode, with ??? indicating the bits that I don't know how to do):
{% if manor_stats %}
<ul>
{% for manor_stats.property??? in manor_stats %}
{% if manor_stats.property %}
<li>{{ manor_stats.property.field_name??? }} {{ manor_stats.property.value??? }}</li>
{% endif %}
{% endfor %
{% endif %}
Hope that makes sense...

You could add a method to your Manors model that will return all the field values, from there you can just loop over these values in your template checking to see if the value isn't null.
-- models.py
class Manors(models.Model)
#field declarations
def get_fields(self):
return [(field.name, field.value_to_string(self)) for field in Manors._meta.fields]
-- manor_detail.html
{% for name, value in manor_stats.get_fields %}
{% if value %}
{{ name }} => {{ value }}
{% endif %}
{% endfor %}

The following approach only requires defining a single custom template filter (see the docs) - it's DRY'er than having to write or copy a method into every model you need this functionality for.
-- my_app/templatetags/my_tags.py
from django import template
register = template.Library()
#register.filter
def get_fields(obj):
return [(field.name, field.value_to_string(obj)) for field in obj._meta.fields]
You'll need to restart your server to get the new tags registered and available in your templates.
-- my_app/templates/my_app/my_template.html
{% load my_tags %}
<ul>
{% for key, val in object|get_fields %}
<li>{{ key }}: {{ val }}</li>
{% endfor %}
</ul>
Tested in Django 1.11.
NB: Since ._meta is a private attribute it's functionality might well change in the future.
Thanks to the other answers on this post that took me most of the way, particularly W. Perrin.

Use Model._meta.get_fields() in python code.
>>> from django.contrib.auth.models import User
>>> User._meta.get_fields()
(<ManyToOneRel: admin.logentry>,
<django.db.models.fields.AutoField: id>,
<django.db.models.fields.CharField: password>,
<django.db.models.fields.DateTimeField: last_login>,
<django.db.models.fields.BooleanField: is_superuser>,
....
In template, we can define a filter to retrieve all the fields.
├─my_app
│ │
│ ├─templatetags
│ │ │ my_filters.py
│ │
from django import template
register = template.Library()
#register.filter
def get_fields(obj):
return obj._meta.get_fields()
{% for k,v in obj|get_fields %}
<td> {{k}} </td>
{% endfor %}
See this link: https://docs.djangoproject.com/en/2.0/ref/models/meta/#retrieving-all-field-instances-of-a-model

Related

Query the same object multiple times in Django view [duplicate]

I have a QuerySet like:
items = Item.objects.all()
Item has a 'name' field. In the template I want to show:
A
Axes
Alcohol
B
Bazookas
C
Coins
Cartridges
S
Swords
Sparrows
So the items are ordered and group by the first letter. Missing letters are omitted. Does anyone have any ideas?
There's a template tag for this, if all you care about is its presentation on the page. First, define an organizational principle in the class. In your case, it's the first letter:
class Item(models.Model):
...
def first_letter(self):
return self.name and self.name[0] or ''
And then define a regroup in the template, using the first_letter call:
{% regroup items by first_letter as letter_list %}
<ul>
{% for letter in letter_list %}
<li>{{ letter.grouper }}
<ul>
{% for item in letter.list %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
Just wanted to add that if you use this and your item has a lower-case first character it will be a separate group. I added upper to it.
return self.name and self.name.upper()[0] or ''
Alternatively you could use slice inline in the template without the need for a first_letter method on your model.
{% regroup items by name|slice:":1" as letter_list %}
<ul>
{% for letter in letter_list %}
<li>{{ letter.grouper }}
<ul>
{% for item in letter.list %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
Even easier. You can group by first leter just in 'regroup':
{% regroup items|dictsort:"name" by name.0 as item_letter %}
<ul>
{% for letter in item_letter %}
<h4>{{ letter.grouper|title }}</h4>
{% for i in letter.list|dictsort:"name" %}
<li>{{ i.name }}</li>
{% endfor %}
{% empty %}
<span>There is no items yet...</span>
{% endfor %}
</ul>
name.0 in this case the same as item.name[0] in Python.
Tested in Django 1.10
For Django REST you can do like this,
import string
import collections
from rest_framework.response import Response
from rest_framework import status, viewsets
def groupby(self, request):
result = []
for i in list(string.ascii_uppercase):
c = City.objects.filter(name__startswith=i)
if c:
result.append((i, map((lambda x: x['name']),list(c.values('name')))
))
return Response(collections.OrderedDict(sorted(dict(result).items())), status=status.HTTP_200_OK)
City Models
class City(models.Model):
"""All features model"""
name = models.CharField(max_length=99)
Response
{
"A": [
"Adelanto",
"Azusa",
"Alameda",
"Albany",
"Alhambra",
"Anaheim"
],
"B": [
"Belmont",
"Berkeley",
"Beverly Hills",
"Big Sur",
"Burbank"
],
......
}
This is another take for doing in straight Django and Python. The other solution offered was terribly inefficient
import itertools
collector = {}
item_qs = Item.objects.all().order_by('name')
for alphabet_letter, items in itertools.groupby(item_qs, lambda x: x.name[0].lower()):
# you can do any modifications you need at this point
# you can do another loop if you want or a dictionary comprehension
collector[alphabet_letter] = items
What does this give you? A single query to the db.
Should I use a collector? No, you should maybe use a yield this is just a proof of concept.
What ever you do, DO NOT add a query call inside a loop.

Check if object is in manytomany list in template

How can I check if a certain object/id is in a list?
I want something to be displayed if the ID of the connected object is not "6".
Tried with something like this:
{% if user.benefits.all != "6" %}
You do not have a benefit with ID 6.
{% endif %}
It is better to not put much logic into templates. View (or model) - is a better place for that.
For example in view you can check, that user.benefits has element with id=6 by this code:
has_benefit = user.benefits.filter(id=6).count() > 0
context['has_benefit'] = has_benefit
Now in template just use this new context variable:
{% if not has_benefit %}
You do not have a benefit with ID 6.
{% endif %}
UPDATED:
If you still want to do it in template, it is better to create a custom template filter:
from django import template
register = template.Library()
#register.filter(name='has_benefit')
def has_benefit(user, benefit_id):
b_id = int(benefit_id)
return user.benefits.filter(id=b_id).count() > 0
Now in template load your templatetags module using {% load module_name %} and use:
{% if not user|has_benefit:"6" %}
You do not have a benefit with ID 6.
{% endif %}
{% for benefit in user.benefits.all %}
{% if benefit.id != 6 %}
You do not have a benefit with id 6
{% endif %}
{% endfor %}
But this will loop through all the benefits and print it every time the condition passes.
So, you should write a template tag which returns you a list of ids for all benefits for a particular user and once you have that list you can do:
{% if 6 not in list_of_benefit_ids %}
You do not have a benefit with id 6
{% endif %}

Django object is not iterable

I have a object that I'm trying to iterate over within my template.
My issue is that one of the fields response contains json data and I get this error message
transaction' object is not iterable
{% for item in transaction %}
{{ item.notjson_fields}}
{% for jsonItem in item.response %}
{{jsonItem}}
{% endfor %}
{% endfor %}
Model:
date_created = models.DateTimeField(auto_now_add=True)
request = JSONField()
response = JSONField()
You are trying to iterate over a Transaction object which is not iterable.
Try something like
{{ transaction.notjson_fields}}
{% for jsonItem in transaction.response %}
{{ jsonItem }}
{% endfor %}
I can't be sure though without knowing what Transaction looks like
Edit:
Since response is a JSONField, you can access it like a dict. Just do
{{ transaction.response.amount }}
If like Ngenator said, your field is not a JSON object but a string, you may need to load it first. First register a new template tag
from django import template
register = template.Library()
import json
#register.filter
def load_json(value):
return json.loads(value)
Then to get (just) the amount in your template
{% with transaction.response|load_json as jsondict %}
{% for k, v in jsondict.items %}
{% if k == "amount" %}
<td>{{ v }}</td>
{% endif %}
{% endfor %}
{% endwith %}
Good Luck.

Django getting properties of model dynamically

I have a model like
class Document(models.Model):
comment = models.TextField(null=True,blank=True)
upload_date = models.DateTimeField(blank=False)
Document objects are listed in a template as
{% for doc in doc_list %}
{{ doc.comment }}
{{ doc.upload_date }}
{% endfor %}
However, I'd like to reach the properties of doc dynamically like
{{ doc."comment" }}
or
{{ doc|getField:"comment" }}
How can I do that?
Thanks
I assume you mean you want to access fields on a model by using another variable, which you don't necessarily know at the time. So you might have some_var be passed into the template, and this is the field in the model that you'd like to display, such as comment or upload_date.
You could build a template tag to do this:
#register.simple_tag
def get_model_attr(instance, key):
return getattr(instance, key)
Now in your template you can do stuff like:
{% for doc in doc_list %}
{% get_model_attr doc "comment" %}
{% get_model_attr doc some_var %}
{% endfor %}

Use variable as dictionary key in Django template

I'd like to use a variable as an key in a dictionary in a Django template. I can't for the life of me figure out how to do it. If I have a product with a name or ID field, and ratings dictionary with indices of the product IDs, I'd like to be able to say:
{% for product in product_list %}
<h1>{{ ratings.product.id }}</h1>
{% endfor %}
In python this would be accomplished with a simple
ratings[product.id]
But I can't make it work in the templates. I've tried using with... no dice. Ideas?
Create a template tag like this (in yourproject/templatetags):
#register.filter
def keyvalue(dict, key):
return dict[key]
Usage:
{{dictionary|keyvalue:key_variable}}
You need to prepare your data beforehand, in this case you should pass list of two-tuples to your template:
{% for product, rating in product_list %}
<h1>{{ product.name }}</h1><p>{{ rating }}</p>
{% endfor %}
Building on eviltnan's answer, his filter will raise an exception if key isn't a key of dict.
Filters should never raise exceptions, but should fail gracefully. This is a more robust/complete answer:
#register.filter
def keyvalue(dict, key):
try:
return dict[key]
except KeyError:
return ''
Basically, this would do the same as dict.get(key, '') in Python code, and could also be written that way if you don't want to include the try/except block, although it is more explicit.
There is a very dirty solution:
<div>We need d[{{ k }}]</div>
<div>So here it is:
{% for key, value in d.items %}
{% if k == key %}
{{ value }}
{% endif %}
{% endfor %}
</div>