2012-08-31 34 views
41

Tôi có một ứng dụng django trong đó có hai mô hình như thế này:Tôi có thể tạo list_filter trong quản trị django để chỉ hiển thị ForeignKeys được tham chiếu không?

class MyModel(models.Model): 
    name = models.CharField() 
    country = models.ForeignKey('Country') 

class Country(models.Model): 
    code2 = models.CharField(max_length=2, primary_key=True) 
    name = models.CharField() 

Lớp quản trị cho MyModel trông như thế này:

class MyModelAdmin(admin.ModelAdmin): 
    list_display = ('name', 'country',) 
    list_filter = ('country',) 
admin.site.register(models.MyModel, MyModelAdmin) 

Bảng Country chứa ~ 250 quốc gia. Chỉ một số ít quốc gia thực sự được tham chiếu bởi một số ví dụ MyModel.

Vấn đề là bộ lọc danh sách trong quản trị django liệt kê TẤT CẢ các nước trong bảng điều khiển bộ lọc. Liệt kê tất cả các quốc gia (và không chỉ những quốc gia được tham chiếu bởi một cá thể) đã đánh bại khá nhiều mục đích của việc có bộ lọc danh sách trong trường hợp này.

Có một số chỉ hiển thị các quốc gia được tham chiếu bởi MyModel làm lựa chọn trong bộ lọc danh sách không? (Tôi sử dụng Django 1.3.)

Trả lời

58

Như của Django 1.8, có một được xây dựng trong RelatedOnlyFieldListFilter, mà bạn có thể sử dụng để hiển thị các quốc gia liên quan.

class MyModelAdmin(admin.ModelAdmin): 
    list_display = ('name', 'country',) 
    list_filter = (
     ('country', admin.RelatedOnlyFieldListFilter), 
    ) 

Đối với Django 1,4-1,7, list_filter phép bạn sử dụng một lớp con của SimpleListFilter. Bạn có thể tạo bộ lọc danh sách đơn giản liệt kê các giá trị bạn muốn.

Nếu bạn không thể nâng cấp từ Django 1.3, bạn cần sử dụng api nội bộ và không có giấy tờ, FilterSpec. Câu hỏi về Stack Overflow Custom Filter in Django Admin sẽ hướng bạn đi đúng hướng.

+0

Cảm ơn bạn đã trả lời. Di chuyển đến Django 1.4 được lên kế hoạch cho tương lai gần, vì vậy tôi sẽ trì hoãn bất kỳ bản sửa lỗi nào cho đến khi đó. – m000

+1

Vì '1.8' ... http://stackoverflow.com/a/27836981/953553 – andi

+0

@andi cảm ơn, tôi đã cập nhật câu trả lời với thông tin mới – Alasdair

30

Tôi biết câu hỏi về Django 1.3 tuy nhiên bạn đã đề cập sớm nâng cấp lên 1.4. Ngoài ra với mọi người, như tôi những người đang tìm kiếm giải pháp cho 1.4, nhưng không tìm thấy cụm từ này, tôi quyết định để hiển thị ví dụ đầy đủ của việc sử dụng SimpleListFilter (có sẵn Django 1.4) để chỉ hiển thị tham chiếu (liên quan, sử dụng) ngoài giá trị chính

from django.contrib.admin import SimpleListFilter 

# admin.py 
class CountryFilter(SimpleListFilter): 
    title = 'country' # or use _('country') for translated title 
    parameter_name = 'country' 

    def lookups(self, request, model_admin): 
     countries = set([c.country for c in model_admin.model.objects.all()]) 
     return [(c.id, c.name) for c in countries] 
     # You can also use hardcoded model name like "Country" instead of 
     # "model_admin.model" if this is not direct foreign key filter 

    def queryset(self, request, queryset): 
     if self.value(): 
      return queryset.filter(country__id__exact=self.value()) 
     else: 
      return queryset 

# Example setup and usage 

