remotes/origin/HEAD
Max Yakovenko 8 years ago
parent a91413a839
commit 17b7f012dd
  1. 1
      README.md
  2. 4
      accounts_ext/admin.py
  3. 7
      accounts_ext/forms.py
  4. 5
      accounts_ext/models.py
  5. 6
      cabinet/views.py
  6. 60
      cart/models.py
  7. 3
      cart/signals.py
  8. 4
      cart/urls.py
  9. 40
      cart/views.py
  10. 5
      core/views.py
  11. 8
      eshop_project/settings/base.py
  12. 5
      products/forms.py
  13. 86
      products/models.py
  14. 5
      products/urls.py
  15. 4
      products/views.py
  16. 38
      referral/admin.py
  17. 18
      referral/forms.py
  18. 32
      referral/models.py
  19. 7
      static/css/main.fix.css
  20. 6
      templates/accounts_ext/authentication.html
  21. 2
      templates/base.html
  22. 6
      templates/cabinet/index.html
  23. 24
      templates/cart/bought_history.html
  24. 170
      templates/cart/cart.html
  25. 70
      templates/common.html
  26. 9
      templates/components/breadcrumbs.html
  27. 4
      templates/components/footer.html
  28. 4
      templates/components/header.html
  29. 7
      templates/components/navbar.html
  30. 255
      templates/products/product_category_list.html
  31. 321
      templates/products/product_search.html

@ -26,3 +26,4 @@
2. Make migrations for modules 2. Make migrations for modules
3. Collect static 3. Collect static
4. Restart gunicorn-eshop and celery-eshop daemons in systemd 4. Restart gunicorn-eshop and celery-eshop daemons in systemd
5. Add initial migration to core app and redefine site domain name

@ -31,7 +31,7 @@ class UserAdmin(SafeModelAdmin, BaseUserAdmin):
) )
fieldsets = ( fieldsets = (
(None, {'fields': ('email', 'username', 'password', 'referral_code')}), (None, {'fields': ('email', 'username', 'password', 'referral_user')}),
(_('Permissions'), {'fields': ('status', 'is_superuser', (_('Permissions'), {'fields': ('status', 'is_superuser',
'groups', 'user_permissions')}), 'groups', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'confirmed_at')}), (_('Important dates'), {'fields': ('last_login', 'confirmed_at')}),
@ -41,7 +41,7 @@ class UserAdmin(SafeModelAdmin, BaseUserAdmin):
(None, { (None, {
'classes': ('wide',), 'classes': ('wide',),
'fields': ( 'fields': (
'username', 'email', 'password1', 'password2', 'referral_code', 'status', 'is_superuser' 'username', 'email', 'password1', 'password2', 'referral_user', 'status', 'is_superuser'
), ),
}), }),
) )

