You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
419 lines
16 KiB
419 lines
16 KiB
from django.db import models
|
|
from django.contrib.postgres.fields import ArrayField
|
|
from decimal import *
|
|
from django.db.models import Count, Min, Sum
|
|
from accounts.models import Profile
|
|
from main.models import STARS_CHOICES
|
|
from store.alemtat import alemtat_get_cities_tuple, alemtat_get_services_tuple
|
|
|
|
FIELD_TYPE_CHAR = 'char'
|
|
FIELD_TYPE_INT = 'int'
|
|
FIELD_TYPE_DECIMAL = 'decimal'
|
|
FIELD_TYPE_SELECT = 'select'
|
|
|
|
FIELD_TYPE_CHOICES = (
|
|
(FIELD_TYPE_CHAR, 'Строка'),
|
|
(FIELD_TYPE_INT, 'Целое число'),
|
|
(FIELD_TYPE_DECIMAL, 'Дробное число'),
|
|
(FIELD_TYPE_SELECT, 'Список'),
|
|
)
|
|
|
|
|
|
def photo_filename(instance, filename):
|
|
from slugify import slugify_filename
|
|
|
|
return 'photo_uploads/' + slugify_filename(filename)
|
|
|
|
|
|
class Category(models.Model):
|
|
title = models.CharField('Наименование', max_length=256, default='')
|
|
h1 = models.CharField('Заголовок h1', max_length=256, default='', null=True, blank=True)
|
|
meta_keyw = models.TextField('Мета ключевые слова', default='', null=True, blank=True)
|
|
meta_desc = models.TextField('Мета описание', default='', null=True, blank=True)
|
|
meta_text = models.TextField('Мета текст', default='', null=True, blank=True)
|
|
slug = models.SlugField('Ссылка', max_length=256, default='')
|
|
description = models.TextField(
|
|
'Описание', default='', null=True, blank=True)
|
|
parent = models.ForeignKey(
|
|
'self', default=None, null=True, blank=True, related_name='childs',
|
|
verbose_name='Родительская категория')
|
|
hide_products = models.BooleanField('Показывать дочерние категории вместо товаров', default=False)
|
|
image = models.ImageField('Картинка', upload_to=photo_filename, default=None, null=True, blank=True)
|
|
attributes = models.ManyToManyField(
|
|
'Attribute', through='AttributeForCategory')
|
|
priority = models.IntegerField('Приоритет', default=0)
|
|
|
|
class Meta:
|
|
verbose_name = 'категория'
|
|
verbose_name_plural = 'категории'
|
|
|
|
def __str__(self):
|
|
titles = []
|
|
for p in self.get_parents():
|
|
titles.append(p.title)
|
|
if titles:
|
|
return '{} > {}'.format(' > '.join(titles), self.title)
|
|
else:
|
|
return self.title
|
|
|
|
def get_absolute_url(self):
|
|
retval = '/store/'
|
|
for p in self.get_parents():
|
|
retval += p.slug + '/'
|
|
retval += self.slug + '/'
|
|
return retval
|
|
|
|
def get_parents(self):
|
|
retval = []
|
|
current_category = self.parent
|
|
while current_category is not None:
|
|
retval.append(current_category)
|
|
current_category = current_category.parent
|
|
retval.reverse()
|
|
return retval
|
|
|
|
def get_all_products(self):
|
|
return Product.objects.filter(categories__in=self.get_all_childs())
|
|
|
|
def get_all_childs(self, include_self=True):
|
|
r = ()
|
|
r += (self,)
|
|
for c in self.childs.all():
|
|
r += c.get_all_childs(include_self=False)
|
|
return r
|
|
|
|
def brands(self):
|
|
brand_qs = self.get_all_products().values(
|
|
'brand').annotate(count_brands=Count('brand'))
|
|
brand_pks = [v['brand'] for v in brand_qs]
|
|
return Brand.objects.filter(pk__in=brand_pks).order_by('title')
|
|
|
|
def get_childs_ordered_by_count(self):
|
|
return self.childs.annotate(Count('childs')).order_by('priority') # , '-childs__count')
|
|
|
|
|
|
class Attribute(models.Model):
|
|
title = models.CharField('Наименование', max_length=256, default='')
|
|
slug = models.SlugField('Ссылка', max_length=256, default='')
|
|
|
|
class Meta:
|
|
verbose_name = 'атрибут'
|
|
verbose_name_plural = 'атрибуты'
|
|
|
|
def __str__(self):
|
|
return self.title
|
|
|
|
|
|
class AttributeForCategory(models.Model):
|
|
category = models.ForeignKey(
|
|
Category, verbose_name='Категория', related_name='attr_cat')
|
|
attribute = models.ForeignKey(
|
|
Attribute, verbose_name='Атрибут в категории',
|
|
related_name='for_category')
|
|
field_type = models.CharField(
|
|
'Тип атрибута', choices=FIELD_TYPE_CHOICES,
|
|
default='char', max_length=10)
|
|
choises_array = ArrayField(
|
|
models.CharField(max_length=50), blank=True, default=[])
|
|
priority = models.IntegerField('Приоритет', default=0)
|
|
|
|
class Meta:
|
|
verbose_name = 'атрибут в категории'
|
|
verbose_name_plural = 'атрибуты в категориях'
|
|
|
|
def __str__(self):
|
|
if self.field_type == FIELD_TYPE_SELECT:
|
|
return '{}: {} ({}: {})'.format(
|
|
self.category.title, self.attribute.title,
|
|
self.get_field_type_display(), self.choises_array)
|
|
else:
|
|
return '{}: {} ({})'.format(
|
|
self.category.title, self.attribute.title,
|
|
self.get_field_type_display())
|
|
|
|
def get_posible_values(self):
|
|
retval = []
|
|
for val in self.in_product.all():
|
|
if self.field_type == FIELD_TYPE_INT:
|
|
if int(val.value) not in retval:
|
|
retval.append(int(val.value))
|
|
elif self.field_type == FIELD_TYPE_DECIMAL:
|
|
if Decimal(val.value) not in retval:
|
|
retval.append(Decimal(val.value))
|
|
elif self.field_type == FIELD_TYPE_CHAR:
|
|
if val.value not in retval:
|
|
retval.append(val.value)
|
|
retval.sort()
|
|
return retval
|
|
|
|
|
|
class Brand(models.Model):
|
|
title = models.CharField('Наименование', max_length=256, default='')
|
|
slug = models.SlugField('Ссылка', max_length=256, default='')
|
|
image = models.ImageField('Логотип (60px в высоту)', upload_to=photo_filename, default=None, blank=True, null=True)
|
|
|
|
class Meta:
|
|
verbose_name = 'бренд'
|
|
verbose_name_plural = 'бренды'
|
|
|
|
def __str__(self):
|
|
return self.title
|
|
|
|
|
|
# class Image(models.Model):
|
|
# image = models.ImageField('Картинка',
|
|
# upload_to=os.path.join(settings.BASE_DIR, "static", 'uploads'))
|
|
|
|
|
|
class Product(models.Model):
|
|
title = models.CharField('Наименование', max_length=256, default='')
|
|
slug = models.SlugField('Ссылка', max_length=256, default='')
|
|
categories = models.ManyToManyField(
|
|
Category, verbose_name='Категории', related_name='products')
|
|
description = models.TextField(
|
|
'Описание', default='', null=True, blank=True)
|
|
brand = models.ForeignKey(Brand, verbose_name='Брэнд')
|
|
video = models.CharField('Код Youtube', max_length=256, default='')
|
|
attirbutes = models.ManyToManyField(
|
|
AttributeForCategory, through='AttributesInProduct')
|
|
is_leader = models.BooleanField('Лидер продаж', default=False)
|
|
on_main = models.BooleanField('На главной', default=False)
|
|
priority = models.IntegerField('Приоритет для главной', default=0)
|
|
|
|
class Meta:
|
|
verbose_name = 'товар'
|
|
verbose_name_plural = 'товары'
|
|
|
|
def __str__(self):
|
|
return self.title
|
|
|
|
def main_image(self):
|
|
try:
|
|
return self.images.order_by('-is_main')[0].image
|
|
except:
|
|
return None
|
|
|
|
def in_stock(self):
|
|
return self.variations.filter(in_stock__gt=0).count()
|
|
|
|
def min_price_variation(self):
|
|
min_v = None
|
|
for v in self.variations.all():
|
|
if not min_v:
|
|
min_v = v
|
|
else:
|
|
if v.get_price() < min_v.get_price():
|
|
min_v = v
|
|
return min_v
|
|
|
|
def min_price(self, profile=None):
|
|
return self.min_price_variation().get_price(profile)
|
|
|
|
def is_discount(self):
|
|
return self.variations.filter(discount__gt=0).count() > 0
|
|
|
|
def get_stars(self):
|
|
fb_count = self.feedback.count()
|
|
fb_sum = self.feedback.aggregate(Sum('stars'))
|
|
if fb_count:
|
|
return int(fb_sum / fb_count)
|
|
else:
|
|
return 0
|
|
|
|
# retval = self.variations.filter(in_stock__gt=0).aggregate(
|
|
# Min('price'))['price__min'] or 0
|
|
# if not retval:
|
|
# retval = self.variations.aggregate(Min('price'))['price__min'] or 0
|
|
#
|
|
# if retval > 10000:
|
|
# return int(retval * Decimal('.95'))
|
|
# else:
|
|
# return int(retval * Decimal('.93'))
|
|
|
|
def get_absolute_url(self):
|
|
retval = '/store/'
|
|
main_category = self.categories.last()
|
|
for p in main_category.get_parents():
|
|
retval += p.slug + '/'
|
|
retval += main_category.slug + '/product-' + self.slug
|
|
return retval
|
|
|
|
|
|
class ProductVariation(models.Model):
|
|
product = models.ForeignKey(
|
|
Product, verbose_name='Товар', related_name='variations')
|
|
variation = models.CharField('Вариация', max_length=512, default='')
|
|
price = models.IntegerField(
|
|
'Цена (тенге)', default=0, null=False, blank=False)
|
|
in_stock = models.IntegerField(
|
|
'В наличии (шт.)', default=1, null=False, blank=False)
|
|
article = models.CharField(
|
|
'Артикул', max_length=32, null=True, blank=True, default='')
|
|
weight = models.FloatField('Вес (кг)', default=0.1, null=False, blank=False)
|
|
discount = models.IntegerField('Скидка %', default=0, blank=False, null=False)
|
|
|
|
class Meta:
|
|
verbose_name = 'разновидность товара'
|
|
verbose_name_plural = 'разновидности товаров'
|
|
|
|
def __str__(self):
|
|
return self.variation
|
|
|
|
def get_price(self, profile=None):
|
|
#import pdb; pdb.set_trace()
|
|
#from django.core.mail import mail_admins
|
|
#mail_admins('auth', '{} {}'.format(profile, self.product.brand.slug))
|
|
if profile and profile.is_authenticated() and self.product.brand.slug in ['beuchat', 'scorpena'] and profile.sale:
|
|
return int(self.price - (self.price / Decimal(100) * Decimal(profile.sale)))
|
|
else:
|
|
if self.discount:
|
|
return int(self.price - (self.price / 100 * self.discount))
|
|
else:
|
|
if self.price > 10000:
|
|
return int(self.price * Decimal('.95'))
|
|
else:
|
|
return int(self.price * Decimal('.93'))
|
|
|
|
|
|
class AttributesInProduct(models.Model):
|
|
attribute = models.ForeignKey(
|
|
AttributeForCategory, verbose_name='Атрибут в категории',
|
|
related_name='in_product')
|
|
product = models.ForeignKey(
|
|
Product, verbose_name='Товар', related_name='product_attributes')
|
|
value = models.CharField(
|
|
'Значение', max_length=64, default=None, blank=True, null=True)
|
|
|
|
class Meta:
|
|
verbose_name = 'атрибут в товаре'
|
|
verbose_name_plural = 'атрибуты в товарах'
|
|
|
|
|
|
class SlugImageField(models.ImageField):
|
|
pass
|
|
|
|
|
|
class ImageInProduct(models.Model):
|
|
image = models.ImageField('Картинка', upload_to=photo_filename)
|
|
product = models.ForeignKey(
|
|
Product, verbose_name='Товар', related_name='images')
|
|
is_main = models.BooleanField('Главная', default=False)
|
|
|
|
class Meta:
|
|
verbose_name = 'фото товара'
|
|
verbose_name_plural = 'фото товаров'
|
|
|
|
def __str__(self):
|
|
return '{}: {}'.format(self.pk, self.product.title)
|
|
|
|
|
|
STATUS_CHOICES = (
|
|
(0, 'Ожидает оплаты'),
|
|
(1, 'Формирование посылки'),
|
|
(2, 'Ожидает отправки'),
|
|
(3, 'Отправлено'),
|
|
(4, 'Отменен'),
|
|
(5, 'Выполнен'),
|
|
)
|
|
|
|
KAZPOST_CITIES = (
|
|
(1, "Астана"),
|
|
(2, "Актобе"),
|
|
(3, "Актау"),
|
|
(4, "Алматы"),
|
|
(5, "Атырау"),
|
|
(6, "Караганда"),
|
|
(7, "Кызылорда"),
|
|
(8, "Кокшетау"),
|
|
(9, "Костанай"),
|
|
(10, "Павлодар"),
|
|
(11, "Петропавловск"),
|
|
(12, "Тараз"),
|
|
(13, "Усть-Каменогорск"),
|
|
(14, "Уральск"),
|
|
(15, "Шымкент"),
|
|
(16, "Талдыкорган")
|
|
)
|
|
|
|
|
|
class OrderData(models.Model):
|
|
created = models.DateTimeField('Дата и время', auto_now_add=True, editable=False)
|
|
edited = models.DateTimeField('Изменено', auto_now=True, editable=False)
|
|
profile = models.ForeignKey(Profile, default=None, null=True, blank=False,
|
|
related_name='orders')
|
|
first_name = models.CharField(
|
|
'Имя', max_length=64, blank=False, null=False)
|
|
last_name = models.CharField(
|
|
'Фамилия', max_length=64, blank=False, null=False)
|
|
phone = models.CharField(
|
|
'Номер мобильного телефона', max_length=15, blank=False, null=False)
|
|
email = models.EmailField(
|
|
'Email', blank=False, null=False)
|
|
city = models.CharField(
|
|
'Город Алемтат', max_length=20, blank=True, null=True, choices=alemtat_get_cities_tuple())
|
|
deliv_service = models.CharField(
|
|
'Почтовая служба', max_length=20, blank=True, null=True, default='alemtat')
|
|
kazpost_city = models.IntegerField(
|
|
'Город Казпочта', blank=True, null=True, choices=KAZPOST_CITIES)
|
|
address = models.CharField(
|
|
'Адрес', max_length=100, blank=False, null=False)
|
|
deliv_type = models.CharField(
|
|
'Способ Алемтат', max_length=2, blank=True, null=True, default='',
|
|
choices=alemtat_get_services_tuple())
|
|
items = models.CharField(
|
|
'Товары', max_length=256, blank=False, null=False, default='')
|
|
amount = models.FloatField(
|
|
'Сумма', blank=False, null=False, default=0.0)
|
|
status = models.IntegerField('Статус', default=0, null=False, blank=False,
|
|
choices=STATUS_CHOICES)
|
|
|
|
class Meta:
|
|
verbose_name = 'заказ'
|
|
verbose_name_plural = 'заказы'
|
|
|
|
def __str__(self):
|
|
return '{} {}: {}'.format(
|
|
self.first_name,
|
|
self.last_name,
|
|
self.amount
|
|
)
|
|
|
|
def get_status(self):
|
|
return 'Ожидает оплаты'
|
|
|
|
def get_items(self):
|
|
retval = ()
|
|
for item in eval(self.items):
|
|
variation_id = list(item.keys())[0]
|
|
variation_count = list(item.values())[0]
|
|
variation = ProductVariation.objects.get(pk=variation_id)
|
|
retval += ((variation, variation_count),)
|
|
return retval
|
|
|
|
def get_city(self):
|
|
if self.city:
|
|
return self.get_city_display()
|
|
else:
|
|
return self.get_kazpost_city_display()
|
|
|
|
def get_deliv_type(self):
|
|
if self.deliv_type:
|
|
return self.get_deliv_type_display()
|
|
else:
|
|
return 'Авиапочта EMS'
|
|
|
|
|
|
class ProductFeedback(models.Model):
|
|
created = models.DateTimeField('Дата и время', auto_now_add=True, editable=False)
|
|
name = models.CharField('Имя', max_length=45, null=False, blank=False)
|
|
email = models.EmailField('Email (не будет опубликован)', null=False, blank=False)
|
|
text = models.TextField('Комментарий', null=False, blank=False)
|
|
stars = models.IntegerField('Оценка', default=5, choices=STARS_CHOICES)
|
|
product = models.ForeignKey(Product, related_name='feedback')
|
|
|
|
class Meta:
|
|
verbose_name = 'отзыв о товаре'
|
|
verbose_name_plural = 'отзывы о товарах'
|
|
|
|
def __str__(self):
|
|
return '{}: {}'.format(self.name, self.product.title)
|
|
|