5. Продукт с различными свойствами
2 октября 2016 г. 16:06
В предыдущих примерах мы поняли, что можем смоделировать наши продукты согласно их физическим свойствам, но что если мы хотим продать другой тип продукта с различными свойствами. Здесь полиморфизм выходит на сцену.
5.1. Запуск полиморфного демо (Polymorphic Demo)
Чтобы протестировать этот пример, нужно установить переменную окружения:
export DJANGO_SHOP_TUTORIAL=polymorphic
Затем пересоздать базу данных как объяснено в разделе Создание базы данных для демо-проекта и запустить демо-сервер:
./manage.py runserver
5.2. Полиморфная модель продукта
Если в дополнение к Картам памяти мы также хотим продавать смартфоны, мы должны объявить новую модель. Вместо дублирования всех общих полей, мы объединим их в общий базовый класс под названием Product. Затем этот базовый класс будет расширен для того, чтобы стать либо нашей известной моделью SmartCard или новой моделью SmartPhone.
Для работы с полиморфными моделями в djangoSHOP нам требуется приложение django-polymorphic. Здесь наши модели для Smart Cards или Smart Phones будут разделены в общую и специальную части. Общая часть уходит в новую модель продукта (Product model), в то время как специализированные части остаются в своих моделях.
Вам следует уже начать думать о макете предоставлении списка. Только атрибуты в модели Product будут доступны для списочных представлений, отображающих смартфоны бок о бок с картами памяти. Во-первых, мы должны создать специального менеджера (Model Manager), который объединит методы запроса для переводимых и полиморфных моделей:
myshop/models/i18n/polymorphic/product.py from parler.models import TranslatableModelMixin, TranslatedFieldsModel from parler.fields import TranslatedField from parler.managers import TranslatableManager, TranslatableQuerySet from polymorphic.query import PolymorphicQuerySet from shop.models.product import BaseProductManager, BaseProduct, CMSPageReferenceMixin from shop.models.defaults.mapping import ProductPage, ProductImage from ..manufacturer import Manufacturer class ProductQuerySet(TranslatableQuerySet, PolymorphicQuerySet): pass class ProductManager(BaseProductManager, TranslatableManager): queryset_class = ProductQuerySet return qs.prefetch_related('translations') @python_2_unicode_compatible class Product(CMSPageReferenceMixin, TranslatableModelMixin, BaseProduct): # controlling the catalog
Следующий шаг - определить, какие атрибуты модели претендуют на чать нашей модели Product. К сожалению, нет серебряной пули (универсального и эффективного решения, прим. переводчика) для этой проблемы, и это одна из причин, почему djangoSHOP поставляется без подготовленной модели для нее. Если мы хотим продать как карты памяти и смартфоны, то эта модель продукта сможет сделать это дело:
# myshop/models/i18n/polymorphic/product.py """ Базовый класс для описания полиморфного продукта. Здесь мы объявляем общие поля, которые доступны во всех наших различных типах продукта. Эти общие поля также используются для построения представления, отображающий список всех продуктов. """ product_name = models.CharField(max_length=255, verbose_name=_("Product Name")) slug = models.SlugField(verbose_name=_("Slug"), unique=True) caption = TranslatedField()
5.2.1. Модель Smart Card
Модель используется для хранения переводимых полей и является такой же как в нашем последнем примере. Новая модель для Smart Cards сейчас наследуется от Product:
myshop/models/i18n/polymorphic/smartcard.py from django.db import models from django.utils.translation import ugettext_lazy as _ from djangocms_text_ckeditor.fields import HTMLField from shop.money.fields import MoneyField from parler.models import TranslatedFields class SmartCard(Product): # common product fields unit_price = MoneyField(_("Unit price"), decimal_places=3, help_text=_("Net price for this product")) # product properties CARD_TYPE = (2 * ('{}{}'.format(s, t),) for t in ('SD', 'SDXC', 'SDHC', 'SDHC II') for s in ('', 'micro ')) card_type = models.CharField(_("Card Type"), choices=CARD_TYPE, max_length=15) SPEED = [(str(s), "{} MB/s".format(s)) for s in (4, 20, 30, 40, 48, 80, 95, 280)] speed = models.CharField(_("Transfer Speed"), choices=SPEED, max_length=8) product_code = models.CharField(_("Product code"), max_length=255, unique=True) storage = models.PositiveIntegerField(_("Storage Capacity"),
5.2.2. Модель для Smart Phone
Модель продукта для сматфонов намеренно немного сложнее. Мало того, что у него есть еще несколько атрибутов, смартфоны могут быть проданы с различными спецификациями внутренней памяти. Последнее влияет на цену и код продукта. Здесь отражается причина, почему мы не добавили поля unit_price и products_code в наш базовый класс продукта, хотя каждый продукт в нашем магазине требует эти поля.
При представлении смартфонов в наших представлениях списка, мы хотим сосредоточиться на разных моделях, но не на каждую конкретную, то есть его внутренней памяти. Поэтому клиенты должны будут различать конкретные варианты смартфона, всякий раз, когда они добавляют их в корзину, но не при просмотре их в списке каталога. Для клиента, это было бы очень скучно прокручивать списки со многими аналогичными продуктами, которые только дифференцируются на несколько вариаций.
Это означает что для некоторых моделей Smart Phone имеется более чем одна кнопка "Добавить в корзину".
При моделировании, нам, следовательно, требуется два разных класса: один для модели смартфона и один для каждого варианта смартфона.
myshop/models/polymorphic/smartphone.py from shop.money import Money, MoneyMaker from djangocms_text_ckeditor.fields import HTMLField from shop.money.fields import MoneyField from parler.models import TranslatedFields class SmartPhoneModel(Product): """ A generic smart phone model, which must be concretized by a model `SmartPhone` - see below. """ BATTERY_TYPES = ( (1, "Lithium Polymer (Li-Poly)"), (2, "Lithium Ion (Li-Ion)"), ) WIFI_CONNECTIVITY = ( (1, "802.11 b/g/n"), ) BLUETOOTH_CONNECTIVITY = ( (1, "Bluetooth 4.0"), (2, "Bluetooth 3.0"), (3, "Bluetooth 2.1"), ) battery_type = models.PositiveSmallIntegerField(_("Battery type"), choices=BATTERY_TYPES) battery_capacity = models.PositiveIntegerField(_("Capacity"), help_text=_("Battery capacity in mAh")) ram_storage = models.PositiveIntegerField(_("RAM"), help_text=_("RAM storage in MB")) wifi_connectivity = models.PositiveIntegerField(_("WiFi"),
Здесь метод get_price() может только вернуть минимальную, среднюю и максимальную стоимость нашего продукта. В этой ситуации, большинство торговцев выражают цену, например, так: Цена от € 99.50
Конкретный смартфон моделируется так:
def get_product_variant(self, product_code): try: return self.smartphone_set.get(product_code=product_code) except SmartPhone.DoesNotExist as e: raise SmartPhoneModel.DoesNotExist(e) class SmartPhone(models.Model): product = models.ForeignKey(SmartPhoneModel, verbose_name=_("Smart-Phone Model"))
Чтобы продолжить покупки, покупателю нужны страницы Корзина и Оформления заказа.
5.2.3. Модель для общего Товара (generic Commodity)
Для демонстрационных целей этот полиморфный пример добавляет другой тип модели Product - Общий Товар. Здесь вместо добавления каждого возможного атрибута нашего продукта в модель, мы пытаемся оставаться стандартизовано вместо того, чтобы использовать PlaceholderField, который предоставляется djangoCMS.
myshop/models/commodity.py from cms.models.fields import PlaceholderField from myshop.models.product import Product class Commodity(Product): # other product fields placeholder = PlaceholderField("Commodity Details")
Это позволяет добавить любую произвольную информацию о нашем продукте к карточке товара. Единственное условие для работоспособности состоит в том, что в шаблон нужно добавить templatetag для рендера этого заполнителя.
Поскольку структура djangoSHOP смотрит в каталог папки для шаблона имени своего класса продукта, то нужно добавить этот фрагмент кода HTML:
myshop/catalog/commodity-detail.html {% extends "myshop/pages/default.html" %} {% load cms_tags %} <div class="container"> <div class="row"> <div class="col-xs-12"> <h1>{% render_model product "product_name" %}</h1> {% render_placeholder product.placeholder %} </div> </div> </div>
Шаблон карточки товара расширяет шаблон по умолчанию нашего сайта. Помимо названия продукта (который добавляется в качестве удобства), эт представление остается пустым, когда просматриваешь в первый раз. В режиме редактирования, дважды щелкнув на заголовок, содержащий название продукта, можно открыть редактор редактирования нашего товара.
После переключения в режим "Структура" появляется заполнитель, который назван "Commodity Details". Здесь мы можем добавить любое количество Cascade плагинов, наполняя наш заполнитель строками, колонками, изображениями, текстовыми блоками и т. д. Это позволяет нам редактировать карточку товара в любой вид, который мы хотим. Недостаток использования этого подхода состоит в том, что это может привести к несоответствии с основным дизайном сайта, а также является более трудоемким, чем просто редактирование атрибутов продукта вместе с их соответствующими шаблонами.
5.2.3.1. Конфигурирование заполнителя
Так как мы используем этот заполнитель внутри жестко закодированой колонке Bootstrap, мы должны рассказать Cascade о ширине этого столбца. Это должно быть сделано в настройках проекта:
# myshop/settings.py CMS_PLACEHOLDER_CONF = { ... 'Commodity Details': { 'plugins': ['BootstrapRowPlugin', 'TextPlugin', 'ImagePlugin', 'PicturePlugin'], 'text_only_plugins': ['TextLinkPlugin'], 'parent_classes': {'BootstrapRowPlugin': []}, 'require_parent': False, 'glossary': { 'breakpoints': ['xs', 'sm', 'md', 'lg'], 'container_max_widths': {'xs': 750, 'sm': 750, 'md': 970, 'lg': 1170}, 'fluid': False, 'media_queries': { 'xs': ['(max-width: 768px)'], 'sm': ['(min-width: 768px)', '(max-width: 992px)'], 'md': ['(min-width: 992px)', '(max-width: 1200px)'], 'lg': ['(min-width: 1200px)'], }, } }, ... }
Эта конфигурация заполнителя эмулирует Bootstrap колонку как <divclass="col-xs-12">.
Представляю вашему вниманию книгу, написанную моим близким другом Максимом Макуриным: Секреты эффективного управления ассортиментом.
Книга предназначается для широкого круга читателей и, по мнению автора, будет полезна специалистам отдела закупок и логистики, категорийным и финансовым менеджерам, менеджерам по продажам, аналитикам, руководителям и директорам, в компетенции которых принятие решений по управлению ассортиментом.
Комментарии: 0