“list_display” em um Django ModelAdmin pode exibir atributos de campos ForeignKey?
-
03-07-2019 - |
Pergunta
eu tenho um Person
modelo que tem um relacionamento de chave estrangeira com Book
, que tem vários campos, mas estou mais preocupado com author
(um CharField padrão).
Dito isto, na minha PersonAdmin
modelo, gostaria de exibir book.author
usando list_display
:
class PersonAdmin(admin.ModelAdmin):
list_display = ['book.author',]
Tentei todos os métodos óbvios para fazer isso, mas nada parece funcionar.
Alguma sugestão?
Solução
Como outra opção, você pode procurar ups como:
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'
Outras dicas
Apesar de todas as ótimas respostas acima e por ser novo no Django, eu ainda estava preso. Aqui está minha explicação de uma perspectiva muito novata.
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 (maneira incorreta) - Você acha que funcionaria usando 'Model__field' para fazer referência, mas não
class BookAdmin(admin.ModelAdmin):
model = Book
list_display = ['title', 'author__name', ]
admin.site.register(Book, BookAdmin)
admin.py (maneira correta) - É assim que você faz referência a um nome de chave estrangeiro da maneira 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)
Para referência adicional, consulte o link do modelo Django aqui
Como o resto, eu também fui com callables. Mas eles têm uma desvantagem: por padrão, você não pode pedir neles. Felizmente, há uma solução para isso:
def author(self):
return self.book.author
author.admin_order_field = 'book__author'
Observe que adicionar o get_author
A função retardaria o list_display no administrador, porque mostrar que cada pessoa faria uma consulta SQL.
Para evitar isso, você precisa modificar get_queryset
Método em Personadmin, por exemplo:
def get_queryset(self, request):
return super(PersonAdmin,self).get_queryset(request).select_related('book')
Antes: 73 consultas em 36,02ms (67 consultas duplicadas em admin)
Depois: 6 consultas em 10,81ms
De acordo com a documentação, você só pode exibir o __unicode__
Representação de um ForeignKey:
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#list-display
Parece estranho que ele não suporta o 'book__author'
formato de estilo que é usado em qualquer outro lugar da API de banco de dados.
Acontece que lá está um ingresso para este recurso, que é marcado como não corrigirá.
Você pode mostrar o que quiser no visor da lista usando um chamável. Seria assim:
def book_author(object): return object.book.author class PersonAdmin(admin.ModelAdmin): list_display = [book_author,]
Acabei de publicar um trecho que faz do admin.modeladmin suportar '__' sintaxe:
http://djangosnippets.org/snippets/2887/
Então você pode fazer:
class PersonAdmin(RelatedFieldAdmin):
list_display = ['book__author',]
Isso é basicamente apenas fazendo a mesma coisa descrita nas outras respostas, mas atende automaticamente (1) configurando admin_order_field (2) Configurando Short_description e (3) modificando o consulta para evitar um acerto de banco de dados para cada linha.
Este já foi aceito, mas se houver outros manequins por aí (como eu), que não o tirou imediatamente do resposta atualmente aceita, aqui está um pouco mais de detalhe.
A classe modelo referenciada pelo ForeignKey
precisa ter um __unicode__
Método dentro dele, como aqui:
class Category(models.Model):
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
Isso fez a diferença para mim e deve se aplicar ao cenário acima. Isso funciona no Django 1.0.2.
Se você tentar em linha, você não terá sucesso a menos que:
em sua inline:
class AddInline(admin.TabularInline):
readonly_fields = ['localname',]
model = MyModel
fields = ('localname',)
em seu modelo (mymodel):
class MyModel(models.Model):
localization = models.ForeignKey(Localizations)
def localname(self):
return self.localization.name
Se você tiver muitos campos de atributos de relacionamento para usar em list_display
e não quiser criar uma função (e seus atributos) para cada um, uma solução suja, mas simples, seria substituir o ModelAdmin
instância __getattr__
método, criando os callables dinamicamente:
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
Como essência aqui
Atributos especiais chamáveis como boolean
e short_description
deve ser definido como ModelAdmin
atributos, por exemplo book__author_verbose_name = 'Author name'
e category__is_new_boolean = True
.
O chamável admin_order_field
atributo é definido automaticamente.
Não se esqueça de usar o list_select_relacionado atributo em seu ModelAdmin
para fazer o Django evitar consultas adicionais.
Há um pacote muito fácil de usar disponível no Pypi que lida exatamente com isso: Django-Admin. Você também pode Veja o código no github.
Usando isso, o que você deseja alcançar é tão simples quanto:
class PersonAdmin(RelatedFieldAdmin):
list_display = ['book__author',]
Ambos os links contêm detalhes completos de instalação e uso para que não os coloque aqui, caso eles mudem.
Assim como uma nota lateral, se você já está usando algo diferente model.Admin
(por exemplo, eu estava usando SimpleHistoryAdmin
em vez disso), você pode fazer isso: class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin)
.
A resposta de Alexrobbins funcionou para mim, exceto que as duas primeiras linhas precisam estar no modelo (talvez isso tenha sido assumido?), E deve fazer referência a si mesmo:
def book_author(self):
return self.book.author
Então a parte do administrador funciona bem.
Eu prefiro isso:
class CoolAdmin(admin.ModelAdmin):
list_display = ('pk', 'submodel__field')
@staticmethod
def submodel__field(obj):
return obj.submodel.field