I am really very much irritated by the extra "s" added after my class name in django admin eg class 'About' in my model.py becomes 'Abouts' in admin section. And i want it not to add extra 's'. Here is my model.py file-
class About(models.Model):
about_desc = models.TextField(max_length=5000)
def __unicode__(self): # __str__ on Python 3
return str(self.about_desc)
Please anybody suggest me how django can solve my problem.
You can add another class called Meta in your model to specify plural display name. For example, if the model's name is Category, the admin displays Categorys, but by adding the Meta class, we can change it to Categories.
I have changed your code to fix the issue:
class About(models.Model):
about_desc = models.TextField(max_length=5000)
def __unicode__(self): # __str__ on Python 3
return str(self.about_desc)
class Meta:
verbose_name_plural = "about"
For more Meta options, refer to https://docs.djangoproject.com/en/1.8/ref/models/options/
Take a look at the Model Meta in the django documentation.
Within a Model you can add class Meta this allows additional options for your model which handles things like singular and plural naming.
This can be used in the following way (in english we do not have sheeps) so verbose_name_plural can be used to override djangos attempt at pluralising words:
class Sheep(model.Model):
class Meta:
verbose_name_plural = 'Sheep'
inside model.py or inside your customized model file add class meta within a Model Class.
If not mentioned then a extra 's' will be added at the end of Model Class Name which will be visible in Django Admin Page.
class TestRoles(model.Model):
class Meta: verbose_name_plural = 'TestRoles'
i have one silly problem with Django M2M saving.
I don't use Django Admin (use my own custom templates)
So, I have simple relationship:
# models
class News(models.Model):
title = models.CharField(max_length=256)
class Webmaster(AbstractUser):
...
news = models.ManyToManyField(News)
I need after saving every news mark it as new to every webmaster. So i figure out something like this:
# models
class News(models.Model):
title = models.CharField(max_length=256)
def save(self, *args, **kwargs):
if self.id:
news = News.objects.all()[0]
self.webmasters.add(news)
super(News, self).save(*args, **kwargs)
But i try like 6-7 different ways, and my code still don't work, can you help? Thanks!
You have to use post_save () signal, add this code in models.py
def Webmaster_add(sender, instance, **kwargs):
#you can put here some condition stuff
w = Webmasters.objects.all()
for obj in w:
obj.news.add(instance)
# register the signal
post_save.connect(webmaster_add, sender=News)
I've another quick question, I need to use very often a specific html representation for a model field. I know there is a lot of documentation for forms but what about simple model field ? For instance :
models.py
class Status (models.Model):
order = models.PositiveIntegerField(_(u'Order'))
name = models.CharField(_(u'Name'), max_length=255, unique=True, db_index=True)
color = models.CharField(_(u'Color'), max_length=6, null=True, blank=True)
template.html
{{status.order.as_span}} will be equivalent to <span>{{status.order}}<span>
The first idea I have is to implement a function inside a manager but seems to break the MVC rules ... There is a proper way for this ?
You shouldn't assemble html in your view code, this is what template tags are for. Put this in eg. <app>/templatetags/<app>tags.py (replace <app> with your app name):
from django.utils.safestring import mark_safe
from django import template
register = template.Library()
#register.simple_tag
def spanme(fld):
return mark_safe("<span>%s</span>" % fld)
and use it as:
{% load <app>tags %}
...
{% spanme status.order %}
It is possible to subclass the model field also:
from django.utils.safestring import mark_safe
from django.db.models.fields import CharField
from django.db import models
class SpanValue(unicode):
def as_span(self):
return mark_safe(u"<span>%s</span>" % self)
class SpanField(CharField):
__metaclass__ = models.SubfieldBase
def to_python(self, value):
if value is None:
return None
return SpanValue(value)
class SpanModel(models.Model):
foo = SpanField(max_length=25)
then you could use it like this:
from django import template
from maint.models import *
#val = SpanModel.objects.create(foo='bar')
val = SpanModel.objects.get(foo='bar') # must get it from the db for `to_python` to run
t = template.Template(u"""{{ val.foo.as_span }}""")
print t.render(template.Context({'val': val}))
which would print
<span>bar</span>
Doing something like this would be very surprising to anyone else, so please never ever do this :-)
ps: if you inherit from something that isn't a text field you might be able to override __unicode__ (so you wouldn't have to use .as_span in the template), however that is considerably more work.
I am trying to redefine my admin page for the auth.User model.
Everything is working properly, except for one thing. Check the code below:
from django.contrib import admin
from django.contrib.auth.models import User
from access.models import UserProfile
class UserProfileInline(admin.StackedInline):
model = UserProfile
class UserAdmim(admin.ModelAdmin):
inlines = [UserProfileInline,]
list_display = ['id', 'username', 'get_full_name', 'email']
admin.site.unregister(User)
admin.site.register(User, UserAdmim)
As you can see, one of the fields I want to be displayed in the model page listing -- defined by list_display -- is get_full_name. The problem is that the column label in the admin is displayed as Get full name.
My question is simple: can I override this? If so, how?
Thanks for your help.
Set an attribute in your function called short_description to your desired label in your model definition.
# note, this must be done in the class definition;
# not User.get_full_name.short_description
get_full_name.short_description = 'my label'
Alternatively, if you don't want to pollute your model with admin specific code, you can set list_display to a method on the ModelAdmin which takes one argument: the instance. You'll also have to set readonly_fields so that the admin doesn't try to look up this field in your model. I prefix admin fields with _ to differentiate.
class MyAdmin(...):
list_display = ('_my_field',)
readonly_fields = ('_my_field', )
def _my_field(self, obj):
return obj.get_full_name()
_my_field.short_description = 'my custom label'
Update:
Note that this will break default admin ordering. Your admin will no longer sort fields by clicking the label. To enable this functionality again, define an admin_order_field.
def _date_created(self, obj):
return obj.date_created.strftime('%m/%d/%Y')
_date_created.short_description = "Date Created"
_date_created.admin_order_field = 'date_created'
Update 2:
I've written an admin method decorator that simplifies this process, because once I started using highly descriptive verbose method names, setting attributes on the function became massively repetitive and cluttering.
def admin_method_attributes(**outer_kwargs):
""" Wrap an admin method with passed arguments as attributes and values.
DRY way of extremely common admin manipulation such as setting short_description, allow_tags, etc.
"""
def method_decorator(func):
for kw, arg in outer_kwargs.items():
setattr(func, kw, arg)
return func
return method_decorator
# usage
class ModelAdmin(admin.ModelAdmin):
#admin_method_attributes(short_description='Some Short Description', allow_tags=True)
def my_admin_method(self, obj):
return '''<em>obj.id</em>'''
I have a Person model that has a foreign key relationship to Book, which has a number of fields, but I'm most concerned about author (a standard CharField).
With that being said, in my PersonAdmin model, I'd like to display book.author using list_display:
class PersonAdmin(admin.ModelAdmin):
list_display = ['book.author',]
I've tried all of the obvious methods for doing so, but nothing seems to work.
Any suggestions?
As another option, you can do lookups like:
#models.py
class UserAdmin(admin.ModelAdmin):
list_display = (..., 'get_author')
def get_author(self, obj):
return obj.book.author
get_author.short_description = 'Author'
get_author.admin_order_field = 'book__author'
Since Django 3.2 you can use display() decorator:
#models.py
class UserAdmin(admin.ModelAdmin):
list_display = (..., 'get_author')
#admin.display(ordering='book__author', description='Author')
def get_author(self, obj):
return obj.book.author
Despite all the great answers above and due to me being new to Django, I was still stuck. Here's my explanation from a very newbie perspective.
models.py
class Author(models.Model):
name = models.CharField(max_length=255)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=255)
admin.py (Incorrect Way) - you think it would work by using 'model__field' to reference, but it doesn't
class BookAdmin(admin.ModelAdmin):
model = Book
list_display = ['title', 'author__name', ]
admin.site.register(Book, BookAdmin)
admin.py (Correct Way) - this is how you reference a foreign key name the Django way
class BookAdmin(admin.ModelAdmin):
model = Book
list_display = ['title', 'get_name', ]
def get_name(self, obj):
return obj.author.name
get_name.admin_order_field = 'author' #Allows column order sorting
get_name.short_description = 'Author Name' #Renames column head
#Filtering on side - for some reason, this works
#list_filter = ['title', 'author__name']
admin.site.register(Book, BookAdmin)
For additional reference, see the Django model link here
Like the rest, I went with callables too. But they have one downside: by default, you can't order on them. Fortunately, there is a solution for that:
Django >= 1.8
def author(self, obj):
return obj.book.author
author.admin_order_field = 'book__author'
Django < 1.8
def author(self):
return self.book.author
author.admin_order_field = 'book__author'
Please note that adding the get_author function would slow the list_display in the admin, because showing each person would make a SQL query.
To avoid this, you need to modify get_queryset method in PersonAdmin, for example:
def get_queryset(self, request):
return super(PersonAdmin,self).get_queryset(request).select_related('book')
Before: 73 queries in 36.02ms (67 duplicated queries in admin)
After: 6 queries in 10.81ms
For Django >= 3.2
The proper way to do it with Django 3.2 or higher is by using the display decorator
class BookAdmin(admin.ModelAdmin):
model = Book
list_display = ['title', 'get_author_name']
#admin.display(description='Author Name', ordering='author__name')
def get_author_name(self, obj):
return obj.author.name
According to the documentation, you can only display the __unicode__ representation of a ForeignKey:
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#list-display
Seems odd that it doesn't support the 'book__author' style format which is used everywhere else in the DB API.
Turns out there's a ticket for this feature, which is marked as Won't Fix.
I just posted a snippet that makes admin.ModelAdmin support '__' syntax:
http://djangosnippets.org/snippets/2887/
So you can do:
class PersonAdmin(RelatedFieldAdmin):
list_display = ['book__author',]
This is basically just doing the same thing described in the other answers, but it automatically takes care of (1) setting admin_order_field (2) setting short_description and (3) modifying the queryset to avoid a database hit for each row.
There is a very easy to use package available in PyPI that handles exactly that: django-related-admin. You can also see the code in GitHub.
Using this, what you want to achieve is as simple as:
class PersonAdmin(RelatedFieldAdmin):
list_display = ['book__author',]
Both links contain full details of installation and usage so I won't paste them here in case they change.
Just as a side note, if you're already using something other than model.Admin (e.g. I was using SimpleHistoryAdmin instead), you can do this: class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin).
You can show whatever you want in list display by using a callable. It would look like this:
def book_author(object):
return object.book.author
class PersonAdmin(admin.ModelAdmin):
list_display = [book_author,]
This one's already accepted, but if there are any other dummies out there (like me) that didn't immediately get it from the presently accepted answer, here's a bit more detail.
The model class referenced by the ForeignKey needs to have a __unicode__ method within it, like here:
class Category(models.Model):
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
That made the difference for me, and should apply to the above scenario. This works on Django 1.0.2.
If you have a lot of relation attribute fields to use in list_display and do not want create a function (and it's attributes) for each one, a dirt but simple solution would be override the ModelAdmin instace __getattr__ method, creating the callables on the fly:
class DynamicLookupMixin(object):
'''
a mixin to add dynamic callable attributes like 'book__author' which
return a function that return the instance.book.author value
'''
def __getattr__(self, attr):
if ('__' in attr
and not attr.startswith('_')
and not attr.endswith('_boolean')
and not attr.endswith('_short_description')):
def dyn_lookup(instance):
# traverse all __ lookups
return reduce(lambda parent, child: getattr(parent, child),
attr.split('__'),
instance)
# get admin_order_field, boolean and short_description
dyn_lookup.admin_order_field = attr
dyn_lookup.boolean = getattr(self, '{}_boolean'.format(attr), False)
dyn_lookup.short_description = getattr(
self, '{}_short_description'.format(attr),
attr.replace('_', ' ').capitalize())
return dyn_lookup
# not dynamic lookup, default behaviour
return self.__getattribute__(attr)
# use examples
#admin.register(models.Person)
class PersonAdmin(admin.ModelAdmin, DynamicLookupMixin):
list_display = ['book__author', 'book__publisher__name',
'book__publisher__country']
# custom short description
book__publisher__country_short_description = 'Publisher Country'
#admin.register(models.Product)
class ProductAdmin(admin.ModelAdmin, DynamicLookupMixin):
list_display = ('name', 'category__is_new')
# to show as boolean field
category__is_new_boolean = True
As gist here
Callable especial attributes like boolean and short_description must be defined as ModelAdmin attributes, eg book__author_verbose_name = 'Author name' and category__is_new_boolean = True.
The callable admin_order_field attribute is defined automatically.
Don't forget to use the list_select_related attribute in your ModelAdmin to make Django avoid aditional queries.
if you try it in Inline, you wont succeed unless:
in your inline:
class AddInline(admin.TabularInline):
readonly_fields = ['localname',]
model = MyModel
fields = ('localname',)
in your model (MyModel):
class MyModel(models.Model):
localization = models.ForeignKey(Localizations)
def localname(self):
return self.localization.name
I may be late, but this is another way to do it. You can simply define a method in your model and access it via the list_display as below:
models.py
class Person(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
def get_book_author(self):
return self.book.author
admin.py
class PersonAdmin(admin.ModelAdmin):
list_display = ('get_book_author',)
But this and the other approaches mentioned above add two extra queries per row in your listview page. To optimize this, we can override the get_queryset to annotate the required field, then use the annotated field in our ModelAdmin method
admin.py
from django.db.models.expressions import F
#admin.register(models.Person)
class PersonAdmin(admin.ModelAdmin):
list_display = ('get_author',)
def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
_author = F('book__author')
)
return queryset
#admin.display(ordering='_author', description='Author')
def get_author(self, obj):
return obj._author
AlexRobbins' answer worked for me, except that the first two lines need to be in the model (perhaps this was assumed?), and should reference self:
def book_author(self):
return self.book.author
Then the admin part works nicely.
I prefer this:
class CoolAdmin(admin.ModelAdmin):
list_display = ('pk', 'submodel__field')
#staticmethod
def submodel__field(obj):
return obj.submodel.field