| @@ -0,0 +1,16 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import unicode_literals | ||
|
|
||
| from django.core.management.base import BaseCommand | ||
| from django.utils.translation import ugettext_lazy as _ | ||
|
|
||
| from filer.models.imagemodels import Image | ||
|
|
||
|
|
||
| class Command(BaseCommand): | ||
| help = _("Fix https://github.com/divio/django-filer/issues/965") | ||
|
|
||
| def handle(self, verbosity, *args, **options): | ||
| for img in Image.objects.all(): | ||
| img.file_data_changed() | ||
| img.save() |
| @@ -0,0 +1,39 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import unicode_literals | ||
|
|
||
| from django.db import models | ||
| from django.utils.translation import ugettext_lazy as _ | ||
| from cms.models.fields import PlaceholderField | ||
| from parler.managers import TranslatableManager | ||
| from shop.money.fields import MoneyField | ||
| from .product import Product | ||
|
|
||
|
|
||
| class Commodity(Product): | ||
| """ | ||
| This Commodity model inherits from polymorphic Product, and therefore has to be redefined. | ||
| """ | ||
| unit_price = MoneyField( | ||
| _("Unit price"), | ||
| decimal_places=3, | ||
| help_text=_("Net price for this product"), | ||
| ) | ||
|
|
||
| product_code = models.CharField( | ||
| _("Product code"), | ||
| max_length=255, | ||
| unique=True, | ||
| ) | ||
|
|
||
| # controlling the catalog | ||
| placeholder = PlaceholderField("Commodity Details") | ||
| show_breadcrumb = True # hard coded to always show the product's breadcrumb | ||
|
|
||
| default_manager = TranslatableManager() | ||
|
|
||
| class Meta: | ||
| verbose_name = _("Commodity") | ||
| verbose_name_plural = _("Commodities") | ||
|
|
||
| def get_price(self, request): | ||
| return self.unit_price |
| @@ -0,0 +1,177 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import unicode_literals | ||
|
|
||
| from django.db import models | ||
| from django.utils.translation import ugettext_lazy as _ | ||
| from django.utils.encoding import python_2_unicode_compatible | ||
| from shop.money import Money, MoneyMaker | ||
| from djangocms_text_ckeditor.fields import HTMLField | ||
| from shop.money.fields import MoneyField | ||
| from parler.managers import TranslatableManager | ||
| from parler.models import TranslatedFields | ||
| from .product import Product | ||
|
|
||
|
|
||
| @python_2_unicode_compatible | ||
| class OperatingSystem(models.Model): | ||
| name = models.CharField( | ||
| _("Name"), | ||
| max_length=50, | ||
| ) | ||
|
|
||
| def __str__(self): | ||
| return self.name | ||
|
|
||
|
|
||
| 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"), | ||
| choices=WIFI_CONNECTIVITY, | ||
| help_text=_("WiFi Connectivity"), | ||
| ) | ||
|
|
||
| bluetooth = models.PositiveIntegerField( | ||
| _("Bluetooth"), | ||
| choices=BLUETOOTH_CONNECTIVITY, | ||
| help_text=_("Bluetooth Connectivity"), | ||
| ) | ||
|
|
||
| gps = models.BooleanField( | ||
| _("GPS"), | ||
| default=False, | ||
| help_text=_("GPS integrated"), | ||
| ) | ||
|
|
||
| operating_system = models.ForeignKey( | ||
| OperatingSystem, | ||
| verbose_name=_("Operating System"), | ||
| ) | ||
|
|
||
| width = models.DecimalField( | ||
| _("Width"), | ||
| max_digits=4, | ||
| decimal_places=1, | ||
| help_text=_("Width in mm"), | ||
| ) | ||
|
|
||
| height = models.DecimalField( | ||
| _("Height"), | ||
| max_digits=4, | ||
| decimal_places=1, | ||
| help_text=_("Height in mm"), | ||
| ) | ||
|
|
||
| weight = models.DecimalField( | ||
| _("Weight"), | ||
| max_digits=5, | ||
| decimal_places=1, | ||
| help_text=_("Weight in gram"), | ||
| ) | ||
|
|
||
| screen_size = models.DecimalField( | ||
| _("Screen size"), | ||
| max_digits=4, | ||
| decimal_places=2, | ||
| help_text=_("Diagonal screen size in inch"), | ||
| ) | ||
|
|
||
| multilingual = TranslatedFields( | ||
| description=HTMLField( | ||
| verbose_name=_("Description"), | ||
| configuration='CKEDITOR_SETTINGS_DESCRIPTION', | ||
| help_text=_("Full description used in the catalog's detail view of Smart Phones."), | ||
| ), | ||
| ) | ||
|
|
||
| default_manager = TranslatableManager() | ||
|
|
||
| class Meta: | ||
| verbose_name = _("Smart Phone") | ||
| verbose_name_plural = _("Smart Phones") | ||
|
|
||
| def get_price(self, request): | ||
| """ | ||
| Return the starting price for instances of this smart phone model. | ||
| """ | ||
| if not hasattr(self, '_price'): | ||
| if self.smartphone_set.exists(): | ||
| currency = self.smartphone_set.first().unit_price.currency | ||
| aggr = self.smartphone_set.aggregate(models.Min('unit_price')) | ||
| self._price = MoneyMaker(currency)(aggr['unit_price__min']) | ||
| else: | ||
| self._price = Money() | ||
| return self._price | ||
|
|
||
| def is_in_cart(self, cart, watched=False, **kwargs): | ||
| from shop.models.cart import CartItemModel | ||
| try: | ||
| product_code = kwargs['extra']['product_code'] | ||
| except KeyError: | ||
| return | ||
| cart_item_qs = CartItemModel.objects.filter(cart=cart, product=self) | ||
| for cart_item in cart_item_qs: | ||
| if cart_item.extra.get('product_code') == product_code: | ||
| return cart_item | ||
|
|
||
| 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"), | ||
| ) | ||
|
|
||
| product_code = models.CharField( | ||
| _("Product code"), | ||
| max_length=255, | ||
| unique=True, | ||
| ) | ||
|
|
||
| unit_price = MoneyField( | ||
| _("Unit price"), | ||
| decimal_places=3, | ||
| help_text=_("Net price for this product"), | ||
| ) | ||
|
|
||
| storage = models.PositiveIntegerField( | ||
| _("Internal Storage"), | ||
| help_text=_("Internal storage in MB"), | ||
| ) | ||
|
|
||
| def get_price(self, request): | ||
| return self.unit_price |
| @@ -0,0 +1,32 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import unicode_literals | ||
|
|
||
| from decimal import Decimal | ||
| from django.core.exceptions import ObjectDoesNotExist | ||
| from django.db import models | ||
| from django.utils.translation import ugettext_lazy as _ | ||
| from shop.models import order | ||
| from shop.models.cart import CartItemModel | ||
|
|
||
|
|
||
| class OrderItem(order.BaseOrderItem): | ||
| """ | ||
| Modified OrderItem which keeps track on the delivered items. To be used in combination with | ||
| the Order workflow :class:`shop.shipping.delivery.PartialDeliveryWorkflowMixin`. | ||
| """ | ||
| quantity = models.IntegerField(_("Ordered quantity")) | ||
| canceled = models.BooleanField(_("Item canceled "), default=False) | ||
|
|
||
| def populate_from_cart_item(self, cart_item, request): | ||
| from .smartphone import SmartPhoneModel | ||
| super(OrderItem, self).populate_from_cart_item(cart_item, request) | ||
| # the product code and price must be fetched from the product's variant | ||
| try: | ||
| if isinstance(cart_item.product, SmartPhoneModel): | ||
| product = cart_item.product.get_product_variant(cart_item.extra['product_code']) | ||
| else: | ||
| product = cart_item.product | ||
| self.product_code = product.product_code | ||
| self._unit_price = Decimal(product.unit_price) | ||
| except (KeyError, ObjectDoesNotExist) as e: | ||
| raise CartItemModel.DoesNotExist(e) |
| @@ -0,0 +1,85 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import unicode_literals | ||
|
|
||
| from django.db import models | ||
| from django.utils.encoding import python_2_unicode_compatible | ||
| from django.utils.translation import ugettext_lazy as _ | ||
| from djangocms_text_ckeditor.fields import HTMLField | ||
|
|
||
| from shop.models.product import BaseProductManager, BaseProduct, CMSPageReferenceMixin | ||
| from shop.models.defaults.mapping import ProductPage, ProductImage | ||
| from ..manufacturer import Manufacturer | ||
|
|
||
|
|
||
| @python_2_unicode_compatible | ||
| class Product(CMSPageReferenceMixin, BaseProduct): | ||
| """ | ||
| Base class to describe a polymorphic product. Here we declare common fields available in all of | ||
| our different product types. These common fields are also used to build up the view displaying | ||
| a list of all products. | ||
| """ | ||
| product_name = models.CharField( | ||
| _("Product Name"), | ||
| max_length=255, | ||
| ) | ||
|
|
||
| slug = models.SlugField( | ||
| _("Slug"), | ||
| unique=True, | ||
| ) | ||
|
|
||
| caption = HTMLField( | ||
| verbose_name=_("Caption"), | ||
| blank=True, | ||
| null=True, | ||
| configuration='CKEDITOR_SETTINGS_CAPTION', | ||
| help_text=_("Short description used in the catalog's list view of products."), | ||
| ) | ||
|
|
||
| # common product properties | ||
| manufacturer = models.ForeignKey( | ||
| Manufacturer, | ||
| verbose_name=_("Manufacturer"), | ||
| ) | ||
|
|
||
| # controlling the catalog | ||
| order = models.PositiveIntegerField( | ||
| _("Sort by"), | ||
| db_index=True, | ||
| ) | ||
|
|
||
| cms_pages = models.ManyToManyField( | ||
| 'cms.Page', | ||
| through=ProductPage, | ||
| help_text=_("Choose list view this product shall appear on."), | ||
| ) | ||
|
|
||
| images = models.ManyToManyField( | ||
| 'filer.Image', | ||
| through=ProductImage, | ||
| ) | ||
|
|
||
| class Meta: | ||
| ordering = ('order',) | ||
| verbose_name = _("Product") | ||
| verbose_name_plural = _("Products") | ||
|
|
||
| objects = BaseProductManager() | ||
|
|
||
| # filter expression used to lookup for a product item using the Select2 widget | ||
| lookup_fields = ('product_name__icontains',) | ||
|
|
||
| def __str__(self): | ||
| return self.product_name | ||
|
|
||
| @property | ||
| def sample_image(self): | ||
| return self.images.first() | ||
|
|
||
| def get_product_variant(self, extra): | ||
| """ | ||
| Get a variant of the product or itself, if the product has no flavors. | ||
| Raises `Product.objects.DoesNotExists` if there is no variant for the given `extra`. | ||
| """ | ||
| msg = "Method get_product_variant(extra) must be implemented by subclass: `{}`" | ||
| raise NotImplementedError(msg.format(self.__class__.__name__)) |
| @@ -0,0 +1,67 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import unicode_literals | ||
|
|
||
| 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 .product import Product, BaseProductManager | ||
|
|
||
|
|
||
| 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"), | ||
| help_text=_("Storage capacity in GB"), | ||
| ) | ||
|
|
||
| description = HTMLField( | ||
| verbose_name=_("Description"), | ||
| configuration='CKEDITOR_SETTINGS_DESCRIPTION', | ||
| help_text=_("Full description used in the catalog's detail view of Smart Cards."), | ||
| ) | ||
|
|
||
| default_manager = BaseProductManager() | ||
|
|
||
| class Meta: | ||
| verbose_name = _("Smart Card") | ||
| verbose_name_plural = _("Smart Cards") | ||
|
|
||
| def get_price(self, request): | ||
| return self.unit_price | ||
|
|
||
| def get_product_variant(self, extra): | ||
| """ | ||
| SmartCards do not have flavors, they are the product. | ||
| """ | ||
| return self |
| @@ -0,0 +1,177 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import unicode_literals | ||
|
|
||
| from django.db import models | ||
| from django.utils.translation import ugettext_lazy as _ | ||
| from django.utils.encoding import python_2_unicode_compatible | ||
|
|
||
| from shop.money import Money, MoneyMaker | ||
|
|
||
| from djangocms_text_ckeditor.fields import HTMLField | ||
|
|
||
| from shop.money.fields import MoneyField | ||
|
|
||
| from .product import Product, BaseProductManager | ||
|
|
||
|
|
||
| @python_2_unicode_compatible | ||
| class OperatingSystem(models.Model): | ||
| name = models.CharField( | ||
| _("Name"), | ||
| max_length=50, | ||
| ) | ||
|
|
||
| def __str__(self): | ||
| return self.name | ||
|
|
||
|
|
||
| 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"), | ||
| choices=WIFI_CONNECTIVITY, | ||
| help_text=_("WiFi Connectivity"), | ||
| ) | ||
|
|
||
| bluetooth = models.PositiveIntegerField( | ||
| _("Bluetooth"), | ||
| choices=BLUETOOTH_CONNECTIVITY, | ||
| help_text=_("Bluetooth Connectivity"), | ||
| ) | ||
|
|
||
| gps = models.BooleanField( | ||
| _("GPS"), | ||
| default=False, | ||
| help_text=_("GPS integrated"), | ||
| ) | ||
|
|
||
| operating_system = models.ForeignKey( | ||
| OperatingSystem, | ||
| verbose_name=_("Operating System"), | ||
| ) | ||
|
|
||
| width = models.DecimalField( | ||
| _("Width"), | ||
| max_digits=4, | ||
| decimal_places=1, | ||
| help_text=_("Width in mm"), | ||
| ) | ||
|
|
||
| height = models.DecimalField( | ||
| _("Height"), | ||
| max_digits=4, | ||
| decimal_places=1, | ||
| help_text=_("Height in mm"), | ||
| ) | ||
|
|
||
| weight = models.DecimalField( | ||
| _("Weight"), | ||
| max_digits=5, | ||
| decimal_places=1, | ||
| help_text=_("Weight in gram"), | ||
| ) | ||
|
|
||
| screen_size = models.DecimalField( | ||
| _("Screen size"), | ||
| max_digits=4, | ||
| decimal_places=2, | ||
| help_text=_("Diagonal screen size in inch"), | ||
| ) | ||
|
|
||
| description = HTMLField( | ||
| verbose_name=_("Description"), | ||
| configuration='CKEDITOR_SETTINGS_DESCRIPTION', | ||
| help_text=_("Full description used in the catalog's detail view of Smart Phones."), | ||
| ) | ||
|
|
||
| default_manager = BaseProductManager() | ||
|
|
||
| class Meta: | ||
| verbose_name = _("Smart Phone") | ||
| verbose_name_plural = _("Smart Phones") | ||
|
|
||
| def get_price(self, request): | ||
| """ | ||
| Return the starting price for instances of this smart phone model. | ||
| """ | ||
| if not hasattr(self, '_price'): | ||
| if self.smartphone_set.exists(): | ||
| currency = self.smartphone_set.first().unit_price.currency | ||
| aggr = self.smartphone_set.aggregate(models.Min('unit_price')) | ||
| self._price = MoneyMaker(currency)(aggr['unit_price__min']) | ||
| else: | ||
| self._price = Money() | ||
| return self._price | ||
|
|
||
| def is_in_cart(self, cart, watched=False, **kwargs): | ||
| from shop.models.cart import CartItemModel | ||
| try: | ||
| product_code = kwargs['extra']['product_code'] | ||
| except KeyError: | ||
| return | ||
| cart_item_qs = CartItemModel.objects.filter(cart=cart, product=self) | ||
| for cart_item in cart_item_qs: | ||
| if cart_item.extra.get('product_code') == product_code: | ||
| return cart_item | ||
|
|
||
| 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"), | ||
| ) | ||
|
|
||
| product_code = models.CharField( | ||
| _("Product code"), | ||
| max_length=255, | ||
| unique=True, | ||
| ) | ||
|
|
||
| unit_price = MoneyField( | ||
| _("Unit price"), | ||
| decimal_places=3, | ||
| help_text=_("Net price for this product"), | ||
| ) | ||
|
|
||
| storage = models.PositiveIntegerField( | ||
| _("Internal Storage"), | ||
| help_text=_("Internal storage in MB"), | ||
| ) | ||
|
|
||
| def get_price(self, request): | ||
| return self.unit_price |
| @@ -0,0 +1,22 @@ | ||
| """ | ||
| source: https://gist.github.com/1311010 | ||
| Get django-sekizai, django-compessor (and django-cms) playing nicely together | ||
| re: https://github.com/ojii/django-sekizai/issues/4 | ||
| using: https://github.com/django-compressor/django-compressor.git | ||
| and: https://github.com/ojii/django-sekizai.git@0.6 or later | ||
| """ | ||
| from compressor.templatetags.compress import CompressorNode | ||
| from compressor.exceptions import UncompressableFileError | ||
| from django.template.base import TextNode | ||
|
|
||
|
|
||
| def compress(context, data, name): | ||
| """ | ||
| Data is the string from the template (the list of js files in this case) | ||
| Name is either 'js' or 'css' (the sekizai namespace) | ||
| Basically passes the string through the {% compress 'js' %} template tag | ||
| """ | ||
| try: | ||
| return CompressorNode(nodelist=TextNode(data), kind=name, mode='file').render(context=context) | ||
| except UncompressableFileError: | ||
| return CompressorNode(nodelist=TextNode(data), kind=name, mode='file').get_original_content(context=context) |
| @@ -0,0 +1,81 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import unicode_literals | ||
|
|
||
| from django.conf import settings | ||
| from django.utils.safestring import mark_safe | ||
|
|
||
| from rest_framework import serializers | ||
|
|
||
| from shop.serializers.bases import ProductSerializer | ||
| from shop.search.serializers import ProductSearchSerializer as BaseProductSearchSerializer | ||
|
|
||
| from myshop.search_indexes import myshop_search_index_classes | ||
|
|
||
| __all__ = ['ProductSummarySerializer', 'ProductSearchSerializer', 'CatalogSearchSerializer'] | ||
|
|
||
|
|
||
| class ProductSummarySerializer(ProductSerializer): | ||
| media = serializers.SerializerMethodField() | ||
|
|
||
| class Meta(ProductSerializer.Meta): | ||
| fields = ['id', 'product_name', 'product_url', 'product_model', 'price', 'media', 'caption'] | ||
|
|
||
| def get_media(self, product): | ||
| return self.render_html(product, 'media') | ||
|
|
||
| if settings.SHOP_TUTORIAL in ['commodity', 'i18n_commodity']: | ||
|
|
||
| class ProductDetailSerializer(ProductSerializer): | ||
| class Meta(ProductSerializer.Meta): | ||
| fields = ['product_name', 'slug', 'unit_price', 'product_code'] | ||
|
|
||
| __all__.append('ProductDetailSerializer') | ||
|
|
||
| elif settings.SHOP_TUTORIAL in ['smartcard', 'i18n_smartcard']: | ||
|
|
||
| class ProductDetailSerializer(ProductSerializer): | ||
| class Meta(ProductSerializer.Meta): | ||
| fields = ['product_name', 'slug', 'unit_price', 'manufacturer', 'card_type', 'speed', | ||
| 'product_code', 'storage'] | ||
|
|
||
| __all__.append('ProductDetailSerializer') | ||
|
|
||
| elif settings.SHOP_TUTORIAL in ['i18n_polymorphic', 'polymorphic']: | ||
|
|
||
| from .polymorphic import (SmartCardSerializer, AddSmartCardToCartSerializer, | ||
| SmartPhoneSerializer, AddSmartPhoneToCartSerializer) | ||
|
|
||
| __all__.extend(['SmartCardSerializer', 'AddSmartCardToCartSerializer', | ||
| 'SmartPhoneSerializer', 'AddSmartPhoneToCartSerializer']) | ||
|
|
||
|
|
||
| class ProductSearchSerializer(BaseProductSearchSerializer): | ||
| """ | ||
| Serializer to search over all products in this shop | ||
| """ | ||
| media = serializers.SerializerMethodField() | ||
|
|
||
| class Meta(BaseProductSearchSerializer.Meta): | ||
| fields = BaseProductSearchSerializer.Meta.fields + ['media', 'caption'] | ||
| field_aliases = {'q': 'text'} | ||
| search_fields = ['text'] | ||
| index_classes = myshop_search_index_classes | ||
|
|
||
| def get_media(self, search_result): | ||
| return mark_safe(search_result.search_media) | ||
|
|
||
|
|
||
| class CatalogSearchSerializer(BaseProductSearchSerializer): | ||
| """ | ||
| Serializer to restrict products in the catalog | ||
| """ | ||
| media = serializers.SerializerMethodField() | ||
|
|
||
| class Meta(BaseProductSearchSerializer.Meta): | ||
| fields = BaseProductSearchSerializer.Meta.fields + ['media', 'caption'] | ||
| field_aliases = {'q': 'autocomplete'} | ||
| search_fields = ['autocomplete'] | ||
| index_classes = myshop_search_index_classes | ||
|
|
||
| def get_media(self, search_result): | ||
| return mark_safe(search_result.catalog_media) |
| @@ -0,0 +1,57 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import unicode_literals | ||
|
|
||
| from rest_framework.fields import empty | ||
|
|
||
| from shop.serializers.bases import ProductSerializer | ||
| from shop.serializers.defaults import AddToCartSerializer | ||
|
|
||
| from myshop.models import SmartCard, SmartPhoneModel | ||
|
|
||
|
|
||
| class SmartCardSerializer(ProductSerializer): | ||
| class Meta: | ||
| model = SmartCard | ||
| fields = ['product_name', 'slug', 'unit_price', 'manufacturer', 'card_type', 'speed', | ||
| 'product_code', 'storage'] | ||
|
|
||
|
|
||
| class AddSmartCardToCartSerializer(AddToCartSerializer): | ||
| """ | ||
| Modified AddToCartSerializer which handles SmartCards | ||
| """ | ||
| def get_instance(self, context, data, extra_args): | ||
| product = context['product'] | ||
| extra = context['request'].data.get('extra', {}) | ||
| extra.setdefault('product_code', product.product_code) | ||
| instance = { | ||
| 'product': product.id, | ||
| 'unit_price': product.unit_price, | ||
| 'extra': extra, | ||
| } | ||
| return instance | ||
|
|
||
|
|
||
| class SmartPhoneSerializer(ProductSerializer): | ||
| class Meta: | ||
| model = SmartPhoneModel | ||
| fields = ['product_name', 'slug', 'battery_type', 'battery_capacity'] | ||
|
|
||
|
|
||
| class AddSmartPhoneToCartSerializer(AddToCartSerializer): | ||
| """ | ||
| Modified AddToCartSerializer which handles SmartPhones | ||
| """ | ||
| def get_instance(self, context, data, extra_args): | ||
| product = context['product'] | ||
| extra = data['extra'] if data is not empty else {} | ||
| try: | ||
| variant = product.get_product_variant(extra.get('product_code')) | ||
| except product.DoesNotExist: | ||
| variant = product.smartphone_set.first() | ||
| instance = { | ||
| 'product': product.id, | ||
| 'unit_price': variant.unit_price, | ||
| 'extra': {'product_code': variant.product_code, 'storage': variant.storage} | ||
| } | ||
| return instance |
| @@ -1,4 +1,5 @@ | ||
| @import "shop/css/django-shop"; | ||
| @import "footer"; | ||
|
|
||
| .product-detail { | ||
| bsp-scrollpanel { | ||
| @@ -1,8 +1,8 @@ | ||
| {% load static sekizai_tags %} | ||
|
|
||
| {% addtoblock "js" %}<script src="{% static 'shop/js/filter-form.js' %}" type="text/javascript"></script>{% endaddtoblock %} | ||
| {% addtoblock "ng-requires" %}django.shop.filter{% endaddtoblock %} | ||
|
|
||
| <form shop-product-filter="manufacturer" style="margin-bottom: 10px;"> | ||
| {{ filter.filter_set.form.as_div }} | ||
| </form> |
| @@ -1,11 +1,16 @@ | ||
| {% load thumbnail %} | ||
| {% thumbnail product.sample_image 80x80 crop as thumb %} | ||
| {% thumbnail product.sample_image 160x160 crop as thumb2 %} | ||
| <div class="media-left"> | ||
| {% block media-left %} | ||
| <a href="{{ product.get_absolute_url }}"> | ||
| <img src="{{ thumb.url }}" width="{{ thumb.width }}" height="{{ thumb.height }}" srcset="{{ thumb.url }} 1x, {{ thumb2.url }} 2x" /> | ||
| </a> | ||
| {% endblock %} | ||
| </div> | ||
| <div class="media-body"> | ||
| {% block media-body %} | ||
| <h4 class="media-heading"><a href="{{ product.get_absolute_url }}">{{ product.product_name }}</a></h4> | ||
| {{ product.description|truncatewords_html:30 }} | ||
| {% endblock %} | ||
| </div> |
| @@ -1,4 +1,4 @@ | ||
| {% load thumbnail %} | ||
| {% thumbnail product.sample_image 244x244 crop as thumb %} | ||
| {% thumbnail product.sample_image 488x488 crop as thumb2 %} | ||
| <img class="img-responsive" src="{{ thumb.url }}" width="{{ thumb.width }}" height="{{ thumb.height }}" srcset="{{ thumb.url }} 1x, {{ thumb2.url }} 2x" /> |
| @@ -1,11 +1,16 @@ | ||
| {% load thumbnail %} | ||
| {% thumbnail product.sample_image 80x80 crop as thumb %} | ||
| {% thumbnail product.sample_image 160x160 crop as thumb2 %} | ||
| <div class="media-left"> | ||
| {% block media-left %} | ||
| <a href="{{ product.get_absolute_url }}"> | ||
| <img src="{{ thumb.url }}" width="{{ thumb.width }}" height="{{ thumb.height }}" srcset="{{ thumb.url }} 1x, {{ thumb2.url }} 2x" /> | ||
| </a> | ||
| {% endblock %} | ||
| </div> | ||
| <div class="media-body"> | ||
| {% block media-body %} | ||
| <h4 class="media-heading"><a href="{{ product.get_absolute_url }}">{{ product.product_name }}</a></h4> | ||
| {{ product.description|truncatewords_html:50 }} | ||
| {% endblock %} | ||
| </div> |
| @@ -1,11 +1,16 @@ | ||
| {% load thumbnail %} | ||
| {% thumbnail product.sample_image 80x80 crop as thumb %} | ||
| {% thumbnail product.sample_image 160x160 crop as thumb2 %} | ||
| <div class="media-left"> | ||
| {% block media-left %} | ||
| <a href="{{ product.get_absolute_url }}"> | ||
| <img src="{{ thumb.url }}" width="{{ thumb.width }}" height="{{ thumb.height }}" srcset="{{ thumb.url }} 1x, {{ thumb2.url }} 2x" /> | ||
| </a> | ||
| {% endblock %} | ||
| </div> | ||
| <div class="media-body"> | ||
| {% block media-body %} | ||
| <h4 class="media-heading"><a href="{{ product.get_absolute_url }}">{{ product.product_name }}</a></h4> | ||
| {{ product.caption|truncatewords_html:50 }} | ||
| {% endblock %} | ||
| </div> |
| @@ -0,0 +1,5 @@ | ||
| {% extends "myshop/products/search-product-media.html" %} | ||
| {% block media-body %} | ||
| <h4 class="media-heading"><a href="{{ product.get_absolute_url }}">{{ product.product_name }}</a></h4> | ||
| {{ product.description|truncatewords_html:50 }} | ||
| {% endblock %} |
| @@ -0,0 +1 @@ | ||
| {% extends "myshop/products/search-smartcard-media.html" %} |
| @@ -1,11 +1 @@ | ||
| {% extends "myshop/products/cart-product-media.html" %} |
| @@ -1 +1,3 @@ | ||
| {{ object.product_name }} | ||
| {{ object.product_code }} | ||
| {{ object.caption|striptags }} |
| @@ -1,7 +1,7 @@ | ||
| {% load cms_tags shop_tags %} | ||
| {{ object.product_name }} | ||
| {{ object.product_code }} | ||
| {{ object.caption|striptags }} | ||
| {% for page in object.cms_pages.all %} | ||
| {{ page.get_title }}{% endfor %} | ||
| {% render_placeholder object.placeholder %} |
| @@ -1 +1,3 @@ | ||
| {{ object.product_name }} | ||
| {{ object.product_code }} | ||
| {# could be added as well {{ object.caption|striptags }} #} |
| @@ -1,6 +1,6 @@ | ||
| {{ object.product_name }} | ||
| {{ object.product_code }} | ||
| {{ object.description|striptags }} | ||
| {# TODO: render placeholder content #} | ||
| {% for page in object.cms_pages.all %} | ||
| {{ page.get_title }}{% endfor %} |
| @@ -1 +1,4 @@ | ||
| {{ object.product_name }} | ||
| {% for product_code in object.smartphone_set.all %} | ||
| {{ product_code }}{% endfor %} | ||
| {{ object.caption|striptags }} |
| @@ -3,7 +3,7 @@ | ||
|
|
||
| import json | ||
| from django.core.urlresolvers import reverse | ||
| from myshop.models import SmartCard | ||
| from bs4 import BeautifulSoup | ||
| from .test_shop import ShopTestCase | ||
|
|
||