update cart

remotes/origin/HEAD
Max Yakovenko 8 years ago
parent 87df0e9285
commit 4d74e4d173
  1. 91
      cart/forms.py
  2. 152
      cart/models.py
  3. 54
      cart/tasks.py
  4. 3
      cart/urls.py
  5. 1
      cart/utils.py
  6. 85
      cart/views.py

@ -10,7 +10,10 @@ from django.urls import reverse_lazy
from cart.models import (
Buying, BUYING_STATUS_IN_CART, Offer, SupplyType, SupplyTarget, Discount, Order
)
from cart.tasks import send_user_order_notification, send_admin_order_notification
from contact_us.mixins import RequestNotifiable
from core.forms import QueryFormBase
from core.models import City
from core.utils import parse_path
from django.utils.translation import ugettext_lazy as _
@ -28,15 +31,13 @@ class CartAddInlineForm(forms.ModelForm):
Field('offer'),
Field('amount'),
Div(
Submit('Купить',value='submit'),
Submit('add', value=_('Купить')),
css_class='catalog__btn'
)
)
super().__init__(*args, **kwargs)
def clean_amount(self):
amount = self.cleaned_data['amount']
offer = self.cleaned_data['offer']
@ -73,7 +74,7 @@ class CartRemoveBuyingForm(forms.ModelForm):
self.helper.layout = Layout(
Field('offer'),
Div(
Submit('Убрать'),
Submit('cancel', value=_('Убрать')),
css_class='catalog__btn'
)
)
@ -88,38 +89,90 @@ class CartRemoveBuyingForm(forms.ModelForm):
}
CartRemoveBuyingFormset = formset_factory(CartRemoveBuyingForm)
class CartCheckoutBuyingForm(forms.Form):
order = forms.Field()
offer = forms.Field()
amount = forms.IntegerField()
bonus_points = forms.IntegerField()
def get_initial_for_field(self, field, field_name):
if field_name == 'offer':
field = Offer.active.get(product_id=self.initia[field_name])
elif field_name == 'order':
field = Order.objects.get(order_code=self.initial[field_name])
elif field_name == 'amount' or field_name == 'bonus_points':
field.value = self.initial[field_name]
return super().get_initial_for_field(field, field_name)
def save(self, user):
buying = Buying()
buying.user = user
buying.offer = self.offer
buying.order = self.order
buying.bonus_points = self.bonus_points
buying.amount = self.amount
buying.total_price = self.offer.get_price_with_discount * self.amount
buying.save()
return buying
class CartCheckoutForm(RequestNotifiable, forms.ModelForm):
form_action = {'viewname': 'cart:confirm', 'kwargs': {}}
field_template = 'bootstrap/forms/cart_checkout.html'
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'),
Field('customer_name', css_class="order__input", template=self.field_template),
Field('customer_email', css_class="order__input", template=self.field_template),
Field('customer_user', css_class="order__input", template=self.field_template),
Field('phone', css_class="order__input", template=self.field_template),
Field('customer_address', css_class="order__input", template=self.field_template),
Field('city', css_class="order__input", template=self.field_template),
Field('comment', css_class="order__input", template=self.field_template),
Div(
Submit('Подвердить'),
Submit('checkout', value=_('Подтвердить'), style="margin: 0 auto;"),
css_class='catalog__btn'
)
)
super().__init__(*args, **kwargs)
class Model:
def save(self, commit=True):
if not City.objects.filter(name=self.cleaned_data['city']).exists():
city = City()
city.name = self.cleaned_data['city']
city.save()
self.instance.city = city
return super().save(commit)
def send_order_invoice(self, request):
return send_user_order_notification.delay(self.instance.id, request)
def send_order_request(self, request):
context = {
'from_email': settings.DEFAULT_FROM_EMAIL,
'recipients': (settings.DEFAULT_FROM_EMAIL,),
'email': {
'subject': _('У вас новый заказ'),
'order': self.instance,
},
'send_at_date': self.instance.create_at,
}
return send_admin_order_notification.delay(context)
class Meta:
model = Order
fields = (
'customer_name', 'customer_email', 'customer_user',
'phone', 'customer_address', 'city', 'buyings',
'comment'
'customer_name', 'customer_email', 'phone', 'customer_address', 'city', 'comment'
)
widgets = {
'city': forms.TextInput()
}
class ProductOfferPriceFilterForm(QueryFormBase):
min_price = 0

