update cart app

remotes/origin/HEAD
Max Yakovenko 8 years ago
parent 0547ed23ba
commit 4f26a4b7c3
  1. 142
      cart/admin.py
  2. 87
      cart/cart.py
  3. 4
      cart/context_processors.py
  4. 1
      cart/fixtures/supply_targets.json
  5. 1
      cart/fixtures/supply_types.json
  6. 272
      cart/forms.py
  7. 13
      cart/middleware.py
  8. 274
      cart/models.py
  9. 45
      cart/tasks.py
  10. 0
      cart/templatetags/__init__.py
  11. 18
      cart/templatetags/cart_filters.py
  12. 124
      cart/templatetags/cart_tags.py
  13. 14
      cart/urls.py
  14. 65
      cart/utils.py
  15. 200
      cart/views.py
  16. 51
      templates/cart/bought_history.html
  17. 4
      templates/cart/buying_history.html
  18. 130
      templates/cart/cart.html
  19. 1
      templates/cart/checkout.html
  20. 1
      templates/cart/confirm.html

@ -1,3 +1,143 @@
import csv
import datetime
import pytils
import weasyprint
from decimal import Decimal
from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.http import HttpResponse
from django.template.loader import render_to_string
from jet.admin import CompactInline
from jet.filters import DateRangeFilter
from rangefilter.filter import DateTimeRangeFilter
from core.admin import SafeModelAdmin
from core.models import Certificate
from eshop_project.settings.base import PAY_REQUISITES
from .models import (
Offer, SupplyType,
Currency, Buying,
SupplyTarget,
Order, Discount,
Client)
class ProductOfferInlineAdmin(CompactInline):
model = Offer
exclude = ('status',)
extra = 1
show_change_link = 1
max_num = 1
# Supply admins
@admin.register(SupplyType)
class SupplyTypeAdmin(admin.ModelAdmin):
list_display = ('name', 'min_term', 'max_term')
search_fields = ('name', 'min_term', 'max_term')
@admin.register(SupplyTarget)
class SupplyTargetAdmin(admin.ModelAdmin):
list_display = ('name', 'slug')
search_fields = ('name', 'slug',)
@admin.register(Discount)
class DiscountAdmin(admin.ModelAdmin):
list_display = ['code', 'valid_from', 'valid_to', 'value', 'active']
list_filter = ['valid_from', 'valid_to', 'active']
search_field = ['code']
# Offer admins
@admin.register(Offer)
class ProductOfferAdmin(SafeModelAdmin):
list_display = ('product', 'price', 'amount', 'currency')
search_fields = ('product__name',)
list_filter = ('currency',)
@admin.register(Buying)
class BuyingAdmin(SafeModelAdmin):
def export_buyings_to_csv(self, buying, queryset):
opts = buying._meta
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=Orders-{}.csv'.format(
datetime.datetime.now().strftime("%d/%m/%Y"))
writer = csv.writer(response)
fields = [field for field in opts.get_fields() if not field.many_to_many and not field.one_to_many]
writer.writerow([field.verbose_name for field in fields])
for obj in queryset:
data_row = []
for field in fields:
value = getattr(obj, field.name)
if isinstance(value, datetime.datetime):
value = value.strftime('%d/%m/%Y')
data_row.append(value)
writer.writerow(data_row)
return response
export_buyings_to_csv.short_description = _('экспортировать CSV')
def print_order_in_pdf(self,buyings):
verb_price = pytils.numeral.in_words(round(buyings.total_price))
verb_cur = pytils.numeral.choose_plural(round(buyings.total_price), ("рубль", "рубля", "рублей"))
html = render_to_string('bootstrap/pdf/buyings.html', {
**PAY_REQUISITES, 'order': buyings, 'verb_cur': verb_cur, 'verb_price': verb_price
})
rendered_html = html.encode(encoding="UTF-8")
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'filename=order_{}.pdf'.format(buyings.id)
weasyprint.HTML(
string=rendered_html,
base_url=self.request.build_absolute_uri()
).write_pdf(
response,
stylesheets = [
weasyprint.CSS(settings.STATIC_ROOT + '/css/bootstrap.min.css')
]
)
return response
print_order_in_pdf.short_description = _('Распечатать заказ в pdf')
def mark_buyings_as_paid(self, request, queryset):
for buying in queryset:
user_profile = buying.user.profile
if user_profile.parent:
parent_profile = user_profile.parent.profile
parent_profile.user_points += round(buying.total_price * Decimal(0.01))
parent_profile.save()
buying.status = BUYING_STATUS_PAID
buying.save()
mark_buyings_as_paid.short_description = _('Отметить как оплаченные')
inlines = ()
list_display = ('user', 'offer', 'status', 'amount', 'total_price')
search_fields = ('user', 'offer',)
list_filter = (
('create_at', DateRangeFilter), ('updated_at', DateTimeRangeFilter)
)
actions = (export_buyings_to_csv, print_order_in_pdf, mark_buyings_as_paid)
@admin.register(Order)
class OrderAdmin(SafeModelAdmin):
list_display = ('order_code', 'customer_user', 'customer_name', 'customer_email','phone')
# Register your models here. @admin.register(Client)
class ClientAdmin(SafeModelAdmin):
list_display = ('name','image','status',)
search_fields = ('name',)
list_filter = ('status',)

