Bisakah "list_display" dalam Django ModelAdmin menampilkan atribut bidang ForeignKey?

296

Saya memiliki Personmodel yang memiliki hubungan kunci asing Book, yang memiliki sejumlah bidang, tapi saya paling khawatir tentang author(standar CharField).

Dengan itu, dalam PersonAdminmodel saya , saya ingin menampilkan book.authormenggunakan list_display:

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

Saya sudah mencoba semua metode yang jelas untuk melakukannya, tetapi sepertinya tidak ada yang berhasil.

Ada saran?

Huuuze
sumber

Jawaban:

472

Sebagai pilihan lain, Anda dapat melakukan look up seperti:

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
sumber
Tidak boleh keduanya get_author, karena itu adalah apa yang Anda mengembalikan string (dan deskripsi singkat) sebenarnya? Atau ubah argumen format string ke obj.book.reviews?
Carl G
1
@AnatoliyArkhipov, ada cara (berdasarkan jawaban Terr ). Saya sudah memperbarui kode dalam jawaban ini.
Denilson Sá Maia
mengapa Anda tidak bisa memiliki author = ForeignKey(Author)model buku saja, lalu list_display = ('author')?
alias51
3
Ini menyebabkan satu kueri per baris ditampilkan di admin :(
marcelm
1
@marcelm itu untuk apa select_related. yang get_queryset()dari UserAdminharus ditimpa.
interDist
142

Terlepas dari semua jawaban hebat di atas dan karena saya baru mengenal Django, saya masih terjebak. Inilah penjelasan saya dari sudut pandang pemula.

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 (Cara Salah) - Anda pikir itu akan berhasil dengan menggunakan 'model__field' untuk referensi, tetapi tidak

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

admin.site.register(Book, BookAdmin)

admin.py (Cara yang Benar) - ini adalah bagaimana Anda mereferensikan nama kunci asing dengan cara 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)

Untuk referensi tambahan, lihat tautan model Django di sini

Akan
sumber
3
untuk bidang pesanan bukankah seharusnya = 'author__name'?
Yunti
2
Ini berfungsi dengan baik, namun saya tidak yakin mengapa. objadalah BookAdmin?
Steven Church
Wow. Butuh saya satu jam di web untuk menemukan ini. Ini harus dibuat jauh lebih jelas dalam dokumentasi Django
Sevenearths
67

Seperti yang lainnya, saya juga membawa perlengkapan telepon. Tetapi mereka memiliki satu kelemahan: secara default, Anda tidak dapat memesannya. Untungnya, ada solusi untuk itu:

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
sumber
metode tanda tangan harusdef author(self, obj):
sheats
Kembali ketika saya membuat komentar itu tidak terjadi tetapi tampaknya sejak versi 1.8 metode mendapat objek yang diteruskan ke sana. Saya telah memperbarui jawaban saya.
Arjen
46

Harap dicatat bahwa menambahkan get_authorfungsi akan memperlambat list_display di admin, karena menunjukkan setiap orang akan membuat query SQL.

Untuk menghindari ini, Anda perlu memodifikasi get_querysetmetode di PersonAdmin, misalnya:

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

Sebelum: 73 kueri dalam 36.02ms (67 kueri yang digandakan di admin)

Setelah: 6 kueri dalam 10.81ms

Kelaparan
sumber
3
Ini sangat penting dan harus selalu dilaksanakan
xleon
Ini memang penting. Atau, jika seseorang harus turun __str__rute, cukup tambahkan foreignkey ke list_displaydanlist_select_related
Scratch'N'Purr
22

Menurut dokumentasi, Anda hanya dapat menampilkan __unicode__representasi ForeignKey:

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

Tampaknya aneh bahwa itu tidak mendukung 'book__author'format gaya yang digunakan di tempat lain di DB API.

Ternyata ada tiket untuk fitur ini , yang ditandai sebagai Won't Fix.

