@@ -2,36 +2,34 @@
from __future__ import unicode_literals

from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
from cms.cms_menus import SoftRootCutter
from menus.menu_pool import menu_pool

from shop.cms_apphooks import CatalogListCMSApp, CatalogSearchCMSApp, OrderCMSApp

class ProductsListApp(CMSApp):
name = _("Products List")
if settings.SHOP_TUTORIAL == 'polymorphic':
urls = ['myshop.urls.polymorphic_products']
elif settings.SHOP_TUTORIAL == 'i18n_commodity':
urls = ['myshop.urls.i18n_products']
else:
urls = ['myshop.urls.simple_products']

apphook_pool.register(ProductsListApp)
class CatalogListApp(CatalogListCMSApp):
def get_urls(self, page=None, language=None, **kwargs):
if settings.SHOP_TUTORIAL in ['i18n_polymorphic', 'polymorphic']:
return ['myshop.urls.polymorphic_products']
elif settings.SHOP_TUTORIAL == 'i18n_commodity':
return ['myshop.urls.i18n_products']
else:
return ['myshop.urls.simple_products']

apphook_pool.register(CatalogListApp)

class ProductSearchApp(CMSApp):
name = _("Search")
urls = ['myshop.urls.search']

apphook_pool.register(ProductSearchApp)
class CatalogSearchApp(CatalogSearchCMSApp):
def get_urls(self, page=None, language=None, **kwargs):
return ['myshop.urls.search']

apphook_pool.register(CatalogSearchApp)

class OrderApp(CMSApp):
name = _("View Orders")
urls = ['shop.urls.order']
cache_placeholders = False

class OrderApp(OrderCMSApp):
pass

apphook_pool.register(OrderApp)

@@ -6,20 +6,21 @@
import django_filters
from djng.forms import NgModelFormMixin
from djng.styling.bootstrap3.forms import Bootstrap3Form
from .models.manufacturer import Manufacturer
from .models.polymorphic.product import Product
from myshop.models.manufacturer import Manufacturer
from myshop.models import Product


class FilterForm(NgModelFormMixin, Bootstrap3Form):
scope_prefix = 'filters'


class ManufacturerFilter(django_filters.FilterSet):
class ManufacturerFilterSet(django_filters.FilterSet):
manufacturer = django_filters.ModelChoiceFilter(
queryset=Manufacturer.objects.all(),
widget=Select(attrs={'ng-change': 'filterChanged()'}),
empty_label=_("Any Manufacturer"),
help_text=_("Restrict product on this manufacturer only"))
help_text=_("Restrict product on this manufacturer only"),
)

class Meta:
model = Product
@@ -28,7 +29,9 @@ class Meta:

@classmethod
def get_render_context(cls, request, queryset):
filter_set = cls()
# create filter set with bound form, to enable the selected option
filter_set = cls(data=request.GET)

# 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(
@@ -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()
@@ -33,12 +33,19 @@ def set_options(self, **options):

def handle(self, verbosity, *args, **options):
self.set_options(**options)

mesg = ("\nThis will overwrite your workdir and install a new database for the django-SHOP demo: {tutorial}\n"
"Are you sure you want to do this?\n\n"
"Type 'yes' to continue, or 'no' to cancel: ").format(tutorial=settings.SHOP_TUTORIAL)
if self.interactive and input(mesg) != 'yes':
raise CommandError("Collecting static files cancelled.")
fixture = '{workdir}/{tutorial}/fixtures/myshop.json'.format(workdir=settings.WORK_DIR,
tutorial=settings.SHOP_TUTORIAL)

if self.interactive:
mesg = ("\nThis will overwrite your workdir and install a new database for the django-SHOP demo: {tutorial}\n"
"Are you sure you want to do this?\n\n"
"Type 'yes' to continue, or 'no' to cancel: ").format(tutorial=settings.SHOP_TUTORIAL)
if input(mesg) != 'yes':
raise CommandError("Collecting static files cancelled.")
else:
if os.path.isfile(fixture):
self.stdout.write(self.style.WARNING("Can not override downloaded data in input-less mode."))
return

extract_to = os.path.join(settings.WORK_DIR, os.pardir)
msg = "Downloading workdir and extracting to {}. Please wait ..."
@@ -52,6 +59,5 @@ def handle(self, verbosity, *args, **options):
zip_ref.close()

call_command('migrate')
fixture = '{workdir}/{tutorial}/fixtures/myshop.json'
call_command('loaddata', fixture.format(workdir=settings.WORK_DIR,
tutorial=settings.SHOP_TUTORIAL))
call_command('loaddata', fixture)
call_command('fix_filer_bug_965')

Large diffs are not rendered by default.

Large diffs are not rendered by default.

This file was deleted.

Large diffs are not rendered by default.

File renamed without changes.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -9,24 +9,36 @@
from shop.models.defaults.cart_item import CartItem
from shop.models.defaults.customer import Customer

__all__ = ['ShippingAddress', 'BillingAddress', 'Cart', 'CartItem', 'Customer', 'OrderItem',
'Commodity', 'SmartCard', 'SmartPhoneModel', 'SmartPhone', 'Delivery', 'DeliveryItem']

# models defined by the myshop instance itself
if settings.SHOP_TUTORIAL == 'commodity' or settings.SHOP_TUTORIAL == 'i18n_commodity':
from shop.models.defaults.order_item import OrderItem
from shop.models.defaults.commodity import Commodity # NOQA
from shop.models.defaults.commodity import Commodity

elif settings.SHOP_TUTORIAL == 'smartcard':
from shop.models.defaults.order_item import OrderItem
from .smartcard import SmartCard

elif settings.SHOP_TUTORIAL == 'i18n_smartcard':
from shop.models.defaults.order_item import OrderItem
from .i18n_smartcard import SmartCard
elif settings.SHOP_TUTORIAL == 'polymorphic':
from .polymorphic.order import OrderItem
from .polymorphic.smartcard import SmartCard
from .polymorphic.smartphone import SmartPhoneModel, SmartPhone
from shop.models.defaults.delivery import Delivery, DeliveryItem