@ -99,12 +99,20 @@ class SupplyTarget(AbstractDateTimeModel):
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,
def upload_file_to(self, filename):
return "discounts/{name}/{filename}".format(**{
'name': self.name,
'filename': filename
})
name = models.CharField(_('Имя'), max_length=255)
image = models.FileField(_('Изображение'), upload_to=upload_file_to, blank=True, null=True)
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)
active = models.BooleanField(_('Активная'), default=True)
def __str__(self):
return self.code
@ -171,71 +179,6 @@ class Offer(AbstractStatusModel):
verbose_name_plural = _('Позиции')
# ------------------------------------------ Buying status --------------------------------------------------- #
BUYING_STATUS_IN_CART = 25
BUYING_STATUS_PENDING = 50
BUYING_STATUS_PAID = 75
BUYING_STATUS_CHOICES = (
(BUYING_STATUS_IN_CART, _('В корзине')),
(BUYING_STATUS_PENDING, _('Обрабатываеться')),
(BUYING_STATUS_PAID, _('Оплаченно'))
)
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):
user = models.ForeignKey(get_user_model(), 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)
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:
verbose_name = _('Покупка')
verbose_name_plural = _('Покупки')
STATUS_GAINED = 0
STATUS_SPENT = 100
CASHBACK_STATUS_CHOICES = (
@ -285,7 +228,7 @@ 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_name = models.CharField(_('имя'), 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,
@ -302,7 +245,6 @@ class Order(AbstractStatusModel):
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)
@ -316,6 +258,72 @@ class Order(AbstractStatusModel):
verbose_name_plural = _('Заказы')
# ------------------------------------------ Buying status --------------------------------------------------- #
BUYING_STATUS_IN_CART = 25
BUYING_STATUS_PENDING = 50
BUYING_STATUS_PAID = 75
BUYING_STATUS_CHOICES = (
(BUYING_STATUS_IN_CART, _('В корзине')),
(BUYING_STATUS_PENDING, _('Обрабатываеться')),
(BUYING_STATUS_PAID, _('Оплаченно'))
)
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):
order = models.ForeignKey(Order, 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)
bonus_points = models.IntegerField(_('бонусы'), validators=(MinValueValidator(0),))
status = models.SmallIntegerField(_('статус'), default=BUYING_DEFAULT_CHOICE, choices=BUYING_STATUS_CHOICES)
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:
verbose_name = _('Покупка')
verbose_name_plural = _('Покупки')
@receiver(post_save, sender=Order)
def product_in_order_post_save(sender, instance, created, **kwargs):
order = instance.order

