Закрыть

Для эффективной работы на сайте используются cookie и обработка персональных данных. Пользуясь этим сайтом, вы соглашаетесь с правилами использования сайта. Подробнее

Цитата дня

Vivazzi.ru

Личный сайт Мальцева Артема

В этом мире есть только один способ заслужить любовь - перестать требовать её и начать дарить любовь, не надеясь на благодарность.

Дейл Карнеги

7. Фильтр продуктов по его атрибутам

22 октября 2016 г. 3: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 и добавляет выбранные свойства. Это необходимо, когда пользователь уходит от представления списка продукта и возвращается назад, в этом случает применяются одни и те же фильтры. Кроме того, директива очищает поле запроса поиска, поскольку поиск полного текста в сочетании с фильтром сбивает с толку и не имеет смысла.

Оцените статью

3 из 5 (всего 2 оценки)

Поля, отмеченные звёздочкой ( * ) , являются обязательными.

Спасибо за ваш отзыв!

Автор перевода

Права на использование данной статьи, расположенной на настоящей странице http://vivazzi.ru/django-shop/filters/:

Разрешается копировать статью с указанием её автора и ссылки на оригинал без использования параметра rel="nofollow" в теге <a>. Использование:

Автор статьи: Мальцев Артём
Ссылка на статью: <a href="http://vivazzi.ru/django-shop/filters/">http://vivazzi.ru/django-shop/filters/</a>

Подробнее: Правила использования сайта

Вам нужно саморазвиваться или вы хотите зарабатывать деньги?

Или вы ищите хорошие IT сервисы или книги? Сохраните свое время и взгляните на мою подборку рекомендаций, которыми постоянно пользуюсь.
Посмотреть рекомендации

Комментариев: 0

Вы можете оставить комментарий как незарегистрированный пользователь. Но, зарегистрировавшись, вы сможете получать оповещения об ответах, а также иметь доступ к своему личному аккаунту для просмотра своих комментариев.

Чтобы оставить комментарий от своего имени войдите или зарегистрируйтесь обычным способом или через социальные сети:

Отправить

На данный момент нет специального поиска, поэтому я предлагаю воспользоваться обычной поисковой системой, например, Google, добавив "vivazzi" после своего запроса.

Попробуйте