@ -1,87 +0,0 @@
from decimal import Decimal
from django.conf import settings
from django.contrib import auth
from products.models import Product
# from discount.models import Discount
class Cart(object):
def __init__(self, request):
self.session = request.session
# self.discount_id = self.session.get('discount_id')
if request.user.is_authenticated():
# self.points = self.session.get('points')
self.points_quant = auth.get_user(request).profile.user_points
cart = self.session.get(settings.CART_SESSION_ID)
if not cart:
request.session['points'] = False
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart
def add(self, offer, price_per_itom, quantity=1, update_quantity=False):
offer_slug = offer.slug
if offer_slug not in self.cart:
self.cart[offer_slug] = {'quantity': 0,
'price': str(price_per_itom)}
if update_quantity:
self.cart[offer_slug]['quantity'] = int(quantity)
else:
self.cart[offer_slug]['quantity'] += int(quantity)
self.save()
def save(self):
self.session[settings.CART_SESSION_ID] = self.cart
self.session.modified = True
def remove(self, offer_slug):
# product_id = str(products.id)
if offer_slug in self.cart:
del self.cart[offer_slug]
self.save()
def __iter__(self):
offers_ids = self.cart.keys()
offers = Offer.objects.filter(slug__in=offers_ids)
for offer in offers:
self.cart[str(offer.slug)]['offer'] = offer
for item in self.cart.values():
item['price'] = Decimal(item['price'])
item['total_price'] = item['price'] * item['quantity']
yield item
def __len__(self):
return sum(item['quantity'] for item in self.cart.values())
def get_total_price(self):
return sum(Decimal(item['price']) * item['quantity'] for item in self.cart.values())
def get_max(self):
return min(self.points_quant, self.get_total_price() - 1)
def clear(self):
del self.session[settings.CART_SESSION_ID]
self.session.modified = True
# @property
# def discount(self):
# if self.discount_id:
# return Discount.objects.get(id=self.discount_id)
# return None
# def get_discount(self):
# if self.discount:
# return (self.discount.discount / Decimal('100')) * self.get_total_price()
# return Decimal('0')
# def get_total_price_after_discount(self):
# return self.get_total_price() - self.get_discount()
def get_total_deduct_points(self):
total_price = self.get_total_price()
if total_price <= self.points_quant:
# self.points_quant = self.points_quant - total_price + 1
# self.save()
return 1
return total_price - self.points_quant

@ -1,6 +1,6 @@
from .cart import Cart from cart.utils import Cart
def cart(request): def cart_basket(request):
return {'cart': Cart(request) } return {'cart': Cart(request) }

@ -0,0 +1 @@
[{"model": "cart.supplytarget", "pk": 1, "fields": {"create_at": "2018-08-12T20:48:30.165Z", "updated_at": "2018-08-12T20:48:30.165Z", "name": "\u0414\u043e\u043c\u0430\u0448\u043d\u044f\u044f", "slug": "domashnyaya", "status": 25}}, {"model": "cart.supplytarget", "pk": 2, "fields": {"create_at": "2018-08-12T20:48:38.954Z", "updated_at": "2018-08-12T20:49:01.074Z", "name": "\u0413\u043e\u0441\u0443\u0434\u0430\u0440\u0441\u0442\u0432\u0435\u043d\u043d\u0430\u044f", "slug": "gosudarstvennaya", "status": 75}}, {"model": "cart.supplytarget", "pk": 3, "fields": {"create_at": "2018-08-12T20:48:49.758Z", "updated_at": "2018-08-12T20:48:49.758Z", "name": "\u041a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u0430\u044f", "slug": "korporativnaya", "status": 50}}, {"model": "cart.supplytarget", "pk": 4, "fields": {"create_at": "2018-08-12T20:49:11.950Z", "updated_at": "2018-08-12T20:49:11.950Z", "name": "\u0410\u043a\u0430\u0434\u0435\u043c\u0438\u0447\u0435\u0441\u043a\u0430\u044f", "slug": "akademicheskaya", "status": 100}}]

