美文网首页@IT·互联网程序员
第九章 扩展你的商店(上)

第九章 扩展你的商店(上)

作者: lakerszhy | 来源:发表于2017-05-17 17:16 被阅读179次

    9 扩展你的商店

    上一章中,你学习了如何在商店中集成支付网关。你完成了支付通知,学习了如何生成CSV和PDF文件。在这一章中,你会在商店中添加优惠券系统。你将学习如何处理国际化和本地化,并构建一个推荐引擎。

    本章会覆盖以下知识点:

    • 创建优惠券系统实现折扣
    • 在项目中添加国际化
    • 使用Rosetta管理翻译
    • 使用django-parler翻译模型
    • 构建一个商品推荐系统

    9.1 创建优惠券系统

    很多在线商店会给顾客发放优惠券,在购买商品时可以兑换折扣。在线优惠券通常是一组发放给用户的代码,这个代码在某个时间段内有效。这个代码可以兑换一次或多次。

    我们将为我们的商品创建一个优惠券系统。顾客在某个时间段内输入我们的优惠券才有效。优惠券没有使用次数限制,可以抵扣购物车的总金额。对于这个功能,我们需要创建一个模型,用于存储优惠券码,有效时间和折扣金额。

    使用以下命令在myshop项目中添加一个新应用:

    python manage.py startapp coupons
    

    编辑myshopsettings.py文件,把应用添加到INSTALLED_APPS中:

    INSTALLED_APPS = (
        # ...
        'coupons',
    )
    

    现在新应用已经在我们的Django项目中激活了。

    9.1.1 构建优惠券模型

    让我们从创建Coupon模型开始。编辑coupons应用的models.py文件,并添加以下代码:

    from django.db import models
    from django.core.validators import MinValueValidator
    from django.core.validators import MaxValueValidator
    
    class Coupon(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
    

    这是存储优惠券的模型。Coupon模型包括以下字段:

    • code:用户必须输入优惠券码才能使用优惠券。
    • valid_from:优惠券开始生效的时间。
    • valid_to:优惠券过期的时间。
    • discount:折扣率(这是一个百分比,所以范围是0到100)。我们使用验证器限制这个字段的最小值和最大值。
    • active:表示优惠券是否有效的布尔值。

    执行以下命令,生成coupon应用的初始数据库迁移:

    python manage.py makemigrations
    

    输出会包括以下行:

    Migrations for 'coupons':
      coupons/migrations/0001_initial.py
        - Create model Coupon
    

    然后执行以下命令,让数据库迁移生效:

    python manage.py migrate
    

    你会看到包括这一行的输出:

    Applying coupons.0001_initial... OK
    

    现在迁移已经应用到数据库中了。让我们把Coupon模型添加到管理站点。编辑coupons应用的admin.py文件,并添加以下代码:

    from django.contrib import admin
    from .models import Coupon
    
    class CouponAdmin(admin.ModelAdmin):
        list_display = ['code', 'valid_from', 'valid_to', 'discount', 'active']
        list_filter = ['active', 'valid_from', 'valid_to']
        search_fields = ['code']
    admin.site.register(Coupon, CouponAdmin)
    

    现在Coupon模型已经在管理站点注册。执行python manage.py runserver命令启动开发服务器,然后在浏览器中打开http://127.0.0.1:8000/admin/coupons/coupon/add/。你会看下图中的表单:

    填写表单,创建一个当天可用的优惠券,并勾选Active,然后点击Save按钮。

    9.1.2 在购物车中使用优惠券

    我们可以存储新的优惠券,并且可以查询已存在的优惠券。现在我们需要让顾客可以使用优惠券。考虑一下应该怎么实现这个功能。使用优惠券的流程是这样的:

    1. 用户添加商品到购物车中。
    2. 用户在购物车详情页面的表单中输入优惠券码。
    3. 当用户输入了优惠券码,并提交了表单,我们用这个优惠券码查找当前有效地一张优惠券。我们必须检查这张优惠券码匹配用户输入的优惠券码,active属性为True,以及当前时间在valid_fromvalid_to之间。
    4. 如果找到了优惠券,我们把它保存在用户会话中,显示包括折扣的购物车,然后更新总金额。
    5. 当用户下单时,我们把优惠券保存到指定的订单中。

    coupons应用目录中创建forms.py文件,并添加以下代码:

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

    我们用这个表单让用户输入优惠券码。编辑coupons应用的views.py文件,并添加以下代码:

    from django.shortcuts import render, redirect
    from django.utils import timezone
    from django.views.decorators.http import require_POST
    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.DoesNotExist:
                request.session['coupon_id'] = None
        return redirect('cart:cart_detail')
    

    coupon_apply视图验证优惠券,并把它存储在用户会话中。我们用require_POST装饰器装饰这个视图,只允许POST请求。在视图中,我们执行以下任务:

    1. 我们用提交的数据实例化CouponApplyForm表单,并检查表单是否有效。
    2. 如果表单有效,我们从表单的cleaned_data字典中获得用户输入的优惠券码。我们用给定的优惠券码查询Coupon对象。我们用iexact字段执行大小写不敏感的精确查询。优惠券必须是有效的(active=True),并且在当前时间是有效地。我们用Django的timezone.now()函数获得当前时区的时间,我们把它与valid_fromvalid_to字段比较,对这两个字段分别执行lte(小于等于)和gte(大于等于)字段查询。
    3. 我们在用户会话中存储优惠券ID。
    4. 我们重定向用户到cart_detail URL,显示使用了优惠券的购物车。

    我们需要一个coupon_apply视图的URL模式。在coupons应用目录中添加urls.py文件,并添加以下代码:

    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
        url(r'^apply/$', views.coupon_apply, name='apply'),
    ]
    

    然后编辑myshop项目的主urls.py文件,添加coupons的URL模式:

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

    记住,把这个模式放到shop.urls模式之前。

    现在编辑cart应用的cart.py文件,添加以下导入:

    from coupons.models import Coupon
    

    Cart类的__init__()方法最后添加以下代码,从当前会话中初始化优惠券:

    # store current applied coupon
    self.coupon_id = self.session.get('coupon_id')
    

    这行代码中,我们尝试从当前会话中获得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。如果cart中包括coupon_id属性,则返回给定idCoupon对象。
    • get_discount():如果cart包括coupon,则查询它的折扣率,并返回从购物车总金额中扣除的金额。
    • get_total_price_after_discount():减去get_discount()方法返回的金额后,购物车的总金额。

    现在Cart类已经准备好处理当前会话中的优惠券,并且可以减去相应的折扣。

    让我们在购物车详情视图中引入优惠券系统。编辑cart应用的views.py,在文件顶部添加以下导入:

    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应用的cart/detail.html目录,找到以下代码:

    <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>
    

    这段代码显示一个可选的优惠券和它的折扣率。如果购物车包括一张优惠券,我们在第一行显示购物车总金额为Subtotal。然后在第二行显示购物车使用的当前优惠券。最后,我们调用cart对象的cart.get_total_price_after_discount()方法,显示折扣之后的总金额。

    在同一个文件的</table>标签之后添加以下代码:

    <p>Apply a coupon:</p>
    <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应用的orders/order/create.html模板,找到以下代码:

    <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/。你会看到订单汇总包括了使用的优惠券:

    现在用户可以在购物车中使用优惠券了。但是当用户结账时,我们还需要在创建的订单中存储优惠券信息。

    9.1.3 在订单中使用优惠券

    我们将存储每个订单使用的优惠券。首先,我们需要修改Order模型来存储关联的Coupon对象(如果存在的话)。

    编辑orders应用的models.py文件,并添加以下导入:

    from decimal import Decimal
    from django.core.validators import MinValueValidator
    from django.core.validators import MaxValueValidator
    from coupons.models import Coupon
    

    然后在Order模型中添加以下字段:

    coupon = models.ForeignKey(Coupon, related_name='orders', null=True, blank=True)
    discount = models.IntegerField(default=0, 
            validators=[MinValueValidator(0), MaxValueValidator(100)])
    

    这些字段允许我们存储一个可选的订单使用的优惠券和优惠券的折扣。折扣存储在关联的Coupon对象中,但我们在Order模型中包括它,以便优惠券被修改或删除后还能保存。

    因为修改了Order模型,所以我们需要创建一个数据库迁移。在命令行中执行以下命令:

    python manage.py makemigrations
    

    你会看到类似这样的输出:

    Migrations for 'orders':
      orders/migrations/0002_auto_20170515_0731.py
        - Add field coupon to order
        - Add field discount to order
    

    执行以下命令同步数据库迁移:

    python manage.py migrate orders
    

    你会看到新的数据库迁移已经生效,现在Order模型的字段修改已经同步到数据库中。

    回到models.py文件,修改Order模型的get_total_cost()方法:

    def get_total_cost(self):
        total_cost = sum(item.get_cost() for item in self.items.all())
        return total_cost - total_cost * self.discount / Decimal('100')
    

    如果存在优惠券,Order模型的get_total_cost()方法会计算优惠券的折扣。

    编辑orders应用的views.py文件,修改其中的order_create视图,当创建新订单时,保存关联的优惠券。找到这一行代码:

    order = form.save()
    

    替换为以下代码:

    order = form.save(commit=False)
    if cart.coupon:
        order.coupon = cart.coupon
        order.discount = cart.coupon.discount
    order.save()
    

    在新代码中,我们用OrderCreateForm表单的save()方法创建了一个Order对象,并用commit=False避免保存到数据库中。如果购物车中包括优惠券,则存储使用的关联优惠券和折扣。然后我们把order对象存储到数据库中。

    执行python manage.py runserver命令启动开发服务器,并使用./ngrok http 8000命令启动Ngrok。

    在浏览器中打开Ngrok提供的URL,并使用你创建的优惠券完成一次购物。当你成功完成一次购物,你可以访问http://127.0.0.1:8000/admin/orders/order/,检查订单是否包括优惠券和折扣,如下图所示:

    你还可以修改管理订单详情模板和订单PDF账单,用跟购物车同样的方式显示使用的优惠券。

    接下来,我们要为项目添加国际化。

    相关文章

      网友评论

        本文标题:第九章 扩展你的商店(上)

        本文链接:https://www.haomeiwen.com/subject/itndxxtx.html