Django-Admin: атрибут list_filter из профиля пользователя - PullRequest
7 голосов
/ 12 февраля 2010

Я хочу разрешить администраторам моего сайта фильтровать пользователей из определенной страны на сайте администратора. Таким образом, естественным делом будет что-то вроде этого:

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

Но из-за того, как пользователи и их пользовательские профили обрабатываются в django, это приводит к следующей ошибке:

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

Как мне обойти это ограничение?

Ответы [ 2 ]

10 голосов
/ 19 февраля 2010

То, что вы ищете, это Custom Admin FilterSpecs . Плохая новость заключается в том, что поддержка тех, кто может не появиться в ближайшее время (вы можете отслеживать обсуждение здесь ).

Однако ценой грязного взлома вы можете обойти ограничение. Некоторые основные моменты того, как FilterSpecs построен перед погружением в коде:

  • При построении списка FilterSpec для отображения на странице Django использует список полей, предоставленных вами в list_filter
  • Эти поля должны быть действительными полями в модели, а не обратной связью или пользовательскими свойствами.
  • Django поддерживает список FilterSpec классов, каждый из которых связан с test функцией.
  • Для каждого поля в list_filter Django будет использовать первый класс FilterSpec, для которого функция test возвращает True для поля.

Хорошо, теперь, с учетом этого, взгляните на следующий код. Он адаптирован из фрагмента django . Организация кода остается на ваше усмотрение, просто имейте в виду, что это должно быть импортировано приложением 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).

5 голосов
/ 15 декабря 2011

Django 1.3 исправил это. Теперь вам разрешено охватывать отношения в list_filter

https://docs.djangoproject.com/en/1.3/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter

...