Применение купона к корзине
Мы можем хранить новые купоны и создавать запросы для получения существующих купонов. Теперь нам нужен способ, с помощью которого клиенты могли бы применять купоны к своим покупкам. Подумайте о том, как будет работать эта функция. Способ применения купона будет следующим:
- Пользователь добавляет товары в корзину для покупок.
- Пользователь может ввести код купона в форме, отображаемой на странице корзины для покупок.
- Когда пользователь вводит код купона и отправляет форму, мы ищем существующий купон с заданным кодом, который в настоящее время действителен. Мы должны проверить, что код купона совпадает с введенным пользователем, атрибут active имеет значение True, а текущее значение datetime находится между значениями valid_from и valid_to.
- Если купон найден, мы его сохраняем в сессии пользователя и выводим на экран корзину, включая применяемую к ней скидку, и обновленную общую сумму.
- Когда пользователь размещает заказ, мы сохраняем купон в этот заказ.
Создайте новый файл в каталоге приложения coupons и назовите его forms.py. Добавьте в него следующий код:
from django import forms
class CouponApplyForm(forms.Form):
code = forms.CharField()
Это форма, которая будет использоваться пользователем для ввода кода купона. Измените файл views.py в приложении coupons и добавьте в него следующий код:
from django.shortcuts import render, redirect
from django.views.decorators.http import require_POST
from django.utils import timezone
from .models import Coupon
from .forms import CouponApplyForm
@require_POST
def coupon_apply(request):
now = timezone.now()
form = CouponApplyForm(request.POST)
if form.is_valid():
code = form.cleaned_data['code']
try:
coupon = Coupon.objects.get(code__iexact=code,
valid_from__lte=now,
valid_to__gte=now,
active=True)
request.session['coupon_id'] = coupon.id
except Coupon.DoesNotExists:
request.session['coupon_id'] = None
return redirect('cart:cart_detail')
Представление coupon_apply проверяет купон и сохраняет его в сессии пользователя. Мы применяем декоратор require_POST к этому представлению, чтобы ограничить его учетом запросов. В представлении мы выполняем следующие задачи:
- Мы создаем экземпляр формы CouponApplyForm, используя учтенные данные, и проверяем, что форма является валидной.
- Если форма является валидной, мы получим код, введенный пользователем из формы cleaned_data. Мы пытаемся извлечь объект Coupon с данным кодом. Мы используем поиск в поле iexact, чтобы проверить точное совпадение без учета регистра. Купон должен быть активен в данный момент (active=True) и действителен для текущего datetime. Мы используем функцию Джанго timezone.now(), чтобы получить текущую дату и время, сопоставленные с часовым поясом, и сравнить ее с полями valid_from и valid_to, выполняющими lte(меньше или равными) и gte(больше или равным).
- Идентификатор купона хранится в сессии пользователя.
- Мы перенаправим пользователя на URL-адрес cart_detail, чтобы отобразить корзину с примененным купоном.
Нам нужен шаблон URL-адреса для представления coupon_apply. Создайте новый файл в каталоге приложения coupons и назовите его urls.py. Добавьте в него следующий код:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^apply/$', views.coupon_apply, name='apply'),
]
Затем отредактируйте основной urls.py проекта myshop и включите шаблоны URL-адресов купонов следующим образом:
url(r'^coupons/', include('coupons.urls', namespace='coupons')),
Не забудьте поместить этот шаблон перед шаблоном shop.urls.
Теперь отредактируйте файл cart.py приложения cart. Включите следующий импорт:
from coupons.models import Coupon
Добавьте следующий код в конец метода __init__() класса Cart для инициализации купона из текущей сессии:
# сохранение текущего примененного купона
self.coupon_id = self.session.get('coupon_id')
В этом коде мы пытаемся получить session key coupon_id из текущей сессии и сохранить его значение в объекте Cart. Добавьте в объект Cart следующие методы:
@property
def coupon(self):
if self.coupon_id:
return Coupon.objects.get(id=self.coupon_id)
return None
def get_discount(self):
if self.coupon:
return (self.coupon.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()
Вот что делают эти методы:
- coupon() : Этот метод определяется как property. Если корзина содержит функцию coupon_id, возвращается объект Coupon с заданным id.
- get_discount() : Если корзина содержит купон, мы получаем скидку по ставке и возвращаем сумму, которая будет вычтена из общей суммы корзины.
- get_total_price_after_discount() : Мы возвращаем общую сумму корзины после вычета суммы, возвращенной методом get_discount().
Теперь класс Cart готов обработать купон, примененный к текущей сессии, и применить соответствующую скидку.
Давайте включим систему купонов в detail view корзины. Отредактируйте файл views.py приложения cart и добавьте следующий импорт в верхнюю часть файла:
from coupons.forms import CouponApplyForm
Далее, отредактируйте представление cart_detail и добавьте в него новую форму следующим образом:
def cart_detail(request):
cart = Cart(request)
for item in cart:
item['update_quantity_form'] = CartAddProductForm(
initial={'quantity': item['quantity'],
'update': True})
coupon_apply_form = CouponApplyForm()
return render(request,
'cart/detail.html',
{'cart': cart,
'coupon_apply_form': coupon_apply_form})
Измените шаблон cart/detail.html приложения cart и найдите следующие строки:
<tr class="total">
<td>Total</td>
<td colspan="4"></td>
<td class="num">${{ cart.get_total_price }}</td>
</tr>
Замените их следующими:
{% if cart.coupon %}
<tr class="subtotal">
<td>Subtotal</td>
<td colspan="4"></td>
<td class="num">${{ cart.get_total_price }}</td>
</tr>
<tr>
<td>
"{{ cart.coupon.code }}" coupon
({{ cart.coupon.discount }}% off)
</td>
<td colspan="4"></td>
<td class="num neg">
- ${{ cart.get_discount|floatformat:"2" }}
</td>
</tr>
{% endif %}
<tr class="total">
<td>Total</td>
<td colspan="4"></td>
<td class="num">
${{ cart.get_total_price_after_discount|floatformat:"2" }}
</td>
</tr>
Это код для отображения дополнительного купона и его скидки. Если корзина содержит купон, мы выводим первую строку, включая общую сумму корзины в качестве промежуточного итога. Затем мы используем вторую строку для отображения текущего купона, примененного к корзине. Наконец, мы отображаем общую цену, включая скидки, вызвав метод get_total_price_after_discount() объекта cart.
В том же файле следует включить следующий код после тега </table>:
<form action="{% url "coupons:apply" %}" method="post">
{{ coupon_apply_form }}
<input type="submit" value="Apply">
{% csrf_token %}
</form>
Будет отображена форма для ввода кода купона и применения его к текущей корзине.
Откройте в браузере http://127.0.0.1:8000/, добавьте товар в корзину и примените купон, созданный с помощью ввода его кода в форму. В корзине отобразится скидка по купону:
Давайте добавим купон к следующему шагу процесса покупки. Измените шаблон orders/order/create.html приложения orders и найдите следующие строки:
<ul>
{% for item in cart %}
<li>
{{ item.quantity }}x {{ item.product.name }}
<span>${{ item.total_price }}</span>
</li>
{% endfor %}
</ul>
Замените их следующим кодом:
<ul>
{% for item in cart %}
<li>
{{ item.quantity }}x {{ item.product.name }}
<span>${{ item.total_price }}</span>
</li>
{% endfor %}
{% if cart.coupon %}
<li>
"{{ cart.coupon.code }}" ({{ cart.coupon.discount }}% off)
<span>- ${{ cart.get_discount|floatformat:"2" }}</span>
</li>
{% endif %}
</ul>
Заказ должен теперь включать примененный купон, если он есть. Теперь найдите следующую строку:
<p>Total: ${{ cart.get_total_price }}</p>
Замените ее на следующий код:
<p>Total: ${{ cart.get_total_price_after_discount|floatformat:"2" }}</p>
При этом общая цена будет также рассчитываться путем применения скидки купона.
Откройте в браузере http://127.0.0.1:8000/orders/create/. Вы увидите, что заказ примененный купон:
Теперь пользователи могут применять купоны к своим покупкам.