from shop.models.defaults.order import Order # NOQA
elif settings.SHOP_TUTORIAL == 'polymorphic':
from .polymorphic_.order import OrderItem
from .polymorphic_.product import Product
from .polymorphic_.commodity import Commodity
from .polymorphic_.smartcard import SmartCard
from .polymorphic_.smartphone import SmartPhoneModel, SmartPhone

elif settings.SHOP_TUTORIAL == 'i18n_polymorphic':
from .i18n_polymorphic.order import OrderItem
from .i18n_polymorphic.product import Product
from .i18n_polymorphic.commodity import Commodity
from .i18n_polymorphic.smartcard import SmartCard
from .i18n_polymorphic.smartphone import SmartPhoneModel, SmartPhone

__all__ = ['ShippingAddress', 'BillingAddress', 'Cart', 'CartItem', 'Customer', 'Order', 'OrderItem',
'Commodity', 'SmartCard', 'SmartPhoneModel', 'SmartPhone', 'Delivery', 'DeliveryItem']
if settings.SHOP_TUTORIAL in ['polymorphic', 'i18n_polymorphic']:
from shop.models.defaults.delivery import Delivery, DeliveryItem
__all__.extend(['SmartCard', 'SmartPhoneModel', 'SmartPhone', 'Delivery', 'DeliveryItem'])
File renamed without changes.
@@ -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
File renamed without changes.
@@ -33,21 +33,45 @@ class Product(CMSPageReferenceMixin, TranslatableModelMixin, BaseProduct):
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(max_length=255, verbose_name=_("Product Name"))
slug = models.SlugField(verbose_name=_("Slug"), unique=True)
product_name = models.CharField(
_("Product Name"),
max_length=255,
)

slug = models.SlugField(
_("Slug"),
unique=True,
)

caption = TranslatedField()

# common product properties
manufacturer = models.ForeignKey(Manufacturer, verbose_name=_("Manufacturer"))
manufacturer = models.ForeignKey(
Manufacturer,
verbose_name=_("Manufacturer"),
)

# controlling the catalog
order = models.PositiveIntegerField(verbose_name=_("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)
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 = ProductManager()

@@ -71,11 +95,19 @@ def get_product_variant(self, extra):


class ProductTranslation(TranslatedFieldsModel):
master = models.ForeignKey(Product, related_name='translations', null=True)
master = models.ForeignKey(
Product,
related_name='translations',
null=True,
)

caption = HTMLField(
verbose_name=_("Caption"), blank=True, null=True,
verbose_name=_("Caption"),
blank=True,
null=True,
configuration='CKEDITOR_SETTINGS_CAPTION',
help_text=_("Short description used in the catalog's list view of products."))
help_text=_("Short description used in the catalog's list view of products."),
)

class Meta:
unique_together = [('language_code', 'master')]
@@ -5,28 +5,55 @@
from django.utils.translation import ugettext_lazy as _
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


class SmartCard(Product):
# common product fields
unit_price = MoneyField(_("Unit price"), decimal_places=3,
help_text=_("Net price for this product"))
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)
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"))
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"),
)

multilingual = TranslatedFields(
description=HTMLField(verbose_name=_("Description"),
configuration='CKEDITOR_SETTINGS_DESCRIPTION',
help_text=_("Full description used in the catalog's detail view of Smart Cards.")))
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 = TranslatableManager()

class Meta:
verbose_name = _("Smart Card")
@@ -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
@@ -4,16 +4,22 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import python_2_unicode_compatible

from djangocms_text_ckeditor.fields import HTMLField
from parler.managers import TranslatableManager, TranslatableQuerySet
from parler.models import TranslatableModelMixin, TranslatedFieldsModel
from parler.fields import TranslatedField
from polymorphic.query import PolymorphicQuerySet

from shop.money.fields import MoneyField
from shop.models.product import BaseProductManager, BaseProduct, CMSPageReferenceMixin
from shop.models.defaults.mapping import ProductPage, ProductImage
from shop.models.defaults.order import Order

from ..manufacturer import Manufacturer

__all__ = ['SmartCard', 'Order']


class ProductQuerySet(TranslatableQuerySet, PolymorphicQuerySet):
pass
@@ -83,7 +89,7 @@ class SmartCardTranslation(TranslatedFieldsModel):
help_text=_("Short description used in the catalog's list view of products."))
description = HTMLField(verbose_name=_("Description"),
configuration='CKEDITOR_SETTINGS_DESCRIPTION',
help_text=_("Description for the list view of products."))
help_text=_("Full description used in the catalog's detail view of Smart Cards."))

class Meta:
unique_together = [('language_code', 'master')]

This file was deleted.

Empty file.
@@ -3,7 +3,9 @@

from django.db import models
from django.utils.translation import ugettext_lazy as _

from cms.models.fields import PlaceholderField

from shop.money.fields import MoneyField
from .product import Product

@@ -12,11 +14,17 @@ 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"),
)

# common product fields
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)
product_code = models.CharField(
_("Product code"),
max_length=255,
unique=True,
)

# controlling the catalog
placeholder = PlaceholderField("Commodity Details")
@@ -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
@@ -4,41 +4,94 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import python_2_unicode_compatible

from djangocms_text_ckeditor.fields import HTMLField

from shop.money.fields import MoneyField
from shop.models.product import BaseProduct, BaseProductManager, CMSPageReferenceMixin
from shop.models.defaults.mapping import ProductPage, ProductImage
from shop.models.defaults.order import Order

from ..manufacturer import Manufacturer

__all__ = ['SmartCard', 'Order']