@ -21,6 +21,7 @@ from .models import Profile, Company
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class RegistrationForm(RegistrationFormUniqueEmail): class RegistrationForm(RegistrationFormUniqueEmail):
email = forms.EmailField(label=_("E-mail"), widget=forms.EmailInput(attrs={'class': 'reg__text'})) email = forms.EmailField(label=_("E-mail"), widget=forms.EmailInput(attrs={'class': 'reg__text'}))
password1 = forms.CharField( password1 = forms.CharField(
@ -261,14 +262,12 @@ class CompanyForm(forms.Form):
class UserChangeForm(UserChangeFormBase): class UserChangeForm(UserChangeFormBase):
class Meta: class Meta:
model = get_user_model() model = get_user_model()
fields = ('status','referral_code',) fields = ('status', 'referral_user',)
class UserCreationForm(UserCreationFormBase): class UserCreationForm(UserCreationFormBase):
def clean_email(self): def clean_email(self):
email = self.cleaned_data['email'] email = self.cleaned_data['email']
if DisposableEmailChecker().is_disposable(email): if DisposableEmailChecker().is_disposable(email):
@ -277,7 +276,7 @@ class UserCreationForm(UserCreationFormBase):
class Meta: class Meta:
model = get_user_model() model = get_user_model()
fields = ('email', 'username', 'status', 'referral_code', 'is_superuser') fields = ('email', 'username', 'status', 'referral_user', 'is_superuser')
field_classes = { field_classes = {
'email': forms.EmailField, 'email': forms.EmailField,
'username': UsernameField, 'username': UsernameField,

@ -46,11 +46,6 @@ class User(AbstractStatusModel, PermissionsMixin, AbstractBaseUser):
}, },
) )
referral_code = models.ForeignKey('referral.Referral',
verbose_name=_('Реферальная ссылка'),
on_delete=models.SET_NULL,
blank=True, null=True
)
referral_user = models.ForeignKey('self', referral_user = models.ForeignKey('self',
verbose_name=_('Реферальный пользователь'), verbose_name=_('Реферальный пользователь'),
on_delete=models.SET_NULL, on_delete=models.SET_NULL,

@ -29,7 +29,7 @@ class IndexView(ProtectedView):
return paginator.page(the_page) return paginator.page(the_page)
def get_full_name(self,user): def get_full_name(self,user):
return '{last_name}{first_name}{patronymic}'.format(**{ return '{last_name} {first_name} {patronymic}'.format(**{
'last_name': user.profile.last_name or "", 'last_name': user.profile.last_name or "",
'first_name': user.profile.first_name or "", 'first_name': user.profile.first_name or "",
'patronymic': user.profile.patronymic or "" 'patronymic': user.profile.patronymic or ""
@ -43,6 +43,6 @@ class IndexView(ProtectedView):
context['full_name'] = self.get_full_name(self.request.user) context['full_name'] = self.get_full_name(self.request.user)
context['email'] = self.request.user.email context['email'] = self.request.user.email
context['phone_number'] = self.request.user.profile.phone context['phone_number'] = self.request.user.profile.phone
context['company'] = self.request.user.company context['company'] = self.request.user.company or None
context['profile'] = self.request.user.profile context['profile'] = self.request.user.profile or None
return context return context

@ -1,12 +1,13 @@
from django.db import models from django.db import models
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import HStoreField from django.contrib.postgres.fields import HStoreField
from django.db.models import Avg
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from autoslug import AutoSlugField from autoslug import AutoSlugField
# Create your models here. # Create your models here.
from core.models import AbstractStatusModel from core.models import AbstractStatusModel, AbstractDateTimeModel
from products.models import Product from products.models import Product
OFFER_STATUS_ACTIVE = 25 OFFER_STATUS_ACTIVE = 25
@ -20,16 +21,23 @@ OFFER_STATUS_CHOICES = (
OFFER_DEFAULT_CHOICE = OFFER_STATUS_INACTIVE OFFER_DEFAULT_CHOICE = OFFER_STATUS_INACTIVE
class SupplyType(AbstractDateTimeModel):
name = models.CharField(max_length=255)
term = models.ValueRange()
class Offer(AbstractStatusModel): class Offer(AbstractStatusModel):
name = models.CharField(max_length=64, blank=True, null=True, default=None) product = models.OneToOneField(Product, on_delete=models.CASCADE, primary_key=True)
slug = AutoSlugField(populate_from='name')
price = models.DecimalField(max_digits=8, decimal_places=2, null=True, default=0.00) price = models.DecimalField(max_digits=8, decimal_places=2, null=True, default=0.00)
product = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, default=None, currency = models.CharField(max_length=64, blank=True, null=True, default=None)
related_name='variants') with_nds = models.BooleanField(default=False)
attributes = HStoreField(blank=True, null=True, default={}) # attributes = HStoreField(blank=True, null=True, default={})
suply_type = models.ForeignKey(SupplyType, on_delete=models.SET_NULL, blank=True, null=True)
note = models.TextField(blank=True, null=True)
status = models.SmallIntegerField(_('статус'), default=OFFER_DEFAULT_CHOICE, choices=OFFER_STATUS_CHOICES) status = models.SmallIntegerField(_('статус'), default=OFFER_DEFAULT_CHOICE, choices=OFFER_STATUS_CHOICES)
amount = models.IntegerField(blank=True, null=True)
def __str__(self): def __str__(self):
return self.name return self.name
@ -44,7 +52,7 @@ BUYING_STATUS_PENDING = 50
BUYING_STATUS_BOUGHT = 75 BUYING_STATUS_BOUGHT = 75
BUYING_STATUS_CHOICES = ( BUYING_STATUS_CHOICES = (
(BUYING_STATUS_IN_CART, _('В корзине')), (BUYING_STATUS_IN_CART, _('В корзине')),
(BUYING_STATUS_PENDING,_('Обрабатываеться')), (BUYING_STATUS_PENDING, _('Обрабатываеться')),
(BUYING_STATUS_BOUGHT, _('Куплен')) (BUYING_STATUS_BOUGHT, _('Куплен'))
) )
@ -52,10 +60,44 @@ BUYING_DEFAULT_CHOICE = BUYING_STATUS_IN_CART
class Buying(AbstractStatusModel): class Buying(AbstractStatusModel):
user = models.ForeignKey(get_user_model(),verbose_name=_('пользователь'), on_delete=models.CASCADE) user = models.ForeignKey(get_user_model(), verbose_name=_('пользователь'), on_delete=models.CASCADE)
offer = models.ForeignKey(Offer, verbose_name=_('позиция'), on_delete=models.CASCADE) offer = models.ForeignKey(Offer, verbose_name=_('позиция'), on_delete=models.CASCADE)
status = models.SmallIntegerField(_('статус'), default=BUYING_DEFAULT_CHOICE, choices=BUYING_STATUS_CHOICES) status = models.SmallIntegerField(_('статус'), default=BUYING_DEFAULT_CHOICE, choices=BUYING_STATUS_CHOICES)
amount = models.SmallIntegerField(_('колличество'), default=0)
class Meta: class Meta:
verbose_name = _('Покупка') verbose_name = _('Покупка')
verbose_name_plural = _('Покупки') verbose_name_plural = _('Покупки')
STATUS_GAINED = 0
STATUS_SPENT = 100
CASHBACK_STATUS_CHOICES = (
(STATUS_GAINED, _('заработанный')),
(STATUS_SPENT, _('потраченный')),
)
STATUS_DEFAULT = STATUS_GAINED
class CashBackManager(models.Manager):
def get_gained_cashback_sum(self, user):
return self.get_queryset().filter(user=user, status=STATUS_GAINED).aggregate(Avg('amount'))
def get_spent_cashback_sum(self, user):
return self.get_queryset().filter(user=user, status=STATUS_SPENT).aggregate(Avg('amount'))
class BuyingCashback(AbstractDateTimeModel):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
buying = models.OneToOneField(Buying, on_delete=models.CASCADE)
amount = models.DecimalField(_('Сумма'), 'cashback', 7, 2, default=0)
status = models.SmallIntegerField(_('статус'), default=STATUS_DEFAULT, choices=CASHBACK_STATUS_CHOICES)
objects = CashBackManager()
class Meta:
verbose_name_plural = _('cashback')
verbose_name = _('cashback')

@ -0,0 +1,3 @@
from django.dispatch import Signal
buying_bought = Signal(providing_args=['buying'])

@ -7,6 +7,6 @@ urlpatterns = [
# url(r'^$', views.CartDetail, name='CartDetail'), # url(r'^$', views.CartDetail, name='CartDetail'),
# url(r'^remove/(?P<offer_slug>[-\w]+)/$', views.CartRemove, name='CartRemove'), # url(r'^remove/(?P<offer_slug>[-\w]+)/$', views.CartRemove, name='CartRemove'),
# url(r'^add/$', views.CartAdd, name='CartAdd'), # url(r'^add/$', views.CartAdd, name='CartAdd'),
re_path(r'^buyings/history/', views.BuyingsHistory.as_view(), name='buyings_history') re_path(r'^history/', views.BuyingsHistory.as_view(), name='history'),
re_path(r'^buyings/$', views.CartView.as_view(), name='buyings'),
] ]

@ -5,12 +5,15 @@ from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import auth from django.contrib import auth
from django.views.generic import ListView from django.views.generic import ListView
from django.utils.translation import ugettext_lazy as _
from cart.models import Buying from cart.models import Buying
from core.views import ProtectedView from core.views import ProtectedListView
from .cart import Cart from .cart import Cart
from .forms import CartAddProductForm from .forms import CartAddProductForm
# from discount.layout import DiscountApllyForm # from discount.layout import DiscountApllyForm
# @csrf_exempt # @csrf_exempt
@ -56,16 +59,37 @@ from .forms import CartAddProductForm
# # 'discount_apply_form': discount_apply_form}) # # 'discount_apply_form': discount_apply_form})
class BuyingsHistory(ListView,ProtectedView): class CartView(ProtectedListView):
model = Buying model = Buying
paginate_by = settings.DEFAULT_PAGE_AMMOUNT paginate_by = settings.DEFAULT_PAGE_AMOUNT
context_object_name = 'bought_item_list' context_object_name = 'cart_items'
ordering = '-created_at' template_name = 'cart/cart.html'
template_name = 'cart/bought_history.html' ordering = '-create_at'
title = _('Корзина')
def get_queryset(self): def get_queryset(self):
self.object_list = super().get_queryset() qs = super().get_queryset()
return self.object_list return qs.filter(user=self.request.user)
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
context['title'] = self.title
return context
class BuyingsHistory(ProtectedListView):
model = Buying
paginate_by = settings.DEFAULT_PAGE_AMOUNT
context_object_name = 'bought_item_list'
ordering = '-create_at'
template_name = 'cart/bought_history.html'
title = _('История покупок')
def get_queryset(self):
qs = super().get_queryset()
return qs.filter(user=self.request.user)
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
context['title'] = self.title
return context

@ -1,13 +1,16 @@
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
# Create your views here. # Create your views here.
from django.views.generic import TemplateView from django.views.generic import TemplateView, ListView
class ProtectedView(LoginRequiredMixin, TemplateView): class ProtectedView(LoginRequiredMixin, TemplateView):
pass pass
class ProtectedListView(LoginRequiredMixin, ListView):
pass
class Handler404View(TemplateView): class Handler404View(TemplateView):
template_name = '404.html' template_name = '404.html'

@ -284,4 +284,10 @@ CART_SESSION_ID = 'cart'
IMPORT_EXPORT_USE_TRANSACTIONS = True IMPORT_EXPORT_USE_TRANSACTIONS = True
# PAGINATION SETTINGS # PAGINATION SETTINGS
DEFAULT_PAGE_AMMOUNT = 10 DEFAULT_PAGE_AMOUNT = 10
# DEFAULT REFERRAL POINTS
DEFAULT_REFERRAL_POINTS = 120
# CASHBACK RATIO IN PERCENTAGE
CASHBACK_RATIO = 4

@ -29,7 +29,7 @@
# query += u'"%s"' % sqs.query.clean(producer) # query += u'"%s"' % sqs.query.clean(producer)
# sqs = sqs.narrow(u'brand_exact:%s' % query) # sqs = sqs.narrow(u'brand_exact:%s' % query)
# return sqs # return sqs
from crispy_forms.layout import Layout, ButtonHolder, Submit, HTML, Field, Button from crispy_forms.layout import Layout, Field, Button
from django import forms from django import forms
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -38,6 +38,9 @@ from django.utils.translation import ugettext_lazy as _
from .models import Product from .models import Product
class ProductFilterForm(forms.Form):
pass
class ProductSearchForm(forms.ModelForm): class ProductSearchForm(forms.ModelForm):
field_template = 'bootstrap/forms/product_search.html' field_template = 'bootstrap/forms/product_search.html'
form_action = reverse_lazy('products:product_search') form_action = reverse_lazy('products:product_search')

@ -1,3 +1,4 @@
from django.contrib.auth import get_user_model
from django.db import models from django.db import models
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.contrib.postgres.fields import HStoreField from django.contrib.postgres.fields import HStoreField
@ -11,14 +12,41 @@ from mptt import (
from mptt.models import MPTTModel, TreeForeignKey from mptt.models import MPTTModel, TreeForeignKey
from autoslug import AutoSlugField from autoslug import AutoSlugField
from core.models import AbstractStatusModel, AbstractDateTimeModel, STATUS_ACTIVE, STATUS_DELETED from core.models import AbstractStatusModel, AbstractDateTimeModel, STATUS_ACTIVE, STATUS_DELETED, ActualOnlyManager
# --------------------------------- PRODUCT ATTRIBUTE ------------------------------------#
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(models.Manager):
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): class ProductAttribute(AbstractStatusModel):
name = models.CharField(max_length=64, blank=True, null=True, default=None) name = models.CharField(max_length=64, blank=True, null=True, default=None)
slug = AutoSlugField(populate_from='name') slug = AutoSlugField(populate_from='name')
type = models.SmallIntegerField(_('тип'), choices=PRODUCT_ATTRIBUTE_TYPE_CHOICES,
default=PRODUCT_ATTRIBUTE_TYPE_DEFAULT)
main_attribute = models.BooleanField(default=False) main_attribute = models.BooleanField(default=False)
objects = ProductAttributeManager()
def __str__(self): def __str__(self):
return self.name return self.name
@ -31,7 +59,7 @@ class ProductAttribute(AbstractStatusModel):
class Manufacturer(AbstractStatusModel): class Manufacturer(AbstractStatusModel):
name = models.CharField(max_length=64, blank=True, null=True, default=None) name = models.CharField(max_length=64, blank=True, null=True, default=None)
slug = AutoSlugField(populate_from='name') slug = AutoSlugField(populate_from='name')
image = models.ImageField(upload_to='producers', blank=True, verbose_name="image of producer") image = models.ImageField(upload_to='producers', blank=True, null=True, verbose_name=("Изображение"))
def __str__(self): def __str__(self):
return self.name return self.name
@ -45,7 +73,7 @@ class Manufacturer(AbstractStatusModel):
verbose_name_plural = _('Производители') verbose_name_plural = _('Производители')
class ProductCategoryManager(mptt_managers.TreeManager): class ProductCategoryManager(mptt_managers.TreeManager, ActualOnlyManager):
def get_root_categories(self): def get_root_categories(self):
return self.get_queryset().all() return self.get_queryset().all()
@ -75,21 +103,18 @@ register(ProductCategory, order_insertion_py=['name'])
class Product(AbstractStatusModel): class Product(AbstractStatusModel):
name = models.CharField(max_length=64, db_index=True, blank=True, null=True, default=None) name = models.CharField(_('имя'), max_length=64, db_index=True)
slug = AutoSlugField(populate_from='name') slug = AutoSlugField(_('slug'), populate_from='name', db_index=True)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00) description = models.TextField(_('описание'), blank=True, null=True, default=None)
description = models.TextField(db_index=True, blank=True, null=True, default=None) manufacturer = models.ForeignKey(Manufacturer, on_delete=models.SET_NULL, blank=True, null=True)
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.PROTECT) category = models.ForeignKey(ProductCategory, on_delete=models.SET_NULL, blank=True, null=True)
category = models.ForeignKey(ProductCategory, on_delete=models.SET_NULL, blank=True, null=True, default=None)
attributes = models.ManyToManyField(ProductAttribute, blank=True) attributes = models.ManyToManyField(ProductAttribute, blank=True)
discount_policy = HStoreField(blank=True, null=True, default={})
is_active = models.BooleanField(default=True)
def __str__(self): def __str__(self):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self, request):
return reverse_lazy('products:item', args=[self.slug]) return request.build_absolute_uri(reverse_lazy('products:item', args=[self.slug]))
class Meta: class Meta:
indexes = [ indexes = [
@ -112,19 +137,38 @@ class Product(AbstractStatusModel):
# pp.save() # pp.save()
class ProductAttributeValue(AbstractDateTimeModel): class ProductRate(AbstractStatusModel):
product = models.ForeignKey(Product, on_delete=models.PROTECT) user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
name = models.CharField(max_length=64, blank=True, null=True, default=None) product = models.ForeignKey(Product, on_delete=models.CASCADE)
rate = models.IntegerField(_('оценка'), 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.BooleanField(_('статус'), default=False)
class Meta:
verbose_name = _('Дисконт')
verbose_name_plural = _('Дисконты')
class ProductAttributeValue(AbstractStatusModel):
attribute = models.ForeignKey(ProductAttribute, on_delete=models.CASCADE, related_name='value')
slug = AutoSlugField(populate_from='name') slug = AutoSlugField(populate_from='name')
attribute = models.ForeignKey(ProductAttribute, on_delete=models.CASCADE, related_name='values') value = HStoreField(_('значение'), default={})
def __str__(self): def __str__(self):
return self.name return self.value.serialize
class Meta: class Meta:
unique_together = ('name', 'attribute') unique_together = ('attribute', 'value')
verbose_name = 'Product attribute value' verbose_name = _('Значение аттрибута')
verbose_name_plural = 'Product attribute values' verbose_name_plural = _('Значение аттрибутов')
# ----------------- PRODUCT IMAGE STATUS LIST ------------------ # ----------------- PRODUCT IMAGE STATUS LIST ------------------

@ -20,10 +20,9 @@ from .models import ProductCategory
urlpatterns = [ urlpatterns = [
re_path(r'^product/category/search/$', views.ProductSearchView.as_view(), name='product_search'), re_path(r'^search/$', views.ProductSearchView.as_view(), name='product_search'),
re_path(r'^product/(?P<product_slug>\w+)/$', views.ProductDetailsView.as_view(), name='product_details'), re_path(r'^product/(?P<product_slug>\w+)/$', views.ProductDetailsView.as_view(), name='product_details'),
re_path(r'^product/category/list/$', views.ProductCategoryListView.as_view(), name='product_category'), re_path(r'^category/list/$', views.ProductCategoryListView.as_view(), name='product_category'),
# Uncomment for elasticsearch # Uncomment for elasticsearch
# url(r'^autocomplete/$', autocomplete), # url(r'^autocomplete/$', autocomplete),

@ -28,7 +28,7 @@ class ProductSearchView(ListView):
model = Product model = Product
template_name = 'products/product_search.html' template_name = 'products/product_search.html'
context_object_name = 'products' context_object_name = 'products'
paginate_by = settings.DEFAULT_PAGE_AMMOUNT paginate_by = settings.DEFAULT_PAGE_AMOUNT
title = _('Поиск товара') title = _('Поиск товара')
def get_queryset(self): def get_queryset(self):
@ -47,7 +47,7 @@ class ProductListView(ListView):
model = Product model = Product
template_name = 'products/product_list.html' template_name = 'products/product_list.html'
context_object_name = 'products' context_object_name = 'products'
paginate_by = settings.DEFAULT_PAGE_AMMOUNT paginate_by = settings.DEFAULT_PAGE_AMOUNT
title = _('Список товаров') title = _('Список товаров')
def get_queryset(self): def get_queryset(self):

@ -8,8 +8,8 @@ from django.utils.translation import ugettext_lazy as _
from rangefilter.filter import DateRangeFilter, DateTimeRangeFilter from rangefilter.filter import DateRangeFilter, DateTimeRangeFilter
from core.admin import SafeModelAdmin from core.admin import SafeModelAdmin
from .models import Referral, ReferralStats, PartnerStats from .models import Referral, ReferralStats
from .forms import ReferralAdminForm, ReferralStatsAdminForm, PartnerStatsAdminForm from .forms import ReferralAdminForm, ReferralStatsAdminForm
class ReferralAdminInline(admin.TabularInline): class ReferralAdminInline(admin.TabularInline):
@ -69,37 +69,3 @@ class ReferralStatsAdmin(SafeModelAdmin):
list_select_related = ('referral',) list_select_related = ('referral',)
search_fields = ('referral__name', 'referral__code',) search_fields = ('referral__name', 'referral__code',)
ordering = ('-create_at',) ordering = ('-create_at',)
@register(PartnerStats)
class PartnerStatsAdmin(SafeModelAdmin):
def has_add_permission(self, request):
return False
def stats_owner(self, stats):
try:
link = reverse_lazy(
'admin:{}_{}_change'.format(stats.user._meta.app_label, stats.user._meta.object_name.lower()),
args=(stats.user.id,)
)
name = stats.user.email
except Exception as e:
link = '#'
name = "None"
return format_html('<a href="{}">{}</a>', link, name)
stats_owner.short_description = _('User')
def formatted_total_conversion(self, stats):
return str(stats.formatted_total_conversion) + "%"
formatted_total_conversion.short_description = _('Total conversion')
def converted_total_earnings(self, stats):
return stats.total_earnings if stats.total_earnings > 0 else 0
converted_total_earnings.short_description = _('Total earnings')
form = PartnerStatsAdminForm
list_display = ('stats_owner', 'total_visits', 'total_regs')
search_fields = ('user__email',)

@ -1,7 +1,7 @@
from django import forms from django import forms
from django.forms import URLInput, ALL_FIELDS from django.forms import ALL_FIELDS
from .models import Referral, ReferralStats, PartnerStats from .models import Referral, ReferralStats
class ReferralAdminForm(forms.ModelForm): class ReferralAdminForm(forms.ModelForm):
@ -30,17 +30,3 @@ class ReferralStatsAdminForm(forms.ModelForm):
def disable_fields(self, fields): def disable_fields(self, fields):
for field_name, field in fields.items(): for field_name, field in fields.items():
field.disabled = True field.disabled = True
class PartnerStatsAdminForm(forms.ModelForm):
class Meta:
model = PartnerStats
fields = ALL_FIELDS
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.disable_fields(self.fields)
def disable_fields(self, fields):
for field_name, field in fields.items():
field.disabled = True

@ -15,6 +15,8 @@ from django.utils.translation import ugettext_lazy as _
# Create your models here. # Create your models here.
from registration.signals import user_activated from registration.signals import user_activated
from cart.models import Buying
from cart.signals import buying_bought
from core.models import AbstractStatusModel, STATUS_ACTIVE, STATUS_CHOICES from core.models import AbstractStatusModel, STATUS_ACTIVE, STATUS_CHOICES
from referral.mixins import StatsFormatterMixin from referral.mixins import StatsFormatterMixin
from referral.utils import get_cookie from referral.utils import get_cookie
@ -30,7 +32,7 @@ REFERRAL_DEFAULT_CHOICE = STATUS_ACTIVE
class Referral(AbstractStatusModel): class Referral(AbstractStatusModel):
CODE_LENGTH = 8 CODE_LENGTH = 8
referral_owner = models.OneToOneField(get_user_model(), verbose_name=_('пользователь'), on_delete=models.CASCADE, user = models.OneToOneField(get_user_model(), verbose_name=_('пользователь'), on_delete=models.CASCADE,
primary_key=True primary_key=True
) )
code = models.CharField(_('код'), max_length=255) code = models.CharField(_('код'), max_length=255)
@ -38,7 +40,7 @@ class Referral(AbstractStatusModel):
@classmethod @classmethod
def create(cls, user, code): def create(cls, user, code):
referral = cls(referral_owner=user, code=code) referral = cls(user=user, code=code)
return referral return referral
@staticmethod @staticmethod
@ -69,23 +71,12 @@ class ReferralStats(StatsFormatterMixin, AbstractStatusModel):
referral = models.OneToOneField(Referral, verbose_name=_('Реферал'), on_delete=models.CASCADE, primary_key=True) referral = models.OneToOneField(Referral, verbose_name=_('Реферал'), on_delete=models.CASCADE, primary_key=True)
visits = models.IntegerField(_('Посищения'), default=0) visits = models.IntegerField(_('Посищения'), default=0)
registrations = models.IntegerField(_('Регистрации'), default=0) registrations = models.IntegerField(_('Регистрации'), default=0)
points = models.IntegerField(_('партенрские балы'), default=0)
class Meta: class Meta:
verbose_name = _('Реферальная статистика') verbose_name = _('Реферальная статистика')
verbose_name_plural = _('Реферальная статистика') verbose_name_plural = _('Реферальная статистика')
class PartnerStats(StatsFormatterMixin, AbstractStatusModel):
user = models.OneToOneField(get_user_model(), verbose_name=_('username'), on_delete=models.CASCADE,
primary_key=True)
total_visits = models.BigIntegerField(_('Всего посещений'), default=0)
total_regs = models.BigIntegerField(_('Всего регистраций'), default=0)
class Meta:
verbose_name = _('Партнеская статистика')
verbose_name_plural = _('Партнерская статистика')
@receiver(post_save, sender=get_user_model()) @receiver(post_save, sender=get_user_model())
def create_user_referral(sender, instance, created, **kwargs): def create_user_referral(sender, instance, created, **kwargs):
if created and Referral.objects.filter(user=instance).first() is None: if created and Referral.objects.filter(user=instance).first() is None:
@ -95,8 +86,6 @@ def create_user_referral(sender, instance, created, **kwargs):
@receiver(post_save, sender=Referral) @receiver(post_save, sender=Referral)
def init_referral_stuff(sender, instance, created, *args, **kwargs): def init_referral_stuff(sender, instance, created, *args, **kwargs):
if created: if created:
if PartnerStats.objects.filter(user=instance.referral_owner).count() == 0:
PartnerStats.objects.create(user=instance.referral_owner).save()
if ReferralStats.active.filter(referral=instance).count() == 0: if ReferralStats.active.filter(referral=instance).count() == 0:
ReferralStats.active.create(referral=instance).save() ReferralStats.active.create(referral=instance).save()
@ -108,11 +97,14 @@ def update_ref_stats(sender, user, request, **kwargs):
referral = Referral.objects.filter(code__exact=referral_code).first() referral = Referral.objects.filter(code__exact=referral_code).first()
if referral: if referral:
referral.referralstats.registrations += 1 referral.referralstats.registrations += 1
referral.referralstats.points += settings.DEFAULT_REFERRAL_POINTS
referral.referralstats.save() referral.referralstats.save()
referral.user.partnerstats.total_regs += 1 user.referral_user = referral.user
referral.user.partnerstats.save()
user.referral_code = referral
user.referral_user = referral.referral_owner
user.save() user.save()
else: else:
logger.warning("Missing referral code in database: " + referral_code) logger.warning("Missing referral code in database: " + referral_code)
@receiver(buying_bought,sender=Buying)
def charge_referral_points(sender,buying,*args,**kwargs):
pass

@ -1,3 +1,10 @@
.reg__text__center { .reg__text__center {
text-align: center; text-align: center;
} }
@media (max-width: 991.98px) {
.mb-10 {
margin-bottom: 30px !important;
}
}

@ -2,7 +2,7 @@
{% load static %} {% load static %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% block title %}Вход{% endblock %} {% block title %}{{ title }}{% endblock %}
{% block content %} {% block content %}
@ -13,6 +13,10 @@
<div class="col-12 text-center"> <div class="col-12 text-center">
<div class="content__title title__data">Вход</div> <div class="content__title title__data">Вход</div>
</div> </div>
<div class="col-12 text-center ">
<div class="reg title__data-mob mb-10">Вход</div>
</div>
</div> </div>
{% crispy form %} {% crispy form %}
</div> </div>

@ -39,6 +39,8 @@
<div class="main"> <div class="main">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
{% block content_message %}
{% endblock content_message %}
{% block content %} {% block content %}
{% endblock content %} {% endblock content %}
</div> </div>

@ -21,7 +21,7 @@
</div> </div>
<div class="d-sm-flex flex-row cabinet__wrap"> <div class="d-sm-flex flex-row cabinet__wrap">
<div class="cabinet__title bonus__title"> <div class="cabinet__title bonus__title">
Ваша ссылка: <a href=""title="{{ ref_link }}">{{ ref_link }}</a> Ваша ссылка: <a href="#"title="{{ ref_link }}">{{ ref_link }}</a>
</div> </div>
<div class="bonus__btn_bx"> <div class="bonus__btn_bx">
<a href="{{ request.path }}#" id="ref-link" class="cabinet__link_fill">Скопировать</a> <a href="{{ request.path }}#" id="ref-link" class="cabinet__link_fill">Скопировать</a>
@ -38,7 +38,7 @@
{% for ref_user in referral_user_list %} {% for ref_user in referral_user_list %}
<div class="d-flex flex-row friends__row"> <div class="d-flex flex-row friends__row">
<div class="friends__title">{{ ref_user.name }}</div> <div class="friends__title">{{ ref_user.name }}</div>
<div class="friends__info">{{ ref_user. }}</div> <div class="friends__info">{{ ref_user }}</div>
</div> </div>
{% empty %} {% empty %}
<div class="d-flex flex-row friends__row"> <div class="d-flex flex-row friends__row">
@ -81,7 +81,7 @@
{% endfor %} {% endfor %}
</table> </table>
<div class="history__btn_bx"> <div class="history__btn_bx">
<a href="{% url 'cart:buyings_history' %}" class="cabinet__link_fill">Вся история покупок</a> <a href="{% url 'cart:history' %}" class="cabinet__link_fill">Вся история покупок</a>
</div> </div>
</div> </div>
<div class="cabinet__userdata"> <div class="cabinet__userdata">

@ -2,7 +2,12 @@
{% block content %} {% block content %}
<div class="col-12 "> <div class="col-12 ">
<div class="cabinet__history"> <div class="cabinet__history">
<div class="cabinet__title">История покупок</div> <div class="history__btn_bx">
<a href="{% url 'cabinet:index' %}" class="cabinet__link_fill">Вернуться</a>
</div>
<div class="cabinet__title text-center">
<h3>История покупок</h3>
</div>
<table class="table"> <table class="table">
<tr> <tr>
<th>Дата</th> <th>Дата</th>
@ -29,6 +34,23 @@
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
<div class="pagination">
<span class="step-links">
{% if bought_item_list.has_previous %}
<a href="?page=1">&laquo; 1</a>
<a href="?page={{ bought_item_list.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Страница {{ bought_item_list.number }} of {{ bought_item_list.paginator.num_pages }}.
</span>
{% if contacts.has_next %}
<a href="?page={{ bought_item_list.next_page_number }}">next</a>
<a href="?page={{ bought_item_list.paginator.num_pages }}">last &raquo;</a>
{% endif %}
</span>
</div>
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}

@ -0,0 +1,170 @@
{% extends 'base.html' %}
{% block content %}
<div class="col-12">
<div class="content">
<div class="content__title">Корзина</div>
<div class="basket">
<div class="basket__title">
<div class="row">
<div class="col-6">
Товар
</div>
<div class="col-2">
Цена
</div>
<div class="col-2">
Количество
</div>
<div class="col-2">
Сумма
</div>
</div>
</div>
<div class="basket__item">
<div class="row align-items-center">
<div class="col-md-6 col-12">
<div class="basket__good d-flex align-items-center">
<div class="basket__image"><img src="./img/item-image-min.png" alt=""></div>
<div class="basket__info">
<ul class="basket__breadcrumbs breadcrumbs">
<li>Товары</li>
<li>Антивирусы</li>
</ul>
<div class="basket__good-name">Microsoft Windows 7 Professional SP1
(x32/x64) GGK [6PC-00024]
</div>
</div>
</div>
</div>
<div class="col-md-6 col-12 basket__option">
<div class="row">
<div class="col-4 align-self-start">
<div class="basket__title-mob">Цена</div>
<div class="basket__price">10 000 ₽</div>
</div>
<div class="col-4 align-self-start">
<div class="basket__title-mob align-self-start">Количество</div>
<div class="basket__count">2 шт</div>
</div>
<div class="col-4 align-self-start">
<div class="basket__title-mob ">Сумма</div>
<div class="basket__sum">20 000 ₽</div>
</div>
</div>
</div>
</div>
</div>
<div class="basket__item">
<div class="row align-items-center">
<div class="col-md-6 col-12">
<div class="basket__good d-flex align-items-center">
<div class="basket__image"><img src="./img/item-image-min.png" alt=""></div>
<div class="basket__info">
<ul class="basket__breadcrumbs breadcrumbs">
<li>Товары</li>
<li>Антивирусы</li>
</ul>
<div class="basket__good-name">Microsoft Windows 7 Professional SP1
(x32/x64) GGK [6PC-00024]
</div>
</div>
</div>
</div>
<div class="col-md-6 col-12 basket__option">
<div class="row">
<div class="col-4 align-self-start">
<div class="basket__title-mob">Цена</div>
<div class="basket__price">10 000 ₽</div>
</div>
<div class="col-4 align-self-start">
<div class="basket__title-mob align-self-start">Количество</div>
<div class="basket__count">2 шт</div>
</div>
<div class="col-4 align-self-start">
<div class="basket__title-mob ">Сумма</div>
<div class="basket__sum">20 000 ₽</div>
</div>
</div>
</div>
</div>
</div>
<div class="basket__item">
<div class="row align-items-center">
<div class="col-md-6 col-12">
<div class="basket__good d-flex align-items-center">
<div class="basket__image"><img src="./img/item-image-min.png" alt=""></div>
<div class="basket__info">
<ul class="basket__breadcrumbs breadcrumbs">
<li>Товары</li>
<li>Антивирусы</li>
</ul>
<div class="basket__good-name">Microsoft Windows 7 Professional SP1
(x32/x64) GGK [6PC-00024]
</div>
</div>
</div>
</div>
<div class="col-md-6 col-12 basket__option">
<div class="row">
<div class="col-4 align-self-start">
<div class="basket__title-mob">Цена</div>
<div class="basket__price">10 000 ₽</div>
</div>
<div class="col-4 align-self-start">
<div class="basket__title-mob align-self-start">Количество</div>
<div class="basket__count">2 шт</div>
</div>
<div class="col-4 align-self-start">
<div class="basket__title-mob ">Сумма</div>
<div class="basket__sum">20 000 ₽</div>
</div>
</div>
</div>
</div>
</div>
<div class="basket__item">
<div class="row align-items-center">
<div class="col-md-6 col-12">
<div class="basket__good d-flex align-items-center">
<div class="basket__image"><img src="./img/item-image-min.png" alt=""></div>
<div class="basket__info">
<ul class="basket__breadcrumbs breadcrumbs">
<li>Товары</li>
<li>Антивирусы</li>
</ul>
<div class="basket__good-name">Microsoft Windows 7 Professional SP1
(x32/x64) GGK [6PC-00024]
</div>
</div>
</div>
</div>
<div class="col-md-6 col-12 basket__option">
<div class="row">
<div class="col-4 align-self-start">
<div class="basket__title-mob">Цена</div>
<div class="basket__price">10 000 ₽</div>
</div>
<div class="col-4 align-self-start">
<div class="basket__title-mob align-self-start">Количество</div>
<div class="basket__count">2 шт</div>
</div>
<div class="col-4 align-self-start">
<div class="basket__title-mob ">Сумма</div>
<div class="basket__sum">20 000 ₽</div>
</div>
</div>
</div>
</div>
</div>
<div class="basket__result">
<div class="result__sum">Итого: 20 000 ₽</div>
<div class="result__cashback">Кэшбек 800 ₽</div>
</div>
<div class="basket__controls">
<a class="basket__btn-fill" href="#">Оформить</a>
<a class="basket__btn" href="#">Продолжить покупки</a>
</div>
</div>
</div>
</div>
{% endblock content %}

@ -4,47 +4,42 @@
{% block content %} {% block content %}
<div class="col-4 left-menu"> <div class="col-4 left-menu">
<div class=""> {% if left_product_search_form %}
{% if left_product_search_form %} <div class="left-menu__search">
<div class="left-menu__search"> {% crispy left_product_search_form %}
{% crispy left_product_search_form %} </div>
</div> {% endif %}
{% endif %}
{% block extra_left_menu_items %} {% block extra_left_menu_items %}
{% endblock extra_left_menu_items %} {% endblock extra_left_menu_items %}
{% if product_root_categories %}
<div class="left-menu__category"> {% if product_root_categories %}
{% for category in product_root_categories %} <div class="left-menu__category">
<a class="left-menu__category-item" {% for category in product_root_categories %}
href="{% url "products:category" slug=category.slug %}"> <a class="left-menu__category-item"
href="{% url "products:category" slug=category.slug %}">
<span class="category-item__image"> <span class="category-item__image">
<img src="{% static category.img %}" alt="{{ category.alt }}"> <img src="{% static category.img %}" alt="{{ category.alt }}">
</span> </span>
<span class="category-item__name">{{ category.name }}</span> <span class="category-item__name">{{ category.name }}</span>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
{% if contact_us_form %} {% if contact_us_form %}
<div class="left-menu__contact"> <div class="left-menu__contact">
<div class="left-menu__title"> <div class="left-menu__title">
Не нашли нужную программу или разработчика? Не нашли нужную программу или разработчика?
Заполните форму и с Вами свяжется наш специалист. Заполните форму и с Вами свяжется наш специалист.
</div> </div>
{% if contact_us_form_message %} {% if contact_us_form_message %}
{% if "success" in contact_us_form_message.tags %} <div class="text-{% if "success" in contact_us_form_message.tags %}success{% elif "error" in contact_us_form_message.tags %}danger{% endif %}">
<div class="text-success"> {{ contact_us_form_message }}
{% elif "error" in contact_us_form_message.tags %} </div>
<div class="text-danger">
{% endif %}
{{ contact_us_form_message }}
</div>
{% endif %} {% endif %}
{% crispy contact_us_form %} {% crispy contact_us_form %}
</div> </div>
{% endif %} {% endif %}
</div>
</div> </div>
<div class="col-lg-8 col-md-12"> <div class="col-lg-8 col-md-12">
<div class="content"> <div class="content">
@ -62,6 +57,25 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% if product_root_categories %}
<div class="content__category">
<ul class="content__category-list">
{% for category in product_root_categories %}
<li>
<a href="{% url "products:category" slug=category.slug %}">
<span class="category-item__image">
<img src="{% static category.img %}" alt="{{ category.alt }}">
</span>
<span class="category-item__name">{{ category.name }}</span>
</a>
</li>
{% empty %}
<li><a href="#">Microsoft</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% include 'components/breadcrumbs.html' %}
{% block right_common_content %} {% block right_common_content %}
{% endblock right_common_content %} {% endblock right_common_content %}

@ -1,2 +1,7 @@
{% block breadcrumbs %} {% if request.resolver_match.url_name != "index" %}
{% endblock breadcrumbs %} <ul class="breadcrumbs">
<li><a href="{% url 'index:index' %}">Главная страница</a></li>
{% block breadcrumbs %}
{% endblock breadcrumbs %}
</ul>
{% endif %}

@ -4,12 +4,12 @@
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col-lg-3 col-md-12"> <div class="col-lg-3 col-md-12">
<div class="footer__about"> <div class="footer__about">
&copy; Компания «Русские Программы» 2015–2018 &copy; Компания «Русские Программы» 2015–{% now 'Y' %}
</div> </div>
</div> </div>
<div class="col-md col-md-2 col-5"> <div class="col-md col-md-2 col-5">
<ul class="footer__menu"> <ul class="footer__menu">
<li><a href="catalog.html">Каталог</a></li> <li><a href="{% url 'products:product_category' %}">Каталог</a></li>
<li><a href="news.html">Новости</a></li> <li><a href="news.html">Новости</a></li>
<li><a href="cashback.html">Акции</a></li> <li><a href="cashback.html">Акции</a></li>
<li><a href="about.html">О компании</a></li> <li><a href="about.html">О компании</a></li>

@ -29,7 +29,7 @@
<div class="col-lg-2 col align-self-center"> <div class="col-lg-2 col align-self-center">
<div class="row"> <div class="row">
<div class="header__contact"> <div class="header__contact">
<a href="#">8 (495) 799-65-54</a> <a href="call:84957996554">8 (495) 799-65-54</a>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@ -49,7 +49,7 @@
<a href="{% url 'cabinet:index' %}" title="{{ request.user.username }}" class="header__link">Личный кабинет</a> <a href="{% url 'cabinet:index' %}" title="{{ request.user.username }}" class="header__link">Личный кабинет</a>
</div> </div>
{% endif %} {% endif %}
<a href="basket.html" class="header__cart"> <a href="{% url 'cart:buyings' %}" class="header__cart">
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<img src="{% static "img/header/cart-icon.svg" %}" <img src="{% static "img/header/cart-icon.svg" %}"
alt="Перейти в корзину"><span class="cart__count">{{ "5" }}</span> alt="Перейти в корзину"><span class="cart__count">{{ "5" }}</span>

@ -7,6 +7,13 @@
<li><a href="/flatpages_ext">Новости</a></li> <li><a href="/flatpages_ext">Новости</a></li>
<li><a href="/flatpages_ext/cashback">Акции</a></li> <li><a href="/flatpages_ext/cashback">Акции</a></li>
<li><a href="/flatpages_ext/about">О компании</a></li> <li><a href="/flatpages_ext/about">О компании</a></li>
{% if not user.is_authenticated %}
<li><a href="{% url 'accounts_ext:register' %}">Регистрация</a></li>
<li><a href="{% url 'accounts_ext:login' %}">Войти</a></li>
{% else %}
<li><a href="{% url "cabinet:index" %}">Личный кабинет</a></li>
<li><a href="{% url "accounts_ext:logout" %}">Выйти</a></li>
{% endif %}
</ul> </ul>
</div> </div>
</div> </div>

@ -1,26 +1,58 @@
{% extends 'common.html' %} {% extends 'common.html' %}
{% block extra_left_menu_items %} {% load static %}
{% block extra_left_menu_items %}
<div class="left-menu__filter">
<div class="filter__title">
Фильтр товаров
</div>
<div class="filter__subtitle">
Цена
</div>
<form action="">
<div class="filter__controls">
<span>от</span>
<input type="text" class="filter__min" value="">
<span>до</span>
<input type="text" class="filter__max" value="">
</div>
<div id="range" data-min="20" data-max="9999"></div>
</form>
<div class="filter__category">
<div class="filter__category-item">
<div class="category__title">Производитель</div>
<ul>
<li><a href="#">Nokia</a></li>
<li><a href="#">Apple</a></li>
<li><a href="#">Samsung</a></li>
<li><a href="#">Dexp</a></li>
<li><a href="#">Seven</a></li>
</ul>
</div>
<div class="filter__category-item">
<div class="category__title">Тип поставки</div>
<ul>
<li><a href="#">Nokia</a></li>
<li><a href="#">Apple</a></li>
<li><a href="#">Samsung</a></li>
<li><a href="#">Dexp</a></li>
<li><a href="#">Seven</a></li>
</ul>
</div>
<div class="filter__category-item">
<div class="category__title">Назначение</div>
<ul>
<li><a href="#">Nokia</a></li>
<li><a href="#">Apple</a></li>
<li><a href="#">Samsung</a></li>
<li><a href="#">Dexp</a></li>
<li><a href="#">Seven</a></li>
</ul>
</div>
</div>
</div>
{% endblock extra_left_menu_items %} {% endblock extra_left_menu_items %}
{% block right_common_content %} {% block right_common_content %}
<div class="content__category">
<ul class="content__category-list">
<li><a href="#">Microsoft</a></li>
<li><a href="#">Антивирусы</a></li>
<li><a href="#">1C</a></li>
<li><a href="#">Графика и видео</a></li>
<li><a href="#">Проектирование</a></li>
<li><a href="#">Офисные программы</a></li>
<li><a href="#">Резервное копирование</a></li>
<li><a href="#">Виртуализация</a></li>
</ul>
</div>
<ul class="breadcrumbs">
<li><a href="#">Главная страница</a></li>
<li><a href="#">Ещё страница</a></li>
<li><a href="#">Товары</a></li>
<li>Антивирусы</li>
</ul>
<div class="content__subtitle">Производители</div> <div class="content__subtitle">Производители</div>
<ul class="content__maker-list"> <ul class="content__maker-list">
<li><a href="#">Avast</a></li> <li><a href="#">Avast</a></li>
@ -42,142 +74,73 @@
</form> </form>
</div> </div>
<div class="catalog"> <div class="catalog">
<div class="catalog__item"> {% for product in products %}
<div class="catalog__information"> <div class="catalog__item">
<div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div> <div class="catalog__information">
<div class="catalog__dec"> <div class="catalog__image">
<div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия <img src="{% static product.product_image.url %}" alt="{{ product.name }}">
для 26 компьютеров</a></div>
<div class="catalog__star">
<form action="">
<select class="stars" data-rating="2">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</form>
</div> </div>
<div class="catalog__price">79 400 ₽ / 1398 $</div> <div class="catalog__dec">
<div class="catalog__dec-list"> <div class="catalog__title">
<ul> <a href="{% url 'products:product' slug=product.slug %}">
<li><span>Артикул:</span>057J1-WWW8695-T548</li> {{ product.name }}
<li><span>НДС:</span>Включен</li> </a>
<li><span>Платформа:</span>Windows</li> </div>
<li><span>Тип поставки:</span>Любой из доступных</li> <div class="catalog__price">
<li><span>Срок поставки:</span>3-14 дней</li> {{ product.offer.price }} {{ product.offer.currency }}
<li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li> </div>
</ul> <div class="catalog__dec-list">
<ul>
<li><span>Артикул:</span>057J1-WWW8695-T548</li>
<li><span>НДС:</span>Включен</li>
<li><span>Платформа:</span>Windows</li>
<li><span>Тип поставки:</span>Любой из доступных</li>
<li><span>Срок поставки:</span>3-14 дней</li>
<li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li>
</ul>
</div>
</div> </div>
</div> </div>
</div> <div class="catalog__btn">
<div class="catalog__btn"> <a href="#">Купить</a>
<a href="#">Купить</a>
</div>
</div>
<div class="catalog__item">
<div class="catalog__information">
<div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div>
<div class="catalog__dec">
<div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия
для 26 компьютеров</a></div>
<div class="catalog__star">
<form action="">
<select class="stars" data-rating="2">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</form>
</div>
<div class="catalog__price">79 400 ₽ / 1398 $</div>
<div class="catalog__dec-list">
<ul>
<li><span>Артикул:</span>057J1-WWW8695-T548</li>
<li><span>НДС:</span>Включен</li>
<li><span>Платформа:</span>Windows</li>
<li><span>Тип поставки:</span>Любой из доступных</li>
<li><span>Срок поставки:</span>3-14 дней</li>
<li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li>
</ul>
</div>
</div> </div>
</div> </div>
<div class="catalog__btn"> {% empty %}
<a href="#">Купить</a> <div class="catalog__item">
</div> <div class="catalog__information">
</div> <div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div>
<div class="catalog__item"> <div class="catalog__dec">
<div class="catalog__information"> <div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия
<div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div> для 26 компьютеров</a></div>
<div class="catalog__dec"> <div class="catalog__star">
<div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия <form action="">
для 26 компьютеров</a></div> <select class="stars" data-rating="2">
<div class="catalog__star"> <option value="1">1</option>
<form action=""> <option value="2">2</option>
<select class="stars" data-rating="2"> <option value="3">3</option>
<option value="1">1</option> <option value="4">4</option>
<option value="2">2</option> <option value="5">5</option>
<option value="3">3</option> </select>
<option value="4">4</option> </form>
<option value="5">5</option> </div>
</select> <div class="catalog__price">79 400 ₽ / 1398 $</div>
</form> <div class="catalog__dec-list">
</div> <ul>
<div class="catalog__price">79 400 ₽ / 1398 $</div> <li><span>Артикул:</span>057J1-WWW8695-T548</li>
<div class="catalog__dec-list"> <li><span>НДС:</span>Включен</li>
<ul> <li><span>Платформа:</span>Windows</li>
<li><span>Артикул:</span>057J1-WWW8695-T548</li> <li><span>Тип поставки:</span>Любой из доступных</li>
<li><span>НДС:</span>Включен</li> <li><span>Срок поставки:</span>3-14 дней</li>
<li><span>Платформа:</span>Windows</li> <li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li>
<li><span>Тип поставки:</span>Любой из доступных</li> </ul>
<li><span>Срок поставки:</span>3-14 дней</li> </div>
<li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li>
</ul>
</div> </div>
</div> </div>
</div> <div class="catalog__btn">
<div class="catalog__btn"> <a href="basket.html">Купить</a>
<a href="#">Купить</a>
</div>
</div>
<div class="catalog__item">
<div class="catalog__information">
<div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div>
<div class="catalog__dec">
<div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия
для 26 компьютеров</a></div>
<div class="catalog__star">
<form action="">
<select class="stars" data-rating="2">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</form>
</div>
<div class="catalog__price">79 400 ₽ / 1398 $</div>
<div class="catalog__dec-list">
<ul>
<li><span>Артикул:</span>057J1-WWW8695-T548</li>
<li><span>НДС:</span>Включен</li>
<li><span>Платформа:</span>Windows</li>
<li><span>Тип поставки:</span>Любой из доступных</li>
<li><span>Срок поставки:</span>3-14 дней</li>
<li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li>
</ul>
</div>
</div> </div>
</div> </div>
<div class="catalog__btn"> {% endfor %}
<a href="basket.html">Купить</a>
</div>
</div>
</div> </div>
<div class="contact"> <div class="contact">
<form action=""> <form action="">

@ -1,27 +1,58 @@
{% extends 'common.html' %} {% extends 'common.html' %}
{% block title %}{{ title }}{% endblock title %}
{% block extra_left_menu_items %} {% block extra_left_menu_items %}
<div class="left-menu__filter">
<div class="filter__title">
Фильтр товаров
</div>
<div class="filter__subtitle">
Цена
</div>
<form action="">
<div class="filter__controls">
<span>от</span>
<input type="text" class="filter__min" value="">
<span>до</span>
<input type="text" class="filter__max" value="">
</div>
<div id="range" data-min="20" data-max="9999"></div>
</form>
<div class="filter__category">
<div class="filter__category-item">
<div class="category__title">Производитель</div>
<ul>
<li><a href="#">Nokia</a></li>
<li><a href="#">Apple</a></li>
<li><a href="#">Samsung</a></li>
<li><a href="#">Dexp</a></li>
<li><a href="#">Seven</a></li>
</ul>
</div>
<div class="filter__category-item">
<div class="category__title">Тип поставки</div>
<ul>
<li><a href="#">Nokia</a></li>
<li><a href="#">Apple</a></li>
<li><a href="#">Samsung</a></li>
<li><a href="#">Dexp</a></li>
<li><a href="#">Seven</a></li>
</ul>
</div>
<div class="filter__category-item">
<div class="category__title">Назначение</div>
<ul>
<li><a href="#">Nokia</a></li>
<li><a href="#">Apple</a></li>
<li><a href="#">Samsung</a></li>
<li><a href="#">Dexp</a></li>
<li><a href="#">Seven</a></li>
</ul>
</div>
</div>
</div>
{% endblock extra_left_menu_items %} {% endblock extra_left_menu_items %}
{% block right_common_content %} {% block right_common_content %}
<div class="content__category">
<ul class="content__category-list">
<li><a href="#">Microsoft</a></li>
<li><a href="#">Антивирусы</a></li>
<li><a href="#">1C</a></li>
<li><a href="#">Графика и видео</a></li>
<li><a href="#">Проектирование</a></li>
<li><a href="#">Офисные программы</a></li>
<li><a href="#">Резервное копирование</a></li>
<li><a href="#">Виртуализация</a></li>
</ul>
</div>
<ul class="breadcrumbs">
<li><a href="#">Главная страница</a></li>
<li><a href="#">Ещё страница</a></li>
<li><a href="#">Товары</a></li>
<li>Антивирусы</li>
</ul>
<div class="content__subtitle">Производители</div> <div class="content__subtitle">Производители</div>
<ul class="content__maker-list"> <ul class="content__maker-list">
<li><a href="#">Avast</a></li> <li><a href="#">Avast</a></li>
@ -43,142 +74,144 @@
</form> </form>
</div> </div>
<div class="catalog"> <div class="catalog">
<div class="catalog__item"> {% for product in products %}
<div class="catalog__information"> <div class="catalog__item">
<div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div> <div class="catalog__information">
<div class="catalog__dec"> <div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div>
<div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия <div class="catalog__dec">
для 26 компьютеров</a></div> <div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия
<div class="catalog__star"> для 26 компьютеров</a></div>
<form action=""> <div class="catalog__star">
<select class="stars" data-rating="2"> <form action="">
<option value="1">1</option> <select class="stars" data-rating="2">
<option value="2">2</option> <option value="1">1</option>
<option value="3">3</option> <option value="2">2</option>
<option value="4">4</option> <option value="3">3</option>
<option value="5">5</option> <option value="4">4</option>
</select> <option value="5">5</option>
</form> </select>
</div> </form>
<div class="catalog__price">79 400 ₽ / 1398 $</div> </div>
<div class="catalog__dec-list"> <div class="catalog__price">79 400 ₽ / 1398 $</div>
<ul> <div class="catalog__dec-list">
<li><span>Артикул:</span>057J1-WWW8695-T548</li> <ul>
<li><span>НДС:</span>Включен</li> <li><span>Артикул:</span>057J1-WWW8695-T548</li>
<li><span>Платформа:</span>Windows</li> <li><span>НДС:</span>Включен</li>
<li><span>Тип поставки:</span>Любой из доступных</li> <li><span>Платформа:</span>Windows</li>
<li><span>Срок поставки:</span>3-14 дней</li> <li><span>Тип поставки:</span>Любой из доступных</li>
<li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li> <li><span>Срок поставки:</span>3-14 дней</li>
</ul> <li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li>
</ul>
</div>
</div> </div>
</div> </div>
<div class="catalog__btn">
<a href="#">Купить</a>
</div>
</div> </div>
<div class="catalog__btn"> <div class="catalog__item">
<a href="#">Купить</a> <div class="catalog__information">
</div> <div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div>
</div> <div class="catalog__dec">
<div class="catalog__item"> <div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия
<div class="catalog__information"> для 26 компьютеров</a></div>
<div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div> <div class="catalog__star">
<div class="catalog__dec"> <form action="">
<div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия <select class="stars" data-rating="2">
для 26 компьютеров</a></div> <option value="1">1</option>
<div class="catalog__star"> <option value="2">2</option>
<form action=""> <option value="3">3</option>
<select class="stars" data-rating="2"> <option value="4">4</option>
<option value="1">1</option> <option value="5">5</option>
<option value="2">2</option> </select>
<option value="3">3</option> </form>
<option value="4">4</option> </div>
<option value="5">5</option> <div class="catalog__price">79 400 ₽ / 1398 $</div>
</select> <div class="catalog__dec-list">
</form> <ul>
</div> <li><span>Артикул:</span>057J1-WWW8695-T548</li>
<div class="catalog__price">79 400 ₽ / 1398 $</div> <li><span>НДС:</span>Включен</li>
<div class="catalog__dec-list"> <li><span>Платформа:</span>Windows</li>
<ul> <li><span>Тип поставки:</span>Любой из доступных</li>
<li><span>Артикул:</span>057J1-WWW8695-T548</li> <li><span>Срок поставки:</span>3-14 дней</li>
<li><span>НДС:</span>Включен</li> <li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li>
<li><span>Платформа:</span>Windows</li> </ul>
<li><span>Тип поставки:</span>Любой из доступных</li> </div>
<li><span>Срок поставки:</span>3-14 дней</li>
<li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li>
</ul>
</div> </div>
</div> </div>
<div class="catalog__btn">
<a href="#">Купить</a>
</div>
</div> </div>
<div class="catalog__btn"> <div class="catalog__item">
<a href="#">Купить</a> <div class="catalog__information">
</div> <div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div>
</div> <div class="catalog__dec">
<div class="catalog__item"> <div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия
<div class="catalog__information"> для 26 компьютеров</a></div>
<div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div> <div class="catalog__star">
<div class="catalog__dec"> <form action="">
<div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия <select class="stars" data-rating="2">
для 26 компьютеров</a></div> <option value="1">1</option>
<div class="catalog__star"> <option value="2">2</option>
<form action=""> <option value="3">3</option>
<select class="stars" data-rating="2"> <option value="4">4</option>
<option value="1">1</option> <option value="5">5</option>
<option value="2">2</option> </select>
<option value="3">3</option> </form>
<option value="4">4</option> </div>
<option value="5">5</option> <div class="catalog__price">79 400 ₽ / 1398 $</div>
</select> <div class="catalog__dec-list">
</form> <ul>
</div> <li><span>Артикул:</span>057J1-WWW8695-T548</li>
<div class="catalog__price">79 400 ₽ / 1398 $</div> <li><span>НДС:</span>Включен</li>
<div class="catalog__dec-list"> <li><span>Платформа:</span>Windows</li>
<ul> <li><span>Тип поставки:</span>Любой из доступных</li>
<li><span>Артикул:</span>057J1-WWW8695-T548</li> <li><span>Срок поставки:</span>3-14 дней</li>
<li><span>НДС:</span>Включен</li> <li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li>
<li><span>Платформа:</span>Windows</li> </ul>
<li><span>Тип поставки:</span>Любой из доступных</li> </div>
<li><span>Срок поставки:</span>3-14 дней</li>
<li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li>
</ul>
</div> </div>
</div> </div>
<div class="catalog__btn">
<a href="#">Купить</a>
</div>
</div> </div>
<div class="catalog__btn"> <div class="catalog__item">
<a href="#">Купить</a> <div class="catalog__information">
</div> <div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div>
</div> <div class="catalog__dec">
<div class="catalog__item"> <div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия
<div class="catalog__information"> для 26 компьютеров</a></div>
<div class="catalog__image"><img src="./img/item-image-min.png" alt=""></div> <div class="catalog__star">
<div class="catalog__dec"> <form action="">
<div class="catalog__title"><a href="#">ESET NOD32 Secure Enterprise новая лицензия <select class="stars" data-rating="2">
для 26 компьютеров</a></div> <option value="1">1</option>
<div class="catalog__star"> <option value="2">2</option>
<form action=""> <option value="3">3</option>
<select class="stars" data-rating="2"> <option value="4">4</option>
<option value="1">1</option> <option value="5">5</option>
<option value="2">2</option> </select>
<option value="3">3</option> </form>
<option value="4">4</option> </div>
<option value="5">5</option> <div class="catalog__price">79 400 ₽ / 1398 $</div>
</select> <div class="catalog__dec-list">
</form> <ul>
</div> <li><span>Артикул:</span>057J1-WWW8695-T548</li>
<div class="catalog__price">79 400 ₽ / 1398 $</div> <li><span>НДС:</span>Включен</li>
<div class="catalog__dec-list"> <li><span>Платформа:</span>Windows</li>
<ul> <li><span>Тип поставки:</span>Любой из доступных</li>
<li><span>Артикул:</span>057J1-WWW8695-T548</li> <li><span>Срок поставки:</span>3-14 дней</li>
<li><span>НДС:</span>Включен</li> <li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li>
<li><span>Платформа:</span>Windows</li> </ul>
<li><span>Тип поставки:</span>Любой из доступных</li> </div>
<li><span>Срок поставки:</span>3-14 дней</li>
<li><span>Примечание:</span>Временная лицензия, 1 год – новая покупка, локальная</li>
</ul>
</div> </div>
</div> </div>
<div class="catalog__btn">
<a href="basket.html">Купить</a>
</div>
</div> </div>
<div class="catalog__btn"> {% endfor %}
<a href="basket.html">Купить</a>
</div>
</div>
</div> </div>
<div class="contact"> <div class="contact">
<form action=""> <form action="">

Loading…
Cancel
Save