from django.db import models from django.contrib.postgres.fields import ArrayField from decimal import * from django.db.models import Count, Min 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, 'Список'), ) class Category(models.Model): title = models.CharField('Наименование', max_length=256, default='') 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') attributes = models.ManyToManyField( 'Attribute', through='AttributeForCategory') 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.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') class Attribute(models.Model): title = models.CharField('Наименование', max_length=256, default='') slug = models.SlugField('Ссылка', max_length=256, default='') 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) 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='') 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='Брэнд') attirbutes = models.ManyToManyField( AttributeForCategory, through='AttributesInProduct') 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(self): 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 return retval 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) def __str__(self): return self.variation 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) def photo_filename(instance, filename): from slugify import slugify_filename return 'photo_uploads/' + slugify_filename(filename) 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)