Can “list_display” in a Django ModelAdmin display

2019-01-01 17:12发布

问题:

I have a Person model that has a foreign key relationship to Book. Book 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\". I\'ve tried all of the obvious methods for doing so (see below), but nothing seems to work. Any suggestions?

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

回答1:

As another option, you can do look ups like:

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\'


回答2:

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



回答3:

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:

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


回答4:

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



回答5:

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.



回答6:

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,]


回答7:

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.



回答8:

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.



回答9:

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


回答10:

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.



回答11:

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).



回答12:

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.



回答13:

I prefer this:

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

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