2010-02-12 44 views
7

我想允許我的網站管理員在管理網站上過濾特定國家/地區的用戶。所以很自然的事情會是這樣的:Django-Admin:來自UserProfile的list_filter屬性

#admin.py 
class UserAdmin(django.contrib.auth.admin.UserAdmin): 
    list_filter=('userprofile__country__name',) 

#models.py 
class UserProfile(models.Model) 
    ... 
    country=models.ForeignKey('Country') 

class Country(models.Model) 
    ... 
    name=models.CharField(max_length=32) 

但是,由於方式的用戶和他們的UserProfiles在Django的處理,這導致了以下錯誤:

'UserAdmin.list_filter[0]' refers to field 'userprofile__country__name' that is missing from model 'User' 

我如何繞過這個限制?

回答

10

你在找什麼是定製管理FilterSpecs。壞消息是,這些支持可能不會很快出貨(您可以跟蹤討論here)。

但是,以一個骯髒的黑客的價格,你可以解決這個限制。如何FilterSpecs一些亮點潛水前都建在代碼:

  • 當建築FilterSpec列表顯示在頁面上,Django使用你list_filter
  • 這些領域提供的字段列表必須模型上的實際字段,不是反向關係,也不是自定義屬性。
  • Django維護一個FilterSpec類的列表,每個類都與一個測試函數關聯。
  • 對於list_filter每個字段,Django會使用第一FilterSpec類的量,測試功能的字段返回

好吧,現在考慮到這一點,看看下面的代碼。它改編自a django snippet。代碼的組織由您自行決定,請記住,這應該由admin應用導入。

from myapp.models import UserProfile, Country 
from django.contrib.auth.models import User 
from django.contrib.auth.admin import UserAdmin 

from django.contrib.admin.filterspecs import FilterSpec, ChoicesFilterSpec 
from django.utils.encoding import smart_unicode 
from django.utils.translation import ugettext_lazy as _ 

class ProfileCountryFilterSpec(ChoicesFilterSpec): 
    def __init__(self, f, request, params, model, model_admin): 
     ChoicesFilterSpec.__init__(self, f, request, params, model, model_admin) 

     # The lookup string that will be added to the queryset 
     # by this filter 
     self.lookup_kwarg = 'userprofile__country__name' 
     # get the current filter value from GET (we will use it to know 
     # which filter item is selected) 
     self.lookup_val = request.GET.get(self.lookup_kwarg) 

     # Prepare the list of unique, country name, ordered alphabetically 
     country_qs = Country.objects.distinct().order_by('name') 
     self.lookup_choices = country_qs.values_list('name', flat=True) 

    def choices(self, cl): 
     # Generator that returns all the possible item in the filter 
     # including an 'All' item. 
     yield { 'selected': self.lookup_val is None, 
       'query_string': cl.get_query_string({}, [self.lookup_kwarg]), 
       'display': _('All') } 
     for val in self.lookup_choices: 
      yield { 'selected' : smart_unicode(val) == self.lookup_val, 
        'query_string': cl.get_query_string({self.lookup_kwarg: val}), 
        'display': val } 

    def title(self): 
     # return the title displayed above your filter 
     return _('user\'s country') 

# Here, we insert the new FilterSpec at the first position, to be sure 
# it gets picked up before any other 
FilterSpec.filter_specs.insert(0, 
    # If the field has a `profilecountry_filter` attribute set to True 
    # the this FilterSpec will be used 
    (lambda f: getattr(f, 'profilecountry_filter', False), ProfileCountryFilterSpec) 
) 


# Now, how to use this filter in UserAdmin, 
# We have to use one of the field of User model and 
# add a profilecountry_filter attribute to it. 
# This field will then activate the country filter if we 
# place it in `list_filter`, but we won't be able to use 
# it in its own filter anymore. 

User._meta.get_field('email').profilecountry_filter = True 

class MyUserAdmin(UserAdmin): 
    list_filter = ('email',) + UserAdmin.list_filter 

# register the new UserAdmin 
from django.contrib.admin import site 
site.unregister(User) 
site.register(User, MyUserAdmin) 

這顯然不是萬能的,但它會做的工作,等待一個更好的解決方案上來。(例如,一個將繼承ChangeList,並覆蓋get_filters)。

+1

謝謝,我堅持使用Django 1.2的一個項目,這是真的很有幫助。兩個註釋: 1)上面的「userprofile__」位是從用戶配置文件類的型號名稱派生的。我的個人資料類叫做個人資料,所以在我的情況下我想要「profile__country__name」 - 花了我一分鐘的時間來弄清楚。 2)對於一般情況,您還需要重寫ModelAdmin的'lookup_allowed'方法,以便在對關係進行查找時防止SuspiciousOperation異常。 http://www.hoboes.com/Mimsy/hacks/fixing-django-124s-suspiciousoperation-filtering/在這裏幫了忙。 – ejucovy 2011-11-30 15:10:39