У меня есть модель Person, которая имеет отношение внешнего ключа к Book, у которого есть несколько полей, но меня больше всего беспокоит author (стандартный CharField).
С учетом сказанного, в моей модели PersonAdmin я хотел бы отображать book.author с помощью list_display:
class PersonAdmin(admin.ModelAdmin):
list_display = ['book.author',]
Я испробовал все очевидные методы для этого, но, похоже, ничего не работает.
Какие-либо предложения?






Согласно документации, вы можете отображать только представление __unicode__ для ForeignKey:
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#list-display
Кажется странным, что он не поддерживает формат стиля 'book__author', который используется повсюду в API БД.
Оказывается, есть билет на эту функцию, который помечен как "Не исправить".
1.11 до сих пор не существует. Занимаюсь django уже десяток лет и никогда не помню этого :(
В качестве другого варианта вы можете выполнять такие поисковые запросы:
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'
Разве оба не должны быть get_author, поскольку именно на это ссылается возвращаемая вами строка (и краткое описание)? Или изменить аргумент формата строки на obj.book.reviews?
@AnatoliyArkhipov, способ есть (на основе Терр ответ). Я уже обновил код в этом ответе.
почему нельзя просто в книжной модели поставить author = ForeignKey(Author), а потом list_display = ('author')?
Угу, я случайно нажал «против» вместо «за», и это не позволит мне изменить его, пока ответ не будет отредактирован. :( Извиняюсь.
@ alias51: потому что пользователь связан не с автором, а с книгой, а книга связана с автором?
Это вызывает один запрос на строку, отображаемую в админке :(
@marcelm - вот для чего нужен select_related. get_queryset()UserAdmin придется перезаписать.
Этот уже принят, но если есть другие манекены (например, я), которые не сразу получили его от принятый в настоящее время ответ, вот немного подробностей.
Класс модели, на который ссылается ForeignKey, должен иметь внутри метод __unicode__, как здесь:
class Category(models.Model):
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
Это имело для меня значение и должно применяться к описанному выше сценарию. Это работает в Django 1.0.2.
На python 3 это будет def __str__(self):.
Вы можете отображать все, что хотите, в отображении списка, используя вызываемый. Это выглядело бы так:
def book_author(object): return object.book.author class PersonAdmin(admin.ModelAdmin): list_display = [book_author,]
Это удобно для ситуаций, когда множество разных моделей часто обращаются к одному и тому же атрибуту; это поддерживается в 1.3+?
Проблема в том, что в конечном итоге выполняется количество SQL-запросов. Для каждого объекта в списке будет выполнен запрос. Вот почему field__attribute было бы очень удобно, потому что Django определенно охватит это только одним запросом SQL. Странно, что это уже не поддерживается.
Ответ AlexRobbins сработал для меня, за исключением того, что первые две строки должны быть в модели (возможно, это предполагалось?) И должны ссылаться на себя:
def book_author(self):
return self.book.author
Тогда административная часть работает нормально.
Как и все остальное, я тоже использовал callables. Но у них есть один недостаток: по умолчанию на них нельзя заказывать. К счастью, для этого есть решение:
def author(self, obj):
return obj.book.author
author.admin_order_field = 'book__author'
def author(self):
return self.book.author
author.admin_order_field = 'book__author'
подпись метода должна быть def author(self, obj):
Когда я делал комментарий, это было не так, но похоже, что начиная с версии 1.8 метод получает объект, переданный ему. Я обновил свой ответ.
Я только что опубликовал фрагмент, который заставляет admin.ModelAdmin поддерживать синтаксис '__':
http://djangosnippets.org/snippets/2887/
Итак, вы можете:
class PersonAdmin(RelatedFieldAdmin):
list_display = ['book__author',]
Это в основном просто делает то же самое, что описано в других ответах, но автоматически заботится о (1) настройке admin_order_field (2) настройке short_description и (3) изменении набора запросов, чтобы избежать попадания в базу данных для каждой строки.
Мне очень нравится эта идея, но, похоже, она больше не работает с последними версиями django: AttributeError: type object 'BaseModel' has no attribute '__metaclass__'
если вы попробуете это в Inline, у вас ничего не получится, если:
в вашем встроенном:
class AddInline(admin.TabularInline):
readonly_fields = ['localname',]
model = MyModel
fields = ('localname',)
в вашей модели (MyModel):
class MyModel(models.Model):
localization = models.ForeignKey(Localizations)
def localname(self):
return self.localization.name
Несмотря на все отличные ответы выше и из-за того, что я новичок в Django, я все еще застрял. Вот мое объяснение с точки зрения новичка.
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 (неверный способ) - вы думаете, что это сработает, используя 'model__field' для ссылки, но это не так
class BookAdmin(admin.ModelAdmin):
model = Book
list_display = ['title', 'author__name', ]
admin.site.register(Book, BookAdmin)
admin.py (правильный путь) - так вы ссылаетесь на имя внешнего ключа способом Django
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)
Для получения дополнительной информации см. Ссылку на модель Django здесь
для поля заказа не должно быть = 'author__name'?
Это работает отлично, но я не уверен, почему. obj - это BookAdmin?
Вау. Мне потребовался час в Интернете, чтобы найти это. Это должно быть намного яснее в документации Django.
Спасибо, это было очень полезно
Обратите внимание, что добавление функции get_author замедлит list_display в админке, потому что отображение каждого человека приведет к выполнению SQL-запроса.
Чтобы этого избежать, вам нужно изменить метод get_queryset в PersonAdmin, например:
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
Это действительно важно и всегда должно выполняться
Это действительно важно. В качестве альтернативы, если кто-то собирается пойти по маршруту __str__, просто добавьте внешний ключ к list_display и list_select_related.
"list_select_related" - лучшее решение вопроса, связанного с заголовком.
Если у вас есть много полей атрибутов отношения для использования в list_display и вы не хотите создавать функцию (и ее атрибуты) для каждого из них, грязное, но простое решение будет переопределить метод ModelAdmin instace __getattr__, создавая вызываемые объекты на лету:
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
Как суть здесь
Вызываемые особые атрибуты, такие как boolean и short_description, должны быть определены как атрибуты ModelAdmin, например book__author_verbose_name = 'Author name' и category__is_new_boolean = True.
Вызываемый атрибут admin_order_field определяется автоматически.
Не забудьте использовать атрибут list_select_related в вашем ModelAdmin, чтобы Django избегал дополнительных запросов.
Просто попробовал это с установкой Django 2.2, и он отлично сработал для меня, в то время как другие подходы не работали по какой-либо причине. Обратите внимание, что в настоящее время вам нужно импортировать сокращение из functools или где-то еще ...
В PyPI есть очень простой в использовании пакет, который обрабатывает именно это: администратор, связанный с django. Вы также можете посмотреть код в GitHub.
Используя это, вы хотите достичь очень просто:
class PersonAdmin(RelatedFieldAdmin):
list_display = ['book__author',]
Обе ссылки содержат полную информацию об установке и использовании, поэтому я не буду вставлять их сюда, если они изменятся.
В качестве примечания, если вы уже используете что-то другое, кроме model.Admin (например, вместо этого я использовал SimpleHistoryAdmin), вы можете сделать это: class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin).
getter_for_related_field не работает в 1.9, так что это не лучший выбор для тех, кто любит настраивать.
Я предпочитаю это:
class CoolAdmin(admin.ModelAdmin):
list_display = ('pk', 'submodel__field')
@staticmethod
def submodel__field(obj):
return obj.submodel.field
@Mermoz правда? Похоже, билет остается установленным как wontfix. Это тоже не работает (Django 1.3)