2012-08-31 26 views
41

我有有兩種型號這樣的Django應用程序:我可以讓django admin中的list_filter只顯示引用的ForeignKeys嗎?

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

MyModel管理類看起來是這樣的:

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

Country表包含〜250個國家。實際上只有少數幾個國家被一些MyModel實例引用。

問題是,django admin 中的列表過濾器列出了過濾器面板中的所有國家。列出所有的國家(而不僅僅是那些被實例引用的國家)在這種情況下幾乎違背了列表過濾器的目的。

是否有一些在列表過濾器中只顯示MyModel引用的國家作爲選擇? (我使用Django 1.3。)

回答

58

從Django 1.8起,有一個內置的RelatedOnlyFieldListFilter,您可以使用它來顯示相關國家。

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

Django的1.4-1.7,list_filter允許您使用的SimpleListFilter一個子類。應該可以創建一個列出所需值的簡單列表篩選器。

如果你不能從Django 1.3升級,你需要使用內部的和沒有記錄的FilterSpec api。堆棧溢出問題Custom Filter in Django Admin應該指向正確的方向。

+0

感謝您的回覆。移植到Django 1.4計劃在不久的將來,所以我會推遲到這個問題的任何修復。 – m000

+1

由於'1.8' ... http://stackoverflow.com/a/27836981/953553 – andi

+0

@andi謝謝,我用新的信息更新了答案 – Alasdair

30

我知道的問題是關於Django 1.3,不過你提到即將升級到1.4。 同樣的人,像我這樣誰一直在尋找解決方案1.4,但發現該條目,我決定顯示了使用SimpleListFilter(可用的Django 1.4)的完整的例子,只顯示引用(相關,使用)外鍵值

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) 

在例子中,你可以看到兩個模型 - 城市和國家。城市有外國到國家。如果您使用常規的list_filter =('country',),您將擁有選擇器中的所有國家/地區。然而,該片段僅過濾相關國家 - 與城市至少有一個關係的國家。

原創想法here。非常感謝作者。改進了類名,以便更清晰地使用model_admin.model而不是硬編碼的模型名稱。

例也可以在Django的片段: http://djangosnippets.org/snippets/2885/

+0

這個例子非常接近我在找的東西,但有一個例外:我試圖通過名爲user_type的UserProfile模型上的字段爲User對象添加一個list_filter。因此我定義了一個類UserTypeFilter(SimpleListFilter): 但我不知道你在查詢集函數的返回queryset.filter( = self.value())中放置了什麼。 –

+0

回答我的問題在這裏:http://stackoverflow.com/questions/19187027/django-1-4-user-admin-list-filter-using-userprofile-field/19187261?noredirect=1#19187261 –

+0

如果你需要一個廣義的可重用版本的這個偉大的答案,請參閱下面的我的答案:http://stackoverflow.com/a/29501136/304209 –

5

我會改變darklow的代碼查找這樣的:

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] 

這是很多數據庫更好;)

18

由於Django的1 。8有:admin.RelatedOnlyFieldListFilter

的例子用法是:

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

1.8已經到達並且此功能現在可用 –

+1

@Rrrrrrrrrk更新了我的answe,以清除當前狀態;) – andi

+0

for some原因,似乎這不是在geodjango管理員,並從正常管理拉動它似乎並沒有工作 – Chozabu

1

@andi,謝謝你讓知道的事實,Django的1.8將具有此功能。

我看看它是如何實現的,並基於Django 1.7的創建版本。這是比我以前的答案更好的實現,因爲現在您可以在任何外鍵字段中重複使用此過濾器。僅在Django 1.7中測試過,不確定它是否適用於早期版本。

這是我最終的解決方案:

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] 

用法:

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

不幸的是,在Django 1.4無法正常工作:(''ForeignKey'對象沒有屬性'related_field'' –

+0

工作正常Django 1.6:D –

1

偉大@ darklow的答案的一種廣義可重複使用的版本:

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 

用法:

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

這是我對Django 1.4的一個通用和可重用的實現,如果你碰巧停留在那個版本。它受built-in version that is now part of Django 1.8及以上的啓發。另外,將它調整到1.5-1.7應該是一個相當小的任務,主要是queryset方法已經改變了這些名稱。我已經把過濾器本身放在我有的應用程序中,但你可以將它放在任何地方。

實現:

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

用法:

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

如果以後升級到Django的1.8,你應該能夠只是改變此導入:

from myproject.core.admin.filters import RelatedOnlyFieldListFilter 

對此:

from django.contrib.admin.filters import RelatedOnlyFieldListFilter 
相關問題