@python_2_unicode_compatible
class SmartCard(CMSPageReferenceMixin, BaseProduct):
# common product fields
product_name = models.CharField(max_length=255, verbose_name=_("Product Name"))
slug = models.SlugField(verbose_name=_("Slug"))
unit_price = MoneyField(_("Unit price"), decimal_places=3,
help_text=_("Net price for this product"))
caption = HTMLField(verbose_name=_("Caption"), configuration='CKEDITOR_SETTINGS_CAPTION',
help_text=_("Short description used in the catalog's list view of products."))
description = HTMLField(verbose_name=_("Description"), configuration='CKEDITOR_SETTINGS_DESCRIPTION',
help_text=_("Description for the list view of products."))
product_name = models.CharField(
_("Product Name"),
max_length=255
)

slug = models.SlugField(_("Slug"))

unit_price = MoneyField(
_("Unit price"),
decimal_places=3,
help_text=_("Net price for this product"),
)

caption = HTMLField(
_("Caption"),
configuration='CKEDITOR_SETTINGS_CAPTION',
help_text=_("Short description used in the catalog's list view of products."),
)

description = HTMLField(
_("Description"),
configuration='CKEDITOR_SETTINGS_DESCRIPTION',
help_text=_("Description for the list view of products."),
)

# product properties
manufacturer = models.ForeignKey(Manufacturer, verbose_name=_("Manufacturer"))
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"))
manufacturer = models.ForeignKey(
Manufacturer,
verbose_name=_("Manufacturer")
)

card_type = models.CharField(
_("Card Type"),
choices=(2 * ('{}{}'.format(s, t),)
for t in ('SD', 'SDXC', 'SDHC', 'SDHC II') for s in ('', 'micro ')),
max_length=15,
)

speed = models.CharField(
_("Transfer Speed"),
choices=((str(s), "{} MB/s".format(s))
for s in (4, 20, 30, 40, 48, 80, 95, 280)),
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"),
)

# controlling the catalog
order = models.PositiveIntegerField(verbose_name=_("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)
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,
)

objects = BaseProductManager()

@@ -2,7 +2,7 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from shop.modifiers.pool import cart_modifiers_pool
from shop.rest.serializers import ExtraCartRow
from shop.serializers.cart import ExtraCartRow
from shop.modifiers.base import ShippingModifier
from shop.money import Money
from shop.shipping.defaults import DefaultShippingProvider
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from shop.modifiers.defaults import DefaultCartModifier
from myshop.models.polymorphic.smartphone import SmartPhoneModel
from myshop.models import SmartPhoneModel


class MyShopCartModifier(DefaultCartModifier):
@@ -6,15 +6,14 @@
from shop.search.indexes import ProductIndex as ProductIndexBase


if settings.SHOP_TUTORIAL == 'commodity' or settings.SHOP_TUTORIAL == 'i18n_commodity':
if settings.SHOP_TUTORIAL in ['i18n_commodity', 'commodity']:
from shop.models.defaults.commodity import Commodity
elif settings.SHOP_TUTORIAL == 'smartcard':
from myshop.models.smartcard import SmartCard
elif settings.SHOP_TUTORIAL == 'i18n_smartcard':
from myshop.models.i18n_smartcard import SmartCard
elif settings.SHOP_TUTORIAL == 'polymorphic':
from myshop.models.polymorphic.smartcard import SmartCard
from myshop.models.polymorphic.smartphone import SmartPhoneModel

elif settings.SHOP_TUTORIAL in ['i18n_smartcard', 'smartcard']:
from myshop.models import SmartCard

elif settings.SHOP_TUTORIAL in ['i18n_polymorphic', 'polymorphic']:
from myshop.models import SmartCard, SmartPhoneModel, Commodity


class ProductIndex(ProductIndexBase):
@@ -31,25 +30,19 @@ def prepare_search_media(self, product):

myshop_search_index_classes = []

if settings.SHOP_TUTORIAL in ('commodity', 'i18n_commodity'):
if settings.SHOP_TUTORIAL in ['i18n_commodity', 'commodity', 'i18n_polymorphic', 'polymorphic']:
class CommodityIndex(ProductIndex, indexes.Indexable):
def get_model(self):
return Commodity

def Xprepare_text(self, product):
output = super(CommodityIndex, self).prepare_text(product)
return output
myshop_search_index_classes.append(CommodityIndex)


if settings.SHOP_TUTORIAL in ('smartcard', 'i18n_smartcard', 'polymorphic',):
if settings.SHOP_TUTORIAL in ['i18n_smartcard', 'smartcard', 'i18n_polymorphic', 'polymorphic']:
class SmartCardIndex(ProductIndex, indexes.Indexable):
def get_model(self):
return SmartCard
myshop_search_index_classes.append(SmartCardIndex)


if settings.SHOP_TUTORIAL in ('polymorphic',):
if settings.SHOP_TUTORIAL in ['i18n_polymorphic', 'polymorphic']:
class SmartPhoneIndex(ProductIndex, indexes.Indexable):
def get_model(self):
return SmartPhoneModel
@@ -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)

This file was deleted.

@@ -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
@@ -15,22 +15,26 @@
from decimal import Decimal
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse_lazy

from cmsplugin_cascade.utils import format_lazy

SHOP_APP_LABEL = 'myshop'
BASE_DIR = os.path.dirname(__file__)

SHOP_TUTORIAL = os.environ.get('DJANGO_SHOP_TUTORIAL')
if SHOP_TUTORIAL is None:
raise ImproperlyConfigured("Environment variable DJANGO_SHOP_TUTORIAL is not set")
if SHOP_TUTORIAL not in ('commodity', 'i18n_commodity', 'smartcard', 'i18n_smartcard', 'polymorphic',):
if SHOP_TUTORIAL not in ['commodity', 'i18n_commodity', 'smartcard', 'i18n_smartcard',
'i18n_polymorphic', 'polymorphic']:
msg = "Environment variable DJANGO_SHOP_TUTORIAL has an invalid value `{}`"
raise ImproperlyConfigured(msg.format(SHOP_TUTORIAL))

