Чи може "list_display" у Django ModelAdmin відображати атрибути полів ForeignKey?


296

У мене є Personмодель, яка має відношення із зовнішнім ключем до Bookякої має ряд полів, але мене найбільше хвилює author(стандартний CharField).

Зважаючи на це, у моїй PersonAdminмоделі я хотів би показати, book.authorвикористовуючи list_display:

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

Я спробував усі очевидні методи для цього, але нічого, здається, не працює.

Будь-які пропозиції?

Відповіді:


472

Як інший варіант, ви можете виглядати:

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?
Карл Г

1
@AnatoliyArkhipov, є спосіб (на основі відповіді Терра ). Я вже оновив код у цій відповіді.
Denilson Sá Maia

чому ти просто не можеш мати author = ForeignKey(Author)книжкову модель list_display = ('author')?
alias51

3
Це викликає один запит на рядок, що відображається в адміністраторі :(
marcelm

1
@marcelm саме select_relatedдля цього. get_queryset()з UserAdminповинні бути перезаписані.
interDist

142

Незважаючи на всі чудові відповіді вище і завдяки тому, що я був новим у 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 тут


3
для поля замовлення не повинно бути це = 'автор__ ім'я'?
Юнті

2
Це прекрасно працює, проте я не знаю чому. objє BookAdmin?
Церква Стівена

Ого. Мені потрібна година в Інтернеті, щоб знайти це. Це слід зробити набагато зрозумілішим у документації про Джанго
Севенеартс

67

Як і в решті, я теж поїхав з дзвінками. Але вони мають один мінус: за замовчуванням ви не можете замовити їх. На щастя, для цього є рішення:

Джанго> = 1,8

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

Джанго <1,8

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

метод підпису повинен бутиdef author(self, obj):
обкладинка

Ще коли я зробив коментар, це було не так, але, здається, що з версії 1.8 метод отримує об'єкт, переданий йому. Я оновив свою відповідь.
Арьєн

46

Зауважте, що додавання get_authorфункції призведе до уповільнення відображення list_display у адміністратора, оскільки показ кожної людини здійснить SQL-запит.

Щоб уникнути цього, вам потрібно змінити get_querysetметод у PersonAdmin, наприклад:

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

До: 73 запити за 36,02 мс (67 дублюваних запитів у адміністратора)

Після: 6 запитів за 10,81ms


3
Це дійсно важливо і завжди має бути здійснено
xleon

Це дійсно важливо. Як варіант, якщо хтось ходив би вниз по __str__маршруту, просто додайте іноземну клавішу до list_displayіlist_select_related
Scratch'N'Purr

22

Згідно з документацією, ви можете відображати лише __unicode__представництво ForeignKey:

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

Здається дивним, що він не підтримує 'book__author'формат стилю, який використовується скрізь у API DB.

Виявляється, є квиток на цю функцію , який позначений як Won't Fix.


11
@Mermoz дійсно? Здається, квиток залишається встановленим як wontfix. Це також не працює (Django 1.3)
Дейв

1.11 все ще не існує. Джанго робив десяток років, і цього я ніколи не пам’ятаю :(
Аарон Макміллін

12

Щойно я опублікував фрагмент, який створює синтаксис підтримки підтримки .ModelAdmin '__':

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

Отже, ви можете зробити:

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

Це в основному просто те саме, що описано в інших відповідях, але воно автоматично бере на себе (1) встановлення admin_order_field (2) встановлення короткого опису та (3) зміна набору запитів, щоб уникнути звернення до бази даних для кожного рядка.


Мені ця ідея дуже подобається, але вона, здається, більше не працює з останніми версіями джанго:AttributeError: type object 'BaseModel' has no attribute '__metaclass__'
Вінсент ван

10

Ви можете показувати все, що завгодно, у відображенні списку, використовуючи дзвінок. Це виглядатиме так:

визначити book_author (об'єкт):
  повернути object.book.author

клас PersonAdmin (admin.ModelAdmin):
  list_display = [book_author,]

Це добре для ситуацій, коли багато різних моделей часто закликають до того ж атрибуту; це підтримується в 1.3+?
kagali-san

3
Проблема в цьому - кількість запитів SQL, зрештою виконаних. Для кожного об’єкту зі списку він зробить запит. Ось чому «field__attribute» було б дуже зручно, оскільки, звичайно, Django поширюватиметься лише на один SQL-запит. Як не дивно, що тут вже немає підтримки.
emyller

7

Це вже прийнято, але якщо там є інші манекени (як я), які не одразу отримали це з прийнятої відповіді , ось трохи детальніше.

Клас моделі, на який посилається ForeignKeyпотреба, має в собі __unicode__метод, як тут:

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

    def __unicode__(self):
        return self.name

Це змінило для мене і має застосовуватися до вищезазначеного сценарію. Це працює на Django 1.0.2.


4
На python 3 це було б def __str__(self):.
Martlark

5

Якщо у вас є багато полів атрибутів відношення, які потрібно використовувати, list_displayі ви не хочете створювати функції (і це атрибути) для кожного, бруд, але просте рішення буде замінити метод ModelAdmininstace __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 уникав додаткових запитів.


1
Я просто спробував це з установкою Django 2.2, і він відмінно працював для мене, тоді як інші підходи не зробили з будь-якої причини. Зауважте, що нині вам потрібно імпортувати знижки з фунікулетів чи деінде ...
Пол Брекін

5

Існує дуже простий у використанні пакет, доступний в PyPI, який обробляє саме це: django-related-admin . Ви також можете побачити код у GitHub .

Використовуючи це, те, що ви хочете досягти, так само просто:

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

Обидва посилання містять повну інформацію про встановлення та використання, тому я не вставлятиму їх сюди у разі зміни.

Так само , як примітка боку, якщо ви вже використовуєте що - то інше , ніж model.Admin(наприклад , я використовую SimpleHistoryAdminзамість цього), ви можете зробити це: class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin).


getter_for_related_field не працює в 1.9, тому, здається, це не найкращий вибір для тих, хто любить налаштування.
GriMel

4

якщо ви спробуєте його в 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

-1

Відповідь AlexRobbins працювала на мене, за винятком того, що перші два рядки повинні бути в моделі (можливо, це передбачалося?), І повинен посилатися на себе:

def book_author(self):
  return self.book.author

Тоді адміністративна частина прекрасно працює.


-5

Я вважаю за краще це:

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

    @staticmethod
    def submodel__field(obj):
        return obj.submodel.field
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.