2010-02-12 11 views
7

Chcę umożliwić administratorom mojej witryny filtrowanie użytkowników z określonego kraju w witrynie administracyjnej. Tak naturalną rzeczą byłoby coś takiego:Django-Admin: atrybut listy_filtrów z UserProfile

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

Jednak, ze względu na sposób użytkownicy i ich UserProfiles są obsługiwane w Django to prowadzi do następującego błędu:

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

jak mogę obejść to ograniczenie?

Odpowiedz

10

Czego szukasz niestandardowego administratora FilterSpecs. Zła wiadomość jest taka, że ​​wsparcie dla tych osób może wkrótce nie zostać wysłane (możesz śledzić dyskusję here).

Jednak za cenę brudnego hacka można obejść ograniczenie. Najważniejsze informacje o tym, jak FilterSpecs są zbudowane przed nurkowaniem w kodzie:

  • Przy budowie listę FilterSpec do wyświetlania na stronie, Django wykorzystuje listę pól podanych w list_filter
  • Pola te muszą być prawdziwe pola w modelu, nie odwrócone relacje, ani niestandardowe właściwości.
  • Django przechowuje listę klas FilterSpec, z których każda jest związana z funkcją testu.
  • Dla każdego pola w list_filter Django użyje pierwszego FilterSpec klasę, dla której Test zwraca Prawdziwa na polu.

OK, teraz mając to na uwadze, spójrz na poniższy kod. Jest dostosowany od a django snippet. Organizacja kodu pozostaje w gestii użytkownika, należy jednak pamiętać, że powinna być zaimportowana przez aplikację 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) 

To nie jest wyraźnie panaceum, ale będzie wykonać zadanie, czekając na lepsze rozwiązanie, aby wymyślić. (Na przykład jeden, który będzie podklasą ChangeList i zastąpić get_filters).

+1

Dzięki, utknąłem z Django 1.2 na projekt i to było bardzo pomocne. Dwie uwagi: 1) Bit "userprofile__" w powyższym przykładzie pochodzi z nazwy modelu klasy profilu użytkownika. Moja klasa profilu nazywa się Profile, więc w moim przypadku chcę "profile__country__name" - zajęło mi minutę, aby to zrozumieć. 2) W ogólnym przypadku należy również zastąpić metodę "lookup_allowed" modelu ModelAdmin, aby zapobiec wyjątkom obiektu SuspiciousOperation podczas wyszukiwania relacji. http://www.hoboes.com/Mimsy/hacks/fixing-django-124s-suspiciousoperation-filtering/ pomógł tutaj. – ejucovy

Powiązane problemy