Có thể liệt kê danh sách liệt kê trong danh sách Django ModelAdmin của các trường ForeignKey không?


296

Tôi có một Personmô hình có mối quan hệ khóa ngoài Book, trong đó có một số lĩnh vực, nhưng tôi quan tâm nhất author(một CharField tiêu chuẩn).

Như đã nói, trong PersonAdminmô hình của tôi , tôi muốn hiển thị book.authorbằng list_display:

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

Tôi đã thử tất cả các phương pháp rõ ràng để làm như vậy, nhưng dường như không có gì hiệu quả.

Bất kỳ đề xuất?

Câu trả lời:


472

Như một lựa chọn khác, bạn có thể thực hiện tra cứu như:

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'

Không nên cả hai get_author, vì đó là những gì chuỗi bạn trả về (và mô tả ngắn) thực sự tham chiếu? Hoặc thay đổi đối số định dạng chuỗi thành obj.book.reviews?
Carl G

1
@AnatoliyArkhipov, có một cách (dựa trên câu trả lời Terr ). Tôi đã cập nhật mã trong câu trả lời này.
Denilson Sá Maia

Tại sao bạn không thể có author = ForeignKey(Author)trong mô hình cuốn sách, sau đó list_display = ('author')?
bí danh51

3
Điều này gây ra một truy vấn trên mỗi hàng được hiển thị trong quản trị viên :(
marcelm

1
@marcelm đó là những gì select_relateddành cho. các get_queryset()của UserAdminsẽ phải được ghi đè.
interDist

142

Mặc dù tất cả các câu trả lời tuyệt vời ở trên và do tôi chưa quen với Django, tôi vẫn bị mắc kẹt. Đây là lời giải thích của tôi từ một quan điểm rất mới.

mô hình

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 (Cách không chính xác) - bạn nghĩ rằng nó sẽ hoạt động bằng cách sử dụng 'model__field' để tham khảo, nhưng nó không

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

admin.site.register(Book, BookAdmin)

admin.py (Correct Way) - đây là cách bạn tham chiếu tên khóa ngoại theo cách 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)

Để tham khảo thêm, xem liên kết mô hình Django tại đây


3
đối với trường đơn hàng không nên là = 'Author__name'?
Yunti

2
Điều này hoạt động hoàn hảo, tuy nhiên tôi không chắc tại sao. objBookAdmin?
Nhà thờ Steven

Ồ Mất một giờ trên web để tìm thấy điều này. Điều này cần được làm rõ hơn rất nhiều trong tài liệu Django
Sevenearths

67

Giống như phần còn lại, tôi cũng đã đi với các cuộc gọi. Nhưng chúng có một nhược điểm: theo mặc định, bạn không thể đặt hàng trên chúng. May mắn thay, có một giải pháp cho điều đó:

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'

chữ ký của phương thức phải làdef author(self, obj):
sheats

Quay lại khi tôi đưa ra nhận xét không phải như vậy nhưng có vẻ như từ phiên bản 1.8, phương thức đã đưa đối tượng được truyền cho nó. Tôi đã cập nhật câu trả lời của mình.
Arjen

46

Xin lưu ý rằng việc thêm get_authorchức năng sẽ làm chậm list_display trong quản trị viên, bởi vì hiển thị mỗi người sẽ tạo một truy vấn SQL.

Để tránh điều này, bạn cần sửa đổi get_querysetphương thức trong PersonAdmin, ví dụ:

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

Trước: 73 truy vấn trong 36,02ms (67 truy vấn trùng lặp trong quản trị viên)

Sau: 6 truy vấn trong 10,81ms


3
Điều này thực sự quan trọng và phải luôn được thực hiện
xleon

Đây thực sự là quan trọng. Ngoài ra, nếu một người đi xuống __str__tuyến đường, chỉ cần thêm khóa ngoại vào list_displaylist_select_related
Scratch'N'Purr

22

Theo tài liệu, bạn chỉ có thể hiển thị __unicode__đại diện của ForeignKey:

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

Có vẻ kỳ lạ là nó không hỗ trợ 'book__author'định dạng kiểu được sử dụng ở mọi nơi khác trong API DB.

Hóa ra có một vé cho tính năng này , được đánh dấu là Không sửa.


11
@Mermoz thật sao? Nó xuất hiện vé vẫn được đặt là wontfix. Nó cũng không hoạt động (Django 1.3)
Dave

