7. Фильтр продуктов по его атрибутам
22 октября 2016 г. 7:48
Помимо полнотекстового поиска ещё одним важным моментом является добавление функции фильтрации для интернет-магазина. Клиенты должны иметь возможность сузить огромный список доступных продуктов для небольшого набора желаемых продуктов с использованием комбинации готовых атрибутов фильтра.
В djangoSHOP мы моделируем каждый продукт согласно его собственным свойствам, например, цвет. Покупатель сможет осуществить фильтр по списку продуктов, выбрав один или несколько указанных атрибутов, например, "голубой".
Поэтому, когда создаётся схема базы данных, мы добавляем свойство к нашей модели продукта. Это может быть жёстко закодированный список с вариантами или, если нам нужно более гибкий подход, внешний ключ к другой модели, ссылающийся на специфическое свойство.
Содержание этой дополнительной модели свойств (или жестко закодированного списка свойств) затем используется для создания набора доступных опций фильтрации, из которого клиент может выбрать один (и более, если это разрешено) варианты, чтобы сузить список продуктов с этими конкретными атрибутами.
К счастью, REST framework в комбинации с Django Filter с лёгкостью осуществляет данную задачу по фильтрации с существующими моделями продукта.
7.1. Добавление фильтра в представление списка
В djangoSHOP отображаемый список продуктов обычно контролируется классами shop.views.catalog.ProductListView или shop.views.catalog.CMSPageProductListView. По умолчанию эти View-классы используют фильтр-бэкенды по умолчанию, которые содержаться в REST framework. Эти фильтр-бэкенды могут быть настроены глобально через переменную DEFAULT_FILTER_BACKENDS в settings.py.
Дополнительно мы можем создать подкласс фильтр-бэкендов для каждого View-класса в нашем urls.py. Например, нам нужен специальный фильтр каталога, который сгруппирует наши продукты по определённому атрибуту. Мы можем создать собственный фильтр-бэкенд:
# filters.py from rest_framework.filters import BaseFilterBackend class CatalogFilterBackend(BaseFilterBackend): def filter_queryset(self, request, queryset, view): queryset = queryset.order_by('attribute__sortmetric') return queryset
Затем в urls.py, где мы распределяем запросы в класс shop.views.catalog.ProductListView, мы заменяем фильтр-бэкенды по умолчанию на свою собственную реализацию:
myshop/urls/catalog.py from django.conf.urls import url from rest_framework.settings import api_settings from shop.views.catalog import ProductListView from myshop.serializers import ProductSummarySerializer urlpatterns = [ url(r'^$', ProductListView.as_view( serializer_class=ProductSummarySerializer, filter_backends=[CatalogFilterBackend], ), ]
Рассмотренный выше пример достаточно простой, но даёт примерное представление о его возможностях.
7.1.1. Работа с Django-Filter
Django Filter - это общее, многократно используемое приложение для облегчения написание некоторого куска кода. В частности, она поволяет пользователям фильтровать queryset, основанный на полях модели, а также отображающих форму для работы фильтра.
REST framework также включает поддержку основых фильтр-бэкендов, которые поволяют легко коструировать сложные поиски и фильтры.
Создавая класс, который наследуется от django_filters.FilterSet, мы можем построить фильтры для каждого атрибута нашего продукта. Затем этот фильтр пропустить через поисковые параметры для извлечения набора доступных продуктов от нашего текущего представления каталога. Предположим, что наша модель продукта использует внешний ключ на модель, хранящую данные о всех производителей. Тогда мы можем создать простой фильтр-класс, чтобы извлечь представление списка по конкретному производителю:
# myshop/filters.py from django.forms import forms, widgets import django_filters from djng.forms import NgModelFormMixin from myshop.models.product import MyProduct, Manufacturer class FilterForm(NgModelFormMixin, forms.Form): scope_prefix = 'filters' class ProductFilter(django_filters.FilterSet): manufacturer = django_filters.ModelChoiceFilter( queryset=Manufacturer.objects.all(), widget=Select(attrs={'ng-change': 'filterChanged()'}), empty_label="Any Manufacturer") class Meta: model = MyProduct form = FilterForm fields = ['manufacturer'] @classmethod def get_render_context(cls, request, queryset): """ Prepare the context for rendering the filter. """ filter_set = cls() # we only want to show manufacturers for products available in the current list view filter_field = filter_set.filters['manufacturer'].field filter_field.queryset =filter_field.queryset.filter( id__in=queryset.values_list('manufacturer_id')) return dict(filter_set=filter_set)
В этот фильтр-класс мы можем комбинировать столько полей, сколько пожелаем, но в этом примере мы только используем внешний ключ на модель Производитель. Пожалуйста, каждый доступный тип фильтр-поля уточняйте в соответствующей документации по Django Filter.
Затем мы можем добавить этот фильтр-класс в наше представление списка продуктов. В django-SHOP это обычно делается через urlpatterns:
# myshop/urls.py urlpatterns = [ url(r'^$', ProductListView.as_view( serializer_class=ProductSummarySerializer, filter_class=ProductFilter, )), # other patterns ]
Добавляя ?manufacturer=7 в URL выше рассмотренный класс будет ограничивать продукты в нашем представлении списка. В список попадут те продукты, у которых производитель имеет первичный ключ равный 7.
7.1.2. Наполнение Render Context
Функциональность фильтра без соответствующего интерфейса пользователя не имеет особого смысла. Поэтому, когда происходит рендер представления списка продуктов, мы скорее всего хотим добавить некоторые поля ввода или специальные ссылки, чтобы покупатель мог сузить набор результатов. Чтобы сделать это, шаблону требуется дополнительные данные контекста (context data).
Так как djangoSHOP соблюдает принципы единства, каждый набор фильтров отвечает за обеспечение контекста, требуемого для рендера специальных фильтр-параметров. Этот дополнительный контекст должен обеспечиваться класс-методом, названным get_render_context(request, queryset), который должен вернуть словарь, содержащий экземпляр этого набора фильтров.
Во время рендера HTML страниц, этот дополнительный контекст может быть использован для рендера различных элеметнов выбора, такого как <select>-box. Так как наш ProductFilter может быть отрендерен как поля формы, мы вынуждены использовать этот Django шаблон:
{{ filter.filter_set.form }}
7.1.3. Клиентская сторона
Если твой сайт использует AngularJS директиву <shop-list-products>, тогда мы обычно хотим использовать его так просто, как покупатель применяет фильтры для продукта. Поэтому эта директива прослушивает события, вызванные shopCatalogFilter и запрашивает бэкенд с заданными свойствами. Это позволяет добавить набор параметров фильтра для просмотра списока продуктов без необходимости думать о том, как извлечь этот отфильтрованный список с сервера.
Так как мы не хотим, чтобы событие заботилось о контроле изменения событий на фильтрирующем элементе <select> box, djangoSHOP поставляется с повторно использующей директивой под названием shopProductFilter.
Примере HTML сниппета:
<div shop-product-filter="manufacturer"> {{ filter.filter_set.form }} </div>
Эта директива объявлена внутри модуля shop/js/filters.js в приложении shop, поэтому убедитесь, что подлючили этот файл. К тому же, этот модуль должен инициализироваться во время начальной загрузки нашего Angular приложения:
angular.module('myShop', [..., 'django.shop.filter', ...]);
Каждый раз, когда клиент выбирает другого производителя, функция filterChanged генерирует событие, перехватывающееся директивой AngularJS shopListProducts, которое извлекает список продуктов, используя фильтр-класс, как показано выше.
Помимо изменений, обнаруженных в экспедиторских в нашем <select> box, эта директива также изменяет URL и добавляет выбранные свойства. Это необходимо, когда пользователь уходит от представления списка продукта и возвращается назад, в этом случает применяются одни и те же фильтры. Кроме того, директива очищает поле запроса поиска, поскольку поиск полного текста в сочетании с фильтром сбивает с толку и не имеет смысла.
Представляю вашему вниманию книгу, написанную моим близким другом Максимом Макуриным: Секреты эффективного управления ассортиментом.
Книга предназначается для широкого круга читателей и, по мнению автора, будет полезна специалистам отдела закупок и логистики, категорийным и финансовым менеджерам, менеджерам по продажам, аналитикам, руководителям и директорам, в компетенции которых принятие решений по управлению ассортиментом.
Комментарии: 2
19.11.2018 18:17 #
нихрена непонятно...но очень интересно!
Ответить
28.11.2018 0:43 #
:) да, для понимания django-shop нужно знать стек технологий, которая она использует
Ответить