Related
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
This question is continuation of How to remove Add button in Django admin, for specific Model?
I have realized that my first question was not formulated good, so I tough that it is better that I start new question, that to fix the old one. Because there was already some answers.
So question is how to make Django Model that will be view read only. So that you can not add new, delete old, change current, but that you also do not have button for that on web admin UI.
Solution from first question are all related to fields, but not to whole model.
They all work, in sense that you will not be able to edit those field, but I am not satisfied how they do it.
Current solution are:
Use readonly_fields on all fields from model -> I do not like it because you can click on row to change it, but you can not edit fields.
editable=False on filed definition -> This will not show the field on web admin UI. But you can still click on row, but you will just not see anything, and still have Save button.
def has_add_permission(self, request): -> same as 2
don't give anyone the add permission for this model -> same as 2
Any thoughts ?
You need to set list_display_links attribute of your ModelAdmin class to (None,). But this can be done only in __init__ after standard ModelAdmin __init__ call otherwise it will throw ImproperlyConfigured exception with text ... list_display_links[0]' refers to 'None' which is not defined in 'list_display'. And you should define has_add_permisssion anyway to hide add button:
class AmountOfBooksAdmin(admin.ModelAdmin):
actions = None # disables actions dropbox with delete action
list_display = ('book', 'amount')
def has_add_permission(self, request):
return False
def __init__(self, *args, **kwargs):
super(AmountOfBooksAdmin, self).__init__(*args, **kwargs)
self.list_display_links = (None,)
# to hide change and add buttons on main page:
def get_model_perms(self, request):
return {'view': True}
To hide 'view' and 'change' buttons from madin admin page you must place index.html from django/contrib/admin/templates/admin/ to you templates dir /admin and change it:
{% for model in app.models %}
...
{% if model.perms.view %}
<th scope="row">{{ model.name }}</th>
{% else %}
{% if model.admin_url %}
<th scope="row">{{ model.name }}</th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% endif %}
....
I am new to the Django web framework.
I have a template that displays the list of all objects. I have all the individual objects listed as a link (object title), clicking on which I want to redirect to another page that shows the object details for that particular object.
I am able to list the objects but not able to forward the object/object id to the next template to display the details.
views.py
def list(request):
listings = listing.objects.all()
return render_to_response('/../templates/listings.html',{'listings':listings})
def detail(request, id):
#listing = listing.objects.filter(owner__vinumber__exact=vinumber)
return render_to_response('/../templates/listing_detail.html')
and templates as:
list.html
{% for listing in object_list %}
<!--<li> {{ listing.title }} </li>-->
{{ listing.title}}<br>
{% endfor %}
detail.html
{{ id }}
The variables that you pass in the dictionary of render_to_response are the variables that end up in the template. So in detail, you need to add something like {'listing': MyModel.objects.get(id=vinumber)}, and then the template should say {{ listing.id }}. But hat'll crash if the ID doesn't exist, so it's better to use get_object_or_404.
Also, your template loops over object_list but the view passes in listings -- one of those must be different than what you said if it's currently working....
Also, you should be using the {% url %} tag and/or get_absolute_url on your models: rather than directly saying href="{{ listing.id }}", say something like href="{% url listing-details listing.id %}", where listing-details is the name of the view in urls.py. Better yet is to add a get_absolute_url function to your model with the permalink decorator; then you can just say href="{{ listing.get_absolute_url }}", which makes it easier to change your URL structure to look nicer or use some attribute other than the database id in it.
You should check the #permalink decorator. It enables you to give your models generated links based on your urls pattern and corresponding view_function.
For example:
# example model
class Example(models.Model):
name = models.CharField("Name", max_length=255, unique=True)
#more model fields here
#the permalink decorator with get_absolute_url function
#models.permalink
def get_absolute_url(self):
return ('example_view', (), {'example_name': self.name})
#example view
def example_view(request, name, template_name):
example = get_object_or_404(Example, name=name)
return render_to_response(template_name, locals(),
context_instance=RequestContext(request))
#example urls config
url(r'^(?P<name>[-\w]+)/$', 'example_view', {'template_name': 'example.html'}, 'example_view')
Now you can do in your templates something like this:
<a href={{ example.get_absolute_url }}>{{ example.name }}</a>
Hope this helps.
In your detail method, just pass the listing into your template like so:
def detail(request, id):
l = listing.objects.get(pk=id)
return render_to_response('/../templates/listing_detail.html', {'listing':l})
I have a view definition that (attempts to) outputs a model as a table. This is what I have so far:
def output_table(request):
output = My_Model()
return render_to_response('outputtable.html', {'output': output})
Here's the HTML for outputtable.html:
<html>
<table>
{{ output.as_table }}
</table>
</html>
What am I doing wrong? It doesn't work. Now, it's passing the model correctly, because if I change My_Model() to My_Model.objects.all() and then output it as simply {{ output }} then it shows me what I would see in the Django shell.
So what you need to do is the following:
1) add
from django.forms import ModelForm to your models.py
2) add
class My_Model_Form(ModelForm):
class Meta:
model = My_Model
3) in your views.py, change output = My_Model() to output = My_Model_Form()
Now you are all set. So the trick is to inherit your Form from your original model.
If you're just looking to output one specific model, change your template to something like
<tr>
<td>Name:</td>
<td>{{ output.name}}</td>
</tr>
for all the fields you care about. If this is something you want to be able to do for any arbitrary model in your app, take a look at this snippet. That would provide you with a fields collection to loop over.
The models.Model class doesn't have a method as_table() like the forms.ModelForm class does.
My solution was to also use template tags. I went with an inclusion tag.
myapp/templatetags/model_helpers.py
from django import template
register = template.Library()
#register.inclusion_tag('myapp/model_table.html', takes_context=True)
def model_as_table(context, model_key=None, model_table_attrs_key=None):
if model_key is None:
model_key = 'object'
if model_table_attrs_key is None:
model_table_attrs_key = 'model_table_attrs'
try:
attrs = context[model_table_attrs_key]
except KeyError:
attrs = context[model_key]._meta.get_all_field_names()
table_context = {'rows': []}
for attr in attrs:
try:
value = str(getattr(context[model_key], attr))
if value:
table_context['rows'].append({'attr': attr,
'value': context[model_key][attr]})
except AttributeError:
pass
# Needs a way to display many_to_many fields.
except StopIteration:
pass
return table_context
myapp/templates/myapp/model_table.html
{% for row in rows %}
<tr>
<td class="name">{{ row.attr }}</td>
<td class="field">{{ row.value }}</td>
</tr>
{% endfor %}
myapp/templates/myapp/outputtable.html
{% load model_helpers %}
<table>
{% model_as_table %}
</table>
With this implementation you can pass which model's attributes you want to display and in which order.
myapp/views.py
def output_table(request):
output = My_Model()
return render_to_response('outputtable.html',
{'output': output, 'model_table_attrs': ['attr1', 'attr2']})
I also like this cleanly separate html and python code.
There is no method as_table on a model instance (MyModel())or on Querysets (MyModel.objects.all()). You may have seen as_table as the forms method as_table. You don't have a form there.
If you want to print a model instance/ a queryset as a table, you will have to design it yourself.
OMG this is old but Generic Views seem to be a good fit for this problem, for me at least. A List detail view should help me get running faster. :)
I came up with a solution that worked for my specific need which renders any simple model's data into table rows. Add the following filter into templatetags/your_tags_file.py (don't forget the __init__.py file in the templatetags folder):
from django import template
register = template.Library()
#register.filter()
def as_table(model):
ret = ""
for name in model._meta.get_all_field_names():
try:
field = str(getattr(model, name))
if field:
ret += '<tr><td class="name">'+name+'</td><td class="field">'+field+'</td></td>'
except AttributeError:
pass
return ret
In the template now you can just do:
{% load your_tags_file %}
<table>
{{output|as_table|safe}}
</table>
This will render the model in a simple table for you. You can easily add any desired thead and tbody logic as you see fit by modifying the logic of how ret is generated. Hope this helps someone.
Let's say I have my pizza application with Topping and Pizza classes and they show in Django Admin like this:
PizzaApp
-
Toppings >>>>>>>>>> Add / Change
Pizzas >>>>>>>>>> Add / Change
But I want them like this:
PizzaApp
-
Pizzas >>>>>>>>>> Add / Change
Toppings >>>>>>>>>> Add / Change
How do I configure that in my admin.py?
A workaround that you can try is tweaking your models.py as follows:
class Topping(models.Model):
.
.
.
class Meta:
verbose_name_plural = "2. Toppings"
class Pizza(models.Model):
.
.
.
class Meta:
verbose_name_plural = "1. Pizzas"
Not sure if it is against the django's best practices but it works (tested with django trunk).
Good luck!
PS: sorry if this answer was posted too late but it can help others in future similar situations.
If you want to solve this in 10 seconds just use spaces in verbose_name_plural, for example:
class Topping(models.Model):
class Meta:
verbose_name_plural = " Toppings" # 2 spaces
class Pizza(models.Model):
class Meta:
verbose_name_plural = " Pizzas" # 1 space
Of course it isn't ellegant but may work for a while before we get a better solution.
I eventually managed to do it thanks to this Django snippet, you just need to be aware of the ADMIN_REORDER setting:
ADMIN_REORDER = (
('app1', ('App1Model1', 'App1Model2', 'App1Model3')),
('app2', ('App2Model1', 'App2Model2')),
)
app1 must not be prefixed with the project name, i.e. use app1 instead of mysite.app1.
There's now a nice Django package for that:
https://pypi.python.org/pypi/django-modeladmin-reorder
Answer in June 2018
This answer is similar to Vasil's idea
I tried to solve similar problems, and then I saw such the fragment.
I made some modifications based on this clip. The code is as follows.
# myproject/setting.py
...
# set my ordering list
ADMIN_ORDERING = [
('pizza_app', [
'Pizzas',
'Toppings'
]),
]
# Creating a sort function
def get_app_list(self, request):
app_dict = self._build_app_dict(request)
for app_name, object_list in ADMIN_ORDERING:
app = app_dict[app_name]
app['models'].sort(key=lambda x: object_list.index(x['object_name']))
yield app
# Covering django.contrib.admin.AdminSite.get_app_list
from django.contrib import admin
admin.AdminSite.get_app_list = get_app_list
...
Note that the sorting list used in this sorting function contains the sorting of all app and its modules in the system. If you don't need it, please design the sorting function according to your own needs.
It works great on Django 2.0
This is actually covered at the very bottom of Writing your first Django app, part 7.
Here's the relevant section:
Customize the admin index page
On a similar note, you might want to
customize the look and feel of the
Django admin index page.
By default, it displays all the apps
in INSTALLED_APPS that have been
registered with the admin application,
in alphabetical order. You may want to
make significant changes to the
layout. After all, the index is
probably the most important page of
the admin, and it should be easy to
use.
The template to customize is
admin/index.html. (Do the same as with
admin/base_site.html in the previous
section -- copy it from the default
directory to your custom template
directory.) Edit the file, and you'll
see it uses a template variable called
app_list. That variable contains every
installed Django app. Instead of using
that, you can hard-code links to
object-specific admin pages in
whatever way you think is best.
Here's the snippet Emmanuel used, updated for Django 1.8:
In templatetags/admin_reorder.py:
from django import template
from django.conf import settings
from collections import OrderedDict
register = template.Library()
# from http://www.djangosnippets.org/snippets/1937/
def register_render_tag(renderer):
"""
Decorator that creates a template tag using the given renderer as the
render function for the template tag node - the render function takes two
arguments - the template context and the tag token
"""
def tag(parser, token):
class TagNode(template.Node):
def render(self, context):
return renderer(context, token)
return TagNode()
for copy_attr in ("__dict__", "__doc__", "__name__"):
setattr(tag, copy_attr, getattr(renderer, copy_attr))
return register.tag(tag)
#register_render_tag
def admin_reorder(context, token):
"""
Called in admin/base_site.html template override and applies custom ordering
of apps/models defined by settings.ADMIN_REORDER
"""
# sort key function - use index of item in order if exists, otherwise item
sort = lambda order, item: (order.index(item), "") if item in order else (
len(order), item)
if "app_list" in context:
# sort the app list
order = OrderedDict(settings.ADMIN_REORDER)
context["app_list"].sort(key=lambda app: sort(order.keys(),
app["app_url"].strip("/").split("/")[-1]))
for i, app in enumerate(context["app_list"]):
# sort the model list for each app
app_name = app["app_url"].strip("/").split("/")[-1]
if not app_name:
app_name = app["name"].lower()
model_order = [m.lower() for m in order.get(app_name, [])]
context["app_list"][i]["models"].sort(key=lambda model:
sort(model_order, model["admin_url"].strip("/").split("/")[-1]))
return ""
In settings.py:
ADMIN_REORDER = (
('app1', ('App1Model1', 'App1Model2', 'App1Model3')),
('app2', ('App2Model1', 'App2Model2')),
)
(insert your own app names in here. Admin will place missing apps or models at the end of the list, so long as you list at least two models in each app.)
In your copy of base_site.html:
{% extends "admin/base.html" %}
{% load i18n admin_reorder %}
{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}
{% block branding %}
{% admin_reorder %}
<h1 id="site-name">{% trans 'Django administration' %}</h1>
{% endblock %}
{% block nav-global %}{% endblock %}
ADMIN_ORDERING = {
"PizzaApp": [
"Pizzas",
"Toppings",
],
}
def get_app_list(self, request):
app_dict = self._build_app_dict(request)
for app_name, object_list in app_dict.items():
if app_name in ADMIN_ORDERING:
app = app_dict[app_name]
app["models"].sort(
key=lambda x: ADMIN_ORDERING[app_name].index(x["object_name"])
)
app_dict[app_name]
yield app
else:
yield app_dict[app_name]
admin.AdminSite.get_app_list = get_app_list
This solution works for me, modified the one from 林伟雄.
You get to keep the default auth ordering AND specify your own.
If you're using Suit for the AdminSite you can do menu customization using the menu tag.
I was looking for a simple solution where I could order the apps by their name in the admin panel. I came up with the following template tag:
from django import template
from django.conf import settings
register = template.Library()
#register.filter
def sort_apps(apps):
apps.sort(
key = lambda x:
settings.APP_ORDER.index(x['app_label'])
if x['app_label'] in settings.APP_ORDER
else len(apps)
)
print [x['app_label'] for x in apps]
return apps
Then, just override templates/admin/index.html and add that template tag:
{% extends "admin/index.html" %}
{% block content %}
{% load i18n static sort_apps %}
<div id="content-main">
{% if app_list %}
{% for app in app_list|sort_apps %}
<div class="app-{{ app.app_label }} module">
<table>
<caption>
{{ app.name }}
</caption>
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}">
{% if model.admin_url %}
<th scope="row">{{ model.name }}</th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.add_url %}
<td>{% trans 'Add' %}</td>
{% else %}
<td> </td>
{% endif %}
{% if model.admin_url %}
<td>{% trans 'Change' %}</td>
{% else %}
<td> </td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
{% else %}
<p>{% trans "You don't have permission to edit anything." %}</p>
{% endif %}
</div>
{% endblock %}
Then customized the APP_ORDER in settings.py:
APP_ORDER = [
'app1',
'app2',
# and so on...
]
It works great on Django 1.10
Here's a version that gives you a bit more flexibility, namely:
You can partially define apps ordering, leaving the rest for Django to add to the list
You can specify order on modules, or avoid defining it, by using '*' instead
Your defined apps ordering will appear first, then all the rest of apps appended after it
To check the name of your app, either look at the file apps.py inside the app's directory and check for name property of the class Config(AppConfi): or in case that is not present, use the name of the directory for the app in the project.
Add this code somewhere in your settings.py file:
# ======[Setting the order in which the apps/modules show up listed on Admin]========
# set my ordering list
ADMIN_ORDERING = [
('crm', '*'),
('property', '*'),
]
# Creating a sort function
def get_app_list(self, request):
"""
Returns a sorted list of all the installed apps that have been
registered in this site.
Allows for:
ADMIN_ORDERING = [
('app_1', [
'module_1',
'module_2'
]),
('app_2', '*'),
]
"""
app_dict = self._build_app_dict(request)
# Let's start by sorting the apps alphabetically on a list:
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())
# Sorting the models alphabetically within each app.
for app in app_list:
if app['app_label'] in [el[0] for el in ADMIN_ORDERING]:
app_list.remove(app)
else:
app['models'].sort(key=lambda x: x['name'])
# Now we order the app list in our defined way in ADMIN_ORDERING (which could be a subset of all apps).
my_ordered_apps = []
if app_dict:
for app_name, object_list in ADMIN_ORDERING:
app = app_dict[app_name]
if object_list == '*':
app['models'].sort(key=lambda x: x['name'])
else:
app['models'].sort(key=lambda x: object_list.index(x['object_name']))
my_ordered_apps.append(app)
# Now we combine and arrange the 2 lists together
my_ordered_apps.extend(app_list)
return my_ordered_apps
# Covering django.contrib.admin.AdminSite.get_app_list
from django.contrib import admin
admin.AdminSite.get_app_list = get_app_list
# =========================================
This is nothing more than overwriting the function defined by Django on the file python2.7/site-packages/django/contrib/admin/sites.py.
That get_app_list method of class AdminSite(object): produces a data structure with all apps on the project, including for Django's auth app, such as:
[
{
"app_label": "auth",
"app_url": "/admin/auth/",
"has_module_perms": "True",
"models": [
{
"add_url": "/admin/auth/group/add/",
"admin_url": "/admin/auth/group/",
"name": "<django.utils.functional.__proxy__ object at 0x11057f990>",
"object_name": "Group",
"perms": {
"add": "True",
"change": "True",
"delete": "True"
}
},
{
"add_url": "/admin/auth/user/add/",
"admin_url": "/admin/auth/user/",
"name": "<django.utils.functional.__proxy__ object at 0x11057f710>",
"object_name": "User",
"perms": {
"add": "True",
"change": "True",
"delete": "True"
}
}
],
"name": "<django.utils.functional.__proxy__ object at 0x108b81850>"
},
{
"app_label": "reservations",
"app_url": "/admin/reservations/",
"has_module_perms": "True",
"models": [
{
"add_url": "/admin/reservations/reservationrule/add/",
"admin_url": "/admin/reservations/reservationrule/",
"name": "<django.utils.functional.__proxy__ object at 0x11057f6d0>",
"object_name": "ReservationRule",
"perms": {
"add": "True",
"change": "True",
"delete": "True"
}
}
],
"name": "Availability"
},
{
"app_label": "blog",
"app_url": "/admin/blog/",
"has_module_perms": "True",
"models": [
{
"add_url": "/admin/blog/category/add/",
"admin_url": "/admin/blog/category/",
"name": "Categories",
"object_name": "Category",
"perms": {
"add": "True",
"change": "True",
"delete": "True"
}
},
{
"add_url": "/admin/blog/post/add/",
"admin_url": "/admin/blog/post/",
"name": "<django.utils.functional.__proxy__ object at 0x11057f110>",
"object_name": "Post",
"perms": {
"add": "True",
"change": "True",
"delete": "True"
}
},
{
"add_url": "/admin/blog/tag/add/",
"admin_url": "/admin/blog/tag/",
"name": "<django.utils.functional.__proxy__ object at 0x11057f390>",
"object_name": "Tag",
"perms": {
"add": "True",
"change": "True",
"delete": "True"
}
}
],
"name": "Blog"
},
(...)
]
This is just a wild stab in the dark, but is there any chance that the order in which you call admin.site.register(< Model class >, < ModelAdmin class >) can determine the display order? Actually, I doubt that would work because I believe Django maintains a registry of the Model -> ModelAdmin objects implemented as a standard Python dictionary, which does not maintain iteration ordering.
If that doesn't behave the way you want, you can always play around with the source in django/contrib/admin. If you need the iteration order maintained, you could replace the _registry object in the AdminSite class (in admin/sites.py) with a UserDict or DictMixin that maintains insertion order for the keys. (But please take this advice with a grain of salt, since I've never made these kinds of changes myself and I'm only making an educated guess at how Django iterates over the collection of ModelAdmin objects. I do think that django/contrib/admin/sites.py is the place to look for this code, though, and the AdminSite class and register() and index() methods in particular are what you want.)
Obviously the nicest thing here would be a simple option for you to specify in your own /admin.py module. I'm sure that's the kind of answer you were hoping to receive. I'm not sure if those options exist, though.
My solution was to make subclasses of django.contrib.admin.sites.AdminSite and django.contrib.admin.options.ModelAdmin .
I did this so I could display a more descriptive title for each app and order the appearance of models in each app. So I have a dict in my settings.py that maps app_labels to descriptive names and the order in which they should appear, the models are ordered by an ordinal field I provide in each ModelAdmin when I register them with the admin site.
Although making your own subclasses of AdminSite and ModelAdmin is encouraged in the docs, my solution looks like an ugly hack in the end.
Copy lib\site-packages\django\contrib\admin\templates\admin\index.html template to project1\templates\admin\ directory, where project1 is the name of your project.
In the copied file, i.e. project1\templates\admin\index.html, replace lines:
{% block content %}
...
{% endblock %}
with:
{% block content %}
<div id="content-main">
{% if app_list %}
<div class="module">
<table>
<caption>App 1</caption>
<tr> <th> Model 1 </th> <td>Description of model 1</td> </tr>
<tr> <th> Model 2 </th> <td>Description of model 1</td> </tr>
<tr> <th> <a href="..." >...</a> </th> <td>...</td> </tr>
</table>
</div>
<div class="module">
<table>
<caption>Authentication and authorization</caption>
<tr> <th> <a href="/admin/auth/user/" >Users</a> </th> <td>List of users</td> </tr>
<tr> <th> <a href="/admin/auth/group/" >Groups</a> </th> <td>List of users' groups</td> </tr>
</table>
</div>
{% else %}
<p>{% trans "You don't have permission to view or edit anything." %}</p>
{% endif %}
</div>
{% endblock %}
where:
app1 is the name of your application with models,
modeli is the name of i-th model in app1.
If you defined more than one application with models in your project, then simply add another table in the above index.html file.
Because we change the template, we can freely change its HTML code. For example we can add a description of the models as it was shown above. You can also restore Add and Change links - I deleted them since I think they are redundant.
The answer is a practical demonstration of the solution from Dave Kasper's answer.
custom_admin.py
from django.contrib.admin import AdminSite
class CustomAdminSite(AdminSite):
def get_urls(self):
urls = super(MyAdminSite, self).get_urls()
return urls
def get_app_list(self, request):
app_dict = self._build_app_dict(request)
ordered_app_list = []
if app_dict:
# TODO: change this dict
admin_ordering = {
'app1': ('Model1', 'Model2'),
'app2': ('Model7', 'Model4'),
'app3': ('Model3',),
}
ordered_app_list = []
for app_key in admin_ordering:
app = app_dict[app_key]
app_ordered_models = []
for model_name in admin_ordering[app_key]:
for model in app_dict[app_key]['models']:
if model['object_name'] == model_name:
app_ordered_models.append(model)
break
app['models'] = app_ordered_models
ordered_app_list.append(app)
return ordered_app_list
admin_site = CustomAdminSite()
urls.py
from custom_admin import admin_site
urlpatterns = [
path('admin/', admin_site.urls),
]
Add the below code to your settings.py:
def get_app_list(self, request):
"""
Return a sorted list of all the installed apps that have been
registered on this site.
"""
ordering = {
# for superuser
'Group': 1,
'User': 2,
# fist app
'TopMessage': 101,
'Slider': 102,
'ProductCategory': 103,
'Product': 104,
'ProductPicture': 105,
# 2nd app
'ProductReview': 201,
'Promotion': 202,
'BestSeller': 203,
}
app_dict = self._build_app_dict(request)
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())
for app in app_list:
app['models'].sort(key=lambda x: ordering[x['object_name']])
return app_list
admin.AdminSite.get_app_list = get_app_list
And make changes to the ordering dictionary to match your apps and models. That's it.
The benefit of my solution is that it will show the 'auth' models if the user is a superuser.
Django, by default, orders the models in admin alphabetically. So the order of models in Event admin is Epic, EventHero, EventVillain, Event
Instead you want the order to be
EventHero, EventVillain, Epic then event.
The template used to render the admin index page is admin/index.html and the view function is ModelAdmin.index.
def index(self, request, extra_context=None):
"""
Display the main admin index page, which lists all of the installed
apps that have been registered in this site.
"""
app_list = self.get_app_list(request)
context = {
**self.each_context(request),
'title': self.index_title,
'app_list': app_list,
**(extra_context or {}),
}
request.current_app = self.name
return TemplateResponse(request, self.index_template or
'admin/index.html', context)
The method get_app_list, set the order of the models.:
def get_app_list(self, request):
"""
Return a sorted list of all the installed apps that have been
registered in this site.
"""
app_dict = self._build_app_dict(request)
# Sort the apps alphabetically.
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())
# Sort the models alphabetically within each app.
for app in app_list:
app['models'].sort(key=lambda x: x['name'])
return app_list
So to set the order we override get_app_list as:
class EventAdminSite(AdminSite):
def get_app_list(self, request):
"""
Return a sorted list of all the installed apps that have been
registered in this site.
"""
ordering = {
"Event heros": 1,
"Event villains": 2,
"Epics": 3,
"Events": 4
}
app_dict = self._build_app_dict(request)
# a.sort(key=lambda x: b.index(x[0]))
# Sort the apps alphabetically.
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())
# Sort the models alphabetically within each app.
for app in app_list:
app['models'].sort(key=lambda x: ordering[x['name']])
return app_list
The code app['models'].sort(key=lambda x: ordering[x['name']]) sets the fixed ordering. Your app now looks like this.
Check the Documentation
In Settings.py file setting the order in which the apps/modules show up listed on Admin
set my ordering list
ADMIN_ORDERING = [
('Your_App1', '*'),
('Your_App2', '*'),
]
Creating a sort function below ADMIN_ORDERING
def get_app_list(self, request):
"""
You Can Set Manually Ordering For Your Apps And Models
ADMIN_ORDERING = [
('Your_App1', [
'module_1',
'module_2'
]),
('Your_App2', '*'),
]
"""
app_dict = self._build_app_dict(request)
# Let's start by sorting the apps alphabetically on a list:
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())
# Sorting the models alphabetically within each app.
for app in app_list:
if app['app_label'] in [el[0] for el in ADMIN_ORDERING]:
app_list.remove(app)
else:
app['models'].sort(key=lambda x: x['name'])
# Now we order the app list in our defined way in ADMIN_ORDERING (which could be a subset of all apps).
my_ordered_apps = []
if app_dict:
for app_name, object_list in ADMIN_ORDERING:
app = app_dict[app_name]
if object_list == '*':
app['models'].sort(key=lambda x: x['name'])
else:
app['models'].sort(key=lambda x: object_list.index(x['object_name']))
my_ordered_apps.append(app)
# Now we combine and arrange the 2 lists together
my_ordered_apps.extend(app_list)
return my_ordered_apps
# Covering django.contrib.admin.AdminSite.get_app_list
from django.contrib import admin
admin.AdminSite.get_app_list = get_app_list
Quite a simple and self contained approach can be using the decorator pattern to resort your app and modules in this way:
# admin.py
from django.contrib import admin
def app_resort(func):
def inner(*args, **kwargs):
app_list = func(*args, **kwargs)
# Useful to discover your app and module list:
#import pprint
#pprint.pprint(app_list)
app_sort_key = 'name'
app_ordering = {
"APP_NAME1": 1,
"APP_NAME2": 2,
"APP_NAME3": 3,
}
resorted_app_list = sorted(app_list, key=lambda x: app_ordering[x[app_sort_key]] if x[app_sort_key] in app_ordering else 1000)
model_sort_key = 'object_name'
model_ordering = {
"Model1": 1,
"Model2": 2,
"Model3": 3,
"Model14": 4,
}
for app in resorted_app_list:
app['models'].sort(key=lambda x: model_ordering[x[model_sort_key]] if x[model_sort_key] in model_ordering else 1000)
return resorted_app_list
return inner
admin.site.get_app_list = app_resort(admin.site.get_app_list)
This code sorts at the top only the defined keys in the ordering dict, leaving at the bottom all the other.
The approach is clean but it requires get_app_list to be executed before... which is probably not a good idea when performance is important and you have a large app_list.
This has become a lot easier in Django 4.1:
The AdminSite.get_app_list() method now allows changing the order of apps and models on the admin index page.
You can subclass and override this method to change the order the returned list of apps/models.