# Root directory for this django project
PROJECT_ROOT = os.path.abspath(os.path.join(BASE_DIR, os.path.pardir, os.path.pardir))
PROJECT_ROOT = os.path.abspath(os.path.join(BASE_DIR, os.path.pardir))

# Directory where working files, such as media and databases are kept
WORK_DIR = os.environ.get('DJANGO_WORKDIR', os.path.join(PROJECT_ROOT, 'workdir'))
WORK_DIR = os.environ.get('DJANGO_WORKDIR', os.path.abspath(os.path.join(PROJECT_ROOT, os.path.pardir, 'workdir')))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
@@ -67,7 +71,7 @@
'allauth.account.auth_backends.AuthenticationBackend',
)

INSTALLED_APPS = (
INSTALLED_APPS = [
'django.contrib.auth',
'email_auth',
'polymorphic',
@@ -84,6 +88,7 @@
'cmsplugin_cascade.clipboard',
'cmsplugin_cascade.sharable',
'cmsplugin_cascade.extra_fields',
'cmsplugin_cascade.icon',
'cmsplugin_cascade.segmentation',
'cms_bootstrap3',
'adminsortable2',
@@ -108,11 +113,11 @@
'shop',
'shop_stripe',
'myshop',
)
if SHOP_TUTORIAL in ('i18n_commodity', 'i18n_smartcard', 'polymorphic'):
INSTALLED_APPS += ('parler',)
]
if SHOP_TUTORIAL in ['i18n_commodity', 'i18n_smartcard', 'i18n_polymorphic']:
INSTALLED_APPS.append('parler')

MIDDLEWARE_CLASSES = (
MIDDLEWARE_CLASSES = [
'djng.middleware.AngularUrlMiddleware',
# 'django.middleware.cache.UpdateCacheMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
@@ -127,9 +132,10 @@
'cms.middleware.language.LanguageCookieMiddleware',
'cms.middleware.user.CurrentUserMiddleware',
'cms.middleware.page.CurrentPageMiddleware',
'cms.middleware.utils.ApphookReloadMiddleware',
'cms.middleware.toolbar.ToolbarMiddleware',
# 'django.middleware.cache.FetchFromCacheMiddleware',
)
]

MIGRATION_MODULES = {
'myshop': 'myshop.migrations.{}'.format(SHOP_TUTORIAL)
@@ -151,7 +157,7 @@

LANGUAGE_CODE = 'en'

if SHOP_TUTORIAL in ('i18n_smartcard', 'i18n_commodity', 'polymorphic'):
if SHOP_TUTORIAL in ['i18n_smartcard', 'i18n_commodity', 'i18n_polymorphic']:
USE_I18N = True

LANGUAGES = (
@@ -212,7 +218,7 @@

# Absolute path to the directory that holds static files.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = os.path.join(WORK_DIR, SHOP_TUTORIAL, 'static')
STATIC_ROOT = os.path.join(WORK_DIR, 'static')

# URL that handles the static files served from STATIC_ROOT.
# Example: "http://media.lawrence.com/static/"
@@ -231,11 +237,6 @@
)


# URL prefix for admin media -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = '/static/admin/'

TEMPLATES = [{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
@@ -293,7 +294,7 @@
},
}

SILENCED_SYSTEM_CHECKS = ('auth.W004')
SILENCED_SYSTEM_CHECKS = ['auth.W004']

FIXTURE_DIRS = [os.path.join(WORK_DIR, SHOP_TUTORIAL, 'fixtures')]

@@ -414,14 +415,16 @@
},
'Commodity Details': {
'plugins': ['BootstrapContainerPlugin', 'BootstrapJumbotronPlugin'],
'text_only_plugins': ['TextLinkPlugin'],
'parent_classes': {'BootstrapContainerPlugin': None, 'BootstrapJumbotronPlugin': None},
'glossary': CACSCADE_WORKAREA_GLOSSARY,
},
'Main Content': {
'plugins': ['BootstrapContainerPlugin', 'BootstrapJumbotronPlugin'],
'text_only_plugins': ['TextLinkPlugin'],
'parent_classes': {'BootstrapContainerPlugin': None, 'BootstrapJumbotronPlugin': None},
'parent_classes': {
'BootstrapContainerPlugin': None,
'BootstrapJumbotronPlugin': None,
'TextLinkPlugin': ['TextPlugin', 'AcceptConditionPlugin'],
},
'glossary': CACSCADE_WORKAREA_GLOSSARY,
},
'Static Footer': {
@@ -431,20 +434,22 @@
},
}

CMSPLUGIN_CASCADE_PLUGINS = ('cmsplugin_cascade.segmentation', 'cmsplugin_cascade.generic',
'cmsplugin_cascade.link', 'shop.cascade', 'cmsplugin_cascade.bootstrap3',)
CMSPLUGIN_CASCADE_PLUGINS = (
'cmsplugin_cascade.segmentation',
'cmsplugin_cascade.generic',
'cmsplugin_cascade.icon',
'cmsplugin_cascade.link',
'shop.cascade',
'cmsplugin_cascade.bootstrap3',
)