@ -1,21 +1,257 @@
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Div, HTML, Hidden, Fieldset, Submit
from django import forms from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.forms import ALL_FIELDS, formset_factory
from django.urls import reverse_lazy
class CartAddProductForm(forms.Form): from cart.models import (
quantity = forms.CharField(widget=forms.TextInput(attrs={ Buying, BUYING_STATUS_IN_CART, Offer, SupplyType, SupplyTarget, Discount, Order
'id': 'quantity', )
'name': 'quantity', from core.forms import QueryFormBase
'type': 'number', from core.utils import parse_path
'min': '1', from django.utils.translation import ugettext_lazy as _
'max': '1000',
'value': '1',
'onchange': 'calculate()'}))
product_slug = forms.CharField(widget=forms.TextInput(attrs={
'id': 'product_slug',
'name': 'product_slug',
'type': 'hidden'}))
price_per_itom = forms.IntegerField(widget=forms.TextInput(attrs={
'id': 'price_per_itom',
'name': 'price_per_itom',
'type': 'hidden'}))
update = forms.BooleanField(required=False, initial=False, widget=forms.HiddenInput)
from products.models import Product
class CartAddInlineForm(forms.ModelForm):
form_action = {'viewname': 'cart:add', 'kwargs': {}}
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.form_action = reverse_lazy(**self.form_action)
self.helper.layout = Layout(
Field('offer'),
Field('amount'),
Div(
Submit('Купить',value='submit'),
css_class='catalog__btn'
)
)
super().__init__(*args, **kwargs)
def clean_amount(self):
amount = self.cleaned_data['amount']
offer = self.cleaned_data['offer']
if amount > offer.amount:
raise ValidationError('Колличество товара указано больше доступного')
elif amount <= 0:
raise ValidationError('Укажите колличество товара больше 0')
return amount
def save(self, cart, user, commit=True):
offer = Offer.active.get(self.offer)
self.instance.user = user
self.instance.offer = offer
self.instance.amount = self.cart[offer.product.id]['quantity']
self.instance.total_price = offer.product * self.cart[offer.product.id]['quantity']
return super().save(commit)
class Meta:
model = Buying
fields = ('offer', 'amount',)
widgets = {
'offer': forms.HiddenInput(),
'amount': forms.HiddenInput()
}
class CartRemoveBuyingForm(forms.ModelForm):
form_action = {'viewname': 'cart:remove', 'kwargs': {}}
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.form_action = reverse_lazy(**self.form_action)
self.helper.layout = Layout(
Field('offer'),
Div(
Submit('Убрать'),
css_class='catalog__btn'
)
)
super().__init__(*args, **kwargs)
class Meta:
model = Buying
fields = ('offer',)
widgets = {
'offer': forms.HiddenInput()
}
CartRemoveBuyingFormset = formset_factory(CartRemoveBuyingForm)
class CartCheckoutForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.form_action = reverse_lazy(**self.form_action)
self.helper.layout = Layout(
Field('customer_name'),
Field('customer_email'),
Field('customer_user'),
Field('phone'),
Field('customer_address'),
Field('city'),
Field('buyings'),
Field('comment'),
Div(
Submit('Подвердить'),
css_class='catalog__btn'
)
)
super().__init__(*args, **kwargs)
class Model:
model = Order
fields = (
'customer_name', 'customer_email', 'customer_user',
'phone', 'customer_address', 'city', 'buyings',
'comment'
)
class ProductOfferPriceFilterForm(QueryFormBase):
min_price = 0
max_price = 9999
price = forms.IntegerField(min_value=0, max_value=0)
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 left-menu__price-item'),
Field('price', template=self.field_template)
)
super().__init__(*args, **kwargs)
self.helper.form_action = reverse_lazy(**self.form_action)
self.init_price_bounders()
self.init_field_params()
def init_price_bounders(self):
if Offer.active.exists():
off_qs = Offer.active
category_instance = ''
if self.form_action.get('kwargs', None):
category_instance = parse_path(self.form_action.get('kwargs').get('path', ''))
off_qs = Offer.active.filter(product__parent__name=category_instance,
product__name__icontains=self.query_params.get('name', ''))
if off_qs.exists():
self.min_price = round(off_qs.order_by('price').only('price').first().price, 0)
self.max_price = round(off_qs.order_by('-price').only('price').first().price, 0)
def init_field_params(self):
for field in self.fields:
if field == 'price':
self.fields[field].validators = [
MaxValueValidator(self.max_price),
MinValueValidator(self.min_price)
]
def get_initial_for_field(self, field, field_name):
return super().get_initial_for_field(field, field_name)
class ProductOfferSupplyTypeFilterForm(QueryFormBase):
supply_type = forms.ChoiceField()
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('supply_type', template=self.field_template)
)
super().__init__(*args, **kwargs)
self.helper.form_action = reverse_lazy(**self.form_action)
def get_initial_for_field(self, field, field_name):
if field_name == 'supply_type':
sup_typ_qs = SupplyType.objects
category_instance = ''
if self.form_action.get('kwargs', None):
category_instance = parse_path(self.form_action.get('kwargs').get('path', ''))
off_qs = Offer.active.filter(product__parent__name=category_instance,
product__name__icontains=self.query_params.get('name', ''))
if off_qs.count():
sup_typ_qs = sup_typ_qs.filter(offer__pk__in=off_qs.all())
return sup_typ_qs.distinct('name').only('name', 'slug')
return super().get_initial_for_field(field, field_name)
class ProductOfferSupplyTargetFilterForm(QueryFormBase):
supply_target = forms.ChoiceField()
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('supply_target', template=self.field_template)
)
super().__init__(*args, **kwargs)
self.helper.form_action = reverse_lazy(**self.form_action)
def get_initial_for_field(self, field, field_name):
if field_name == 'supply_target':
sup_tar_qs = SupplyTarget.objects
category_instance = ''
if self.form_action.get('kwargs', None):
category_instance = parse_path(self.form_action.get('kwargs').get('path', ''))
off_qs = Offer.active.filter(product__parent__name=category_instance,
product__name__icontains=self.query_params.get('name', ''))
if off_qs.count():
sup_tar_qs = sup_tar_qs.filter(product__pk__in=off_qs.all())
return sup_tar_qs.distinct('name').only('name', 'slug')
return super().get_initial_for_field(field, field_name)
# @TODO: NOT IMPLEMENTED ON THE FRONT END. TEST BEFORE PRODUCTION
class DiscountForm(forms.ModelForm):
class Meta:
model = Discount
fields = ('code',)
class OrderCreateForm(forms.ModelForm):
customer_name = forms.CharField(max_length=100, required=True, label='Customer_name',
widget=forms.TextInput(attrs={'placeholder': 'Ф.И.О.'}))
customer_phone = forms.CharField(required=True, label='Customer_phone',
widget=forms.TextInput(attrs={'placeholder': 'номер телефона'}))
customer_email = forms.EmailField(required=True, label='Customer_email',
widget=forms.TextInput(attrs={'placeholder': 'e-mail'}))
city = forms.CharField(max_length=100, label='City', widget=forms.TextInput(attrs={'placeholder': 'город'}))
class Meta:
model = Order
exclude = ('status',)

@ -0,0 +1,13 @@
from cart.utils import Cart
class CartMonkeyPatchingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
request.cart = Cart(request)
response = self.get_response(request)
return response

