From 518a3228d17ad2eb3233b19dfc8cbba6d4f12086 Mon Sep 17 00:00:00 2001 From: mitri4 Date: Fri, 3 Feb 2017 02:29:46 +0500 Subject: [PATCH] sort products by availability --- store/managers.py | 2 - store/models.py | 135 +++++++++++++++++++++++++--------------------- store/queryset.py | 15 ++++++ store/views.py | 18 +++++-- 4 files changed, 101 insertions(+), 69 deletions(-) delete mode 100644 store/managers.py create mode 100644 store/queryset.py diff --git a/store/managers.py b/store/managers.py deleted file mode 100644 index faa18be..0000000 --- a/store/managers.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- diff --git a/store/models.py b/store/models.py index 5f1f6aa..749fa4b 100644 --- a/store/models.py +++ b/store/models.py @@ -5,6 +5,7 @@ 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 +from store.queryset import ProductQuerySet FIELD_TYPE_CHAR = 'char' FIELD_TYPE_INT = 'int' @@ -24,6 +25,7 @@ def photo_filename(instance, filename): return 'photo_uploads/' + slugify_filename(filename) + class CartModel(models.Model): cart_id = models.CharField('Сессия', max_length=256, default='') profile = models.ForeignKey(Profile, default=None, null=True, blank=False, @@ -32,6 +34,7 @@ class CartModel(models.Model): weight = models.CharField('Вес', max_length=20, default='0') total = models.CharField('Итого тенге', max_length=20, default='0') + class Category(models.Model): title = models.CharField('Наименование', max_length=256, default='') h1 = models.CharField('Заголовок h1', max_length=256, default='', null=True, blank=True) @@ -40,14 +43,14 @@ class Category(models.Model): meta_text = models.TextField('Мета текст', default='', null=True, blank=True) slug = models.SlugField('Ссылка', max_length=256, default='') description = models.TextField( - 'Описание', default='', null=True, blank=True) + 'Описание', default='', null=True, blank=True) parent = models.ForeignKey( - 'self', default=None, null=True, blank=True, related_name='childs', - verbose_name='Родительская категория') + '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') + 'Attribute', through='AttributeForCategory') priority = models.IntegerField('Приоритет', default=0) class Meta: @@ -91,13 +94,18 @@ class Category(models.Model): def brands(self): brand_qs = self.get_all_products().values( - 'brand').annotate(count_brands=Count('brand')) + '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') + def get_all_products_in_stock_order_by(self, field): + queryset = Product.objects.filter(categories__in=self.get_all_childs()).sorted_in_stock_by_field(field) + + return queryset + class Attribute(models.Model): title = models.CharField('Наименование', max_length=256, default='') @@ -113,15 +121,15 @@ class Attribute(models.Model): class AttributeForCategory(models.Model): category = models.ForeignKey( - Category, verbose_name='Категория', related_name='attr_cat') + Category, verbose_name='Категория', related_name='attr_cat') attribute = models.ForeignKey( - Attribute, verbose_name='Атрибут в категории', - related_name='for_category') + Attribute, verbose_name='Атрибут в категории', + related_name='for_category') field_type = models.CharField( - 'Тип атрибута', choices=FIELD_TYPE_CHOICES, - default='char', max_length=10) + 'Тип атрибута', choices=FIELD_TYPE_CHOICES, + default='char', max_length=10) choises_array = ArrayField( - models.CharField(max_length=50), blank=True, default=[]) + models.CharField(max_length=50), blank=True, default=[]) priority = models.IntegerField('Приоритет', default=0) class Meta: @@ -131,12 +139,12 @@ class AttributeForCategory(models.Model): 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) + 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()) + self.category.title, self.attribute.title, + self.get_field_type_display()) def get_posible_values(self): retval = [] @@ -176,17 +184,19 @@ 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') + Category, verbose_name='Категории', related_name='products') description = models.TextField( - 'Описание', default='', null=True, blank=True) + 'Описание', default='', null=True, blank=True) brand = models.ForeignKey(Brand, verbose_name='Брэнд') video = models.CharField('Код Youtube', max_length=256, default='', null=True, blank=True) attirbutes = models.ManyToManyField( - AttributeForCategory, through='AttributesInProduct') + AttributeForCategory, through='AttributesInProduct') is_leader = models.BooleanField('Лидер продаж', default=False) on_main = models.BooleanField('На главной', default=False) priority = models.IntegerField('Приоритет для главной', default=0) + objects = ProductQuerySet.as_manager() + class Meta: verbose_name = 'товар' verbose_name_plural = 'товары' @@ -254,14 +264,14 @@ class Product(models.Model): class ProductVariation(models.Model): product = models.ForeignKey( - Product, verbose_name='Товар', related_name='variations') + Product, verbose_name='Товар', related_name='variations') variation = models.CharField('Вариация', max_length=512, default='') price = models.IntegerField( - 'Цена (тенге)', default=0, null=False, blank=False) + 'Цена (тенге)', default=0, null=False, blank=False) in_stock = models.IntegerField( - 'В наличии (шт.)', default=1, null=False, blank=False) + 'В наличии (шт.)', default=1, null=False, blank=False) article = models.CharField( - 'Артикул', max_length=32, null=True, blank=True, default='') + 'Артикул', 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) delivery_date = models.CharField('Дата доставки', default=None, blank=True, null=True, max_length=20) @@ -274,10 +284,11 @@ class ProductVariation(models.Model): 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: + # 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: @@ -291,12 +302,12 @@ class ProductVariation(models.Model): class AttributesInProduct(models.Model): attribute = models.ForeignKey( - AttributeForCategory, verbose_name='Атрибут в категории', - related_name='in_product') + AttributeForCategory, verbose_name='Атрибут в категории', + related_name='in_product') product = models.ForeignKey( - Product, verbose_name='Товар', related_name='product_attributes') + Product, verbose_name='Товар', related_name='product_attributes') value = models.CharField( - 'Значение', max_length=64, default=None, blank=True, null=True) + 'Значение', max_length=64, default=None, blank=True, null=True) class Meta: verbose_name = 'атрибут в товаре' @@ -310,7 +321,7 @@ class SlugImageField(models.ImageField): class ImageInProduct(models.Model): image = models.ImageField('Картинка', upload_to=photo_filename) product = models.ForeignKey( - Product, verbose_name='Товар', related_name='images') + Product, verbose_name='Товар', related_name='images') is_main = models.BooleanField('Главная', default=False) class Meta: @@ -331,22 +342,22 @@ STATUS_CHOICES = ( ) KAZPOST_CITIES = ( - (1, "Астана"), - (2, "Актобе"), - (3, "Актау"), - (4, "Алматы"), - (5, "Атырау"), - (6, "Караганда"), - (7, "Кызылорда"), - (8, "Кокшетау"), - (9, "Костанай"), - (10, "Павлодар"), - (11, "Петропавловск"), - (12, "Тараз"), - (13, "Усть-Каменогорск"), - (14, "Уральск"), - (15, "Шымкент"), - (16, "Талдыкорган") + (1, "Астана"), + (2, "Актобе"), + (3, "Актау"), + (4, "Алматы"), + (5, "Атырау"), + (6, "Караганда"), + (7, "Кызылорда"), + (8, "Кокшетау"), + (9, "Костанай"), + (10, "Павлодар"), + (11, "Петропавловск"), + (12, "Тараз"), + (13, "Усть-Каменогорск"), + (14, "Уральск"), + (15, "Шымкент"), + (16, "Талдыкорган") ) @@ -356,28 +367,28 @@ class OrderData(models.Model): profile = models.ForeignKey(Profile, default=None, null=True, blank=False, related_name='orders') first_name = models.CharField( - 'Имя', max_length=64, blank=False, null=False) + 'Имя', max_length=64, blank=False, null=False) last_name = models.CharField( - 'Фамилия', max_length=64, blank=False, null=False) + 'Фамилия', max_length=64, blank=False, null=False) phone = models.CharField( - 'Номер мобильного телефона', max_length=15, blank=False, null=False) + 'Номер мобильного телефона', max_length=15, blank=False, null=False) email = models.EmailField( - 'Email', blank=False, null=False) + 'Email', blank=False, null=False) city = models.CharField( - 'Город Алемтат', max_length=20, blank=True, null=True, choices=alemtat_get_cities_tuple()) + 'Город Алемтат', 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') + 'Почтовая служба', max_length=20, blank=True, null=True, default='alemtat') kazpost_city = models.IntegerField( - 'Город Казпочта', blank=True, null=True, choices=KAZPOST_CITIES) + 'Город Казпочта', blank=True, null=True, choices=KAZPOST_CITIES) address = models.CharField( - 'Адрес', max_length=100, blank=False, null=False) + 'Адрес', max_length=100, blank=False, null=False) deliv_type = models.CharField( - 'Способ Алемтат', max_length=2, blank=True, null=True, default='', - choices=alemtat_get_services_tuple()) + 'Способ Алемтат', max_length=2, blank=True, null=True, default='', + choices=alemtat_get_services_tuple()) items = models.CharField( - 'Товары', max_length=256, blank=False, null=False, default='') + 'Товары', max_length=256, blank=False, null=False, default='') amount = models.FloatField( - 'Сумма', blank=False, null=False, default=0.0) + 'Сумма', blank=False, null=False, default=0.0) status = models.IntegerField('Статус', default=0, null=False, blank=False, choices=STATUS_CHOICES) @@ -387,9 +398,9 @@ class OrderData(models.Model): def __str__(self): return '{} {}: {}'.format( - self.first_name, - self.last_name, - self.amount + self.first_name, + self.last_name, + self.amount ) def get_status(self): diff --git a/store/queryset.py b/store/queryset.py new file mode 100644 index 0000000..b00ff40 --- /dev/null +++ b/store/queryset.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from django.db import models + + +class ProductQuerySet(models.QuerySet): + def sorted_in_stock_by_field(self, field): + queryset = self.extra(select={ + 'have_stock': + 'CASE WHEN (SELECT MAX(in_stock) FROM store_productvariation ' + + 'WHERE store_productvariation.product_id = store_product.id) = 0 ' + + 'THEN 0 ELSE 1 END' + }).order_by('-have_stock', field) + + return queryset diff --git a/store/views.py b/store/views.py index 66e7cc1..7b9a8a8 100644 --- a/store/views.py +++ b/store/views.py @@ -98,25 +98,33 @@ class CategoryView(CategoryBaseView, TemplateView): pass def get_context_data(self, **kwargs): + retval = super(CategoryView, self).get_context_data() q = None + if self.is_search: q = self.request.GET.get('q', '') - self.products_qs = Product.objects.filter(title__icontains=q).order_by('-pk') + self.products_qs = Product.objects.filter(title__icontains=q).\ + sorted_in_stock_by_field('-pk') brands_pks = [item.brand.pk for item in self.products_qs] retval['brands'] = Brand.objects.filter(pk__in=brands_pks).order_by( 'title') # list(set(map(lambda item: item.brand, self.products_qs))) + elif self.is_sale: - self.products_qs = Product.objects.filter(variations__discount__gt=0).distinct().order_by('-pk') + self.products_qs = Product.objects.filter(variations__discount__gt=0).distinct().\ + sorted_in_stock_by_field('-pk') brands_pks = [item.brand.pk for item in self.products_qs] retval['brands'] = Brand.objects.filter(pk__in=brands_pks).order_by('title') + elif self.is_leaders: brands_pks = [item.brand.pk for item in self.products_qs] retval['brands'] = Brand.objects.filter(pk__in=brands_pks).order_by('title') + else: self.category = self._get_full_category(kwargs['categories']) if not self.category.hide_products: - self.products_qs = self.category.get_all_products().order_by('-pk') + self.products_qs = self.category.get_all_products_in_stock_order_by('-pk') + self.brand_pks = self._get_brand_pks() self.sort = self._get_sort() @@ -137,10 +145,10 @@ class CategoryView(CategoryBaseView, TemplateView): '= store_product.id' }) - self.products_qs = preordered_qs.order_by(sort_parameter) + self.products_qs = preordered_qs.order_by('-have_stock', sort_parameter) self._get_filter_attributes() - + # print(self.products_qs.query) self.paginate() retval['category'] = self.category retval['products'] = self.products_qs