Переодически возникает задача когда нужно фильтровать спикок changelist модели по каким нибудь хитрым условиям, и стандартного функционала django admin не хватает.
Например есть такая структура:
Сразу хочется отправиться в фаил admin и в классе ProductAdmin написать что-нибудь вроде list_filter = ('productoptions',), но к сожалению будет ошибка
Например есть такая структура:
class Product(models.Model): name = models.CharField(_(u"Name"), max_length=255, blank=True) class PropertyOption(models.Model): name = models.CharField(_(u"Name"), max_length=255, blank=True) product = models.ForeignKey(Product, verbose_name=_(u"Product"), related_name="productoptions")К списку товаров необходимо приделать правый фильтр по PropertyOption.
Сразу хочется отправиться в фаил admin и в классе ProductAdmin написать что-нибудь вроде list_filter = ('productoptions',), но к сожалению будет ошибка
'ProductAdmin.list_filter[0]' refers to field 'productoptions' that is missing from model 'Product'.У модели товара нету поля productoptions, есть только менеджер объектов с таким именем... в любом случае пока что это не прокатывает, поэтому нужно искать другой вариант. Например создать свой собственный фильтр, унаследовав его от класса ChoicesFilterSpec. В итоге получиться что-то вроде этого:
class PropertyOptionFilterSpec(ChoicesFilterSpec): def __init__(self, request, **kwargs): self.lookup_kwarg = 'productoptions__pk' self.lookup_val = request.GET.get(self.lookup_kwarg, None) self.lookup_choices = PropertyOption.objects.all() def choices(self, cl): yield { 'selected': self.lookup_val is None, 'query_string': cl.get_query_string({}, [self.lookup_kwarg,]), 'display': u'Все'} for option in self.lookup_choices: yield {'selected': smart_unicode(option.pk) == self.lookup_val, 'query_string': cl.get_query_string({self.lookup_kwarg: option.pk}), 'display': option.name } # незабываем переопределить title иначе при рендеринге будет ошибка def title(self): return u'параметрам'Теперь возникает задача как этот фильтр применить. Т.к. нет поля к которому мы бы могли его прицепить, нужно использовать другой подход, а именно:
- Переопределить метод changelist_view у ProductAdmin добавив туда эксземпляр нашего фильтра
- Переопределить шаблон списка товаров, который рендерит changelist_view
class ProductAdmin(admin.ModelAdmin): def changelist_view(self, request, extra_context=None): po_filter = PropertyOptionFilterSpec(request) return super(ProductAdmin, self).changelist_view(request, extra_context={ 'po_filter': po_filter, })а шаблон для списка admin/-my_app_name-/product/change_list.html я сделал таким
{% extends "admin/change_list.html" %} {% load admin_list i18n %} {% block filters %} <div id="changelist-filter"> <h2>{% trans 'Filter' %}</h2> {% if cl.has_filters %} {% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %} {% endif %} {# Последним рендерим наш фильтр #} {% admin_list_filter cl po_filter %} </div> {% endblock %}На этом этапе можно попробвать фильтр в дейсвии. Заходим в список товаров, все отлично рендериться, но если попробовать воспользоваться фильтром выскочит ошибка:
SuspiciousOperation at /admin/ecko/product/ Filtering by productoptions__pk not allowedЭто происходит потому что в классе ModelAdmin есть такой специальный метод lookup_allowed, который занимается тем что проверяет позволено ли параметру в запросе учавстовать в обращении к базе или нет, в нашем случае я не стал заморачиваться и просто застваил его всегда возвращать true. В конечном итоге ProductAdmin стал выглядеть так:
class ProductAdmin(admin.ModelAdmin): def lookup_allowed(self, *args, **kwargs): return True def changelist_view(self, request, extra_context=None): po_filter = PropertyOptionFilterSpec(request) return super(ProductAdmin, self).changelist_view(request, extra_context={ 'po_filter': po_filter, })После этого всё должно заработать.
Комментариев нет:
Отправить комментарий