@ -1,45 +1,170 @@
from django.utils.timezone import now, timedelta
import uuid
from django.core.validators import MinValueValidator, MaxValueValidator, RegexValidator
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.db.models import Avg from django.db.models import Avg
from django.db.models.signals import post_save
from django.dispatch import receiver
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, AbstractDateTimeModel from core.models import (
AbstractStatusModel, AbstractDateTimeModel,
ActiveOnlyManager,
City, Currency)
from products.models import Product from products.models import Product
# -----------------------------------------Client ------------------------------------------------------#
class Client(AbstractStatusModel):
def upload_file_to(self, filename):
return "clients/{name}/{filename}".format(**{
'name': self.name,
'filename': filename
})
name = models.CharField(_('Название'), max_length=255)
image = models.FileField(_('Изображение'), upload_to=upload_file_to)
preview = models.FileField(_('Миниатюрка'), upload_to=upload_file_to)
def __str__(self):
return self.name
class Meta:
verbose_name = _('Клиент')
verbose_name_plural = _('Клиенты')
# -------------------------------------- Supply type dimensions --------------------------------------#
SUPPLY_TYPE_HOUR_DIMENSION = 0
SUPPLY_TYPE_DAY_DIMENSION = 0
SUPPLY_TYPE_WEEK_DIMENSION = 0
SUPPLY_TYPE_MONTH_DIMENSION = 0
SUPPLY_TYPE_DIMENSION_CHOICES = (
(SUPPLY_TYPE_HOUR_DIMENSION, _('Час')),
(SUPPLY_TYPE_DAY_DIMENSION, _('День')),
(SUPPLY_TYPE_WEEK_DIMENSION, _('Неделя')),
(SUPPLY_TYPE_MONTH_DIMENSION, _('Месяц'))
)
SUPPLY_TYPE_DEFAULT_DIMENSION = SUPPLY_TYPE_HOUR_DIMENSION
class SupplyType(AbstractDateTimeModel):
name = models.CharField(_('Тип'), max_length=255)
slug = AutoSlugField(populate_from='name', unique=True)
min_term = models.IntegerField(_('от'), help_text=_('Минимальный срок поставки'))
max_term = models.IntegerField(_('до'), help_text=_('Максимальный срок поставки'))
term_dimension = models.SmallIntegerField(
_('размерность'),
choices=SUPPLY_TYPE_DIMENSION_CHOICES,
default=SUPPLY_TYPE_DEFAULT_DIMENSION
)
def get_formatted_desc(self):
return _(" от {from} до {to} {dimension}".format(**{
'from': self.min_term,
'to': self.max_term,
'dimension': SUPPLY_TYPE_DIMENSION_CHOICES[self.term_dimension][1]
}))
def __str__(self):
return self.name
class Meta:
verbose_name = _('Тип поставки')
verbose_name_plural = _('Тип поставки')
class SupplyTarget(AbstractDateTimeModel):
name = models.CharField(_('Назначение'), max_length=255)
slug = AutoSlugField(populate_from='name', unique=True)
status = models.PositiveSmallIntegerField(_('статус'), help_text=_('Необходимо указать числовой код статус'))
def __str__(self):
return self.name
class Meta:
verbose_name = _('Лицензия')
verbose_name_plural = _('Лицензии')
class Discount(AbstractDateTimeModel):
code = models.CharField(max_length=50, blank=True, unique=True, default=str(uuid.uuid4()))
valid_from = models.DateTimeField(auto_now_add=True, blank=True)
valid_to = models.DateTimeField(default=now() + timedelta(days=7), blank=True)
value = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(100)], default=0,
help_text=_('Указываем целым числом. Пример: 30 = 30%'))
active = models.BooleanField(default=True)
def __str__(self):
return self.code
class Meta:
verbose_name = _('Дисконт')
verbose_name_plural = _('Дисконт')
# -------------------------------------- Offer status list -------------------------------------_#
OFFER_STATUS_INACTIVE = 0
OFFER_STATUS_ACTIVE = 25 OFFER_STATUS_ACTIVE = 25
OFFER_STATUS_INACTIVE = 50 OFFER_STATUS_DELETED = 50
OFFER_STATUS_CHOICES = ( OFFER_STATUS_CHOICES = (
(OFFER_STATUS_ACTIVE, _('Активный')), (OFFER_STATUS_ACTIVE, _('Активный')),
(OFFER_STATUS_INACTIVE, _('Неактивный')) (OFFER_STATUS_INACTIVE, _('Неактивный')),
(OFFER_STATUS_DELETED, _('Удаленный'))
) )
OFFER_DEFAULT_CHOICE = OFFER_STATUS_INACTIVE OFFER_DEFAULT_CHOICE = OFFER_STATUS_INACTIVE
class SupplyType(AbstractDateTimeModel): class Offer(AbstractStatusModel):
name = models.CharField(max_length=255) product = models.OneToOneField(Product, on_delete=models.CASCADE, primary_key=True, verbose_name=_('Продукт'))
term = models.ValueRange()
vendor_code = models.CharField(_('Артикул'), max_length=255, unique=True, help_text=_('Должен быть уникальным'))
class Offer(AbstractStatusModel): price = models.DecimalField(_('цена'), max_digits=10, decimal_places=2, validators=[MinValueValidator(1.00)],
product = models.OneToOneField(Product, on_delete=models.CASCADE, primary_key=True) help_text=_('Цена за продукт'))
price = models.DecimalField(max_digits=8, decimal_places=2, null=True, default=0.00) currency = models.ForeignKey(Currency, verbose_name=_('Валюта'), on_delete=models.PROTECT,
currency = models.CharField(max_length=64, blank=True, null=True, default=None) help_text=_('Цена по умолчанию в рублях'))
with_nds = models.BooleanField(default=False) supply_type = models.ForeignKey(SupplyType, verbose_name=_('Поставка'), on_delete=models.SET_NULL, blank=True,
# attributes = HStoreField(blank=True, null=True, default={}) null=True)
suply_type = models.ForeignKey(SupplyType, on_delete=models.SET_NULL, blank=True, null=True)
note = models.TextField(blank=True, null=True) supply_target = models.ForeignKey(SupplyTarget, on_delete=models.SET_NULL, verbose_name=_('Лицензия'), blank=True,
status = models.SmallIntegerField(_('статус'), default=OFFER_DEFAULT_CHOICE, choices=OFFER_STATUS_CHOICES) null=True, default=None)
discount = models.ForeignKey(Discount, on_delete=models.SET_NULL, verbose_name=_('Дисконт'), blank=True, null=True)
amount = models.IntegerField(blank=True, null=True) amount = models.IntegerField(_('Колличество'), default=1, validators=[MinValueValidator(1)])
cashback = models.DecimalField(_('Кешбек'), max_digits=6, decimal_places=2, default=0,
help_text=_('Указаная сумма будет отображаться в выбранной валюте позиции'))
note = models.TextField(_('Пометка'), blank=True, null=True)
account_nds = models.BooleanField(_('с учетом НДС'), default=False)
def get_price_with_discount(self):
return self.price - ((self.discount.value * self.price) if self.discount else 0)
def __str__(self): def __str__(self):
return self.name return self.product.name
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
if self.product:
self.status = self.product.status
super().save(force_insert, force_update, using, update_fields)
class Meta: class Meta:
verbose_name = _('Позиция') verbose_name = _('Позиция')
@ -49,21 +174,62 @@ class Offer(AbstractStatusModel):
# ------------------------------------------ Buying status --------------------------------------------------- # # ------------------------------------------ Buying status --------------------------------------------------- #
BUYING_STATUS_IN_CART = 25 BUYING_STATUS_IN_CART = 25
BUYING_STATUS_PENDING = 50 BUYING_STATUS_PENDING = 50
BUYING_STATUS_BOUGHT = 75 BUYING_STATUS_PAID = 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_PAID, _('Оплаченно'))
) )
BUYING_DEFAULT_CHOICE = BUYING_STATUS_IN_CART BUYING_DEFAULT_CHOICE = BUYING_STATUS_IN_CART
class BuyingManager(ActiveOnlyManager, models.Manager):
def get_user_buyings(self, user):
qs = self.get_queryset()
return qs.filter(user=user).all()
def get_buying_total_price(self, user=None):
qs = self.get_user_buyings(user) if user else self.get_queryset()
return qs.aggregate(Sum('total_price'))
def get_buying_total_bonus_points(self, user=None):
qs = self.get_user_buyings(user) if user else self.get_queryset()
return qs.aggregate(Sum('bonus_points'))
def get_buying_total_cashback(self, user=None):
qs = self.get_user_buyings(user) if user else self.get_queryset()
return qs.select_related('buying_cashback').aggregate(Sum('amount'))
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)
bonus_points = models.IntegerField(_('бонусы'), validators=(MinValueValidator(0),))
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) amount = models.SmallIntegerField(_('колличество'), default=0)
total_price = models.DecimalField(_('цена'), max_digits=10, decimal_places=2)
active = BuyingManager()
@property
def is_in_cart(self):
return self.status == BUYING_STATUS_IN_CART
@property
def is_pending(self):
return self.status == BUYING_STATUS_PENDING
@property
def is_paid(self):
return self.status == BUYING_STATUS_PAID
def __str__(self):
return "{product_name}({product_amount}) - {price}".format(**{
'product_name': self.offer.product.name,
'product_amount': self.amount,
'price': self.total_price
})
class Meta: class Meta:
verbose_name = _('Покупка') verbose_name = _('Покупка')
@ -80,7 +246,7 @@ CASHBACK_STATUS_CHOICES = (
STATUS_DEFAULT = STATUS_GAINED STATUS_DEFAULT = STATUS_GAINED
class CashBackManager(models.Manager): class CashBackManager(ActiveOnlyManager):
def get_gained_cashback_sum(self, user): def get_gained_cashback_sum(self, user):
return self.get_queryset().filter(user=user, status=STATUS_GAINED).aggregate(Avg('amount')) return self.get_queryset().filter(user=user, status=STATUS_GAINED).aggregate(Avg('amount'))
@ -88,16 +254,78 @@ class CashBackManager(models.Manager):
return self.get_queryset().filter(user=user, status=STATUS_SPENT).aggregate(Avg('amount')) return self.get_queryset().filter(user=user, status=STATUS_SPENT).aggregate(Avg('amount'))
class BuyingCashback(AbstractDateTimeModel): class Cashback(AbstractDateTimeModel):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) 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) amount = models.DecimalField(_('Сумма'), 'cashback', 7, 2, default=0)
status = models.SmallIntegerField(_('статус'), default=STATUS_DEFAULT, choices=CASHBACK_STATUS_CHOICES) status = models.SmallIntegerField(_('статус'), default=STATUS_DEFAULT, choices=CASHBACK_STATUS_CHOICES)
objects = CashBackManager() objects = CashBackManager()
@property
def is_spent(self):
return self.status == STATUS_SPENT
class Meta: class Meta:
verbose_name_plural = _('cashback') verbose_name_plural = _('cashback')
verbose_name = _('cashback') verbose_name = _('cashback')
ORDER_STATUS_NEW = 0
ORDER_STATUS_PENDING = 50
ORDER_STATUS_PAID = 100
ORDER_STATUS_CHOICES = (
(ORDER_STATUS_NEW, _('Новый')),
(ORDER_STATUS_PENDING, _('Обрабатывается')),
(ORDER_STATUS_PAID, _('Оплаченно'))
)
ORDER_STATUS_DEFAULT = ORDER_STATUS_NEW
class Order(AbstractStatusModel):
order_code = models.CharField(_('код заказа'), max_length=255, default=str(uuid.uuid4()))
customer_name = models.CharField(_('bмя'), max_length=255)
customer_email = models.EmailField(_('email'), blank=True, null=True, default=None)
customer_user = models.ForeignKey(
get_user_model(), on_delete=models.SET_NULL,
verbose_name=_('пользователь'),
blank=True, null=True
)
phone_regex = RegexValidator(
regex=r'^\((+7)|8)?\d{10}$',
message="Phone number must be entered in the format: '+99999999999'. Up to 12 digits allowed."
)
phone = models.CharField(_('телефон'), validators=[phone_regex], max_length=12)
customer_address = models.TextField(_('адрес'))
city = models.ForeignKey(City, on_delete=models.PROTECT, verbose_name=_('Город'))
buyings = models.ManyToManyField(Buying, verbose_name=_('Покупки'))
total_price = models.DecimalField(_('стоимость'), max_digits=10, decimal_places=2, default=0)
comment = models.TextField(_('комментарий'), blank=True, null=True, default=None)
status = models.SmallIntegerField(_('статус'), default=ORDER_STATUS_CHOICES, choices=ORDER_STATUS_DEFAULT)
def __str__(self):
return self.order_code
class Meta:
ordering = ('-create_at',)
verbose_name = _('Заказ')
verbose_name_plural = _('Заказы')
@receiver(post_save, sender=Order)
def product_in_order_post_save(sender, instance, created, **kwargs):
order = instance.order
all_products_in_order = Buying.objects.filter(order=instance, status=ORDER_STATUS_NEW)
order_total_price = sum(item.total_price for item in all_products_in_order)
# if order.discount:
# order.total_price = order_total_price * (order.discount_value / Decimal('100'))
if order.points_quant:
order.total_price = order_total_price - order.points_quant
else:
order.total_price = order_total_price
order.save(force_update=True)