CMSPLUGIN_CASCADE = {
'fontawesome_css_url': 'node_modules/font-awesome/css/font-awesome.css',
'dependencies': {
'shop/js/admin/shoplinkplugin.js': 'cascade/js/admin/linkpluginbase.js',
},
'link_plugin_classes': (
'shop.cascade.plugin_base.CatalogLinkPluginBase',
'cmsplugin_cascade.link.plugin_base.LinkElementMixin',
'shop.cascade.plugin_base.CatalogLinkForm',
),
'alien_plugins': ('TextPlugin', 'TextLinkPlugin',),
'alien_plugins': ('TextPlugin', 'TextLinkPlugin', 'AcceptConditionPlugin',),
'bootstrap3': {
'template_basedir': 'angular-ui',
},
@@ -455,15 +460,15 @@
],
},
'plugins_with_sharables': {
'BootstrapImagePlugin': ('image-shapes', 'image-width-responsive', 'image-width-fixed',
'image-height', 'resize-options',),
'BootstrapPicturePlugin': ('image-shapes', 'responsive-heights', 'image-size',
'resize-options',),
'BootstrapImagePlugin': ('image_shapes', 'image_width_responsive', 'image_width_fixed',
'image_height', 'resize_options',),
'BootstrapPicturePlugin': ('image_shapes', 'responsive_heights', 'image_size', 'resize_options',),
},
'bookmark_prefix': '/',
'segmentation_mixins': (
('shop.cascade.segmentation.EmulateCustomerModelMixin', 'shop.cascade.segmentation.EmulateCustomerAdminMixin'),
),
'allow_plugin_hiding': True,
}

CKEDITOR_SETTINGS = {
@@ -483,6 +488,7 @@
['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Table'],
['Source']
],
'stylesSet': format_lazy('default:{}', reverse_lazy('admin:cascade_texticon_wysiwig_config')),
}

CKEDITOR_SETTINGS_CAPTION = {
@@ -527,14 +533,14 @@
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': 'http://localhost:9200/',
'INDEX_NAME': 'myshop-en',
'INDEX_NAME': 'myshop-{}-en'.format(SHOP_TUTORIAL),
},
}
if USE_I18N:
HAYSTACK_CONNECTIONS['de'] = {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': 'http://localhost:9200/',
'INDEX_NAME': 'myshop-de',
'INDEX_NAME': 'myshop-{}-de'.format(SHOP_TUTORIAL),
}

HAYSTACK_ROUTERS = ('shop.search.routers.LanguageRouter',)
@@ -544,26 +550,32 @@

SHOP_VALUE_ADDED_TAX = Decimal(19)
SHOP_DEFAULT_CURRENCY = 'EUR'
SHOP_CART_MODIFIERS = (
'myshop.polymorphic_modifiers.MyShopCartModifier' if SHOP_TUTORIAL == 'polymorphic'
else 'shop.modifiers.defaults.DefaultCartModifier',
SHOP_PRODUCT_SUMMARY_SERIALIZER = 'myshop.serializers.ProductSummarySerializer'
if SHOP_TUTORIAL in ['i18n_polymorphic', 'polymorphic']:
SHOP_CART_MODIFIERS = ['myshop.polymorphic_modifiers.MyShopCartModifier']
else:
SHOP_CART_MODIFIERS = ['shop.modifiers.defaults.DefaultCartModifier']
SHOP_CART_MODIFIERS.extend([
'shop.modifiers.taxes.CartExcludedTaxModifier',
'myshop.modifiers.PostalShippingModifier',
'myshop.modifiers.CustomerPickupModifier',
'shop.modifiers.defaults.PayInAdvanceModifier',
)
])

if 'shop_stripe' in INSTALLED_APPS:
SHOP_CART_MODIFIERS += ('myshop.modifiers.StripePaymentModifier',)
SHOP_CART_MODIFIERS.append('myshop.modifiers.StripePaymentModifier')

SHOP_EDITCART_NG_MODEL_OPTIONS = "{updateOn: 'default blur', debounce: {'default': 2500, 'blur': 0}}"

SHOP_ORDER_WORKFLOWS = (
SHOP_ORDER_WORKFLOWS = [
'shop.payment.defaults.PayInAdvanceWorkflowMixin',
'shop.payment.defaults.CancelOrderWorkflowMixin',
'shop.shipping.delivery.PartialDeliveryWorkflowMixin' if SHOP_TUTORIAL == 'polymorphic'
else 'shop.shipping.defaults.CommissionGoodsWorkflowMixin',
'shop_stripe.payment.OrderWorkflowMixin',
)
]
if SHOP_TUTORIAL in ['i18n_polymorphic', 'polymorphic']:
SHOP_ORDER_WORKFLOWS.append('shop.shipping.delivery.PartialDeliveryWorkflowMixin')
else:
SHOP_ORDER_WORKFLOWS.append('shop.shipping.defaults.CommissionGoodsWorkflowMixin')