1.11 vẫn không tồn tại. Đã làm django trong một chục năm và tôi không bao giờ nhớ điều này :(
Aaron McMillin

12

Tôi vừa đăng một đoạn trích khiến admin.ModelAdmin hỗ trợ cú pháp '__':

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

Vì vậy, bạn có thể làm:

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

Điều này về cơ bản chỉ là thực hiện điều tương tự được mô tả trong các câu trả lời khác, nhưng nó sẽ tự động chăm sóc (1) cài đặt admin_order_field (2) đặt short_description và (3) sửa đổi bộ truy vấn để tránh bị đánh vào cơ sở dữ liệu cho mỗi hàng.


Tôi thích ý tưởng này rất nhiều, nhưng dường như nó không còn hiệu quả nữa với những bản django gần đây:AttributeError: type object 'BaseModel' has no attribute '__metaclass__'
Vincent van Leeuwen

10

Bạn có thể hiển thị bất cứ điều gì bạn muốn trong danh sách hiển thị bằng cách sử dụng một cuộc gọi. Nó sẽ trông như thế này:

def book_ Tác giả (đối tượng):
  return object.book. viên

lớp PersonAdmin (admin.ModelAdmin):
  list_display = [book_ Tác giả,]

Điều này là tốt cho các tình huống, trong đó rất nhiều mô hình khác nhau thường gọi đến cùng một thuộc tính; nó có được hỗ trợ trong 1.3+ không?
kagali-san

3
Vấn đề về điều này là số lượng truy vấn SQL được thực hiện cuối cùng. Đối với mỗi đối tượng trong danh sách, nó sẽ tạo một truy vấn. Đây là lý do tại sao 'field__attribution' sẽ rất tiện dụng, bởi vì chắc chắn Django sẽ chỉ mở rộng điều đó thành một truy vấn SQL. Điều lạ là không có hỗ trợ này.
emyller

7

Điều này đã được chấp nhận, nhưng nếu có bất kỳ người giả nào khác ngoài đó (như tôi) đã không nhận được ngay từ câu trả lời được chấp nhận hiện tại , thì đây là một chi tiết hơn một chút.

Lớp mô hình được tham chiếu bởi các ForeignKeynhu cầu phải có một __unicode__phương thức bên trong nó, như ở đây:

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

    def __unicode__(self):
        return self.name

Điều đó tạo ra sự khác biệt cho tôi, và nên áp dụng cho kịch bản trên. Điều này hoạt động trên Django 1.0.2.


4
Trên python 3 này sẽ là def __str__(self):.
Martlark

5

Nếu bạn có nhiều trường thuộc tính quan hệ để sử dụng list_displayvà không muốn tạo hàm (và thuộc tính của nó) cho từng trường, một giải pháp đơn giản nhưng bẩn thỉu sẽ ghi đè phương thức ModelAdmininstace __getattr__, tạo ra các hàm gọi được khi đang bay:

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

Như ý chính ở đây

Các thuộc tính đặc biệt có thể gọi được như booleanshort_descriptionphải được xác định là ModelAdminthuộc tính, ví dụ book__author_verbose_name = 'Author name'category__is_new_boolean = True.

admin_order_fieldThuộc tính có thể gọi được xác định tự động.

Đừng quên sử dụng thuộc tính list_select_relatedModelAdmin để giúp Django tránh các truy vấn có tính chất quảng cáo.


1
Chỉ cần thử điều này với bản cài đặt Django 2.2 và nó hoạt động rất tốt đối với tôi trong khi các cách tiếp cận khác thì không, vì bất kỳ lý do gì. Lưu ý rằng ngày nay bạn cần nhập giảm từ funcools hoặc ở nơi khác ...
Paul Brackin

5

Có một gói rất dễ sử dụng có sẵn trong PyPI xử lý chính xác đó là: django liên quan đến quản trị viên . Bạn cũng có thể xem mã trong GitHub .

Sử dụng cái này, những gì bạn muốn đạt được cũng đơn giản như:

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

Cả hai liên kết đều chứa đầy đủ chi tiết về cài đặt và sử dụng vì vậy tôi sẽ không dán chúng ở đây trong trường hợp chúng thay đổi.

Cũng như một ghi chú bên lề, nếu bạn đã sử dụng một cái gì đó ngoài model.Admin(ví dụ như tôi đang sử dụng SimpleHistoryAdminthay thế), bạn có thể làm điều này : class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin).


getter_for_related_field không hoạt động trong 1.9 nên có vẻ như đó không phải là lựa chọn tốt nhất cho những người thích tùy chỉnh.
GriMel

4

nếu bạn thử nó trong Inline, bạn sẽ không thành công trừ khi:

trong dòng của bạn:

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

trong mô hình của bạn (MyModel):

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

    def localname(self):
        return self.localization.name

-1

Câu trả lời của AlexRobbins có hiệu quả với tôi, ngoại trừ hai dòng đầu tiên cần có trong mô hình (có lẽ đây là giả định?), Và nên tự tham khảo:

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

Sau đó, phần quản trị hoạt động độc đáo.


-5

Tôi thích điều này:

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

    @staticmethod
    def submodel__field(obj):
        return obj.submodel.field
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.