@ -0,0 +1,45 @@
import celery
from django.conf import settings
from celery import task
from django.template.loader import render_to_string
from django.core.mail import send_mail, EmailMessage
from io import BytesIO
import weasyprint
import pytils
from cart.models import Order
SUPPLIER_INFO = '''ООО "Русские Программы", ИНН 7713409230, КПП 771301001,
127411, Москва г, Дмитровское ш., дом 157, корпус 7, тел.: +74957258950'''
requisites = {'name': 'ООО "Русские Программы"', 'bank': 'АО "СМП БАНК" Г. МОСКВА', 'INN': '7713409230',
'KPP': '771301001', 'BIK': '44525503', 'bank_acc': '30101810545250000503', 'acc': '40702810300750000177',
'sup_info': SUPPLIER_INFO}
@celery.task
def send_user_order_notification(order_id):
"""
Sending Email of order creating
"""
order = Order.objects.get(id=order_id)
verb_price = pytils.numeral.in_words(round(order.total_price))
verb_cur = pytils.numeral.choose_plural(round(order.total_price), ("рубль", "рубля", "рублей"))
subject = 'Заказ № {}'.format(order.id)
message = 'Уважаемый, {}, номер Вашего заказа {}. \
Пожалуйста, совершите платеж по поручению в приложении к этому письму в течение 14 дней.'.format(order.customer_name, order.id)
mail_send = EmailMessage(subject, message, 'admin@myshop.ru', [order.customer_email])
# html = render_to_string('orders:AdminOrderPDF', args=[order_id])
html = render_to_string('orders/pdf.html', {**requisites, 'order': order,
'verb_cur': verb_cur, 'verb_price': verb_price})
rendered_html = html.encode(encoding="UTF-8")
out = BytesIO()
weasyprint.HTML(string=rendered_html).write_pdf(out,
stylesheets=[weasyprint.CSS(settings.STATIC_ROOT + 'css/bootstrap.min.css')])
# weasyprint.HTML(string=rendered_html, base_url=request.build_absolute_uri()).write_pdf(response,
# stylesheets=[weasyprint.CSS(settings.STATIC_ROOT + '/css/bootstrap.min.css')])
mail_send.attach('order_{}.pdf'.format(order_id), out.getvalue(), 'application/pdf')
mail_send.send()
return mail_send