SHOP_STRIPE = {
'PUBKEY': 'pk_test_HlEp5oZyPonE21svenqowhXp',
@@ -4,14 +4,14 @@
from django.contrib.sitemaps import Sitemap
from django.conf import settings

if settings.SHOP_TUTORIAL in ('commodity', 'i18n_commodity'):
if settings.SHOP_TUTORIAL in ['i18n_commodity', 'commodity']:
from shop.models.defaults.commodity import Commodity as Product
elif settings.SHOP_TUTORIAL == 'smartcard':
from myshop.models.smartcard import SmartCard as Product
elif settings.SHOP_TUTORIAL == 'i18n_smartcard':
from myshop.models.i18n_smartcard import SmartCard as Product
elif settings.SHOP_TUTORIAL == 'polymorphic':
from myshop.models.polymorphic.product import Product

elif settings.SHOP_TUTORIAL in ['i18n_smartcard', 'smartcard']:
from myshop.models import SmartCard as Product

elif settings.SHOP_TUTORIAL in ['i18n_polymorphic', 'polymorphic']:
from myshop.models import Product


class ProductSitemap(Sitemap):
@@ -5,6 +5,8 @@
min-height: $navbar-extra-top-height;
border-top: 1px solid transparent;
border-bottom: 1px solid $navbar-default-border;
background-image: linear-gradient(to bottom, #fff 0, $navbar-default-bg 100%);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
.shop-brand {
text-align: center;
}
@@ -1,4 +1,5 @@
@import "default";
@import "shop/css/django-shop";
@import "footer";

.product-detail {
bsp-scrollpanel {
@@ -8,10 +8,9 @@
<td rowspan="2" class="text-center">
<p>{{ cart_item.quantity }}</p>
<p class="text-left">
<small>{% trans "Product Code" %}:
<small>{% trans "Product Code" %}:</small>
<strong>{{ cart_item.extra.product_code }}</strong>
</p>
</small>
</td>
<td rowspan="2" class="text-left">
<div class="media">{{ cart_item.summary.media }}</div>
@@ -1,8 +1,8 @@
{% load static sekizai_tags %}

{% addtoblock "js" %}<script src="{% static 'shop/js/filter.js' %}" type="text/javascript"></script>{% endaddtoblock %}
{% addtoblock "js" %}<script src="{% static 'shop/js/filter-form.js' %}" type="text/javascript"></script>{% endaddtoblock %}
{% addtoblock "ng-requires" %}django.shop.filter{% endaddtoblock %}

<div shop-product-filter="manufacturer" style="margin-bottom: 10px;">
<form shop-product-filter="manufacturer" style="margin-bottom: 10px;">
{{ filter.filter_set.form.as_div }}
</div>
</form>

This file was deleted.

@@ -10,7 +10,7 @@
<meta name="description" content="{% block meta-description %}{% endblock %}" />
{% block head %}{% endblock head %}
{% render_block "ext-css" %}
{% render_block "css" postprocessor "compressor.contrib.sekizai.compress" %}
{% render_block "css" postprocessor "myshop.sekizai_processors.compress" %}
</head>

{% addtoblock "js" %}<script src="{% static 'node_modules/picturefill/dist/picturefill.min.js' %}" type="text/javascript"></script>{% endaddtoblock %}
@@ -21,7 +21,9 @@

<body>
{% cms_toolbar %}
<header>
{% block header %}{% endblock %}
</header>

<main>
{% block breadcrumb %}{% endblock %}
@@ -43,15 +45,18 @@ <h1>Base Template</h1>
</footer>

{% render_block "ext-js" %}
{% render_block "js" postprocessor "compressor.contrib.sekizai.compress" %}
{% render_block "js" postprocessor "myshop.sekizai_processors.compress" %}
<script type="text/javascript">
angular.module('myShop', ['ngAnimate', 'ngSanitize',
{% render_block "ng-requires" postprocessor "djng.sekizai_processors.module_list" %}
]).config(['$httpProvider', function($httpProvider) {
]).config(['$httpProvider', '$locationProvider', function($httpProvider, $locationProvider) {
$httpProvider.defaults.headers.common['X-CSRFToken'] = '{{ csrf_token }}';
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
}]).config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode(false);
$locationProvider.html5Mode({
enabled: true,
requireBase: false,
rewriteLinks: false
});
}]){% render_block "ng-config" postprocessor "djng.sekizai_processors.module_config" %};
</script>
</body>
@@ -38,9 +38,6 @@
{% addtoblock "js" %}<script src="{% static 'myshop/js/navbar.js' %}" type="text/javascript"></script>{% endaddtoblock %}
{% addtoblock "ng-requires" %}myshop.navbar{% endaddtoblock %}

{% addtoblock "js" %}<script src="{% static 'shop/js/utils.js' %}" type="text/javascript"></script>{% endaddtoblock %}
{% addtoblock "js" %}<script src="{% static 'shop/js/search-form.js' %}" type="text/javascript"></script>{% endaddtoblock %}
{% addtoblock "ng-requires" %}django.shop.search{% endaddtoblock %}
{% endblock navbar %}

{% block navbar-nav %}
@@ -52,11 +49,7 @@
{% include "shop/navbar/login-logout.html" %}
{% endwith %}
{% if shop_watch_url %}
<li>
<a href="{{ shop_watch_url }}" title="{% page_attribute 'page_title' 'shop-watch-list' %}">
<i class="fa fa-heart fa-lg"></i>
</a>
</li>
<li>{% include "shop/navbar/watch-icon.html" %}</li>
{% endif %}
<li>{% cart_icon %}</li>
</ul>
@@ -1,11 +1,16 @@
{% load i18n thumbnail %}
{% 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 }}" />
<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 }}
</div>
{{ product.description|truncatewords_html:30 }}
{% endblock %}
</div>
@@ -1,4 +1,4 @@
{% load i18n thumbnail djng_tags %}
{% 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" />
<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 i18n thumbnail %}
{% 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 }}" />
<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 }}
</div>
{% endblock %}
</div>
@@ -1,11 +1,16 @@
{% load i18n thumbnail %}
{% 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 }}" />
<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 }}
</div>
{{ 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 @@
{% load i18n thumbnail %}
{% thumbnail product.sample_image 80x80 crop as thumb %}
<div class="media-left">
<a href="{{ product.get_absolute_url }}">
<img src="{{ thumb.url }}" width="{{ thumb.width }}" height="{{ thumb.height }}" />
</a>
</div>
<div class="media-body">
<h4 class="media-heading"><a href="{{ product.get_absolute_url }}">{{ product.product_name }}</a></h4>
{{ product.description|truncatewords_html:50 }}
</div>
{% extends "myshop/products/cart-product-media.html" %}
@@ -1 +1,3 @@
{% extends "search/indexes/myshop/commodity_text.txt" %}
{{ object.product_name }}
{{ object.product_code }}
{{ object.caption|striptags }}
@@ -1,7 +1,7 @@
{% load shop_tags %}
{% load cms_tags shop_tags %}
{{ object.product_name }}
{{ object.product_code }}
{{ object.description|striptags }}
{{ object.caption|striptags }}
{% for page in object.cms_pages.all %}
{{ page.get_title }}{% endfor %}
{% render_placeholder object.placeholder %}
{% render_placeholder object.placeholder %}
@@ -1 +1,3 @@
{% extends "search/indexes/myshop/smartcard_text.txt" %}
{{ 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.caption|striptags }}
{{ object.description|striptags }}
{# TODO: render placeholder content #}
{% for page in object.cms_pages.all %}
{{ page.get_title }}{% endfor %}
{{ page.get_title }}{% endfor %}
@@ -1 +1,4 @@
{% extends "search/indexes/myshop/smartphonemodel_text.txt" %}
{{ object.product_name }}
{% for product_code in object.smartphone_set.all %}
{{ product_code }}{% endfor %}
{{ object.caption|striptags }}
@@ -6,18 +6,15 @@
from __future__ import unicode_literals

from django.conf.urls import url
from shop.views.catalog import AddToCartView, CMSPageProductListView, ProductRetrieveView
from shop.search.views import SearchView
from myshop.serializers import (ProductSummarySerializer, ProductDetailSerializer,
CatalogSearchSerializer)

from shop.views.catalog import ProductListView, ProductRetrieveView, AddToCartView

from myshop.serializers import ProductSummarySerializer, ProductDetailSerializer

urlpatterns = [
url(r'^$', CMSPageProductListView.as_view(
url(r'^$', ProductListView.as_view(
serializer_class=ProductSummarySerializer,
)),
url(r'^search-catalog$', SearchView.as_view(
serializer_class=CatalogSearchSerializer,
)),
url(r'^(?P<slug>[\w-]+)/?$', ProductRetrieveView.as_view(
serializer_class=ProductDetailSerializer,
lookup_field='translations__slug'
@@ -7,24 +7,21 @@
from __future__ import unicode_literals

from django.conf.urls import url
from shop.views.catalog import AddToCartView, CMSPageProductListView, ProductRetrieveView
from shop.search.views import SearchView
from myshop.filters import ManufacturerFilter
from myshop.serializers import (ProductSummarySerializer, ProductDetailSerializer,
AddSmartCardToCartSerializer, AddSmartPhoneToCartSerializer,

from shop.search.views import CMSPageCatalogWrapper
from shop.views.catalog import AddToCartView, ProductRetrieveView

from myshop.filters import ManufacturerFilterSet
from myshop.serializers import (AddSmartCardToCartSerializer, AddSmartPhoneToCartSerializer,
CatalogSearchSerializer)


urlpatterns = [
url(r'^$', CMSPageProductListView.as_view(
serializer_class=ProductSummarySerializer,
filter_class=ManufacturerFilter,
)),
url(r'^search-catalog$', SearchView.as_view(
serializer_class=CatalogSearchSerializer,
)),
url(r'^(?P<slug>[\w-]+)/?$', ProductRetrieveView.as_view(
serializer_class=ProductDetailSerializer,
url(r'^$', CMSPageCatalogWrapper.as_view(
filter_class=ManufacturerFilterSet,
search_serializer_class=CatalogSearchSerializer,
)),
url(r'^(?P<slug>[\w-]+)/?$', ProductRetrieveView.as_view()),
url(r'^(?P<slug>[\w-]+)/add-to-cart', AddToCartView.as_view(
serializer_class=AddSmartCardToCartSerializer,
)),
@@ -7,18 +7,15 @@
"""

from django.conf.urls import url
from shop.views.catalog import AddToCartView, CMSPageProductListView, ProductRetrieveView
from shop.search.views import SearchView
from myshop.serializers import (ProductSummarySerializer, ProductDetailSerializer,
CatalogSearchSerializer)

from shop.views.catalog import AddToCartView, ProductListView, ProductRetrieveView

from myshop.serializers import ProductSummarySerializer, ProductDetailSerializer

urlpatterns = [
url(r'^$', CMSPageProductListView.as_view(
url(r'^$', ProductListView.as_view(
serializer_class=ProductSummarySerializer,
)),
url(r'^search-catalog$', SearchView.as_view(
serializer_class=CatalogSearchSerializer,
)),
url(r'^(?P<slug>[\w-]+)/?$', ProductRetrieveView.as_view(
serializer_class=ProductDetailSerializer
)),
@@ -1,6 +1,6 @@
{
"name": "django-shop",
"version": "0.9.3",
"version": "0.10.0",
"description": "These dependencies are for the demo installations of django-SHOP.",
"main": "index.js",
"directories": {
@@ -19,20 +19,21 @@
"bugs": {
"url": "https://github.com/jrief/django-shop/issues"
},
"readme": "docker-files/README.md",
"homepage": "https://github.com/jrief/django-shop#readme",
"dependencies": {
"angular": "~1.3.20",
"angular-animate": "~1.3.20",
"angular": "~1.5.11",
"angular-animate": "~1.5.11",
"angular-bootstrap-plus": "^0.6.4",
"angular-i18n": "~1.3.20",
"angular-i18n": "~1.5.11",
"angular-inview": "^1.5.7",
"angular-messages": "~1.3.20",
"angular-sanitize": "~1.3.20",
"angular-ui-bootstrap": "~0.13.4",
"bootstrap": "^3.3.7",
"bootstrap-sass": "^3.3.7",
"font-awesome": "^4.6.3",
"picturefill": "^3.0.2",
"select2": "^4.0.3"
"angular-messages": "~1.5.11",
"angular-sanitize": "~1.5.11",
"angular-ui-bootstrap": "~0.14.3",
"bootstrap": "~3.3.7",
"bootstrap-sass": "~3.3.7",
"font-awesome": "~4.6.3",
"picturefill": "~3.0.2",
"select2": "~4.0.3"
}
}
@@ -7,7 +7,7 @@
from cms.api import add_plugin, create_page
from shop.models.cart import CartModel
from shop.cascade.cart import ShopCartPlugin
from myshop.models.polymorphic.smartcard import SmartCard
from myshop.models import SmartCard
from .test_shop import ShopTestCase


@@ -3,7 +3,7 @@

import json
from django.core.urlresolvers import reverse
from myshop.models.polymorphic.smartcard import SmartCard
from myshop.models import SmartCard
from bs4 import BeautifulSoup
from .test_shop import ShopTestCase

@@ -4,16 +4,17 @@
import json
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.contrib.sessions.backends.db import SessionStore
from django.http import QueryDict

from cms.api import add_plugin, create_page
from bs4 import BeautifulSoup
from shop.cascade.checkout import (
GuestFormPlugin, CustomerFormPlugin, ShippingAddressFormPlugin, BillingAddressFormPlugin,
GuestFormPlugin, CustomerFormPlugin, CheckoutAddressPlugin,
PaymentMethodFormPlugin, ShippingMethodFormPlugin, RequiredFormFieldsPlugin,
ExtraAnnotationFormPlugin, AcceptConditionFormPlugin)
ExtraAnnotationFormPlugin, AcceptConditionPlugin)
from shop.models.cart import CartModel
from myshop.models.polymorphic.smartcard import SmartCard
from myshop.models import SmartCard
from .test_shop import ShopTestCase


@@ -82,15 +83,16 @@ def test_address_forms(self):
placeholder = self.checkout_page.placeholders.get(slot='Main Content')

# add shipping address to checkout page
address_form_element = add_plugin(placeholder, ShippingAddressFormPlugin, 'en',
address_form_element = add_plugin(placeholder, CheckoutAddressPlugin, 'en',
target=self.column_element)
address_form_element.glossary = {'render_type': 'form'}
address_form_element.glossary = {'address_form': 'shipping', 'render_type': 'form'}
address_form_element.save()

# add billing address to checkout page
address_form_element = add_plugin(placeholder, BillingAddressFormPlugin, 'en',
address_form_element = add_plugin(placeholder, CheckoutAddressPlugin, 'en',
target=self.column_element)
address_form_element.glossary = {'render_type': 'form', 'multi_addr': True}
address_form_element.glossary = {'address_form': 'billing', 'render_type': 'form',
'multi_addr': 'on', 'allow_use_primary': 'on'}
address_form_element.save()

self.checkout_page.publish('en')
@@ -115,20 +117,21 @@ def test_address_forms(self):

data = {
'shipping_address': {
'name': "Bart Simpson", 'address1': "Park Ave.", 'address2': "", 'zip_code': "SF123",
'city': "Springfield", 'country': "US",
'active_priority': 'add',
'name': "Bart Simpson", 'address1': "Park Ave.", 'address2': "",
'zip_code': "SF123", 'city': "Springfield", 'country': "US",
'plugin_id': shipping_plugin_id_input['value'],
'plugin_order': shipping_plugin_order_input['value']},
'billing_address': {
'use_shipping_address': True,
'use_primary_address': True,
'plugin_id': billing_plugin_id_input['value'],
'plugin_order': billing_plugin_order_input['value']}
}
url = reverse('shop:checkout-upload')
response = self.client.post(url, data=json.dumps(data), content_type='application/json')
payload = json.loads(response.content.decode('utf-8'))
self.assertIn('shipping_address_form', payload['errors'])
self.assertDictEqual({}, payload['errors']['shipping_address_form'])
self.assertDictEqual(payload['errors']['shipping_address_form'], {})

# check if Bart changed his address and zip code
bart = get_user_model().objects.get(username='bart')
@@ -278,22 +281,25 @@ def test_extra_annotation_form_plugin(self):
payload = json.loads(response.content.decode('utf-8'))
self.assertDictEqual(payload['errors']['extra_annotation_form'], {})

def test_accept_condition_form_plugin(self):
def test_accept_condition_plugin(self):
# create a page populated with Cascade elements used for checkout
placeholder = self.checkout_page.placeholders.get(slot='Main Content')

# add accept condition form plugin to checkout page
accept_condition_element = add_plugin(placeholder, AcceptConditionFormPlugin, 'en',
accept_condition_element = add_plugin(placeholder, AcceptConditionPlugin, 'en',
target=self.column_element)
accept_condition_plugin = accept_condition_element.get_plugin_class_instance(self.admin_site)
self.assertIsInstance(accept_condition_plugin, AcceptConditionFormPlugin)
self.assertIsInstance(accept_condition_plugin, AcceptConditionPlugin)

# edit the plugin's content
self.client.login(username='admin', password='admin')
request = self.client.request()
self.assertTrue(self.client.login(username='admin', password='admin'))

post_data = QueryDict('', mutable=True)
post_data.update({'html_content': "<p>I have read the terms and conditions and agree with them.</p>"})
post_data.update({'body': "<p>I have read the terms and conditions and agree with them.</p>"})
request = self.factory.post('/')
request.session = SessionStore()
request.session.create()

ModelForm = accept_condition_plugin.get_form(request, accept_condition_element)
form = ModelForm(post_data, None, instance=accept_condition_element)
self.assertTrue(form.is_valid())
@@ -310,21 +316,16 @@ def test_accept_condition_form_plugin(self):
response = self.client.get(url)
self.assertEquals(response.status_code, 200)
soup = BeautifulSoup(response.content, 'html.parser')
print(soup.prettify())

# find plugin counterpart on public page
placeholder = self.checkout_page.publisher_public.placeholders.get(slot='Main Content')
plugin = [p for p in placeholder.cmsplugin_set.all() if p.plugin_type == 'AcceptConditionFormPlugin'][0]
plugin = [p for p in placeholder.cmsplugin_set.all() if p.plugin_type == 'AcceptConditionPlugin'][0]
accept_condition_form = soup.find('form', {'name': 'accept_condition_form.plugin_{}'.format(plugin.id)})
self.assertIsNotNone(accept_condition_form)
accept_input = accept_condition_form.find('input', {'id': 'id_accept'})
accept_paragraph = str(accept_input.find_next_siblings('p')[0])
self.assertHTMLEqual(accept_paragraph, "<p>I have read the terms and conditions and agree with them.</p>")

# check the get_identifier method
content = accept_condition_plugin.get_identifier(accept_condition_element)
self.assertEqual('I have read ...', content)

def add_guestform_element(self):
"""Add one GuestFormPlugin to the current page"""
column_element = self.create_page_grid(self.checkout_page)