Pode "list_display" em um Django ModelAdmin exibir atributos dos campos ForeignKey?

296

Eu tenho um Personmodelo que possui uma relação de chave estrangeira Book, que possui vários campos, mas estou mais preocupado com author(um CharField padrão).

Com isso dito, no meu PersonAdminmodelo, eu gostaria de exibir book.authorusando list_display:

class PersonAdmin(admin.ModelAdmin):
    list_display = ['book.author',]

Eu tentei todos os métodos óbvios para fazer isso, mas nada parece funcionar.

Alguma sugestão?

Huuuze
fonte

Respostas:

472

Como outra opção, você pode fazer pesquisas 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'
imjoevasquez
fonte
Ambos não deveriam ser get_author, já que é isso que a string que você está retornando (e a breve descrição) realmente faz referência? Ou altere o argumento de formato de string para obj.book.reviews?
Carl G
1
@AnatoliyArkhipov, existe um caminho (baseado na resposta do Terr ). Eu já atualizei o código nesta resposta.
Denilson Sá Maia
por que você não pode simplesmente ter author = ForeignKey(Author)no modelo de livro e depois list_display = ('author')?
alias51
3
Isso causa uma consulta por linha exibida no admin :(
marcelm 2/17/17
1
@marcelm select_relatedé para isso que serve. o get_queryset()da UserAdminvontade terá que ser substituído.
interDist
142

Apesar de todas as ótimas respostas acima e por eu ser novo no Django, eu ainda estava preso. Aqui está a minha explicação de uma perspectiva muito nova.

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 estrangeira à maneira do 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, veja o link do modelo do Django aqui

Vai
fonte
3
para o campo do pedido, não deveria ser = 'author__name'?
Yunti 26/11/2015
2
Isso funciona perfeitamente, mas não sei por que. objé BookAdmin?
Steven Church
Uau. Levei uma hora na web para encontrar isso. Isso deve ficar muito mais claro na documentação do Django
Sevenearths
67

Como o resto, eu também usei callables. Mas eles têm uma desvantagem: por padrão, você não pode pedir neles. Felizmente, existe uma solução para isso:

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'
Arjen
fonte
a assinatura do método deve serdef author(self, obj):
sheats 19/09/19
Quando fiz o comentário, não era o caso, mas parece que desde a versão 1.8 o método passa o objeto para ele. Eu atualizei minha resposta.
Arjen #
46

Observe que a adição da get_authorfunção atrasaria o list_display no administrador, pois mostrar cada pessoa faria uma consulta SQL.

Para evitar isso, você precisa modificar o get_querysetmétodo no 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

Fome
fonte
3
Isso é muito importante e deve sempre ser implementado
xleon
Isso é realmente importante. Como alternativa, se alguém for seguir a __str__rota, adicione a chave estrangeira a list_displayelist_select_related
Scratch'N'Purr 27/11/19
22

De acordo com a documentação, você só pode exibir a __unicode__representação de uma chave estrangeira:

http://docs.djangoproject.com/en/dev/ref/contrib/admin/#list-display

Parece estranho que ele não suporta o 'book__author'formato de estilo usado em qualquer outro lugar na API do banco de dados.

Acontece que há um ticket para esse recurso , marcado como Não será corrigido.

Jonny Buchanan
fonte
11
@Mermoz realmente? Parece que o tíquete permanece definido como wontfix. Ela não aparece para trabalhar, ou (Django 1.3)
Dave
1.11 ainda não existe. Faz django há uma dúzia de anos e nunca me lembro deste :(
Aaron McMillin
12

Acabei de publicar um fragmento que faz com que admin.ModelAdmin suporte a sintaxe '__':

http://djangosnippets.org/snippets/2887/

Então você pode fazer:

class PersonAdmin(RelatedFieldAdmin):
    list_display = ['book__author',]

Isso basicamente faz o mesmo descrito nas outras respostas, mas cuida automaticamente de (1) configurar admin_order_field (2) configurar short_description e (3) modificar o conjunto de consultas para evitar um acerto no banco de dados para cada linha.

Jack Cushman
fonte
Eu gosto muito dessa idéia, mas ela não parece mais funcionar com as versões recentes do django:AttributeError: type object 'BaseModel' has no attribute '__metaclass__'
Vincent van Leeuwen
10

Você pode mostrar o que quiser na exibição da lista usando uma chamada. Seria assim:

def book_author (objeto):
  retornar object.book.author

classe PersonAdmin (admin.ModelAdmin):
  list_display = [autor do livro,]

fonte
Este é bom para situações em que muitos modelos diferentes costumam chamar o mesmo atributo; é suportado no 1.3+?
kagali-san
3
O problema disso é a quantidade de consultas SQL feitas no final. Para cada objeto da lista, ele fará uma consulta. É por isso que 'field__attribute' seria muito útil, porque certamente o Django abrangeria isso apenas uma consulta SQL. Estranho que não haja suporte para isso já.
emyller
7

Este já foi aceito, mas se houver outros manequins por aí (como eu) que não obtiveram imediatamente a resposta atualmente aceita , aqui está um pouco mais detalhadamente.

A classe de modelo referenciada pelas ForeignKeynecessidades de ter um __unicode__método dentro dela, 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.

Comunidade
fonte
4
No python 3 isso seria def __str__(self):.
Martlark
5

Se você tem muitos campos de atributos de relação para usar list_displaye não deseja criar uma função (e seus atributos) para cada um, uma solução simples, porém simples, substituirá o método ModelAdmininstace __getattr__, criando as chamadas imediatamente:

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 solicitáveis, como booleane short_descriptiondevem ser definidos como ModelAdminatributos, por exemplo, book__author_verbose_name = 'Author name'e category__is_new_boolean = True.

O admin_order_fieldatributo de chamada é definido automaticamente.

Não se esqueça de usar o atributo list_select_related no seu ModelAdminpara fazer o Django evitar consultas adicionais.

Cauê Thenório
fonte
1
Apenas tentei isso com uma instalação do Django 2.2 e funcionou muito bem para mim, enquanto outras abordagens não funcionaram, por qualquer motivo. Observe que hoje em dia você precisa importar reduzir de functools ou de outro lugar ...
Paul Brackin
5

Existe um pacote muito fácil de usar disponível no PyPI que lida exatamente com isso: django-related-admin . Você também pode ver 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, portanto, não os colarei aqui caso eles mudem.

Assim como uma nota lateral, se você já estiver usando algo diferente model.Admin(por exemplo, eu estava usando SimpleHistoryAdminem seu lugar), você pode fazer isso: class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin).

Vlad Schnakovszki
fonte
getter_for_related_field não funciona na versão 1.9, portanto, parece não ser a melhor opção para quem gosta de personalizar.
GriMel
4

se você tentar em linha, você não terá êxito, a menos que:

no seu inline:

class AddInline(admin.TabularInline):
    readonly_fields = ['localname',]
    model = MyModel
    fields = ('localname',)

no seu modelo (MyModel):

class MyModel(models.Model):
    localization = models.ForeignKey(Localizations)

    def localname(self):
        return self.localization.name
Eyal Ch
fonte
-1

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

Em seguida, a parte administrativa funciona bem.


fonte
-5

Eu prefiro isso:

class CoolAdmin(admin.ModelAdmin):
    list_display = ('pk', 'submodel__field')

    @staticmethod
    def submodel__field(obj):
        return obj.submodel.field
wieczorek1990
fonte