I'm trying to create a basic template to display the selected instance's field values, along with their names. Think of it as just a standard output of the values of that instance in table format, with the field name (verbose_name specifically if specified on the field) in the first column and the value of that field in the second column.
For example, let's say we have the following model definition:
class Client(Model):
name = CharField(max_length=150)
email = EmailField(max_length=100, verbose_name="E-mail")
I would want it to be output in the template like so (assume an instance with the given values):
Field Name Field Value
---------- -----------
Name Wayne Koorts
E-mail waynes#email.com
What I'm trying to achieve is being able to pass an instance of the model to a template and be able to iterate over it dynamically in the template, something like this:
<table>
{% for field in fields %}
<tr>
<td>{{ field.name }}</td>
<td>{{ field.value }}</td>
</tr>
{% endfor %}
</table>
Is there a neat, "Django-approved" way to do this? It seems like a very common task, and I will need to do it often for this particular project.
model._meta.get_all_field_names() will give you all the model's field names, then you can use model._meta.get_field() to work your way to the verbose name, and getattr(model_instance, 'field_name') to get the value from the model.
NOTE: model._meta.get_all_field_names() is deprecated in django 1.9. Instead use model._meta.get_fields() to get the model's fields and field.name to get each field name.
You can use Django's to-python queryset serializer.
Just put the following code in your view:
from django.core import serializers
data = serializers.serialize( "python", SomeModel.objects.all() )
And then in the template:
{% for instance in data %}
{% for field, value in instance.fields.items %}
{{ field }}: {{ value }}
{% endfor %}
{% endfor %}
Its great advantage is the fact that it handles relation fields.
For the subset of fields try:
data = serializers.serialize('python', SomeModel.objects.all(), fields=('name','size'))
Finally found a good solution to this on the dev mailing list:
In the view add:
from django.forms.models import model_to_dict
def show(request, object_id):
object = FooForm(data=model_to_dict(Foo.objects.get(pk=object_id)))
return render_to_response('foo/foo_detail.html', {'object': object})
in the template add:
{% for field in object %}
<li><b>{{ field.label }}:</b> {{ field.data }}</li>
{% endfor %}
Here's another approach using a model method. This version resolves picklist/choice fields, skips empty fields, and lets you exclude specific fields.
def get_all_fields(self):
"""Returns a list of all field names on the instance."""
fields = []
for f in self._meta.fields:
fname = f.name
# resolve picklists/choices, with get_xyz_display() function
get_choice = 'get_'+fname+'_display'
if hasattr(self, get_choice):
value = getattr(self, get_choice)()
else:
try:
value = getattr(self, fname)
except AttributeError:
value = None
# only display fields with values and skip some fields entirely
if f.editable and value and f.name not in ('id', 'status', 'workshop', 'user', 'complete') :
fields.append(
{
'label':f.verbose_name,
'name':f.name,
'value':value,
}
)
return fields
Then in your template:
{% for f in app.get_all_fields %}
<dt>{{f.label|capfirst}}</dt>
<dd>
{{f.value|escape|urlize|linebreaks}}
</dd>
{% endfor %}
In light of Django 1.8's release (and the formalization of the Model _meta API, I figured I would update this with a more recent answer.
Assuming the same model:
class Client(Model):
name = CharField(max_length=150)
email = EmailField(max_length=100, verbose_name="E-mail")
Django <= 1.7
fields = [(f.verbose_name, f.name) for f in Client._meta.fields]
>>> fields
[(u'ID', u'id'), (u'name', u'name'), (u'E-mail', u'email')]
Django 1.8+ (formalized Model _meta API)
Changed in Django 1.8:
The Model _meta API has always existed as a Django internal, but wasn’t formally documented and supported. As part of the effort to make this API public, some of the already existing API entry points have changed slightly. A migration guide has been provided to assist in converting your code to use the new, official API.
In the below example, we will utilize the formalized method for retrieving all field instances of a model via Client._meta.get_fields():
fields = [(f.verbose_name, f.name) for f in Client._meta.get_fields()]
>>> fields
[(u'ID', u'id'), (u'name', u'name'), (u'E-mail', u'email')]
Actually, it has been brought to my attention that the above is slightly overboard for what was needed (I agree!). Simple is better than complex. I am leaving the above for reference. However, to display in the template, the best method would be to use a ModelForm and pass in an instance. You can iterate over the form (equivalent of iterating over each of the form's fields) and use the label attribute to retrieve the verbose_name of the model field, and use the value method to retrieve the value:
from django.forms import ModelForm
from django.shortcuts import get_object_or_404, render
from .models import Client
def my_view(request, pk):
instance = get_object_or_404(Client, pk=pk)
class ClientForm(ModelForm):
class Meta:
model = Client
fields = ('name', 'email')
form = ClientForm(instance=instance)
return render(
request,
template_name='template.html',
{'form': form}
)
Now, we render the fields in the template:
<table>
<thead>
{% for field in form %}
<th>{{ field.label }}</th>
{% endfor %}
</thead>
<tbody>
<tr>
{% for field in form %}
<td>{{ field.value|default_if_none:'' }}</td>
{% endfor %}
</tr>
</tbody>
</table>
Ok, I know this is a bit late, but since I stumbled upon this before finding the correct answer so might someone else.
From the django docs:
# This list contains a Blog object.
>>> Blog.objects.filter(name__startswith='Beatles')
[<Blog: Beatles Blog>]
# This list contains a dictionary.
>>> Blog.objects.filter(name__startswith='Beatles').values()
[{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]
You can use the values() method of a queryset, which returns a dictionary. Further, this method accepts a list of fields to subset on. The values() method will not work with get(), so you must use filter() (refer to the QuerySet API).
In view...
def show(request, object_id):
object = Foo.objects.filter(id=object_id).values()[0]
return render_to_response('detail.html', {'object': object})
In detail.html...
<ul>
{% for key, value in object.items %}
<li><b>{{ key }}:</b> {{ value }}</li>
{% endfor %}
</ul>
For a collection of instances returned by filter:
object = Foo.objects.filter(id=object_id).values() # no [0]
In detail.html...
{% for instance in object %}
<h1>{{ instance.id }}</h1>
<ul>
{% for key, value in instance.items %}
<li><b>{{ key }}:</b> {{ value }}</li>
{% endfor %}
</ul>
{% endfor %}
I used https://stackoverflow.com/a/3431104/2022534 but replaced Django's model_to_dict() with this to be able to handle ForeignKey:
def model_to_dict(instance):
data = {}
for field in instance._meta.fields:
data[field.name] = field.value_from_object(instance)
if isinstance(field, ForeignKey):
data[field.name] = field.rel.to.objects.get(pk=data[field.name])
return data
Please note that I have simplified it quite a bit by removing the parts of the original I didn't need. You might want to put those back.
You can have a form do the work for you.
def my_model_view(request, mymodel_id):
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
model = get_object_or_404(MyModel, pk=mymodel_id)
form = MyModelForm(instance=model)
return render(request, 'model.html', { 'form': form})
Then in the template:
<table>
{% for field in form %}
<tr>
<td>{{ field.name }}</td>
<td>{{ field.value }}</td>
</tr>
{% endfor %}
</table>
Below is mine, inspired by shacker's get_all_fields.
It gets a dict of one model instance, if encounter relation field, then asign the field value a dict recursively.
def to_dict(obj, exclude=[]):
"""生成一个 dict, 递归包含一个 model instance 数据.
"""
tree = {}
for field in obj._meta.fields + obj._meta.many_to_many:
if field.name in exclude or \
'%s.%s' % (type(obj).__name__, field.name) in exclude:
continue
try :
value = getattr(obj, field.name)
except obj.DoesNotExist:
value = None
if type(field) in [ForeignKey, OneToOneField]:
tree[field.name] = to_dict(value, exclude=exclude)
elif isinstance(field, ManyToManyField):
vs = []
for v in value.all():
vs.append(to_dict(v, exclude=exclude))
tree[field.name] = vs
elif isinstance(field, DateTimeField):
tree[field.name] = str(value)
elif isinstance(field, FileField):
tree[field.name] = {'url': value.url}
else:
tree[field.name] = value
return tree
This function is mainly used to dump a model instance to json data:
def to_json(self):
tree = to_dict(self, exclude=('id', 'User.password'))
return json.dumps(tree, ensure_ascii=False)
There should really be a built-in way to do this. I wrote this utility build_pretty_data_view that takes a model object and form instance (a form based on your model) and returns a SortedDict.
Benefits to this solution include:
It preserves order using Django's built-in SortedDict.
When tries to get the label/verbose_name, but falls back to the field name if one is not defined.
It will also optionally take an exclude() list of field names to exclude certain fields.
If your form class includes a Meta: exclude(), but you still want to return the values, then add those fields to the optional append() list.
To use this solution, first add this file/function somewhere, then import it into your views.py.
utils.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4
from django.utils.datastructures import SortedDict
def build_pretty_data_view(form_instance, model_object, exclude=(), append=()):
i=0
sd=SortedDict()
for j in append:
try:
sdvalue={'label':j.capitalize(),
'fieldvalue':model_object.__getattribute__(j)}
sd.insert(i, j, sdvalue)
i+=1
except(AttributeError):
pass
for k,v in form_instance.fields.items():
sdvalue={'label':"", 'fieldvalue':""}
if not exclude.__contains__(k):
if v.label is not None:
sdvalue = {'label':v.label,
'fieldvalue': model_object.__getattribute__(k)}
else:
sdvalue = {'label':k,
'fieldvalue': model_object.__getattribute__(k)}
sd.insert(i, k, sdvalue)
i+=1
return sd
So now in your views.py you might do something like this
from django.shortcuts import render_to_response
from django.template import RequestContext
from utils import build_pretty_data_view
from models import Blog
from forms import BlogForm
.
.
def my_view(request):
b=Blog.objects.get(pk=1)
bf=BlogForm(instance=b)
data=build_pretty_data_view(form_instance=bf, model_object=b,
exclude=('number_of_comments', 'number_of_likes'),
append=('user',))
return render_to_response('my-template.html',
RequestContext(request,
{'data':data,}))
Now in your my-template.html template you can iterate over the data like so...
{% for field,value in data.items %}
<p>{{ field }} : {{value.label}}: {{value.fieldvalue}}</p>
{% endfor %}
Good Luck. Hope this helps someone!
Instead of editing every model I would recommend to write one template tag which will return all field of any model given.
Every object has list of fields ._meta.fields.
Every field object has attribute name that will return it's name and method value_to_string() that supplied with your model object will return its value.
The rest is as simple as it's said in Django documentation.
Here is my example how this templatetag might look like:
from django.conf import settings
from django import template
if not getattr(settings, 'DEBUG', False):
raise template.TemplateSyntaxError('get_fields is available only when DEBUG = True')
register = template.Library()
class GetFieldsNode(template.Node):
def __init__(self, object, context_name=None):
self.object = template.Variable(object)
self.context_name = context_name
def render(self, context):
object = self.object.resolve(context)
fields = [(field.name, field.value_to_string(object)) for field in object._meta.fields]
if self.context_name:
context[self.context_name] = fields
return ''
else:
return fields
#register.tag
def get_fields(parser, token):
bits = token.split_contents()
if len(bits) == 4 and bits[2] == 'as':
return GetFieldsNode(bits[1], context_name=bits[3])
elif len(bits) == 2:
return GetFieldsNode(bits[1])
else:
raise template.TemplateSyntaxError("get_fields expects a syntax of "
"{% get_fields <object> [as <context_name>] %}")
Yeah it's not pretty, you'll have to make your own wrapper. Take a look at builtin databrowse app, which has all the functionality you need really.
This may be considered a hack but I've done this before using modelform_factory to turn a model instance into a form.
The Form class has a lot more information inside that's super easy to iterate over and it will serve the same purpose at the expense of slightly more overhead. If your set sizes are relatively small I think the performance impact would be negligible.
The one advantage besides convenience of course is that you can easily turn the table into an editable datagrid at a later date.
I've come up with the following method, which works for me because in every case the model will have a ModelForm associated with it.
def GetModelData(form, fields):
"""
Extract data from the bound form model instance and return a
dictionary that is easily usable in templates with the actual
field verbose name as the label, e.g.
model_data{"Address line 1": "32 Memory lane",
"Address line 2": "Brainville",
"Phone": "0212378492"}
This way, the template has an ordered list that can be easily
presented in tabular form.
"""
model_data = {}
for field in fields:
model_data[form[field].label] = eval("form.data.%s" % form[field].name)
return model_data
#login_required
def clients_view(request, client_id):
client = Client.objects.get(id=client_id)
form = AddClientForm(client)
fields = ("address1", "address2", "address3", "address4",
"phone", "fax", "mobile", "email")
model_data = GetModelData(form, fields)
template_vars = RequestContext(request,
{
"client": client,
"model_data": model_data
}
)
return render_to_response("clients-view.html", template_vars)
Here is an extract from the template I am using for this particular view:
<table class="client-view">
<tbody>
{% for field, value in model_data.items %}
<tr>
<td class="field-name">{{ field }}</td><td>{{ value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
The nice thing about this method is that I can choose on a template-by-template basis the order in which I would like to display the field labels, using the tuple passed in to GetModelData and specifying the field names. This also allows me to exclude certain fields (e.g. a User foreign key) as only the field names passed in via the tuple are built into the final dictionary.
I'm not going to accept this as the answer because I'm sure someone can come up with something more "Djangonic" :-)
Update: I'm choosing this as the final answer because it is the simplest out of those given that does what I need. Thanks to everyone who contributed answers.
Django 1.7 solution for me:
There variables are exact to the question, but you should definitely be able to dissect this example
The key here is to pretty much use the .__dict__ of the model
views.py:
def display_specific(request, key):
context = {
'question_id':question_id,
'client':Client.objects.get(pk=key).__dict__,
}
return render(request, "general_household/view_specific.html", context)
template:
{% for field in gen_house %}
{% if field != '_state' %}
{{ gen_house|getattribute:field }}
{% endif %}
{% endfor %}
in the template I used a filter to access the field in the dict
filters.py:
#register.filter(name='getattribute')
def getattribute(value, arg):
if value is None or arg is None:
return ""
try:
return value[arg]
except KeyError:
return ""
except TypeError:
return ""
I'm using this, https://github.com/miracle2k/django-tables.
<table>
<tr>
{% for column in table.columns %}
<th>{{ column }}</th>
{% endfor %}
</tr>
{% for row in table.rows %}
<tr>
{% for value in row %}
<td>{{ value }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
This approach shows how to use a class like django's ModelForm and a template tag like {{ form.as_table }}, but have all the table look like data output, not a form.
The first step was to subclass django's TextInput widget:
from django import forms
from django.utils.safestring import mark_safe
from django.forms.util import flatatt
class PlainText(forms.TextInput):
def render(self, name, value, attrs=None):
if value is None:
value = ''
final_attrs = self.build_attrs(attrs)
return mark_safe(u'<p %s>%s</p>' % (flatatt(final_attrs),value))
Then I subclassed django's ModelForm to swap out the default widgets for readonly versions:
from django.forms import ModelForm
class ReadOnlyModelForm(ModelForm):
def __init__(self,*args,**kwrds):
super(ReadOnlyModelForm,self).__init__(*args,**kwrds)
for field in self.fields:
if isinstance(self.fields[field].widget,forms.TextInput) or \
isinstance(self.fields[field].widget,forms.Textarea):
self.fields[field].widget=PlainText()
elif isinstance(self.fields[field].widget,forms.CheckboxInput):
self.fields[field].widget.attrs['disabled']="disabled"
Those were the only widgets I needed. But it should not be difficult to extend this idea to other widgets.
Just an edit of #wonder
def to_dict(obj, exclude=[]):
tree = {}
for field in obj._meta.fields + obj._meta.many_to_many:
if field.name in exclude or \
'%s.%s' % (type(obj).__name__, field.name) in exclude:
continue
try :
value = getattr(obj, field.name)
except obj.DoesNotExist as e:
value = None
except ObjectDoesNotExist as e:
value = None
continue
if type(field) in [ForeignKey, OneToOneField]:
tree[field.name] = to_dict(value, exclude=exclude)
elif isinstance(field, ManyToManyField):
vs = []
for v in value.all():
vs.append(to_dict(v, exclude=exclude))
tree[field.name] = vs
else:
tree[field.name] = obj.serializable_value(field.name)
return tree
Let Django handle all the other fields other than the related fields. I feel that is more stable
Take a look at django-etc application. It has model_field_verbose_name template tag to get field verbose name from templates: http://django-etc.rtfd.org/en/latest/models.html#model-field-template-tags
I just tested something like this in shell and seems to do it's job:
my_object_mapped = {attr.name: str(getattr(my_object, attr.name)) for attr in MyModel._meta.fields}
Note that if you want str() representation for foreign objects you should define it in their str method. From that you have dict of values for object. Then you can render some kind of template or whatever.
Django >= 2.0
Add get_fields() to your models.py:
class Client(Model):
name = CharField(max_length=150)
email = EmailField(max_length=100, verbose_name="E-mail")
def get_fields(self):
return [(field.verbose_name, field.value_from_object(self)) for field in self.__class__._meta.fields]
Then call it as object.get_fields on your template.html:
<table>
{% for label, value in object.get_fields %}
<tr>
<td>{{ label }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
</table>
If you model name is Client and you are getting client object by id then proceed like this
client = Client.objects.get(id=id)
fields = Client._meta.get_fields()
for field in fields:
value = getattr(client, field.name)
print(field.name)
print(value)
<table border='1'>
<tr>
{% for mfild in fields%}
<td>{{mfild}}</td>
{% endfor%}
</tr>
{%for v in records%}
<tr>
<td>{{v.id}}</td>
<td>{{v.title}}</td>
<td class="">{{v.desc}}</td>
</tr>
{% endfor%}
</table>
enter code here
Related
the below code is table.py
class StColumn(tables.Column):
def render(self, value, record):
if record.status == 'warning':
self.attrs ={"td": {"bgcolor": "DeepSkyBlue"}}
elif record.status == 'ok':
self.attrs ={"td": {"bgcolor": "SandyBrown"}}
return u"%s" % (record.status.id)
class resultTable(BaseTable):
class Meta(BaseTable.Meta):
model = resultModel
attrs = {'class': 'table table-striped table-bordered table-hover row-color=green' , 'width': '70%'}
status= StColumn(accessor='status.id')
print(status)
fields = (
"field1",
"field2",
"field3",
"status",
"field5",
)
**how we can change the color of row when the status==warning and status ==ok **
Display logic should not be handled in the view but in your templates instead. Take a look at this documentation for further information:
https://docs.djangoproject.com/en/3.1/ref/templates/builtins/#if
Typically you would display data from your model through the use of Views and HTML templates. You would write a view function/class that gets called when a user goes to a particular URL. That view would use a queryset to pass the data from your model into the template. Going into that much detail here would be pretty wasteful as there is a TON of documentation available describing this process.
Basically you would need a view that looks kind of like this:
def your_view(request):
your_instances = YourModel.objects.all()
context = {
'your_instances': your_instances,
}
return render(request, 'your_html_template.html', context=context)
Then your template would look something like this:
<table>
{% for instance in your_instances %}
{% if instance.status == 'warning' %}
<tr style="background-color:#FF0000">
{% endif %}
{% if instance.status == 'ok' %}
<tr style="background-color:#000000">
{% endif %}
<td>{{ instance.field }}</td>
</tr>
{% endfor %}
</table>
Having trouble calling methods of non-Django classes in django template.
I've tried both calling it as an attribute and creating a custom filter of which neither I can get to return anything.
Apologies if this is obvious but I'm not well versed in Django and I can't seem to find any reference of non Django model classes in their documentation
I have a class
class MarketDataSet():
def __init__(self, market_data):
self.market_data = market_data
def filter_data(self, side):
filtered = list()
for dp in self.market_data:
if dp.side == side:
filtered.append(dp)
return filtered
def bids(self):
return self.filter_data(side='bid')
def asks(self):
return self.filter_data(side='bid')
def trades(self):
return self.filter_data(side='trade')
def high_bid(self):
amounts = list()
if not self.bids():
return ''
else:
for bid in self.bids():
amounts.append(bid.amount)
return max(amounts)
def low_ask(self):
amounts = list()
if not self.asks():
return ''
else:
for ask in self.asks():
amounts.append(ask.amount)
return max(amounts)
def average_trade(self):
amounts = list()
if not self.trades():
return ''
else:
for trade in self.trades():
amounts.append(trade.amount)
return statistics.mean(amounts)
and views.py (relevant portion):
if form.is_valid():
style_id = form.get_style().upper()
stats = dict()
st_data = MarketDataSet(market_data=get_full_market_data(style_id=style_id, size=None))
stats.update({'st': st_data})
return render(request=request,
template_name='main/pricer_response.html',
context={'form': form,
'stats': stats,
})
and pricer_response.html template (relevant portion) Notice both approaches:
{% for platform in stats.keys %}
{% block content %}
{% load static %}
<tr>
<td><b>{{platform.title}}</b></td>
<td>{{stats.platform|get_method:high_bid}}</td>
<td>{{stats.platform.low_ask}}</td>
{% endfor %}
{% endblock %}
and custom_tags.py:
#register.filter(name='get_method')
def get_method(obj, method_name):
return getattr(obj, method_name)
Inside your for loop, try accessing the method like so
{{ stats[platform].method_to_call }}
This was more of a misunderstanding as how to access dictionary items in django templates. Solution below:
{% for platform, dataset in stats.items %}
<tr>
<td><b>{{ platform.title }}</b></td>
<td>{{ dataset.high_bid }}</td>
<td>{{ dataset.low_ask }}</td>
I have the following models in my django app:
from django.contrib.auth.models import User
class Poll(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(User)
class Choice(models.Model):
poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
text = models.CharField(max_length=200)
class Vote(models.Model):
choice = models.ForeignKey(Choice)
user = models.ForeignKey(User)
For a given Poll, I need to display in the template a double-entry table like the following, listing all Users who have voted in this Poll and placing an 'x' for each Choice that the user has voted:
[choice1] [choice2] [choice3]
[user1] x
[user2] x x
[user3] x
[user4] x x
What would be the optimal way to implement this using the django ORM in order to minimize the database hits when populating the table?
Which object should be passed to the template in the context variable, in order conduct the logic in the view instead of the template?
The queries I know:
# get all votes for the given Poll (pk)
votes = Vote.objects.filter(choice__poll__pk=pk)
# get all users that has voted
usernames = votes.values('user__username')
# get the choices for the Poll
choices = Poll.objects.get(pk=pk).choice_set.all()
I was able to solve the problem with the following:
# yourapp/models.py
from django.utils.functional import cached_property
from django.db.models import Case, When, BooleanField
class Poll(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(User)
#cached_property
def choices(self):
return self.choice_set.order_by('id')
#cached_property
def users_choices(self):
users = User.objects.filter(vote__choice__poll=self)
for choice in self.choices:
users = users.annotate(
**{str(choice.id): Case(When(vote__choice_id=choice.id, then=True), output_field=BooleanField())}
)
return users
For example in PostgreSQL users qs converted in such sql query:
SELECT
"yourapp_user"."id",
"yourapp_user"."username",
CASE WHEN "yourapp_vote"."choice_id" = 1
THEN TRUE
ELSE NULL END AS "1",
CASE WHEN "yourapp_vote"."choice_id" = 2
THEN TRUE
ELSE NULL END AS "2",
CASE WHEN "yourapp_vote"."choice_id" = 3
THEN TRUE
ELSE NULL END AS "3"
FROM "yourapp_user"
LEFT OUTER JOIN "yourapp_vote" ON ("yourapp_user"."id" = "yourapp_vote"."user_id")
LEFT OUTER JOIN "yourapp_choice" ON ("yourapp_vote"."choice_id" = "yourapp_choice"."id")
WHERE "yourapp_choice"."poll_id" = 1
View and template can look like this (you don't even need to pass context to template, everything will be taken from Poll model attributes):
# yourapp/views.py
class PollDetailView(DetailView):
model = Poll
template_name = 'user_choices.html'
# yourapp/templates/user_choices.html
{% extends 'base.html' %}{% load customtags %}
{% block content %}
<h1>{{ poll.title }}</h1>
<table border="1">
<tr>
<th>username</th>
{% for choice in poll.choices %}<th>choice{{ choice.id }}</th>{% endfor %}
</tr>
{% for user_choice in poll.users_choices %}
<tr>
<td>{{ user_choice.username }}</td>
{% for choice in poll.choices %}
<td>{% if user_choice|get_attr:choice.id %}+{% endif %}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
{% endblock %}
I had to add custom template filter to get attr by choice id (propably there is more elegant solution which i missed):
# yourapp/templatetags/customtags.py
from django import template
register = template.Library()
#register.filter(name='get_attr')
def get_attr(obj, attr_name):
return getattr(obj, str(attr_name), None)
I have a model where a "variable template" is defined, and attached to every Location model in the database, producing a 2-dimensional array of instances of these variables.
class Location(models.Model):
name = models.CharField(max_length=4, blank=False, unique=True)
class LocationVariableDefinition(models.Model):
name = models.CharField(max_length=32, blank=False, unique=True)
type = models.CharField(max_length=60, default="text")
default_value = models.CharField(max_length=250, blank=True, default="[UNSET]")
class LocationBasedVariable(models.Model):
location = models.ForeignKey(Location)
definition = models.ForeignKey(LocationVariableDefinition, default=None)
value = models.CharField(max_length=250, blank=True)
To edit these variables, I have a single page with a table, variable names down the side, and locations along the top. I added a new SimpleTag to allow me to access values from a dict-within-a-dict, and the view produces this dict:
class VarListView(TemplateView):
template_name = "lbv-list.html"
def locations(self):
return Location.objects.all()
def locvars(self):
dict = {}
for v in LocationBasedVariable.objects.all():
dict.setdefault(v.location.id, {})
dict[v.location.id][v.definition.id] = v.value
return dict
def locvarnames(self):
return LocationVariableDefinition.objects.all()
and then in the template:
<table class="table table-striped table-condensed striped">
<thead>
<tr>
<th> </th>
{% for loc in view.locations %}
<th>{{ loc.name }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for lv in view.locvarnames %}
<tr>
<th>{{ lv.name }}</th>
{% for loc in view.locations %}
<td>{% get_dictitem2 view.locvars loc.id lv.id%}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
Which all works, except that with 3 locations and 5 variables, I get 475 SQL queries, taking about 3 seconds to run on my laptop.
What can I do to improve this? I always thought that the queryset would be cached, but the SQL logged in the console seems to be fetching the same sets over and over. Is there a hint I can give Django that the result won't have changed, or a way to do each query only once?
Or should I be just passing a sorted queryset to the template, and figuring out the rows and columns in there? (the way I would normally do that would involve keeping state in the template for the last location to compare with the current, and I don't think I can do that in a template)
The usual approach would be to evaluate the methods once, and add the results to the template context in get_context_data.
class VarListView(TemplateView):
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(VarListView, self).get_context_data(**kwargs)
context['locations'] = self.locations()
context['locvarnames'] = self.locavarnames()
context['locvars'] = self.locvars()
return context
Then, in your template, remove the view prefix e.g. locations instead of view.locations.
Aside from changing .id to _id, you should also be looking at what you actually need to return, since your for loop only ever uses the id you could just use values to filter this to start with
query_gives_locations.values('id')
You have to keep getting .locvars every time so you can move this outside with with
{% with locvars=view.locvars %}
{% for loc in view.locations %}
<td>{% get_dictitem2 locvars loc_id lv_id%}</td>
{% endfor %}
{% endwith %}
You have a couple of options here, but I think the best is to use the _id property for your foreign key relations. Django stores the id of the foreign keys on the model in question, so in your case you could replace your loop with:
for v in LocationBasedVariable.objects.all():
dict.setdefault(v.location_id, {})
dict[v.location_id][v.definition_id] = v.value
This means that you won't have to query the related tables just to get the ID.
Also, it looks like you are invoking {% get_dictitem2 view.locvars loc.id lv.id%} for each item in view.locvarnames which in turn will go to the database, load all of your LocationBasedVariable and populate that dict.
I might be tempted to cache that in the view so you only populate it once:
class VarListView(TemplateView):
template_name = "lbv-list.html"
_location_based_variable_cache = None
def locations(self):
return Location.objects.all()
def locvars(self):
if not self._location_based_variable_cache:
self._location_based_variable_cache = {}
for v in LocationBasedVariable.objects.all():
self._location_based_variable_cache.setdefault(v.location_id, {})
self._location_based_variable_cache[v.location_id][v.definition_id] = v.value
return self._location_based_variable_cache
def locvarnames(self):
return LocationVariableDefinition.objects.all()
I have the following Models and Forms:
#models.py
class NetworkDevice(models.Model):
user = models.ForeignKey(User)
device_name = models.CharField(_('device name'), max_length=100)
...
#forms.py
class NetworkDevicesForm(ModelForm):
class Meta:
model = NetworkDevice
fields=('user', 'device_name',...)
'...' are some fields I left out, since they are not important for this. I want to create a formset based on my ModelForm:
#views.py
in some view:
network_device_formset = modelformset_factory(models.NetworkDevice,
extra=0, form=NetworkDevicesForm, fields=(
'user', 'device_name', ...))
And I display it like this in my template:
<form action="{% url 'some:action' %}" method="post">
{% csrf_token %}
{{ devices_formset.management_form }}
<table>
{% for form in devices_formset %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
{% endfor %}
<tbody>
{% for form in devices_formset %}
<tr>
{% for field in form %}
<td>{{ field }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<input type="submit" value='{% trans "Save" %}'/>
</form>
Now this will display my ForeignKey with an HTML select tag. I don't even want to show all the choices there however. I just want to display the key for the according instance. I can disable the select tag:
class NetworkDevicesForm(ModelForm):
class Meta:
model = NetworkDevice
fields=('user', 'device_name', ...more fields)
widgets = {'user': widgets.Select(attrs={'readonly': True,
'disabled': True})}
But then I get errors on the validation of the user field. Guess I could overwrite the validation for this somehow. But still this would display all the options for the foreign key in the generated html. Is there no way I can just display the value in my template without specifying it in the ModelForm, since I don't want to edit it. Some magic like:
<tbody>
{% for form in devices_formset %}
<tr>
<td>{{ form.user }}</td>
{% for field in form %}
<td>{{ field }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
Except that {{ form.user }} is not working. Can I access that somehow in the template? Hope I was clear about what I want to do and it's possible.
You could try something like this:
class NetworkDevicesForm(ModelForm):
def __init__(self, *args, **kwargs):
super(NetworkDevicesForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self.fields['pk'].widget.attrs['readonly'] = True
def clean_pk(self):
instance = getattr(self, 'instance', None)
if instance and instance.pk:
return instance.pk
else:
return self.cleaned_data['pk']
Alright, I think I got it. I changed my ModelForm to this:
class NetworkDevicesForm(ModelForm):
readonlyUser = CharField(max_length=100 )
class Meta:
model = NetworkDevice
fields=('readonlyUser', 'device_name')
def __init__(self, *args, **kwargs):
super(NetworkDevicesForm, self).__init__(*args, **kwargs)
if self.instance.user:
self.initial['readonlyUser'] = self.instance.user.username
self.fields['readonlyUser'].widget.attrs['readonly'] = True
Had to use readonly here instead of disabled, otherwise the validation does not work, even though I don't use that field. And in the view I create my modelformset with:
network_device_formset = modelformset_factory(models.NetworkDevice,
extra=0, form=NetworkDevicesForm, fields=(
'readonlyUser', 'device_name'))
This seem to do what I want. Didn't touch the template or the model definition.
If you're going to use this often you can create a new baseclass:
class SpanWidget(forms.Widget):
'''Renders a value wrapped in a <span> tag.
Requires use of specific form support. (see ReadonlyForm
or ReadonlyModelForm)
'''
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, name=name)
return mark_safe(u'<span%s >%s</span>' % (
forms.util.flatatt(final_attrs), self.display_value))
def value_from_datadict(self, data, files, name):
return self.original_value
class SpanField(forms.Field):
'''A field which renders a value wrapped in a <span> tag.
Requires use of specific form support. (see ReadonlyForm
or ReadonlyModelForm)
'''
def __init__(self, *args, **kwargs):
kwargs['widget'] = kwargs.get('widget', SpanWidget)
super(SpanField, self).__init__(*args, **kwargs)
class Readonly(object):
'''Base class for ReadonlyForm and ReadonlyModelForm which provides
the meat of the features described in the docstings for those classes.
'''
class NewMeta:
readonly = tuple()
def __init__(self, *args, **kwargs):
super(Readonly, self).__init__(*args, **kwargs)
readonly = self.NewMeta.readonly
if not readonly:
return
for name, field in self.fields.items():
if name in readonly:
field.widget = SpanWidget()
elif not isinstance(field, SpanField):
continue
model_field = self.instance._meta.get_field_by_name(name)[0]
field.widget.original_value = model_field.value_from_object(self.instance)
field.widget.display_value = unicode(getattr(self.instance, name))
class ReadonlyForm(Readonly, forms.Form):
'''A form which provides the ability to specify certain fields as
readonly, meaning that they will display their value as text wrapped
with a <span> tag. The user is unable to edit them, and they are
protected from POST data insertion attacks.
The recommended usage is to place a NewMeta inner class on the
form, with a readonly attribute which is a list or tuple of fields,
similar to the fields and exclude attributes on the Meta inner class.
class MyForm(ReadonlyForm):
foo = forms.TextField()
class NewMeta:
readonly = ('foo',)
'''
pass
class ReadonlyModelForm(Readonly, forms.ModelForm):
'''A ModelForm which provides the ability to specify certain fields as
readonly, meaning that they will display their value as text wrapped
with a <span> tag. The user is unable to edit them, and they are
protected from POST data insertion attacks.
The recommended usage is to place a NewMeta inner class on the
form, with a readonly attribute which is a list or tuple of fields,
similar to the fields and exclude attributes on the Meta inner class.
class Foo(models.Model):
bar = models.CharField(max_length=24)
class MyForm(ReadonlyModelForm):
class Meta:
model = Foo
class NewMeta:
readonly = ('bar',)
'''
pass
This is code I use in production:
class MembershipForm(ReadonlyModelForm):
class Meta:
model = Membership
fields = ('user','board', 'privileged', 'alumni')
class NewMeta:
readonly = ('user')
def email(self):
return self.instance.user.email
To answer your question:
#forms.py
class NetworkDevicesForm(ReadOnlyModelForm):
class Meta:
model = NetworkDevice
fields=('user', 'device_name',...)
class NewMeta:
readonly=('user',)