Jonny Buchanan
sumber
11
@Mermoz benarkah? Tampaknya tiket tetap ditetapkan sebagai wontfix. Tampaknya tidak berfungsi, (Django 1.3)
Dave
1,11 masih belum ada. Telah melakukan django selama belasan tahun dan saya tidak pernah ingat yang ini :(
Aaron McMillin
12

Saya baru saja memposting cuplikan yang membuat admin.ModelAdmin mendukung sintaks '__':

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

Jadi kamu bisa melakukan:

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

Ini pada dasarnya hanya melakukan hal yang sama yang dijelaskan dalam jawaban lain, tetapi secara otomatis menangani (1) pengaturan admin_order_field (2) pengaturan short_description dan (3) memodifikasi queryset untuk menghindari hit database untuk setiap baris.

Jack Cushman
sumber
Saya sangat menyukai ide ini, tetapi tampaknya tidak berfungsi lagi dengan versi Django baru-baru ini:AttributeError: type object 'BaseModel' has no attribute '__metaclass__'
Vincent van Leeuwen
10

Anda dapat menunjukkan apa pun yang Anda inginkan di tampilan daftar dengan menggunakan panggilan. Akan terlihat seperti ini:

def book_author (objek):
  kembalikan objek.book.author

kelas PersonAdmin (admin.ModelAdmin):
  list_display = [book_author,]

sumber
Yang ini bagus untuk situasi, di mana banyak model yang berbeda sering memanggil atribut yang sama; apakah itu didukung di 1.3+?
kagali-san
3
Masalahnya adalah jumlah query SQL yang dilakukan pada akhirnya. Untuk setiap objek dalam daftar, itu akan membuat kueri. Inilah sebabnya mengapa 'field__attribute' akan sangat berguna, karena tentu saja Django akan menjangkau itu hanya untuk satu permintaan SQL. Aneh bahwa sudah tidak ada dukungan ini.
emyller
7

Yang ini sudah diterima, tetapi jika ada boneka lain di luar sana (seperti saya) yang tidak langsung mendapatkannya dari jawaban yang diterima saat ini , berikut ini sedikit lebih detail.

Kelas model dirujuk oleh ForeignKeykebutuhan untuk memiliki __unicode__metode di dalamnya, seperti di sini:

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

    def __unicode__(self):
        return self.name

Itu membuat perbedaan bagi saya, dan harus diterapkan pada skenario di atas. Ini berfungsi pada Django 1.0.2.

Komunitas
sumber
4
Pada python 3 ini akan menjadi def __str__(self):.
Martlark
5

Jika Anda memiliki banyak bidang atribut relasi untuk digunakan list_displaydan tidak ingin membuat fungsi (dan atributnya) untuk masing-masingnya, solusi yang sederhana akan mengesampingkan metode ModelAdmininstace __getattr__, membuat callable dengan cepat:

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

Seperti intinya di sini

Atribut utama yang dapat dipanggil seperti booleandan short_descriptionharus didefinisikan sebagai ModelAdminatribut, misalnya book__author_verbose_name = 'Author name'dan category__is_new_boolean = True.

admin_order_fieldAtribut callable didefinisikan secara otomatis.

Jangan lupa untuk menggunakan atribut list_select_related di Anda ModelAdminuntuk membuat Django menghindari pertanyaan tambahan.

Cauê Thenório
sumber
1
Hanya mencoba ini dengan Django 2.2 instal & itu bekerja sangat baik untuk saya sementara pendekatan lain tidak, untuk alasan apa pun. Perhatikan bahwa saat ini Anda perlu mengimpor pengurangan dari functools atau di tempat lain ...
Paul Brackin
5

Ada paket yang sangat mudah digunakan yang tersedia di PyPI yang menangani persis itu: Django-related-admin . Anda juga dapat melihat kode di GitHub .

Dengan ini, apa yang ingin Anda capai adalah sesederhana:

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

Kedua tautan berisi perincian lengkap tentang pemasangan dan penggunaan, jadi saya tidak akan menempelkannya di sini jika terjadi perubahan.

Hanya sebagai catatan, jika Anda telah menggunakan sesuatu selain model.Admin(misalnya saya menggunakan SimpleHistoryAdmingantinya), Anda dapat melakukan ini: class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin).

Vlad Schnakovszki
sumber
getter_for_related_field tidak berfungsi di 1.9 sehingga sepertinya bukan pilihan terbaik bagi mereka yang suka menyesuaikan.
GriMel
4

jika Anda mencobanya di Inline, Anda tidak akan berhasil kecuali:

di inline Anda:

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

dalam model Anda (MyModel):

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

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

Jawaban AlexRobbins bekerja untuk saya, kecuali bahwa dua baris pertama harus ada dalam model (mungkin ini diasumsikan?), Dan haruskah merujuk diri:

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

Kemudian bagian admin berfungsi dengan baik.


sumber
-5

Saya lebih memilih ini:

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

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