# models.py 
from django.db import models 

class Country(models.Model): 
    name = models.CharField(max_length=64) 

class City(models.Model): 
    name = models.CharField(max_length=64) 
    country = models.ForeignKey(Country) 

# admin.py 
from django.contrib.admin import ModelAdmin 

class CityAdmin(ModelAdmin): 
    list_filter = (CountryFilter,) 

admin.site.register(City, CityAdmin) 

Ví dụ: bạn có thể thấy hai kiểu - Thành phố và Quốc gia. Thành phố có ForeignKey to Country. Nếu bạn sử dụng list_filter = ('country'), bạn sẽ có tất cả các quốc gia trong trình chọn. Tuy nhiên, đoạn mã này chỉ lọc các quốc gia có liên quan - những quốc gia có ít nhất một mối quan hệ với thành phố.

Ý tưởng ban đầu từ here. Cảm ơn tác giả. Cải thiện tên lớp để rõ ràng hơn và sử dụng model_admin.model thay vì tên mô hình được mã hóa.

Ví dụ cũng có sẵn trong Django Snippets: http://djangosnippets.org/snippets/2885/

+0

Ví dụ này rất gần với những gì tôi đang tìm kiếm, nhưng với một ngoại lệ: Tôi đang cố gắng thêm một list_filter cho các đối tượng User bởi một trường trên mô hình UserProfile, có tên là user_type. Vì vậy, tôi xác định một lớp, UserTypeFilter (SimpleListFilter): nhưng tôi không biết những gì bạn đặt trong queryset của hàm queryset.filter ( = self.value()). –

+0

Trả lời cho câu hỏi của tôi là ở đây: http://stackoverflow.com/questions/19187027/django-1-4-user-admin-list-filter-using-userprofile-field/19187261?noredirect=1#19187261 –

+0

Nếu bạn cần một phiên bản tái sử dụng chung của câu trả lời tuyệt vời này, xin vui lòng xem câu trả lời của tôi dưới đây: http://stackoverflow.com/a/29501136/304209 –

5

Tôi muốn thay đổi tra cứu trong mã darklow của như thế này:

def lookups(self, request, model_admin): 
    users = User.objects.filter(id__in = model_admin.model.objects.all().values_list('user_id', flat = True).distinct()) 
    return [(user.id, unicode(user)) for user in users] 

này là tốt hơn nhiều cho cơ sở dữ liệu;)

18

Kể từ Django 1 .8 có: admin.RelatedOnlyFieldListFilter

Việc sử dụng ví dụ là:

class BookAdmin(admin.ModelAdmin): 
    list_filter = (
     ('author', admin.RelatedOnlyFieldListFilter), 
    ) 
+1

1.8 đã đến và tính năng này hiện có sẵn –

+1

@Rrrrrrrrrk cập nhật câu trả lời của tôi làm rõ trạng thái hiện tại;) – andi

+0

cho một số lý do, có vẻ như đây không phải là quản trị viên geodjango, và kéo nó từ quản trị viên thường xuyên dường như không hoạt động – Chozabu

1

@andi, nhờ đã cho biết về thực tế là Django 1.8 sẽ có tính năng này.

Tôi đã xem xét cách triển khai và dựa trên phiên bản đã tạo phù hợp với Django 1.7. Đây là một triển khai tốt hơn so với câu trả lời trước của tôi, bởi vì bây giờ bạn có thể sử dụng lại bộ lọc này với bất kỳ trường khóa ngoài nào. Thử nghiệm chỉ trong Django 1.7, không chắc chắn nếu nó hoạt động trong các phiên bản trước đó.

Dưới đây là giải pháp cuối cùng của tôi:

from django.contrib.admin import RelatedFieldListFilter 

