web-dev-qa-db-fra.com

"List_display" dans un Django ModelAdmin peut-il afficher les attributs des champs ForeignKey?

J'ai un modèle Person qui a une relation de clé étrangère avec Book, qui comporte un certain nombre de champs, mais je m'inquiète surtout de author (un CharField standard).

Cela dit, dans mon modèle PersonAdmin, j'aimerais afficher book.author à l'aide de list_display:

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

J'ai essayé toutes les méthodes évidentes pour le faire, mais rien ne semble fonctionner.

Aucune suggestion?

262
Huuuze

Une autre option consiste à effectuer des recherches comme:

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'
432
imjoevasquez

En dépit de toutes les bonnes réponses ci-dessus et du fait que je sois nouveau à Django, j'étais toujours bloqué. Voici mon explication d'un point de vue très novice.

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 (façon incorrecte) - vous pensez que cela fonctionnerait en utilisant 'model__field' pour faire référence, mais ce n'est pas le cas

class BookAdmin(admin.ModelAdmin):
    model = Book
    list_display = ['title', 'author__name', ]

admin.site.register(Book, BookAdmin)

admin.py (Correct Way) - C’est ainsi que vous faites référence à une clé étrangère nommée de la manière 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)

Pour une référence supplémentaire, voir le lien du modèle Django --- ici

122
Will

Comme le reste, je suis allé avec callables aussi. Mais ils ont un inconvénient: par défaut, vous ne pouvez pas commander sur eux. Heureusement, il existe une solution pour cela:

def author(self):
    return self.book.author
author.admin_order_field  = 'book__author'
61
Arjen

Veuillez noter que l'ajout de la fonction get_author ralentirait l'affichage de la liste dans l'administrateur, car le fait d'afficher chaque personne effectuerait une requête SQL.

Pour éviter cela, vous devez modifier la méthode get_queryset dans PersonAdmin, par exemple:

def get_queryset(self, request):
    return super(PersonAdmin,self).get_queryset(request).select_related('book')

Avant: 73 requêtes en 36.02ms (67 requêtes dupliquées dans admin)

Après: 6 requêtes en 10.81ms

36
Hunger

Selon la documentation, vous ne pouvez afficher que la représentation __unicode__ d'une ForeignKey:

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

Cela semble étrange de ne pas prendre en charge le format de style 'book__author' utilisé partout ailleurs dans l'API de base de données.

Il s'avère qu'il y a n ticket pour cette fonctionnalité , qui est marqué comme ne résoudra pas.

20
Jonny Buchanan

Vous pouvez afficher ce que vous voulez dans l'affichage de liste en utilisant un appelable. Cela ressemblerait à ceci:

 
 def book_author (objet): 
 retourner object.book.author 
 
 classe PersonAdmin (admin.ModelAdmin): 
 list_display = [book_author,]
10
AlexRobbins

Je viens de publier un extrait qui rend la syntaxe '__' de support de admin.ModelAdmin:

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

Alors tu peux faire:

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

En gros, il s’agit essentiellement de la même chose que celle décrite dans les autres réponses, mais cela prend automatiquement en charge (1) le paramétrage de admin_order_field (2) le paramétrage de short_description et (3) la modification du queryset afin d’éviter un hit de base de données pour chaque ligne.

10
Jack Cushman

Celui-ci est déjà accepté, mais s'il y a d'autres mannequins (comme moi) qui ne l'ont pas immédiatement compris par réponse actuellement acceptée , voici un peu plus de détails.

La classe de modèle référencée par la variable ForeignKey doit contenir une méthode __unicode__, comme ici:

class Category(models.Model):
    name = models.CharField(max_length=50)

    def __unicode__(self):
        return self.name

Cela a fait la différence pour moi et devrait s’appliquer au scénario ci-dessus. Cela fonctionne sur Django 1.0.2.

6
bmelton

Si vous avez beaucoup de champs d'attributs de relation à utiliser dans list_display et que vous ne voulez pas créer de fonction (et d'attributs) pour chacun d'eux, une solution simple mais simple aurait priorité sur l'instance ModelAdmin instace __getattr__, créant les callables à la volée:

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

Comme Gist here

Les attributs spéciaux appelables tels que boolean et short_description doivent être définis comme étant les attributs ModelAdmin, par exemple book__author_verbose_name = 'Author name' et category__is_new_boolean = True.

L'attribut callable admin_order_field est défini automatiquement.

N'oubliez pas d'utiliser l'attribut list_select_related dans votre ModelAdmin pour que Django évite les requêtes supplémentaires.

4
Cauê Thenório

si vous l'essayez en ligne, vous ne réussirez que si:

dans votre inline:

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

dans votre modèle (MyModel):

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

    def localname(self):
        return self.localization.name
4
Eyal Ch

PyPI contient un paquet très facile à utiliser qui gère exactement cela: Django-related-admin . Vous pouvez aussi voir le code dans GitHub .

En utilisant ceci, ce que vous voulez réaliser est aussi simple que:

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

Les deux liens contiennent des détails complets sur l'installation et l'utilisation. Par conséquent, je ne les collerai pas ici au cas où ils changeraient.

En guise de remarque, si vous utilisez déjà autre chose que model.Admin (par exemple, j’utilisais plutôt SimpleHistoryAdmin, vous pouvez le faire: class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin).

3