@ -0,0 +1,18 @@
from django.template import Library
register = Library()
@register.filter
def calculate_price(cart, offer):
# @TODO: BUG!!! MAKE TYPE CASTING OF CART KEYS
if offer.product_id in cart:
return offer.price * cart[offer.product_id]['quantity']
return offer.price
@register.filter
def get_cart_offer_amount(cart, offer):
if offer.product_id in cart:
return cart[offer.product_id]['quantity'] if offer.product_id in cart else 0
return 0

@ -0,0 +1,124 @@
from django import template
from django.template import loader, Node, Variable
from django.utils.encoding import smart_str, smart_bytes
from django.template.defaulttags import url
from django.template import VariableDoesNotExist
from mptt.templatetags.mptt_tags import recursetree
register = template.Library()
@register.tag
def breadcrumb(parser, token):
"""
Renders the breadcrumb.
Examples:
{% breadcrumb "Title of breadcrumb" url_var %}
{% breadcrumb context_var url_var %}
{% breadcrumb "Just the title" %}
{% breadcrumb just_context_var %}
Parameters:
-First parameter is the title of the crumb,
-Second (optional) parameter is the url variable to link to, produced by url tag, i.e.:
{% url person_detail object.id as person_url %}
then:
{% breadcrumb person.name person_url %}
@author Andriy Drozdyuk
"""
return BreadcrumbNode(token.split_contents()[1:])
@register.tag
def breadcrumb_url(parser, token):
"""
Same as breadcrumb
but instead of url context variable takes in all the
arguments URL tag takes.
{% breadcrumb "Title of breadcrumb" person_detail person.id %}
{% breadcrumb person.name person_detail person.id %}
"""
bits = token.split_contents()
if len(bits)==2:
return breadcrumb(parser, token)
# Extract our extra title parameter
title = bits.pop(1)
token.contents = ' '.join(bits)
url_node = url(parser, token)
return UrlBreadcrumbNode(title, url_node)
@register.tag
def breadcrumb_mptt_url(parser, token):
return recursetree(parser, token)
class BreadcrumbNode(Node):
def __init__(self, vars):
"""
First var is title, second var is url context variable
"""
self.vars = map(Variable,vars)
def render(self, context):
title = self.vars[0].var
if title.find("'")==-1 and title.find('"')==-1:
try:
val = self.vars[0]
title = val.resolve(context)
except:
title = ''
else:
title=title.strip("'").strip('"')
title=smart_bytes(title)
url = None
if len(self.vars)>1:
val = self.vars[1]
try:
url = val.resolve(context)
except VariableDoesNotExist:
print('URL does not exist', val)
url = None
return create_crumb(title, url)
class UrlBreadcrumbNode(Node):
def __init__(self, title, url_node):
self.title = Variable(title)
self.url_node = url_node
def render(self, context):
title = self.title.var
if title.find("'")==-1 and title.find('"')==-1:
try:
val = self.title
title = val.resolve(context)
except:
title = ''
else:
title=title.strip("'").strip('"')
title=smart_bytes(title)
url = self.url_node.render(context)
return create_crumb(title, url)
def create_crumb(title, url=None):
"""
Helper function
"""
crumb = """<li><a href='%s'>%s</a></span>"""
if url:
crumb = crumb.format(url, title)
else:
crumb = crumb.format('#', title)
return crumb

@ -4,9 +4,17 @@ from django.urls import re_path
from . import views from . import views
urlpatterns = [ urlpatterns = [
# url(r'^$', views.CartDetail, name='CartDetail'),
# url(r'^remove/(?P<offer_slug>[-\w]+)/$', views.CartRemove, name='CartRemove'),
# url(r'^add/$', views.CartAdd, name='CartAdd'),
re_path(r'^history/', views.BuyingsHistory.as_view(), name='history'), re_path(r'^history/', views.BuyingsHistory.as_view(), name='history'),
re_path(r'^buyings/$', views.CartView.as_view(), name='buyings'), re_path(r'^buyings/$', views.CartView.as_view(), name='buyings'),
re_path(r'^add/$', views.CartAddView.as_view(), name='add'),
re_path(r'^remove/$', views.CartRemoveView.as_view(), name='remove'),
re_path(r'^checkout/$', views.CartCheckoutView.as_view(),name='checkout'),
re_path(r'^confirm/$', views.CartConfirmView.as_view(), name='confirm'),
# discount: @TODO: check if this logic is ready for production
re_path(r'^apply', views.DiscountApply, name='apply'),
re_path(r'^create', views.CreateDiscount, name='create'),
re_path(r'^points', views.PointsApply, name='points'),
re_path(r'^revoke_points', views.PointsRevoke, name='revoke_points')
] ]

@ -0,0 +1,65 @@
from decimal import Decimal
from django.conf import settings
from django.contrib import auth
from cart.models import Offer
from products.models import Product
# from discount.models import Discount
class Cart(object):
def __init__(self, request):
self.store = request.session
cart = self.store.get(settings.CART_SESSION_ID)
if not cart:
cart = self.store[settings.CART_SESSION_ID] = {}
self.cart = cart
def add(self, offer_id, quantity=1):
offer_id = str(offer_id)
if offer_id in self.cart:
self.cart[offer_id]['quantity'] += int(quantity)
else:
self.cart[offer_id] = {'quantity': quantity}
self.save()
def save(self):
self.store[settings.CART_SESSION_ID] = self.cart
self.store.modified = True
def remove(self, offer_id):
if offer_id in self.cart:
del self.cart[offer_id]
self.save()
def clear(self):
del self.store[settings.CART_SESSION_ID]
self.store.modified = True
def keys(self):
return self.cart.keys()
def __iter__(self):
return iter(self.cart.keys())
def __setitem__(self, key, value):
try:
self.cart[str(key)] = value
except KeyError:
return setattr(self, key, value)
def __getitem__(self, key):
try:
return self.cart[str(key)]
except KeyError:
return getattr(self, key)
def __contains__(self, item):
return str(item) in self.cart
def __next__(self):
return self.cart.__next__()
def __len__(self):
return self.cart.__len__()