@ -1,23 +1,17 @@
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 io import BytesIO
from cart.models import Order
from django.conf import settings
from django.template.loader import render_to_string, get_template
from django.core.mail import EmailMessage, EmailMultiAlternatives
SUPPLIER_INFO = '''ООО "Русские Программы", ИНН 7713409230, КПП 771301001,
127411, Москва г, Дмитровское ш., дом 157, корпус 7, тел.: +74957258950'''
from cart.models import Order
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):
def send_user_order_notification(order_id, request):
"""
Sending Email of order creating
"""
@ -26,20 +20,44 @@ def send_user_order_notification(order_id):
verb_cur = pytils.numeral.choose_plural(round(order.total_price), ("рубль", "рубля", "рублей"))
subject = 'Заказ № {}'.format(order.id)
message = 'Уважаемый, {}, номер Вашего заказа {}. \
Пожалуйста, совершите платеж по поручению в приложении к этому письму в течение 14 дней.'.format(order.customer_name, order.id)
Пожалуйста, совершите платеж по поручению в приложении к этому письму в течение 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,
html = render_to_string('orders/pdf.html', {**settings.PAY_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')])
stylesheets=[
weasyprint.CSS(settings.STATIC_ROOT + 'css/build.css')])
order_invoice_name = 'Order_' + order.order_code
weasyprint.HTML(string=rendered_html, base_url=request.build_absolute_uri()).write_pdf(order_invoice_name,
stylesheets=[weasyprint.CSS(
settings.STATIC_ROOT + '/css/build.css')])
mail_send.attach('order_{}.pdf'.format(order_id), out.getvalue(), 'application/pdf')
mail_send.send()
return mail_send
@celery.task
def send_admin_order_notification(context):
body = get_template('emails/html/admin_order_request.html')
body_text = get_template('emails/txt/admin_order_request.txt')
email = EmailMultiAlternatives(
context['email']['subject'],
body_text.render(context['email']),
context['from_email'],
context['recipients']
)
email.attach_alternative(body.render(context['email']), 'text/html')
try:
email.send()
except Exception as e:
return False
return True

@ -6,10 +6,11 @@ from . import views
urlpatterns = [
re_path(r'^history/', views.BuyingsHistory.as_view(), name='history'),
re_path(r'^buyings/$', views.CartView.as_view(), name='buyings'),
re_path(r'^discounts/$', views.DiscountListView.as_view(), name='discounts'),
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'^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

@ -29,6 +29,7 @@ class Cart(object):
self.store.modified = True
def remove(self, offer_id):
offer_id = str(offer_id)
if offer_id in self.cart:
del self.cart[offer_id]
self.save()

@ -1,26 +1,30 @@
import datetime
import logging
import uuid
from functools import reduce
from decimal import Decimal
from django.conf import settings
from django.db import transaction
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.views.generic.edit import BaseFormView
from django.views.generic import ListView
from cart.utils import Cart
from cart.models import Buying, Discount, Offer
from core.views import ProtectedListView, ProtectedTemplateView, ProtectedView, ProtectedFormView
from core.models import City
from core.views import ProtectedListView, ProtectedTemplateView, ProtectedView, ProtectedFormView, ProtectedBaseFormView
from .forms import (
CartRemoveBuyingForm,
DiscountForm,
CartAddInlineForm, CartCheckoutForm
)
CartAddInlineForm, CartCheckoutForm,
CartCheckoutBuyingForm)
logger = logging.getLogger(__name__)
class CartAddView(BaseFormView):
class CartAddView(ProtectedBaseFormView):
http_method_names = ('post',)
form_class = CartAddInlineForm
@ -32,7 +36,7 @@ class CartAddView(BaseFormView):
return super().form_valid(form)
class CartRemoveView(BaseFormView):
class CartRemoveView(ProtectedBaseFormView):
http_method_names = ('post',)
model = Offer
form_class = CartRemoveBuyingForm
@ -41,8 +45,8 @@ class CartRemoveView(BaseFormView):
return self.request.META.get('HTTP_REFERRER', reverse_lazy('products:product_list'))
def form_valid(self, form):
self.request.cart.remove(form.cleaned_data['offer'])
return super().form_valid()
self.request.cart.remove(form.cleaned_data['offer'].product_id)
return super().form_valid(form)
class CartView(ProtectedListView):
@ -75,7 +79,7 @@ class CartView(ProtectedListView):
context = super().get_context_data(object_list=object_list, **kwargs)
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_price_currency'] = context['total_cashback_currency'] = self.object_list.first().currency.sign if self.object_list.first() else ''
context['total_cashback'] = self.get_total_cashback(self.object_list)
return context
@ -84,17 +88,50 @@ class CartCheckoutView(ProtectedFormView):
http_method_names = ('get', 'post',)
template_name = 'cart/checkout.html'
form_class = CartCheckoutForm
title = _('Оформление заказа')
def get_form_kwargs(self):
return super().get_form_kwargs()
def form_valid(self, form):
return super().form_valid(form)
class CartConfirmView(ProtectedTemplateView):
kwargs = super().get_form_kwargs()
kwargs['initial'] = {
'customer_name': self.request.user.profile.full_name,
'customer_email': self.request.user.email,
'phone': self.request.user.profile.phone,
'customer_address': self.request.user.profile.address,
'city': City.objects.filter(name=self.request.user.profile.city).first()
}
return kwargs
class CartConfirmView(ProtectedFormView):
http_method_names = ('post',)
template_name = 'cart/confirm.html'
form_class = CartCheckoutForm
title = _('Подтверждение заказа')
def form_valid(self, form):
if form.is_valid():
try:
with transaction.atomic():
form.save()
for item in self.request.cart:
buying_form = CartCheckoutBuyingForm(initial={
'offer': item,
'order': form.instance.order_code,
'amount': self.request.cart[item]['quantity']
})
buying_form.save(self.request.user)
form.send_order_invoice(self.request)
form.send_order_request(self.request)
self.request.cart.clear()
except Exception as e:
logger.critical(e)
return super().form_invalid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = self.title
return context
class BuyingsHistory(ProtectedListView):
@ -114,6 +151,18 @@ class BuyingsHistory(ProtectedListView):
context['title'] = self.title
return context
class DiscountListView(ListView):
template_name = 'cart/discount_list.html'
model = Discount
title = _('Акции / Cashback')
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
# Discount views # @TODO: TEST FOR PRODUCTION
class PointsApply(ProtectedTemplateView):
http_method_names = ('post',)
@ -124,8 +173,6 @@ class PointsApply(ProtectedTemplateView):
return redirect('cart:buyings')
class PointsRevoke(ProtectedView):
http_method_names = ('post',)

Loading…
Cancel
Save