406 lines
11 KiB
Python
406 lines
11 KiB
Python
|
|
from decimal import Decimal
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.core.validators import MinValueValidator
|
|
from django.db import models
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from djangocms_text_ckeditor.fields import HTMLField
|
|
from cms.models.fields import PlaceholderField
|
|
from shop.money import Money, MoneyMaker
|
|
from shop.money.fields import MoneyField
|
|
from shop.models.product import BaseProduct, BaseProductManager, AvailableProductMixin, CMSPageReferenceMixin
|
|
from shop.models.defaults.cart import Cart
|
|
from shop.models.defaults.cart_item import CartItem
|
|
from shop.models.order import BaseOrderItem
|
|
from shop.models.defaults.delivery import Delivery
|
|
from shop.models.defaults.delivery_item import DeliveryItem
|
|
from shop.models.defaults.order import Order
|
|
from shop.models.defaults.mapping import ProductPage, ProductImage
|
|
from shop.models.defaults.address import BillingAddress, ShippingAddress
|
|
from shop.models.defaults.customer import Customer
|
|
|
|
|
|
__all__ = ['Cart', 'CartItem', 'Order', 'Delivery', 'DeliveryItem',
|
|
'BillingAddress', 'ShippingAddress', 'Customer', ]
|
|
|
|
|
|
class OrderItem(BaseOrderItem):
|
|
quantity = models.PositiveIntegerField(_("Ordered quantity"))
|
|
canceled = models.BooleanField(_("Item canceled "), default=False)
|
|
|
|
def populate_from_cart_item(self, cart_item, request):
|
|
super().populate_from_cart_item(cart_item, request)
|
|
# the product's unit_price must be fetched from the product's variant
|
|
try:
|
|
variant = cart_item.product.get_product_variant(
|
|
product_code=cart_item.product_code)
|
|
self._unit_price = Decimal(variant.unit_price)
|
|
except (KeyError, ObjectDoesNotExist) as e:
|
|
raise CartItem.DoesNotExist(e)
|
|
|
|
|
|
class Manufacturer(models.Model):
|
|
name = models.CharField(
|
|
_("Name"),
|
|
max_length=50,
|
|
unique=True,
|
|
)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class ProductManager(BaseProductManager):
|
|
pass
|
|
|
|
|
|
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,
|
|
on_delete=models.CASCADE,
|
|
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 = ProductManager()
|
|
|
|
# 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()
|
|
|
|
|
|
class Commodity(AvailableProductMixin, 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,
|
|
)
|
|
|
|
quantity = models.PositiveIntegerField(
|
|
_("Quantity"),
|
|
default=0,
|
|
validators=[MinValueValidator(0)],
|
|
help_text=_("Available quantity in stock")
|
|
)
|
|
|
|
# controlling the catalog
|
|
placeholder = PlaceholderField("Commodity Details")
|
|
show_breadcrumb = True # hard coded to always show the product's breadcrumb
|
|
|
|
class Meta:
|
|
verbose_name = _("Commodity")
|
|
verbose_name_plural = _("Commodities")
|
|
|
|
default_manager = ProductManager()
|
|
|
|
def get_price(self, request):
|
|
return self.unit_price
|
|
|
|
|
|
class SmartCard(AvailableProductMixin, Product):
|
|
unit_price = MoneyField(
|
|
_("Unit price"),
|
|
decimal_places=3,
|
|
help_text=_("Net price for this product"),
|
|
)
|
|
|
|
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"),
|
|
)
|
|
|
|
description = HTMLField(
|
|
_("Description"),
|
|
configuration='CKEDITOR_SETTINGS_DESCRIPTION',
|
|
help_text=_("Long description for the detail view of this product."),
|
|
)
|
|
|
|
quantity = models.PositiveIntegerField(
|
|
_("Quantity"),
|
|
default=0,
|
|
validators=[MinValueValidator(0)],
|
|
help_text=_("Available quantity in stock")
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = _("Smart Card")
|
|
verbose_name_plural = _("Smart Cards")
|
|
ordering = ['order']
|
|
|
|
# filter expression used to lookup for a product item using the Select2 widget
|
|
lookup_fields = ['product_code__startswith', 'product_name__icontains']
|
|
|
|
def get_price(self, request):
|
|
return self.unit_price
|
|
|
|
default_manager = ProductManager()
|
|
|
|
|
|
class OperatingSystem(models.Model):
|
|
name = models.CharField(
|
|
_("Name"),
|
|
max_length=50,
|
|
unique=True,
|
|
)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class SmartPhoneModel(Product):
|
|
"""
|
|
A generic smart phone model, which must be concretized by a `SmartPhoneVariant` - 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,
|
|
on_delete=models.CASCADE,
|
|
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."),
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = _("Smart Phone")
|
|
verbose_name_plural = _("Smart Phones")
|
|
|
|
default_manager = ProductManager()
|
|
|
|
def get_price(self, request):
|
|
"""
|
|
Return the starting price for instances of this smart phone model.
|
|
"""
|
|
if not hasattr(self, '_price'):
|
|
if self.variants.exists():
|
|
currency = self.variants.first().unit_price.currency
|
|
aggr = self.variants.aggregate(models.Min('unit_price'))
|
|
self._price = MoneyMaker(currency)(aggr['unit_price__min'])
|
|
else:
|
|
self._price = Money()
|
|
return self._price
|
|
|
|
def get_availability(self, request, **kwargs):
|
|
variant = self.get_product_variant(**kwargs)
|
|
return variant.get_availability(request)
|
|
|
|
def deduct_from_stock(self, quantity, **kwargs):
|
|
variant = self.get_product_variant(**kwargs)
|
|
variant.deduct_from_stock(quantity)
|
|
|
|
def is_in_cart(self, cart, watched=False, **kwargs):
|
|
try:
|
|
product_code = kwargs['product_code']
|
|
except KeyError:
|
|
return
|
|
cart_item_qs = CartItem.objects.filter(cart=cart, product=self)
|
|
for cart_item in cart_item_qs:
|
|
if cart_item.product_code == product_code:
|
|
return cart_item
|
|
|
|
def get_product_variant(self, **kwargs):
|
|
try:
|
|
product_code = kwargs.get('product_code')
|
|
return self.variants.get(product_code=product_code)
|
|
except SmartPhoneVariant.DoesNotExist as e:
|
|
raise SmartPhoneModel.DoesNotExist(e)
|
|
|
|
def get_product_variants(self):
|
|
return self.variants.all()
|
|
|
|
|
|
class SmartPhoneVariant(AvailableProductMixin, models.Model):
|
|
product = models.ForeignKey(
|
|
SmartPhoneModel,
|
|
on_delete=models.CASCADE,
|
|
verbose_name=_("Smartphone Model"),
|
|
related_name='variants',
|
|
)
|
|
|
|
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 GB"),
|
|
)
|
|
|
|
quantity = models.PositiveIntegerField(
|
|
_("Quantity"),
|
|
default=0,
|
|
validators=[MinValueValidator(0)],
|
|
help_text=_("Available quantity in stock")
|
|
)
|
|
|
|
def __str__(self):
|
|
return _("{product} with {storage} GB").format(product=self.product, storage=self.storage)
|
|
|
|
def get_price(self, request):
|
|
return self.unit_price
|