@ -1,82 +1,102 @@
import datetime
import uuid
from functools import reduce
from decimal import Decimal
from django.conf import settings from django.conf import settings
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import redirect
from django.views.decorators.http import require_POST from django.urls import reverse_lazy
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required
from django.contrib import auth
from django.views.generic import ListView
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic.edit import BaseFormView
from cart.utils import Cart
from cart.models import Buying, Discount, Offer
from core.views import ProtectedListView, ProtectedTemplateView, ProtectedView, ProtectedFormView
from .forms import (
CartRemoveBuyingForm,
DiscountForm,
CartAddInlineForm, CartCheckoutForm
)
class CartAddView(BaseFormView):
http_method_names = ('post',)
form_class = CartAddInlineForm
def get_success_url(self):
return self.request.META.get('HTTP_REFERRER', reverse_lazy('products:product_list'))
def form_valid(self, form):
self.request.cart.add(offer_id=form.cleaned_data['offer'].product_id, quantity=form.cleaned_data['amount'])
return super().form_valid(form)
class CartRemoveView(BaseFormView):
http_method_names = ('post',)
model = Offer
form_class = CartRemoveBuyingForm
def get_success_url(self):
return self.request.META.get('HTTP_REFERRER', reverse_lazy('products:product_list'))
from cart.models import Buying def form_valid(self, form):
from core.views import ProtectedListView self.request.cart.remove(form.cleaned_data['offer'])
return super().form_valid()
from .cart import Cart
from .forms import CartAddProductForm
# from discount.layout import DiscountApllyForm
# @csrf_exempt
# @require_POST
# @login_required(login_url='accounts_ext:accounts_ext')
# def CartAdd(request):
# cart = Cart(request)
# form = CartAddProductForm(request.POST)
# if form.is_valid():
# cd = form.cleaned_data
# if int(cd['quantity']) < 1 or int(cd['quantity']) > 1000:
# return redirect(request.META.get('HTTP_REFERER'))
# offer = get_object_or_404(Offer, slug=cd['product_slug'])
# cart.add(offer=offer, price_per_itom=cd['price_per_itom'], quantity=cd['quantity'],
# update_quantity=cd['update'])
# request.session.pop('points', None)
# return redirect('cart:CartDetail')
#
# @csrf_exempt
# @login_required(login_url='accounts_ext:accounts_ext')
# def CartRemove(request, offer_slug):
# cart = Cart(request)
# # offer = get_object_or_404(Offer, slug=offer_slug)
# cart.remove(offer_slug)
# request.session.pop('points', None)
# return redirect('cart:CartDetail')
#
# @csrf_exempt
# @login_required(login_url='accounts_ext:accounts_ext')
# def CartDetail(request):
# user = auth.get_user(request)
# cart = Cart(request)
# for item in cart:
# item['update_quantity_form'] = CartAddProductForm(
# initial={
# 'quantity': item['quantity'],
# 'product_slug': item['offer'].slug,
# 'price_per_itom': item['price'],
# 'update': True
# })
# # discount_apply_form = DiscountApllyForm()
# return render(request, 'cart/detail.html', {'username': user.username})
# # 'discount_apply_form': discount_apply_form})
class CartView(ProtectedListView): class CartView(ProtectedListView):
model = Buying model = Offer
paginate_by = settings.DEFAULT_PAGE_AMOUNT paginate_by = settings.DEFAULT_PAGE_AMOUNT
context_object_name = 'cart_items' context_object_name = 'offer_items'
template_name = 'cart/cart.html' template_name = 'cart/cart.html'
ordering = '-create_at' ordering = '-create_at'
title = _('Корзина') title = _('Корзина')
def get_queryset(self): def get_queryset(self):
qs = super().get_queryset() qs = super().get_queryset()
return qs.filter(user=self.request.user) return qs.filter(product__id__in=self.request.cart.keys())
def get_total_price(self, object_list):
return reduce(
lambda initial, offer: initial + offer.price * Decimal(self.request.cart[offer.product_id]['quantity']),
object_list,
Decimal(0)
)
def get_total_cashback(self, object_list):
return reduce(
lambda initial, offer: initial + offer.cashback * Decimal(self.request.cart[offer.product_id]['quantity']),
object_list,
Decimal(0)
)
def get_context_data(self, *, object_list=None, **kwargs): def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs) context = super().get_context_data(object_list=object_list, **kwargs)
context['title'] = self.title context['title'] = self.title
context['total_price'] = self.get_total_price(self.object_list)
context['total_price_currency'] = context['total_cashback_currency'] = self.object_list.first().currency.sign
context['total_cashback'] = self.get_total_cashback(self.object_list)
return context return context
class CartCheckoutView(ProtectedFormView):
http_method_names = ('get', 'post',)
template_name = 'cart/checkout.html'
form_class = CartCheckoutForm
def get_form_kwargs(self):
return super().get_form_kwargs()
def form_valid(self, form):
return super().form_valid(form)
class CartConfirmView(ProtectedTemplateView):
http_method_names = ('post',)
template_name = 'cart/confirm.html'
class BuyingsHistory(ProtectedListView): class BuyingsHistory(ProtectedListView):
model = Buying model = Buying
paginate_by = settings.DEFAULT_PAGE_AMOUNT paginate_by = settings.DEFAULT_PAGE_AMOUNT
@ -93,3 +113,65 @@ class BuyingsHistory(ProtectedListView):
context = super().get_context_data(object_list=object_list, **kwargs) context = super().get_context_data(object_list=object_list, **kwargs)
context['title'] = self.title context['title'] = self.title
return context return context
# Discount views # @TODO: TEST FOR PRODUCTION
class PointsApply(ProtectedTemplateView):
http_method_names = ('post',)
def dispatch(self, request, *args, **kwargs):
super().dispatch(request, *args, **kwargs)
self.request.session['points'] = True
return redirect('cart:buyings')
class PointsRevoke(ProtectedView):
http_method_names = ('post',)
def dispatch(self, request, *args, **kwargs):
super().dispatch(request, *args, **kwargs)
self.request.session.pop('points', None)
return redirect('cart:buyings')
class DiscountApply(ProtectedFormView):
http_method_names = ('post',)
form_class = DiscountForm
def form_valid(self, form):
now = datetime.now()
code = form.cleaned_data['code']
try:
discount = Discount.objects.get(code__iexact=code,
valid_from__lte=now,
valid_to__gte=now,
active=True)
self.request.session['discount_id'] = discount.id
except Discount.DoesNotExist:
self.request.session['discount_id'] = None
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('cart:buyings')
class CreateDiscount(ProtectedFormView):
http_method_names = ('post',)
form_class = DiscountForm
def form_valid(self, form):
now = datetime.now()
Discount.objects.update_or_create(
user=self.request.user,
defaults={'code': str(uuid.uuid4()),
'valid_from': now,
'valid_to': now + datetime.timedelta(days=7),
'active': True}
)
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('cabinet:index')