class RelatedOnlyFieldListFilter(RelatedFieldListFilter): 
    def __init__(self, field, request, params, model, model_admin, field_path): 
     super(RelatedOnlyFieldListFilter, self).__init__(
      field, request, params, model, model_admin, field_path) 
     qs = field.related_field.model.objects.filter(
      id__in=model_admin.get_queryset(request).values_list(
       field.name, flat=True).distinct()) 
     self.lookup_choices = [(each.id, unicode(each)) for each in qs] 

Cách sử dụng:

class MyAdmin(admin.ModelAdmin): 
    list_filter = (
     ('user', RelatedOnlyFieldListFilter), 
     ('category', RelatedOnlyFieldListFilter), 
     # ... 
    ) 
+0

Thật không may, không hoạt động trong Django 1.4 :('' ForeignKey 'đối tượng không có thuộc tính' related_field'' –

+0

Làm việc tốt trên Django 1.6: D –

1

Một phiên bản thể tái sử dụng tổng quát của câu trả lời tuyệt vời @ darklow của:

def make_RelatedOnlyFieldListFilter(attr_name, filter_title): 

    class RelatedOnlyFieldListFilter(admin.SimpleListFilter): 
     """Filter that shows only referenced options, i.e. options having at least a single object.""" 
     title = filter_title 
     parameter_name = attr_name 

     def lookups(self, request, model_admin): 
      related_objects = set([getattr(obj, attr_name) for obj in model_admin.model.objects.all()]) 
      return [(related_obj.id, unicode(related_obj)) for related_obj in related_objects] 

     def queryset(self, request, queryset): 
      if self.value(): 
       return queryset.filter(**{'%s__id__exact' % attr_name: self.value()}) 
      else: 
       return queryset 

    return RelatedOnlyFieldListFilter 

Cách sử dụng:

class CityAdmin(ModelAdmin): 
    list_filter = (
     make_RelatedOnlyFieldListFilter("country", "Country with cities"), 
    ) 
2

Đây là việc tôi thực hiện chung và có thể tái sử dụng cho Django 1.4, nếu bạn tình cờ gặp khó khăn ở phiên bản đó. Nó được lấy cảm hứng từ built-in version that is now part of Django 1.8 trở lên. Ngoài ra, nó phải là một nhiệm vụ khá nhỏ để thích nghi nó với 1,5–1,7, chủ yếu là các phương thức queryset đã thay đổi tên trong đó. Tôi đã tự đặt bộ lọc trong một ứng dụng core mà tôi có nhưng bạn rõ ràng có thể đặt nó ở bất kỳ đâu.

Thực hiện:

# myproject/core/admin/filters.py: 

from django.contrib.admin.filters import RelatedFieldListFilter 


class RelatedOnlyFieldListFilter(RelatedFieldListFilter): 
    def __init__(self, field, request, params, model, model_admin, field_path): 
     self.request = request 
     self.model_admin = model_admin 
     super(RelatedOnlyFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path) 

    def choices(self, cl): 
     limit_choices_to = set(self.model_admin.queryset(self.request).values_list(self.field.name, flat=True)) 
     self.lookup_choices = [(pk_val, val) for pk_val, val in self.lookup_choices if pk_val in limit_choices_to] 
     return super(RelatedOnlyFieldListFilter, self).choices(cl) 

Cách sử dụng:

# myapp/admin.py: 

from django.contrib import admin 
from myproject.core.admin.filters import RelatedOnlyFieldListFilter 
from myproject.myapp.models import MyClass 


class MyClassAdmin(admin.ModelAdmin): 
    list_filter = (
     ('myfield', RelatedOnlyFieldListFilter), 
    ) 

admin.site.register(MyClass, MyClassAdmin) 

Nếu sau đó bạn nâng cấp lên Django 1.8 bạn sẽ có thể chỉ cần thay đổi khẩu này:

from myproject.core.admin.filters import RelatedOnlyFieldListFilter 

Để điều này:

from django.contrib.admin.filters import RelatedOnlyFieldListFilter 
Các vấn đề liên quan