Дикий Григорий

Full-stack веб-разработчик

Интернет-магазин на Django. Система купонов. Часть 11

Доброго времени суток! Наш онлайн-магазин с каждым днем становится все лучше и лучше. Теперь было бы хорошо создать систему купонов, которая бы предполагала наличие кода который даст в итоге скидку. Этот код будет действителен определенное время. Для этого нам нужно создать приложение для купонов в Django:

$ python manage.py startapp cupons

Теперь подключим наше приложение к Django изменив файл settings.py:

INSTALLED_APPS = [
    ...
    'payment',
    'cupons'
]

Теперь надо создать модель купона в нашем приложении, для этого откроем файл models.py из директории cupons и вставим следующее:

from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator

class Cupon(models.Model):
    code = models.CharField(max_length=50, unique=True)
    valid_from = models.DateTimeField()
    valid_to = models.DateTimeField()
    discount = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(100)])
    active = models.BooleanField()

    def __str__(self):
        return self.code
  • code - код, который пользователь должен ввести для активации купона
  • valid_from - дата начала валидности купона
  • valid_to - дата, когда купон становится просроченным
  • discount - скидка в процентах на товары

Теперь нам нужно синхронизировать новую модель с базой данных. Для этого в консоли выполним команду:

$ python manage.py makemigrations
$ python manage.py migrate

Теперь нам нужно зарегистрировать нашу модель в админ-панели, для этого изменим файл admin.py следующим образом:

from django.contrib import admin
from .models import Cupon

class CuponAdmin(admin.ModelAdmin):
    list_display = ['code', 'valid_from', 'valid_to', 'discount', 'active']
    list_filter  = ['valid_from', 'valid_to', 'active']
    search_field = ['code']

admin.site.register(Cupon, CuponAdmin)

Теперь если зайти в админ-панели и попытаться добавить купон мы должны увидеть следующее:

Купоны и корзина пользователя

Нам нужно сделать так, чтобы пользователи могли пользоваться купонной системой во время покупок. Нужно продумать как это будет работать:

  1. Пользователь добавляет товары в свою корзину
  2. Пользователь может ввести купон на странице корзины с товарами
  3. Когда пользователь введет купон, мы отправляем запрос и ищем такой купон в БД и также смотрим если купон у нас валидный.
  4. Если купон найден и он действителен, то мы сохраняем сессию пользователя и отображаем корзину уже с включенным купоном

Давайте создадим форму для ввода кода купона. Для этого нам надо создать файл forms.py в директории нашего приложения cupons и вставить следующее содержимое:

from django import forms
class CuponApllyForm(forms.Form):
    code = forms.CharField()

Теперь нам надо создать вьюху для использования купона. Откроем файл views.py и вставим следующее содержимое:

from django.shortcuts import render, redirect
from django.utils import timezone
from django.views.decorators.http import require_POST
from .models import Cupon
from .forms import CuponApllyForm


@require_POST
def CuponApply(request):
    now = timezone.now()
    form = CuponApllyForm(request.POST)
    if form.is_valid():
        code = form.cleaned_data['code']
        try:
            cupon = Cupon.objects.get(code__iexact=code,
                                      valid_from__lte=now,
                                      valid_to__gte=now,
                                      active=True)
            request.session['cupon_id'] = cupon.id
        except Cupon.DoesNotExist:
            request.session['cupon_id'] = None
    return redirect('cart:CartDetail')

В этом коде вы получаем текущее время, а также проверяем валидность формы. Если форма валидна, то мы пытаемся найти в нашей БД купон который бы соответствовал требованиям: совпадал бы код, поле active было бы True и текущее время было бы между датами действия купона. Если такой купон создан, то создается поле cupon_id в сессиях c идентификатором купона, если нет, то со значение None.

Теперь настала пора настроить роутер. Для этого создадим файл urls.py в нашем приложении со следующим содержимым:

from django.conf import url
from .views import CuponApply


urlpatterns = [
    url(r'^apply', views.CuponApply, name='apply')
]

И включим этот роутер в главный urls.py из директории myshop:

url(r'^cupons/', include('cupons.urls', namespace='cupon')),

Теперь нам нужно изменить файл cart.py из директории cart чтобы учесть наш купон:

from cupons.models import Cupon


class Cart(object):
    def __init__(self, request):
        # Инициализация корзины пользователя
        self.session = request.session
        self.cupon_id = self.session.get('cupon_id')
           ...

    @property
    def cupon(self):
        if self.cupon_id:
            return Cupon.objects.get(id=self.cupon_id)
        return None

    def get_discount(self):
        if self.cupon:
            return (self.cupon.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()
  • cupon() - выбирает из БД наш купон
  • get_discount() - подсчитывает сумму скидки
  • get_total_price_after_discount() - возвращает сумму с учетом скидки

Теперь осталось отобразить нашу форму в корзине пользователя. Для этого изменим файл views.py в директории cart:

from cupons.forms import CuponApllyForm


def CartDetail(request):
    cart = Cart(request)
    for item in cart:
        item['update_quantity_form'] = CartAddProductForm(
                                        initial={
                                            'quantity': item['quantity'],
                                            'update': True
                                        })
    cupon_apply_form = CuponApllyForm()
    return render(request, 'cart/detail.html',
                 {'cart': cart, 'cupon_apply_form': cupon_apply_form})

Теперь нам нужно изменить вывод корзины с товарами с учетом нашего купона, для этого изменим файл detail.html из директории cart/templates/cart/detail.html:

        ....        
          </td>
            <td><a href="{% url "cart:CartRemove" product.id %}">Удалить</a></td>
            <td class="num">{{ item.price }} руб.</td>
            <td class="num">{{ item.total_price }} руб.</td>
          </tr>
        {% endwith %}

      {% endfor %}
        {% if cart.cupon %}
          <tr class="subtotal">
            <td>Сумма без скидки</td>
            <td colspan="4"></td>
            <td>{{ cart.get_total_price }}</td>
          </tr>
          <tr>
            <td>
              "{{ cart.cupon.code }}" купон на ({{ cart.cupon.discount }} % ниже)
            </td>
            <td colspan="4"></td>
            <td class="num discount">- {{ cart.get_discount|floatformat:"2" }} руб.</td>
          </tr>
        {% endif %}
        <tr class="total">
          <td>Всего</td>
          <td colspan="4"></td>
          <td class="num">{{ cart.get_total_price_after_discount|floatformat:"2" }} руб.</td>
        </tr>
    </tbody>
  </table>

  <p>Применить купон</p>
  <form action="{% url "cupon:apply" %}" method="post" class="add">
    {% csrf_token %}
    {{ cupon_apply_form }}
    <input type="submit" value="Обновить">
  </form>

В итоге вы при переходе в корзину и активации купона вы увидите примерно следующее:

В следующий раз мы сделаем так, чтобы купон сработал при оформлении заказа, а на сегодня все.

Репозиторий проекта: github