@ -25,32 +25,41 @@
<td>{{ bought_item.total_price }}7570₽</td> <td>{{ bought_item.total_price }}7570₽</td>
</tr> </tr>
{% empty %} {% empty %}
<tr>
<td>12.06.17</td>
<td>142251366</td>
<td>Windows 7 BOX...</td>
<td>1</td>
<td>7570₽</td>
</tr>
{% endfor %} {% endfor %}
</table> </table>
<div class="pagination"> {% if paginator.num_pages > 1 %}
<span class="step-links"> {% spaceless %}
{% if bought_item_list.has_previous %} <div class="row pagination">
<a href="?page=1">&laquo; 1</a> <div class="col-md-4 text-center">
<a href="?page={{ bought_item_list.previous_page_number }}">previous</a> {% if page_obj.has_previous %}
<a href="{% url 'cart:history' %}?page={{ page_obj.previous_page_number }}">
&laquo;
</a>
{% endif %}
</div>
<div class="col-md-4 text-center">
{% if paginator.has_previous %}
<a href="{% url 'cart:history' %}?page={{ page_obj.number|add:"-1" }}">
{{ page_obj.number|add:"-1" }}
</a>
{% endif %} {% endif %}
<span>{{ page_obj.number }}</span>
<span class="current"> {% if page_obj.has_next %}
Страница {{ bought_item_list.number }} of {{ bought_item_list.paginator.num_pages }}. <a href="{% url 'cart:history' %}?page={{ page_obj.number|add:"+1" }}">
</span> {{ page_obj.number|add:"+1" }}
</a>
{% 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 %} {% endif %}
</span>
</div> </div>
<div class="col-md-4 text-center">
{% if page_obj.has_next %}
<a href="{% url 'cart:history' %}?page={{ page_obj.next_page_number }}">
&raquo;
</a>
{% endif %}
</div>
</div>
{% endspaceless %}
{% endif %}
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}

@ -1,4 +0,0 @@
{% extends 'base.html' %}
{% block content %}
Missing buying history
{% endblock %}

@ -1,4 +1,8 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load core_filters %}
{% load cart_filters %}
{% block content %} {% block content %}
<div class="col-12"> <div class="col-12">
<div class="content"> <div class="content">
@ -20,6 +24,7 @@
</div> </div>
</div> </div>
</div> </div>
{% for offer in offer_items %}
<div class="basket__item"> <div class="basket__item">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col-md-6 col-12"> <div class="col-md-6 col-12">
@ -27,80 +32,10 @@
<div class="basket__image"><img src="./img/item-image-min.png" alt=""></div> <div class="basket__image"><img src="./img/item-image-min.png" alt=""></div>
<div class="basket__info"> <div class="basket__info">
<ul class="basket__breadcrumbs breadcrumbs"> <ul class="basket__breadcrumbs breadcrumbs">
<li>Товары</li> <li><a href="{% url 'products:product_list' %}">Категории</a></li>
<li>Антивирусы</li> <li><a href="{{ offer.product.get_absolute_url }}">{{ offer.product.name }}</a></li>
</ul> </ul>
<div class="basket__good-name">Microsoft Windows 7 Professional SP1 <div class="basket__good-name">{{ offer.product.name }}</div>
(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>
</div> </div>
@ -108,61 +43,36 @@
<div class="row"> <div class="row">
<div class="col-4 align-self-start"> <div class="col-4 align-self-start">
<div class="basket__title-mob">Цена</div> <div class="basket__title-mob">Цена</div>
<div class="basket__price">10 000 ₽</div> <div class="basket__price">{{ offer.get_price_with_discount }} {{ offer.currency.sign }}</div>
</div> </div>
<div class="col-4 align-self-start"> <div class="col-4 align-self-start">
<div class="basket__title-mob align-self-start">Количество</div> <div class="basket__title-mob align-self-start">Количество</div>
<div class="basket__count">2 шт</div> <div class="basket__count">{{ request.cart|get_cart_offer_amount:offer}}</div>
</div> </div>
<div class="col-4 align-self-start"> <div class="col-4 align-self-start">
<div class="basket__title-mob ">Сумма</div> <div class="basket__title-mob ">Сумма</div>
<div class="basket__sum">20 000 ₽</div> <div class="basket__sum">{{ request.cart|calculate_price:offer }} {{ offer.currency.sign }}</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% empty %}
<div class="basket__item"> <div class="basket__item">
<div class="row align-items-center"> <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> </div>
{% endfor %}
<div class="basket__result"> <div class="basket__result">
<div class="result__sum">Итого: 20 000 ₽</div> <div class="result__sum">Итого: {{ total_price }} {{ total_price_currency }}</div>
<div class="result__cashback">Кэшбек 800 ₽</div> {% if total_cashback > 0 %}
<div class="result__cashback">Кэшбек {{ total_cashback }} {{ total_cashback_currency }}</div>
{% endif %}
</div> </div>
<div class="basket__controls"> <div class="basket__controls">
<a class="basket__btn-fill" href="#">Оформить</a> <a class="basket__btn-fill" href="{% url 'cart:checkout' %}">Оформить</a>
<a class="basket__btn" href="#">Продолжить покупки</a> <a class="basket__btn" href="{% url 'products:product_list' %}">Продолжить покупки</a>
</div> </div>
</div> </div>
</div> </div>

@ -0,0 +1 @@
{% extends 'base.html' %}

@ -0,0 +1 @@
{% extends 'base.html' %}
Loading…
Cancel
Save