update products

remotes/origin/HEAD
Max Yakovenko 8 years ago
parent 0d5d465d59
commit 53dd4b4a16
  1. 197
      products/admin.py
  2. 98
      products/context_processors.py
  3. 70
      products/fixtures/category.json
  4. 1
      products/fixtures/manufacturer.json
  5. 533
      products/fixtures/products.json
  6. 150
      products/forms.py
  7. 0
      products/middleware.py
  8. 258
      products/models.py
  9. 0
      products/templatetags/__init__.py
  10. 7
      products/templatetags/product_tags.py
  11. 61
      products/templatetags/products_filters.py
  12. 19
      products/urls.py
  13. 194
      products/views.py

@ -1,12 +1,20 @@
from django.contrib import admin
from django.forms import ALL_FIELDS
from django.utils.translation import ugettext_lazy as _
from import_export import resources, fields, widgets
from import_export.admin import ImportExportModelAdmin
from jet.admin import CompactInline
from rangefilter.filter import DateRangeFilter, DateTimeRangeFilter
from .models import Product, ProductCategory, ProductImage, ProductAttribute, ProductAttributeValue, Manufacturer
from cart.admin import ProductOfferInlineAdmin
from core.admin import SafeModelAdmin
from .forms import ProductAdminForm
from .models import (
Product, ProductCategory, ProductImage, Manufacturer
)
#
class CustomModelResource(resources.ModelResource):
def before_import_row(self, row, **kwargs):
"""
@ -27,77 +35,45 @@ class CustomManyToManyWidget(widgets.ManyToManyWidget):
return self.model.objects.get(name=t1) if t1 else None
#
#
# # class CustomForeignKeyWidget(widgets.ForeignKeyWidget):
# # def clean(self, value, row=None, *args, **kwargs):
# # return self.model.objects.get_or_create(name=value)[0]
#
class ProductImageInline(admin.TabularInline):
model = ProductImage
extra = 0
#
# # class ProductAttributeInline(admin.TabularInline):
# # model = ProductAttribute
# # extra = 1
# # verbose_name_plural = 'ProductAttribute'
# # suit_classes = 'suit-tab suit-tab-PA'
# #
class AttributeChoiceValueInline(admin.TabularInline):
model = ProductAttributeValue
# prepopulated_fields = {'slug': ('name',)}
extra = 1
verbose_name_plural = 'AttributeChoiceValue'
suit_classes = 'suit-tab suit-tab-ACV'
# class AttributeValueInline(admin.TabularInline):
# model = ProductAttributeValue
# form = ProductAttributeValueAdminForm
# extra = 1
# verbose_name_plural = _('Значение аттрибута')
# can_delete = True
# #
# # class OfferInline(admin.TabularInline):
# # model = Offer
# # extra = 1
# # verbose_name_plural = 'Offers'
# # suit_classes = 'suit-tab suit-tab-offers'
#
@admin.register(ProductCategory)
class ProductCategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'slug')
class ProductCategoryAdmin(SafeModelAdmin):
list_display = ('name', 'slug', 'parent', 'status')
search_fields = ('name', 'slug')
list_filter = ('status',('create_at',DateRangeFilter),('updated_at', DateTimeRangeFilter))
list_filter = ('status', ('create_at', DateRangeFilter), ('updated_at', DateTimeRangeFilter))
# # class AttributeChoiceValueAdmin(admin.ModelAdmin):
# # list_display = [field.name for field in ProductCategory._meta.fields]
# #
# # class Meta:
# # model = AttributeChoiceValue
# #
# # admin.site.register(AttributeChoiceValue, AttributeChoiceValueAdmin)
@admin.register(ProductAttribute)
class ProductAttributeAdmin(admin.ModelAdmin):
list_display = ('slug','name')
search_fields = ('slug','name')
inlines = [AttributeChoiceValueInline]
# prepopulated_fields = {'slug': ('name',)}
suit_form_tabs = (('general', 'General'),
('ACV', 'AttributeValues'),)
class Meta:
model = ProductAttribute
# @admin.register(ProductAttribute)
# class ProductAttributeAdmin(ImportExportModelAdmin, SafeModelAdmin):
# inlines = [
# # AttributeValueInline
# ]
# list_display = ('name', 'slug')
# search_fields = ('name', 'slug')
@admin.register(Manufacturer)
class ManufacturerAdmin(admin.ModelAdmin):
list_display = ('slug', 'name')
search_fields = ('slug', 'name'),
class ManufacturerAdmin(ImportExportModelAdmin, SafeModelAdmin):
list_display = ('name', 'slug', 'status')
search_fields = ('name', 'slug',)
list_filter = ('status',)
class Meta:
model = Manufacturer
#
#
class ProductResource(CustomModelResource):
# id = fields.Field(default=generate_Jid(prefix='J'),
# readonly=True,
@ -108,52 +84,31 @@ class ProductResource(CustomModelResource):
default=None,
widget=widgets.CharWidget(),
)
# price = fields.Field(column_name='price', attribute='price',
# default=0,
# widget=widgets.DecimalWidget(),
# )
description = fields.Field(column_name='description', attribute='description',
default=None,
widget=widgets.CharWidget(),
)
# producer = fields.Field(column_name='producer', attribute='producer',
# default=None,
# widget=widgets.CharWidget(),
# )
category = fields.Field(column_name='category', attribute='category',
category = fields.Field(column_name='category', attribute='parent',
default=None,
widget=widgets.ForeignKeyWidget(ProductCategory, field='name'),
)
producer = fields.Field(column_name='producer', attribute='producer',
widget=widgets.ForeignKeyWidget(ProductCategory, field='name'))
producer = fields.Field(column_name='producer',
attribute='producer',
default=None,
widget=widgets.ForeignKeyWidget(Manufacturer, field='name'),
)
attributes = fields.Field(column_name='attributes', attribute='attributes',
default=None,
widget=CustomManyToManyWidget(ProductAttribute, field="name"),
)
is_active = fields.Field(column_name='is_active', attribute='is_active',
default=1,
widget=widgets.BooleanWidget())
discount_policy = fields.Field(column_name='discount_policy', attribute='discount_policy',
widget=widgets.ForeignKeyWidget(Manufacturer, field='name'))
discount_policy = fields.Field(column_name='discount_policy',
attribute='discount_policy',
default={},
widget=widgets.CharWidget())
# delete = fields.Field(column_name='delete', attribute='delete',
# default=0,
# widget=widgets.BooleanWidget())
# def for_delete(self, row, instance):
# return self.fields['delete'].clean(row)
class Meta:
model = Product
fields = ('id', 'name', 'description', 'producer', 'category', 'is_active', 'attributes', 'discount_policy')
fields = ('id', 'name', 'description', 'producer', 'parent', 'is_active', 'attributes', 'discount_policy')
export_order = (
'id', 'name', 'producer', 'is_active', 'category', 'attributes', 'description', 'discount_policy')
'id', 'name', 'producer', 'is_active', 'parent', 'attributes', 'description', 'discount_policy')
# import_id_fields = ('name',)
def dehydrate_str_choices(self, obj):
@ -161,53 +116,23 @@ class ProductResource(CustomModelResource):
return obj.str_choices()
#
@admin.register(Product)
class ProductAdmin(ImportExportModelAdmin):
list_display = ['id', 'name', 'category', 'manufacturer', 'status']
list_filter = ['status', 'create_at', 'updated_at', 'category']
search_fields = ['name', 'id']
resource_class = ProductResource
# class OfferResource(CustomModelResource):
# name = fields.Field(column_name='name', attribute='name',
# default=None,
# widget=widgets.CharWidget(),
# )
#
# price = fields.Field(column_name='price', attribute='price',
# default=0,
# widget=widgets.DecimalWidget(),
# )
#
# products = fields.Field(column_name='products', attribute='products',
# widget=widgets.ForeignKeyWidget(Product, field='name'),
# )
#
# is_active = fields.Field(column_name='is_active', attribute='is_active',
# default=1,
# widget=widgets.BooleanWidget())
#
# attributes = fields.Field(column_name='attributes', attribute='attributes',
# default={},
# widget=widgets.CharWidget())
#
# class Meta:
# model = Offer
# fields = ('name', 'products', 'price', 'is_active', 'attributes')
# export_order = ('name', 'products', 'attributes', 'is_active', 'price')
# import_id_fields = ('name',)
# class OfferAdmin(ImportExportModelAdmin):
# list_display = ['id', 'name', 'products', 'price', 'is_active', 'attributes']
# resource_class = OfferResource
@admin.register(ProductImage)
class ProductImageAdmin(admin.ModelAdmin):
list_display = [field.name for field in ProductImage._meta.fields]
class ProductImageInlineAdmin(CompactInline):
model = ProductImage
exclude = ('filename',)
extra = 1
show_change_link = True
can_delete = True
class Meta:
model = ProductImage
@admin.register(Product)
class ProductAdmin(ImportExportModelAdmin, SafeModelAdmin):
inlines = (
ProductImageInlineAdmin,
ProductOfferInlineAdmin,
)
list_display = ('name', 'parent', 'manufacturer', 'status')
list_filter = ('status', 'create_at', 'updated_at', 'parent')
search_fields = ('name', 'id',)
readonly_fields = ('slug',)
resource_class = ProductResource
form = ProductAdminForm

@ -1,16 +1,25 @@
from django.urls import reverse_lazy
from products.forms import ProductSearchForm
from products.models import ProductCategory
from cart.forms import ProductOfferPriceFilterForm, ProductOfferSupplyTypeFilterForm, ProductOfferSupplyTargetFilterForm
from products.forms import (
ProductSearchForm, ProductManufacturerFilterForm
)
from products.models import ProductCategory, Manufacturer, Product
def product_search_form(request):
#@TODO: APPLY SEARCH IN THE CONTEXT OF CHOSSEN DIRECTORY AND SET OF FILTERS
left_product_search_form = ProductSearchForm(submit_css_class='left-menu__search-btn')
content_product_search_form = ProductSearchForm(submit_css_class='content__search-btn')
# @TODO: APPLY SEARCH IN THE CONTEXT OF SET OF FILTERS
if not "products" in request.resolver_match.view_name:
product_form_action = {'viewname': 'products:product_list', 'kwargs': {}}
else:
product_form_action = {'viewname': request.resolver_match.view_name, 'kwargs': request.resolver_match.kwargs}
left_product_search_form = ProductSearchForm(product_form_action=product_form_action,
submit_css_class='left-menu__search-btn')
content_product_search_form = ProductSearchForm(product_form_action=product_form_action,
submit_css_class='content__search-btn')
if ProductSearchForm.form_action in request.resolver_match.view_name:
if request.resolver_match.kwargs.get('category_slug'):
product_kwargs= request.resolver_match.kwargs
product_kwargs = request.resolver_match.kwargs
product_list_in_cat = reverse_lazy(ProductSearchForm.form_action, kwargs=product_kwargs)
left_product_search_form.helper.form_action = product_list_in_cat
content_product_search_form.helper.form_action = product_list_in_cat
@ -19,17 +28,82 @@ def product_search_form(request):
left_product_search_form.initial = initial_data
content_product_search_form.initial = initial_data
return {
'left_product_search_form': left_product_search_form,
'content_product_search_form': content_product_search_form
}
def product_root_categories(request):
current_category = request.resolver_match.kwargs.get('category_slug',None)
current_category = ProductCategory.objects.filter(slug=current_category).first()
def product_fitler_formset(request):
if not "products" in request.resolver_match.view_name:
product_form_action = {'viewname': 'products:product_list', 'kwarg': {}}
else:
product_form_action = {'viewname': request.resolver_match.view_name, 'kwargs': request.resolver_match.kwargs}
return {
'left_product_filter_formset':{
'manufacturer': ProductManufacturerFilterForm(
product_form_action=product_form_action,
query_params=request.GET
),
'price': ProductOfferPriceFilterForm(
product_form_action=product_form_action,
query_params=request.GET
),
'supply_type': ProductOfferSupplyTypeFilterForm(
product_form_action=product_form_action,
query_params=request.GET
),
'supply_target': ProductOfferSupplyTargetFilterForm(
product_form_action=product_form_action,
query_params=request.GET
)
}
}
def product_manufacture_list(request):
man_qs = Manufacturer.active
if request.resolver_match.kwargs.get('category_slug'):
cat_qs = ProductCategory.objects.filter(slug__exact=request.resolver_match.kwargs.get('category_slug'))
prod_qs = Product.active.filter(category__in=cat_qs.get_descendants(include_self=True))
man_qs = man_qs.filter(pk__in=prod_qs.distinct('manufacturer__pk').all())
return {'manufacturer_list': man_qs.all()}
def product_categories(request):
current_category_path = request.resolver_match.kwargs.get('path', '')
current_category = None
try:
current_category_slug = current_category_path.split('/')[-2] # slug of the instance
except IndexError:
current_category_slug = None
if ProductCategory.active.filter(slug__exact=current_category_slug).exists():
current_category = ProductCategory.objects.filter(slug=current_category_slug).first()
if current_category:
descendant_categories = current_category.get_descendants()
else:
descendant_categories = ProductCategory.active.root_nodes()
return {
'hasCategories': len(descendant_categories) > 0 or current_category,
'product_categories': descendant_categories,
'the_product_category': current_category
}
def product_detail(request):
product_path = request.resolver_match.kwargs.get('path', '')
try:
product_slug = product_path.split('/')[-2] # slug of the instance
except IndexError:
product_slug = None
product = Product.active.filter(slug__exact=product_slug).first()
return {
'product_categories': ProductCategory.active.get_categories(current_category),
'the_product_category' : current_category
'the_product': product
}

@ -0,0 +1,70 @@
[
{
"model": "products.productcategory",
"pk": 1,
"fields": {
"create_at": "2018-08-12T19:23:11.150Z",
"updated_at": "2018-08-12T19:23:33.159Z",
"name": "Microsoft",
"slug": "windows",
"parent": null,
"image": "products/windows/windows.svg",
"status": 25,
"lft": 1,
"rght": 2,
"tree_id": 1,
"level": 0
}
},
{
"model": "products.productcategory",
"pk": 2,
"fields": {
"create_at": "2018-08-12T19:23:49.581Z",
"updated_at": "2018-08-12T19:23:49.581Z",
"name": "\u0410\u043d\u0442\u0438\u0432\u0438\u0440\u0443\u0441",
"slug": "antivirus",
"parent": null,
"image": "products/antivirus/antivirus_oDon9N3.svg",
"status": 25,
"lft": 1,
"rght": 2,
"tree_id": 2,
"level": 0
}
},
{
"model": "products.productcategory",
"pk": 3,
"fields": {
"create_at": "2018-08-12T19:23:59.394Z",
"updated_at": "2018-08-12T19:23:59.394Z",
"name": "\u0412\u0438\u0440\u0442\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f",
"slug": "virtualizatsiya",
"parent": null,
"image": "products/virtualizatsiya/virtualization_tEnq2Bu.svg",
"status": 25,
"lft": 1,
"rght": 4,
"tree_id": 3,
"level": 0
}
},
{
"model": "products.productcategory",
"pk": 4,
"fields": {
"create_at": "2018-08-12T20:05:06.726Z",
"updated_at": "2018-08-12T20:05:06.726Z",
"name": "Oracle",
"slug": "oracle",
"parent": 3,
"image": "",
"status": 25,
"lft": 2,
"rght": 3,
"tree_id": 3,
"level": 1
}
}
]

@ -0,0 +1 @@
[{"model": "products.manufacturer", "pk": 1, "fields": {"create_at": "2018-08-12T19:24:12.126Z", "updated_at": "2018-08-12T19:24:12.126Z", "name": "Microsoft", "slug": "microsoft", "image": "", "status": 25}}, {"model": "products.manufacturer", "pk": 2, "fields": {"create_at": "2018-08-12T19:24:18.725Z", "updated_at": "2018-08-12T19:24:18.725Z", "name": "Parallels", "slug": "parallels", "image": "", "status": 25}}, {"model": "products.manufacturer", "pk": 3, "fields": {"create_at": "2018-08-12T19:24:31.655Z", "updated_at": "2018-08-12T19:24:31.655Z", "name": "Eset", "slug": "eset", "image": "", "status": 25}}]

@ -1,533 +0,0 @@
[
{
"model": "products.productcategory",
"pk": 1,
"fields": {
"name": "Information security",
"slug": "information-security",
"is_active": true,
"parent": null,
"lft": 1,
"rght": 6,
"tree_id": 2,
"level": 0
}
},
{
"model": "products.productcategory",
"pk": 2,
"fields": {
"name": "Document manipulation",
"slug": "document-manipulation",
"is_active": true,
"parent": null,
"lft": 1,
"rght": 4,
"tree_id": 1,
"level": 0
}
},
{
"model": "products.productcategory",
"pk": 3,
"fields": {
"name": "System software",
"slug": "system-software",
"is_active": true,
"parent": null,
"lft": 1,
"rght": 6,
"tree_id": 3,
"level": 0
}
},
{
"model": "products.productcategory",
"pk": 4,
"fields": {
"name": "Anti-virus software",
"slug": "anti-virus-software",
"is_active": true,
"parent": 1,
"lft": 2,
"rght": 3,
"tree_id": 2,
"level": 1
}
},
{
"model": "products.productcategory",
"pk": 5,
"fields": {
"name": "Data recovery",
"slug": "data-recovery",
"is_active": true,
"parent": 1,
"lft": 4,
"rght": 5,
"tree_id": 2,
"level": 1
}
},
{
"model": "products.productcategory",
"pk": 6,
"fields": {
"name": "OS",
"slug": "os",
"is_active": true,
"parent": 3,
"lft": 4,
"rght": 5,
"tree_id": 3,
"level": 1
}
},
{
"model": "products.productcategory",
"pk": 7,
"fields": {
"name": "DB",
"slug": "db",
"is_active": true,
"parent": 3,
"lft": 2,
"rght": 3,
"tree_id": 3,
"level": 1
}
},
{
"model": "products.productcategory",
"pk": 8,
"fields": {
"name": "Microsoft Office",
"slug": "microsoft-office",
"is_active": true,
"parent": 2,
"lft": 2,
"rght": 3,
"tree_id": 1,
"level": 1
}
},
{
"model": "products.productattribute",
"pk": 1,
"fields": {
"name": "License type",
"slug": "license-type"
}
},
{
"model": "products.productattribute",
"pk": 2,
"fields": {
"name": "License term",
"slug": "license-term"
}
},
{
"model": "products.productattribute",
"pk": 3,
"fields": {
"name": "Technical support",
"slug": "technical-support"
}
},
{
"model": "products.productattribute",
"pk": 4,
"fields": {
"name": "Number users",
"slug": "number-users"
}
},
{
"model": "products.productattribute",
"pk": 5,
"fields": {
"name": "Type of organization",
"slug": "type-organization"
}
},
{
"model": "products.attributechoicevalue",
"pk": 1,
"fields": {
"name": "New",
"slug": "new",
"attribute": 1
}
},
{
"model": "products.attributechoicevalue",
"pk": 2,
"fields": {
"name": "Prolongation",
"slug": "prolongation",
"attribute": 1
}
},
{
"model": "products.attributechoicevalue",
"pk": 3,
"fields": {
"name": "Migration",
"slug": "migration",
"attribute": 1
}
},
{
"model": "products.attributechoicevalue",
"pk": 4,
"fields": {
"name": "One year",
"slug": "one-year",
"attribute": 2
}
},
{
"model": "products.attributechoicevalue",
"pk": 5,
"fields": {
"name": "Two years",
"slug": "two-years",
"attribute": 2
}
},
{
"model": "products.attributechoicevalue",
"pk": 6,
"fields": {
"name": "Standard AAS",
"slug": "standard-aas",
"attribute": 3
}
},
{
"model": "products.attributechoicevalue",
"pk": 7,
"fields": {
"name": "Extended AAP",
"slug": "extended-aap",
"attribute": 3
}
},
{
"model": "products.attributechoicevalue",
"pk": 8,
"fields": {
"name": "from 1 to 49",
"slug": "1-49",
"attribute": 4
}
},
{
"model": "products.attributechoicevalue",
"pk": 9,
"fields": {
"name": "from 50 to 99",
"slug": "50-99",
"attribute": 4
}
},
{
"model": "products.attributechoicevalue",
"pk": 10,
"fields": {
"name": "from 100 to 299",
"slug": "100-299",
"attribute": 4
}
},
{
"model": "products.attributechoicevalue",
"pk": 11,
"fields": {
"name": "Version Upgrade",
"slug": "version-upgrade",
"attribute": 1
}
},
{
"model": "products.attributechoicevalue",
"pk": 12,
"fields": {
"name": "commercial",
"slug": "commercial",
"attribute": 5
}
},
{
"model": "products.attributechoicevalue",
"pk": 13,
"fields": {
"name": "educational",
"slug": "educational",
"attribute": 5
}
},
{
"model": "products.productclass",
"pk": 1,
"fields": {
"name": "Antivirus software class",
"has_variants": true,
"variant_attributes": [
2,
1,
5
]
}
},
{
"model": "products.productclass",
"pk": 2,
"fields": {
"name": "Data recovery class",
"has_variants": true,
"variant_attributes": [
1,
3
]
}
},
{
"model": "products.productclass",
"pk": 3,
"fields": {
"name": "Document manipulation class",
"has_variants": true,
"variant_attributes": []
}
},
{
"model": "products.productclass",
"pk": 4,
"fields": {
"name": "DB class",
"has_variants": true,
"variant_attributes": [
2,
4
]
}
},
{
"model": "products.productclass",
"pk": 5,
"fields": {
"name": "OS class",
"has_variants": true,
"variant_attributes": [
5
]
}
},
{
"model": "products.product",
"pk": 85,
"fields": {
"name": "Kaspersky Endpoint Security \u0434\u043b\u044f \u0431\u0438\u0437\u043d\u0435\u0441\u0430 CLOUD",
"slug": "kaspersky-endpoint-security-dlya-biznesa-cloud",
"price": "1730.00",
"points": "173.00",
"description": "Kaspersky Endpoint Security Cloud \u2013 \u044d\u0442\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u044f\u043c \u043c\u0430\u043b\u043e\u0433\u043e \u0431\u0438\u0437\u043d\u0435\u0441\u0430 \u0438 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u043d\u0430\u0434\u0435\u0436\u043d\u0443\u044e \u0437\u0430\u0449\u0438\u0442\u0443 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u043e\u0432, \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0438 \u0444\u0430\u0439\u043b\u043e\u0432\u044b\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 \u0438\u0437 \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0439 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f. \u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043f\u043e\u043a\u0443\u043f\u043a\u0438 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u0441 \u043b\u044e\u0431\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u043a \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443.",
"short_description": "",
"producer": "Kaspersky",
"image": "products/2017/05/26/thumb_1473758588.png",
"discount": 0,
"stock": 10,
"category": 4,
"product_class": 1,
"is_active": true,
"is_hit": false,
"is_new": false,
"created": "2017-05-26",
"updated": "2017-07-07"
}
},
{
"model": "products.product",
"pk": 86,
"fields": {
"name": "ESET NOD32 Antivirus Business Edition",
"slug": "eset-nod32-antivirus-business-edition",
"price": "2400.00",
"points": "240.00",
"description": "ESET Endpoint Antivirus \u2013 \u043d\u043e\u0432\u043e\u0435 \u0441\u043b\u043e\u0432\u043e \u0432 \u043f\u0440\u043e\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u0437\u0430\u0449\u0438\u0442\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0445 \u0440\u0430\u0431\u043e\u0447\u0438\u0445 \u0441\u0442\u0430\u043d\u0446\u0438\u0439 \u043e\u0442 \u043b\u044e\u0431\u043e\u0433\u043e \u0438\u0437 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432 \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0433\u043e \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f. \u041f\u0440\u043e\u0434\u0443\u043a\u0442 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043d\u044b\u0445 \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0444\u0438\u0434\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438. \u0421\u043e\u0447\u0435\u0442\u0430\u043d\u0438\u0435 \u0437\u0430\u043f\u0430\u0442\u0435\u043d\u0442\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 \u044d\u0432\u0440\u0438\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0430 \u0438 \u043d\u043e\u0432\u0435\u0439\u0448\u0438\u0445 \u0438\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0439 \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0442\u0438\u043f\u044b \u0443\u0433\u0440\u043e\u0437, \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u0443\u044f \u043c\u043e\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u0440\u0435\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0430 \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u044f \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u043e\u0433\u043e \u041f\u041e \u0432 \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u0443\u044e \u0441\u0435\u0442\u044c.",
"short_description": "",
"producer": "Eset",
"image": "products/2017/05/26/thumb_1381486034.jpg",
"discount": 0,
"stock": 10,
"category": 4,
"product_class": 1,
"is_active": true,
"is_hit": false,
"is_new": false,
"created": "2017-05-26",
"updated": "2017-07-07"
}
},
{
"model": "products.product",
"pk": 87,
"fields": {
"name": "Acronis Backup 12 Workstation License",
"slug": "acronis-backup-12-workstation-license",
"price": "3190.00",
"points": "319.00",
"description": "\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438\r\n\r\n\u0411\u044b\u0441\u0442\u0440\u043e\u0435 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c, \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u0434\u0430\u043d\u043d\u044b\u0445\r\n\r\n \u0411\u044b\u0441\u0442\u0440\u043e\u0435 \u0438 \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u043e\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u043d\u043e\u0433\u043e \u043e\u0431\u0440\u0430\u0437\u0430 \u0434\u0438\u0441\u043a\u0430\r\n \u0423\u0434\u043e\u0431\u043d\u043e\u0435 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \"\u043d\u0430 \u0433\u043e\u043b\u043e\u0435 \u0436\u0435\u043b\u0435\u0437\u043e\" \u043d\u0430 \u0442\u043e\u043c \u0436\u0435 \u0438\u043b\u0438 \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0449\u0435\u043c\u0441\u044f \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u0438, \u043b\u0438\u0431\u043e \u043d\u0430 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0439 \u043c\u0430\u0448\u0438\u043d\u0435\r\n \u0420\u0435\u0437\u0435\u0440\u0432\u043d\u043e\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043f\u0430\u043f\u043e\u043a \u043d\u0430 \u0434\u0438\u0441\u043a\u0435 \u0438\u043b\u0438 \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u043f\u0430\u043f\u043e\u043a \u043e\u0431\u0449\u0435\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\r\n \u0412\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u043f\u0430\u043f\u043e\u043a \u0438\u0437 \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u043e\u0439 \u043a\u043e\u043f\u0438\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043e\u0431\u0440\u0430\u0437\u0430\r\n \u041f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u044b\u0445 \u043a\u043e\u043f\u0438\u0439 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u043c\u0430\u0448\u0438\u043d",
"short_description": "",
"producer": "Acronis",
"image": "products/2017/05/26/thumb_1384933473.jpg",
"discount": 0,
"stock": 10,
"category": 5,
"product_class": 2,
"is_active": true,
"is_hit": false,
"is_new": false,
"created": "2017-05-26",
"updated": "2017-07-07"
}
},
{
"model": "products.product",
"pk": 88,
"fields": {
"name": "Symantec System Recovery Desktop",
"slug": "symantec-system-recovery-desktop",
"price": "3830.00",
"points": "383.00",
"description": "Symantec System Recovery 2013 \u2013 \u044d\u0442\u043e \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u044b\u0445 \u043a\u043e\u043f\u0438\u0439 \u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0447\u0438\u0445 \u0441\u0442\u0430\u043d\u0446\u0438\u0439 \u0438 \u043d\u043e\u0443\u0442\u0431\u0443\u043a\u043e\u0432 \u0432 \u0430\u0432\u0430\u0440\u0438\u0439\u043d\u044b\u0445 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044f\u0445, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043c\u0438\u043d\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u044f \u0438 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u0432\u0435\u0440\u043d\u0443\u0442\u044c\u0441\u044f \u043a \u0440\u0435\u0448\u0435\u043d\u0438\u044e \u0440\u0430\u0431\u043e\u0447\u0438\u0445 \u0437\u0430\u0434\u0430\u0447.\r\n\r\n\u0422\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f Restore Anyware, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0430\u044f \u0432 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0435, \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u043c \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e \u0440\u0435\u0448\u0430\u0442\u044c \u0432\u043e\u043f\u0440\u043e\u0441 \u043e \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0442\u043e\u0433\u043e, \u0447\u0435\u0433\u043e \u043d\u0443\u0436\u043d\u043e, \u0432 \u043d\u0443\u0436\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0438 \u0432 \u043d\u0443\u0436\u043d\u043e\u043c \u043c\u0435\u0441\u0442\u0435. \u0414\u0430\u043d\u043d\u0430\u044f \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0444\u0430\u0439\u043b\u044b, \u043f\u0430\u043f\u043a\u0438 \u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u043d\u043e \u0438 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440 \u0446\u0435\u043b\u0438\u043a\u043e\u043c, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u0438\u043b\u0438 \u043d\u043e\u0432\u043e\u0435 \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u0435.",
"short_description": "",
"producer": "Symantec",
"image": "products/2017/05/26/thumb_1401701995.png",
"discount": 0,
"stock": 5,
"category": 5,
"product_class": 2,
"is_active": true,
"is_hit": false,
"is_new": false,
"created": "2017-05-26",
"updated": "2017-07-07"
}
},
{
"model": "products.product",
"pk": 89,
"fields": {
"name": "Microsoft Windows 10 Professional GetGenuine",
"slug": "microsoft-windows-10-professional-getgenuine",
"price": "12300.00",
"points": "1230.00",
"description": "Get Gunuine Windows Agreement GGWA \u2013 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0434\u043b\u044f \u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u043e\u043f\u0438\u0439 \u041e\u0421 Windows, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u0440\u0430\u043d\u0435\u0435 \u0431\u0435\u0437 \u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0438. \u0421\u0438\u0441\u0442\u0435\u043c\u0430 \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442 \u043e\u0442 5 \u0438 \u0431\u043e\u043b\u0435\u0435 \u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0439 \u0438 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u0430 \u043d\u0430 \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439.",
"short_description": "",
"producer": "Microsoft",
"image": "products/2017/05/26/thumb_1438765887.jpg",
"discount": 0,
"stock": 2,
"category": 6,
"product_class": 5,
"is_active": true,
"is_hit": false,
"is_new": false,
"created": "2017-05-26",
"updated": "2017-07-07"
}
},
{
"model": "products.product",
"pk": 90,
"fields": {
"name": "PERFEXPERT",
"slug": "perfexpert",
"price": "50000.00",
"points": "5000.00",
"description": "\u041a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430, \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f, \u0430\u043d\u0430\u043b\u0438\u0437\u0430 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0438 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 24\u04257. \u041e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u043c\u0438 \u0438\u043d\u0446\u0438\u0434\u0435\u043d\u0442\u0430\u043c\u0438 \u0431\u0438\u0437\u043d\u0435\u0441 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043f\u0440\u0435\u0434\u043f\u0440\u0438\u044f\u0442\u0438\u044f, \u0438 \u0442\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0430\u043c\u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0431\u0438\u0437\u043d\u0435\u0441 \u0441\u0438\u0441\u0442\u0435\u043c\u044b. PERFEXPERT \u043d\u043e\u0432\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u0432\u0441\u0435\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0431\u0438\u0437\u043d\u0435\u0441 \u0441\u0438\u0441\u0442\u0435\u043c\u044b.",
"short_description": "",
"producer": "SOFTPOINT",
"image": "products/2017/05/26/thumb_1424247372.png",
"discount": 0,
"stock": 5,
"category": 7,
"product_class": 4,
"is_active": true,
"is_hit": false,
"is_new": false,
"created": "2017-05-26",
"updated": "2017-07-07"
}
},
{
"model": "products.product",
"pk": 91,
"fields": {
"name": "Office 365 Business Open",
"slug": "office-365-business-open",
"price": "5700.00",
"points": "570.00",
"description": "\u041f\u043b\u0430\u043d Office 365 \u0411\u0438\u0437\u043d\u0435\u0441 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0438 \u0446\u0435\u043d\u043e\u0432\u044b\u0435 \u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u044b, \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0443\u0434\u043e\u0432\u043b\u0435\u0442\u0432\u043e\u0440\u0435\u043d\u0438\u044f \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u0435\u0439 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u043c\u0430\u043b\u043e\u0433\u043e \u0438 \u0441\u0440\u0435\u0434\u043d\u0435\u0433\u043e \u0431\u0438\u0437\u043d\u0435\u0441\u0430 \u2013 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0439 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c \u043e\u0442 1 \u0434\u043e 300 \u0441\u043e\u0442\u0440\u0443\u0434\u043d\u0438\u043a\u043e\u0432.",
"short_description": "",
"producer": "Microsoft",
"image": "products/2017/05/26/office.png",
"discount": 5,
"stock": 13,
"category": 8,
"product_class": 3,
"is_active": true,
"is_hit": false,
"is_new": false,
"created": "2017-05-26",
"updated": "2017-07-07"
}
},
{
"model": "products.offer",
"pk": 30,
"fields": {
"product": 85,
"name": "Kaspersky-New-One-year",
"price": "1730.00",
"points": "0.00",
"attributes": "{\"License type\": \"New\", \"License term\": \"One year\"}"
}
},
{
"model": "products.offer",
"pk": 31,
"fields": {
"product": 85,
"name": "Kaspersky-New-One-year",
"price": "2600.00",
"points": "0.00",
"attributes": "{\"License type\": \"New\", \"License term\": \"Two years\"}"
}
},
{
"model": "products.offer",
"pk": 32,
"fields": {
"product": 85,
"name": "Kaspersky-Prolongation-One-year",
"price": "1200.00",
"points": "0.00",
"attributes": "{\"License type\": \"Prolongation\", \"License term\": \"One year\"}"
}
},
{
"model": "products.offer",
"pk": 33,
"fields": {
"product": 85,
"name": "Kaspersky-Migration-One-year",
"price": "1900.00",
"points": "0.00",
"attributes": "{\"License type\": \"Migration\", \"License term\": \"One year\"}"
}
}
]

@ -1,68 +1,128 @@
# from haystack.forms import FacetedSearchForm
# class FacetedProductSearchForm(FacetedSearchForm):
# def __init__(self, *args, **kwargs):
# data = dict(kwargs.get("data", []))
# self.categories = data.get('category', [])
# self.producers = data.get('producer', [])
# super(FacetedProductSearchForm, self).__init__(*args, **kwargs)
#
# def search(self):
# sqs = super(FacetedProductSearchForm, self).search()
# if self.categories:
# query = None
# for category in self.categories:
# if query:
# query += u' OR '
# else:
# query = u''
# query += u'"%s"' % sqs.query.clean(category)
# sqs = sqs.narrow(u'category_exact:%s' % query)
# if self.producers:
# query = None
# for producer in self.producers:
# if query:
# query += u' OR '
# else:
# query = u''
# query += u'"%s"' % sqs.query.clean(producer)
# sqs = sqs.narrow(u'brand_exact:%s' % query)
# return sqs
from crispy_forms.layout import Layout, Field, Button
from ckeditor.widgets import CKEditorWidget
from crispy_forms.layout import Layout, Field, Button, Div, HTML
from django import forms
from crispy_forms.helper import FormHelper
from django.forms import formset_factory
from django.forms import ALL_FIELDS
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from .models import Product, ProductAttribute
from core.forms import QueryFormBase
from core.utils import parse_path
from products.templatetags.products_filters import apply_query_params
from .models import Product, Manufacturer
class ProductFilterForm(forms.Form):
def __init__(self, *args,**kwargs):
super().__init__(*args,**kwargs)
class ProductSearchForm(forms.ModelForm):
class ProductSearchForm(QueryFormBase, forms.ModelForm):
field_template = 'bootstrap/forms/product_search.html'
form_action = 'products:product_list'
submit_css_class = None
def __init__(self, *args, **kwargs):
self.submit_css_class = kwargs.pop('submit_css_class')
self.helper = FormHelper()
self.helper.form_action = reverse_lazy(self.form_action)
self.helper.form_method = 'get'
self.helper.layout = Layout(
Field('name', template=self.field_template, placeholder="Поиск программы..."),
Button(_('search'),value="search", css_class=self.submit_css_class, template=self.field_template)
Button(_('search'), value="search", css_class=self.submit_css_class, template=self.field_template)
)
super().__init__(*args, **kwargs)
self.helper.form_action = reverse_lazy(**self.form_action) + apply_query_params(
self.query_params) if self.query_params else ""
class Meta:
model = Product
fields = ['name']
fields = ('name',)
# --------------------------- Product Filter Types -------------------------#
class ProductManufacturerFilterForm(QueryFormBase):
manufacturer = forms.ChoiceField()
submit_css_button = None
field_template = 'bootstrap/forms/product_filter.html'
title = _('Производитель')
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = 'get'
self.helper.layout = Layout(
Div(HTML(self.title), css_class='category__title'),
Field('manufacturer', template=self.field_template)
)
super().__init__(*args, **kwargs)
self.helper.form_action = self.get_form_action_url()
def get_initial_for_field(self, field, field_name):
if field_name == 'manufacturer':
man_qs = Manufacturer.active
category_instance = ''
if self.form_action.get('kwargs', None):
category_instance = parse_path(self.form_action.get('kwargs').get('path', ''))
prod_qs = Product.active.filter(
parent__name=category_instance,
name__icontains=self.query_params.get('name', '')
).only('pk')
if prod_qs.count():
man_qs = man_qs.filter(product__pk__in=prod_qs.all())
return man_qs.distinct('name').only('name', 'slug')
return super().get_initial_for_field(field, field_name)
inline_product_filter_formset = formset_factory(ProductFilterForm,extra=ProductAttribute.objects.all(),can_order=True,can_delete=False)
class ProductSortForm(QueryFormBase):
submit_css_button = None
field_template = 'bootstrap/forms/product_sorting.html'
title = _('Сортировать по')
sort_fields = {
'price': _('Цене'),
'popular': _('Популярности'),
'rate': _('Рейтингу')
}
sort = forms.ChoiceField(label=title)
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = 'get'
self.helper.layout = Layout(
HTML(self.title),
Field('sort', template=self.field_template)
)
super().__init__(*args, **kwargs)
self.helper.form_action = self.get_form_action_url()
def get_sort_field_initial_data(self):
query_keys = self.query_params.keys()
initial_data = []
for key in self.sort_fields:
order = "DESC" if key in query_keys else "ASC"
initial_data.append({
'value': "{url}?{query_params}".format(**{
'url': reverse_lazy(**self.form_action),
'query_params': apply_query_params({key: order, **self.query_params}, True)
}),
'name': self.sort_fields[key]
})
return initial_data
def get_initial_for_field(self, field, field_name):
if field_name == 'sort':
return self.get_sort_field_initial_data()
return super().get_initial_for_field(field, field_name)
# ----------------------------------------- Admin forms ------------------------------------#
class ProductAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['description'].widget = CKEditorWidget(config_name='awesome_ckeditor')
class Meta:
model = Product
fields = ALL_FIELDS

@ -1,6 +1,7 @@
from django.contrib.auth import get_user_model
from django.db import models
from django.urls import reverse_lazy
from django.db.models import Q
from django.urls import reverse_lazy, reverse
from django.contrib.postgres.fields import HStoreField
from django.utils.translation import ugettext_lazy as _
@ -12,7 +13,8 @@ from mptt import (
from mptt.models import MPTTModel, TreeForeignKey
from autoslug import AutoSlugField
from core.models import AbstractStatusModel, ActualOnlyManager, AbstractDateTimeModel, ActiveOnlyManager
from core.models import AbstractStatusModel, ActualOnlyManager, AbstractDateTimeModel, ActiveOnlyManager, \
AbstractStatusMPTTModel
# ---------------------------------- COMMON PRODUCT STATUS ---------------------------------------#
# Create your models here.
@ -28,119 +30,136 @@ STATUS_CHOICES = (
(STATUS_DELETED, _('Удаленный')),
)
# --------------------------------- PRODUCT ATTRIBUTE STATTUS------------------------------------#
PRODUCT_ATTRIBUTE_TYPE_NONE = 0
PRODUCT_ATTRIBUTE_TYPE_RANGE = 50
PRODUCT_ATTRIBUTE_TYPE_SELECT = 100
PRODUCT_ATTRIBUTE_TYPE_CHOICES = (
(PRODUCT_ATTRIBUTE_TYPE_NONE, _('отсуствует')),
(PRODUCT_ATTRIBUTE_TYPE_RANGE, _('диапазон')),
(PRODUCT_ATTRIBUTE_TYPE_SELECT, _('выбор'))
)
PRODUCT_ATTRIBUTE_TYPE_DEFAULT = PRODUCT_ATTRIBUTE_TYPE_NONE
class ProductAttributeManager(ActiveOnlyManager):
def get_range_type_attributes(self, product=None):
pass
def get_select_type_attributes(self, product=None):
pass
def get_all_type_attributes(self):
pass
class ProductAttribute(AbstractStatusModel):
name = models.CharField(max_length=64, blank=True, null=True, default=None)
slug = AutoSlugField(populate_from='name')
type = models.SmallIntegerField(_('тип'), choices=PRODUCT_ATTRIBUTE_TYPE_CHOICES,
default=PRODUCT_ATTRIBUTE_TYPE_DEFAULT)
main_attribute = models.BooleanField(default=False)
status = models.SmallIntegerField(_('статус'), default=STATUS_DEFAULT, choices=STATUS_CHOICES)
objects = ProductAttributeManager()
def __str__(self):
return self.name
class Meta:
ordering = ('slug',)
verbose_name = _('Аттрибут продукта')
verbose_name_plural = _('Аттрибуты продукта')
# --------------------------------- PRODUCT ATTRIBUTE STATTUS------------------------------------#
# PRODUCT_ATTRIBUTE_TYPE_NONE = 0
# PRODUCT_ATTRIBUTE_TYPE_RANGE = 25
# PRODUCT_ATTRIBUTE_TYPE_SELECT = 50
# PRODUCT_ATTRIBUTE_TYPE_CHECKBOX = 75
# PRODUCT_ATTRIBUTE_TYPE_INPUT = 100
#
# PRODUCT_ATTRIBUTE_TYPE_CHOICES = (
# (PRODUCT_ATTRIBUTE_TYPE_NONE, _('отсуствует')),
# (PRODUCT_ATTRIBUTE_TYPE_RANGE, _('диапазон')),
# (PRODUCT_ATTRIBUTE_TYPE_SELECT, _('выбор из списка')),
# (PRODUCT_ATTRIBUTE_TYPE_CHECKBOX, _('мн. выбор из списка')),
# (PRODUCT_ATTRIBUTE_TYPE_INPUT, _('значение'),
# )
# )
# PRODUCT_ATTRIBUTE_TYPE_DEFAULT = PRODUCT_ATTRIBUTE_TYPE_NONE
# class ProductAttributeManager(ActiveOnlyManager):
# def get_range_type_attributes(self, product=None):
# pass
#
# def get_select_type_attributes(self, product=None):
# pass
#
# def get_all_type_attributes(self):
# pass
# # @TODO: tranlsate into english and use traslation
# class ProductAttribute(AbstractStatusModel):
# name = models.CharField(_('название'), max_length=64)
# slug = AutoSlugField(populate_from='name', verbose_name=_('код'), help_text="поисковый код аттрибута,по котором возможен поиск")
# type = models.SmallIntegerField(_('тип'), choices=PRODUCT_ATTRIBUTE_TYPE_CHOICES,
# default=PRODUCT_ATTRIBUTE_TYPE_DEFAULT)
# status = models.SmallIntegerField(_('статус'), default=STATUS_DEFAULT, choices=STATUS_CHOICES)
# main_attribute = models.BooleanField(_('основной атрибут'), default=False, help_text=_('будет отображен рядом с товаром при отображении в списке'))
#
# objects = ProductAttributeManager()
#
# def __str__(self):
# return self.name
#
# class Meta:
# ordering = ('slug',)
# verbose_name = _('Аттрибут продукта')
# verbose_name_plural = _('Аттрибуты продукта')
# @TODO: tranlsate into english and use traslation
class Manufacturer(AbstractStatusModel):
name = models.CharField(max_length=64, blank=True, null=True, default=None)
slug = AutoSlugField(populate_from='name')
image = models.ImageField(upload_to='producers', blank=True, null=True, verbose_name=("Изображение"))
name = models.CharField(_('название'), max_length=64, blank=True, null=True, default=None)
slug = AutoSlugField(verbose_name=_('код'), unique=True, populate_from='name')
image = models.ImageField(_('изображение'), upload_to='producers', blank=True, null=True)
status = models.SmallIntegerField(_('статус'), default=STATUS_DEFAULT, choices=STATUS_CHOICES)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse_lazy('products:manufacturer', kwargs={'producer_slug': self.slug, 'path': ''})
# @TODO: tranlsate into english and use traslation
class Meta:
verbose_name = _('Производитель')
verbose_name_plural = _('Производители')
class ProductCategoryManager(mptt_managers.TreeManager, ActualOnlyManager):
def get_categories(self, parent=None):
return self.get_queryset().filter(parent=parent).all()
class ProductActiveCategoryManager(mptt_models.TreeManager, ActiveOnlyManager):
def get_categories(self, parent=None):
return self.get_queryset().filter(parent=parent).all()
# @TODO: tranlsate into english and use traslation
class ProductCategory(AbstractStatusMPTTModel):
def get_file_path(self, filename):
return "products/{category}/{filename}".format(**{
'category': self.slug,
'filename': filename
})
class ProductCategory(MPTTModel, AbstractStatusModel):
name = models.CharField(_('название'), db_index=True, unique=True, max_length=64, blank=True, null=True,
default=None)
slug = AutoSlugField(_('slug'), populate_from='name')
slug = AutoSlugField(_('код'), populate_from='name')
parent = TreeForeignKey('self', verbose_name=_('родительская категория'), on_delete=models.CASCADE, null=True,
blank=True, related_name='children')
image = models.ImageField(_("иконка"), upload_to='categories', blank=True)
image = models.FileField(_("иконка"), upload_to=get_file_path, blank=True)
status = models.SmallIntegerField(_('статус'), default=STATUS_DEFAULT, choices=STATUS_CHOICES)
objects = ProductCategoryManager()
active = ProductActiveCategoryManager()
def __str__(self):
return self.name
@property
def viewname(self):
return 'products:product_list'
@property
def viewname_kwargs(self):
return {'path': self.get_path()}
class MPTTMeta:
order_insertion_by = ('name',)
# @TODO: tranlsate into english and use traslation
class Meta:
unique_together = ('slug', 'parent')
ordering = ('tree_id', 'level')
verbose_name = _("Категория")
verbose_name_plural = _("Категории")
ordering = ('tree_id', 'level')
class MPTTMeta:
order_insertion_by = ['name']
register(ProductCategory, order_insertion_py=['name'])
class Product(AbstractStatusModel):
# @TODO: translate into english and use translation
class Product(AbstractStatusMPTTModel):
name = models.CharField(_('имя'), max_length=64, db_index=True)
slug = AutoSlugField(_('slug'), populate_from='name', db_index=True)
description = models.TextField(_('описание'), blank=True, null=True, default=None)
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.SET_NULL, blank=True, null=True)
category = models.ForeignKey(ProductCategory, on_delete=models.SET_NULL, blank=True, null=True)
attributes = models.ManyToManyField(ProductAttribute, blank=True)
manufacturer = models.ForeignKey(Manufacturer, verbose_name=_('производитель'), on_delete=models.SET_NULL,
blank=True, null=True)
parent = TreeForeignKey(ProductCategory, verbose_name=_('категория'), on_delete=models.SET_NULL, blank=True,
null=True, help_text="Категория")
platform = models.CharField(_('Платформа'), max_length=255, null=True, blank=True)
status = models.SmallIntegerField(_('статус'), default=STATUS_DEFAULT, choices=STATUS_CHOICES)
def __str__(self):
return self.name
def get_absolute_url(self, request):
return request.build_absolute_uri(reverse_lazy('products:item', args=[self.slug]))
@property
def viewname(self):
return 'products:product_details'
@property
def viewname_kwargs(self):
return {'path': self.parent.get_path() + self.get_path()}
class MPTTMeta:
order_insertion_by = ('name',)
class Meta:
indexes = [
@ -150,72 +169,75 @@ class Product(AbstractStatusModel):
verbose_name_plural = _('Продукты')
# def save(self, *args, **kwargs):
# if self.category:
# super(Product, self).save(*args, **kwargs)
#
# for cp in ProductClass.objects.filter(category=self.product_class):
# pp = ProductProperty.objects.filter(category_property=cp,
# products=self)
# if not pp:
# pp = ProductProperty(category_property=cp, products=self, value="--")
# pp.save()
# @TODO: tranlsate into english and use traslation
class ProductRate(AbstractDateTimeModel):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
rate = models.IntegerField(_('оценка'), default=0)
rate = models.PositiveSmallIntegerField(_('оценка'), default=0)
class Meta:
verbose_name = _('Рейтинг продукта')
verbose_name_plural = _('Рейтинг продукта')
class ProductDiscount(AbstractStatusModel):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
percentage = models.DecimalField(_('процент'), max_digits=3, decimal_places=2)
status = models.SmallIntegerField(_('статус'), choices=STATUS_CHOICES, default=STATUS_DEFAULT)
#
# # @TODO: translate into english and use traslation
# class ProductAttributeValue(AbstractDateTimeModel):
# attribute = models.ForeignKey(ProductAttribute, on_delete=models.CASCADE, related_name='value')
# value = HStoreField(_('значение'),default="")
#
# def __str__(self):
# return self.value.serialize
#
# class Meta:
# unique_together = ('attribute', 'value')
# verbose_name = _('Значение аттрибута')
# verbose_name_plural = _('Значение аттрибутов')
class Meta:
verbose_name = _('Дисконт')
verbose_name_plural = _('Дисконты')
# ----------------- PRODUCT IMAGE STATUS LIST ------------------
class ProductAttributeValue(AbstractStatusModel):
attribute = models.ForeignKey(ProductAttribute, on_delete=models.CASCADE, related_name='value')
value = HStoreField(_('значение'), default={})
class ProductImageManager(models.Manager):
def get_default_image(self):
return self.get_queryset().filter(product=self.instance, is_default=True).first()
def __str__(self):
return self.value.serialize
def get_all_images(self):
return self.get_queryset().filter(product=self.instance).order_by('-is_default').all()
class Meta:
unique_together = ('attribute', 'value')
verbose_name = _('Значение аттрибута')
verbose_name_plural = _('Значение аттрибутов')
def get_all_images_except_default(self):
return self.get_queryset().filter(product=self.instance, is_default=False).all()
# ----------------- PRODUCT IMAGE STATUS LIST ------------------
class ProductImage(AbstractStatusModel):
class ProductImage(AbstractDateTimeModel):
def get_file_path(self, filename):
return "products/attachments/{product}/{filename}".format(**{
'product': self.product.id,
return "products/{product}/{filename}".format(**{
'product': self.product.slug,
'filename': filename
})
product = models.ForeignKey(Product, on_delete=models.CASCADE)
status = models.SmallIntegerField(_('Статус'), choices=STATUS_CHOICES, default=STATUS_DEFAULT)
filename = models.CharField(_('Имя файла'), max_length=255)
image = models.FileField(_('Изображение'), upload_to=get_file_path, max_length=500)
is_default = models.BooleanField(_('По умолчанию'), default=False)
filename = models.CharField(_('имя файла'), max_length=255)
image = models.FileField(_('изображение'), upload_to=get_file_path, max_length=500)
is_default = models.BooleanField(_('по умолчанию'), default=False)
objects = ProductImageManager()
@classmethod
def create(cls, request, file):
product_image = cls(request=request, file=file, filename=file.name)
return product_image
def __str__(self):
return self.filename
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
if not self.product.productimage_set.count():
self.is_default = True
elif self.is_default:
self.product.productimage_set.update(is_default=False)
self.filename = self.image.name
return super().save(force_insert, force_update, using, update_fields)
class Meta:
verbose_name = _('Изображение продукта')
verbose_name_plural = _('Изображения продуктов')
verbose_name_plural = _('Изображения продукта')

@ -0,0 +1,7 @@
from django.template import Library
from django.utils.translation import ugettext_lazy as _
register = Library()

@ -0,0 +1,61 @@
from functools import reduce
from django.utils.translation import ugettext_lazy as _
from django.template import Library
from cart.forms import CartAddInlineForm
register = Library()
@register.filter
def apply_nds_status(doesNdsInclude):
return _('Включено') if doesNdsInclude else _('Не включено')
@register.filter
def apply_product_offer_form(product):
initial = {
'offer': product.id,
'amount': 1
}
return CartAddInlineForm(initial=initial)
@register.filter
def apply_query_params(params, doAppend=False):
"""
:param params dict:
:param arg return as additional query params or initial:
:return string:
"""
formated_params = ""
if not params:
return formated_params
if doAppend:
formated_params = "&" + formated_params
formated_params = reduce(
lambda q_str, q_key: q_str + "{key}={val}&".format(**{'key': q_key, 'val': params[q_key]}),
params,
formated_params
)
return formated_params[:-1]
@register.filter
def filter_query_params(params, query_key):
new_params = {**params} if isinstance(params, dict) else {}
if query_key in new_params:
new_params.pop(query_key)
return new_params
@register.filter
def get_item(dictionary, key):
return dictionary.get(key)
@register.filter
def apply_desc_preview(description):
return description

@ -16,15 +16,22 @@ Including another URLconf
from django.urls import re_path
import mptt_urls
from . import views
from .models import ProductCategory
from .models import ProductCategory, Product
urlpatterns = [
re_path(r'^list/$', views.ProductListView.as_view(), name='product_list'),
re_path(r'^list/(?P<category_slug>\w+)/$', views.ProductListView.as_view(),name='product_list'),
re_path(r'^(?P<product_slug>\w+)/$', views.ProductDetailsView.as_view(), name='product_details'),
re_path(
r'^list/(?P<path>.*)',
mptt_urls.view(model=ProductCategory, view=views.ProductListView.as_view(),slug_field='slug'),
name='product_list'
),
re_path(
r'^item/(?P<path>.*)',
mptt_urls.view(model=Product, view=views.ProductDetailView.as_view(),slug_field='slug'),
name='product_details'
),
# Uncomment for elasticsearch
@ -36,6 +43,4 @@ urlpatterns = [
# url(r'^(?P<producer_slug>[-\w]+)/(?P<path>.*)',
# mptt_urls.view(model=ProductCategory, view=categorieslist, slug_field='slug'),
# name='CategoriesListByProducer'),
# url(r'^(?P<producer_slug>[-\w]+)/$', categorieslist, name='CategoriesListByProducer'),
# url(r'^(?P<producer_slug>[-\w]+)/(?P<category_slug>[-\w]+)/$', productslist, name='ProductListByCategory')
]

@ -1,151 +1,95 @@
import json
import decimal
from functools import reduce
from django.conf import settings
from django.shortcuts import render
from django.contrib import auth
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.views.generic import ListView, DetailView
from core.views import ProtectedListView
from .models import (
Manufacturer, Product, ProductCategory
)
from cart.forms import CartAddInlineForm
from core.utils import parse_path
from .forms import (ProductSortForm)
from .models import (Product,ProductCategory)
from .forms import *
class ProductDetailView(DetailView):
http_method_names = ('get',)
model = Product
pk_url_kwarg = None
slug_url_kwarg = 'path'
context_object_name = 'product'
template_name = 'products/product_detail.html'
class ManufactureListView(ListView):
model = Manufacturer
template_name = 'products/manufacture_list.html'
def dispatch(self, request, *args, **kwargs):
if self.kwargs.get(self.slug_url_kwarg):
slug_url_kwarg_val = self.kwargs.pop(self.slug_url_kwarg)
slug_url_kwarg_val = parse_path(slug_url_kwarg_val)
self.kwargs[self.slug_url_kwarg] = slug_url_kwarg_val
return super().dispatch(request, *args, **kwargs)
def get_slug_field(self):
return 'slug'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = self.object.name
return context
class ProductDetailsView(DetailView):
model = Product
template_name = 'products/product_detail.html'
title = _('Продукт')
class ProductListView(ListView):
http_method_names = ('get',)
model = Product
form = ProductSearchForm
template_name = 'products/product_list.html'
context_object_name = 'products'
paginate_by = settings.DEFAULT_PAGE_AMOUNT
title = _('Список товаров')
title = _('Каталог')
def get_title(self):
def filter_products(self,qs):
if self.kwargs.get('path'):
category_intance = parse_path(self.kwargs.get('path'))
cat_qs = ProductCategory.objects.filter(slug__exact=category_intance).first()
qs = qs.filter(parent__in=cat_qs.get_descendants(include_self=True))
if self.request.GET.get('name'):
return _('Поиск товара') + ":" + self.request.GET.get('name')
title = _('Список товаров')
if self.request.resolver_match.kwargs.get('category_slug'):
return title + ":" + self.request.resolver_match.kwargs.get('category_slug')
return title
qs = qs.filter(name__icontains=self.request.GET.get('name'))
if self.request.GET.get('manufacturer'):
qs = qs.filter(manufacturer__slug=self.request.GET.get('manufacturer'))
if self.request.GET.get('supply_type'):
qs = qs.filter(offer__supply_type__slug=self.request.GET.get('supply_type'))
if self.request.GET.get('supply_target'):
qs = qs.filter(offer__supply_target__slug=self.request.GET.get('supply_target'))
qs = qs.filter(offer__amount__gte=0)
return qs
def sort_products(self,qs):
if not self.request.GET:
return qs
if self.request.GET.get('sort'):
qs = qs.order_by()
return qs
def get_queryset(self):
qs = super().get_queryset()
if self.kwargs.get('category_slug'):
qs = qs.filter(category__slug=self.kwargs.get('category_slug'))
if self.request.GET.get('name'):
qs = qs.filter(name__icontains=self.request.GET.get('name'))
qs = self.filter_products(qs)
qs = self.sort_products(qs)
return qs
def get_cart_add_formset(self, products):
return { product.id: CartAddInlineForm(initial={'offer': product.id,'amount': 1}) for product in products}
def get_product_sorting_form(self):
if not "products" in self.request.resolver_match.view_name:
product_form_action = {'viewname': 'products:product_list', 'kwarg': {}}
else:
product_form_action = {'viewname': self.request.resolver_match.view_name,
'kwargs': self.request.resolver_match.kwargs}
return ProductSortForm(
product_form_action=product_form_action,
query_params=self.request.GET
)
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
context['title'] = self.get_title()
context['title'] = self.title
context['product_sort_form'] = self.get_product_sorting_form()
context['product_cart_add_formset'] = self.get_cart_add_formset(self.object_list)
return context
# Uncomment for elasticsearch
# from .layout import FacetedProductSearchForm
# from haystack.generic_views import FacetedSearchView as BaseFacetedSearchView
# from haystack.query import SearchQuerySet
# def serialize_decimal(obj):
# if isinstance(obj, decimal.Decimal):
# return str(obj)
# return json.JSONEncoder.default(obj)
#
#
# def producerslist(request):
# username = auth.get_user(request).username
# # category = None
# # categories = ProductCategory.objects.filter(level__lte=0)
# # products = Product.objects.filter(is_active=True)
# producers = Manufacturer.objects.filter(is_active=True)
# # if category_slug:
# # category = get_object_or_404(ProductCategory, slug=category_slug)
# # products = products.filter(category__in=category.get_descendants(include_self=True))
# return render(request, 'products/list.html', locals())
# def categorieslist(request, producer_slug, category_slug=None):
# username = accounts_ext.get_user(request).username
# producer = Producer.objects.get(slug=producer_slug)
# if category_slug:
# _categories = ProductCategory.objects.filter(is_active=True, parent=category_slug)
# else:
# _categories = ProductCategory.objects.filter(is_active=True, producer=producer, level__lte=0)
# categories, products = expand_categories(_categories)
# return render(request, 'products/categorieslist.html', {'username': username, 'categories':categories,
# 'products': products})
#
# def categorieslist(request, path, instance, producer_slug):
# username = auth.get_user(request).username
# if instance:
# _categories = instance.get_children()
# else:
# _categories = get_list_or_404(ProductCategory, producer__slug=producer_slug, level__lte=0)
# if _categories:
# categories, products = expand_categories(_categories)
# else:
# return productslist(request, producer_slug, instance.slug)
# return render(
# request,
# 'products/categorieslist.html',
# {
# 'username': username,
# 'instance': instance,
# 'categories': categories,
# 'producer_slug': producer_slug,
# 'products': products
# }
# )
#
# def productslist(request, producer_slug, category_slug):
# username = auth.get_user(request).username
# category = ProductCategory.objects.get(slug=category_slug)
# products = Product.objects.filter(is_active=True, category=category)
# return render(request, 'products/productslist.html', locals())
#
# def product(request, product_slug):
# username = auth.get_user(request).username
# product = get_object_or_404(Product, slug=product_slug, is_active=True)
# cart_product_form = CartAddProductForm()
# variant_picker_data = get_variant_picker_data(product)
# show_variant_picker = all([v.attributes for v in product.variants.all()])
# # session_key = request.session.session_key
# # if not session_key:
# # request.session.cycle_key()
#
# return render(request, 'products/product.html', {'username': username, 'products': product, 'form': cart_product_form,
# 'show_variant_picker': show_variant_picker,
# 'variant_picker_data': variant_picker_data,
# })
# Uncomment for elasticsearch
# def autocomplete(request):
# sqs = SearchQuerySet().autocomplete(content_auto=request.GET.get('query', ''))[:5]
# s = []
# for result in sqs:
# print(result)
# d = {"value": result.name, "data": result.object.slug}
# s.append(d)
# output = {'suggestions': s}
# return JsonResponse(output)
#
# class FacetedSearchView(BaseFacetedSearchView):
# form_class = FacetedProductSearchForm
# facet_fields = ['category', 'producer']
# template_name = 'search/search.html'
# paginate_by = 3
# context_object_name = 'object_list'

Loading…
Cancel
Save