# DjangoShop
**Repository Path**: Iamlovepython/DjangoShop
## Basic Information
- **Project Name**: DjangoShop
- **Description**: Django电商(生鲜)项目搭建
- **Primary Language**: CSS
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 9
- **Forks**: 4
- **Created**: 2019-08-06
- **Last Updated**: 2025-06-10
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
[](https://www.python.org/downloads/release/python-363/)
[](https://docs.djangoproject.com/en/2.1/releases/2.1/)
[](https://code.jquery.com/jquery-3.3.1.min.js/)
[](https://getbootstrap.com/)
# Django电商项目
## 一、环境配置
**1、创建Django项目**
django-admin startproject DjangoShop
**2、创建App(Store店铺)**
python manage.py startapp Store
**3、创建App下模板目录及静态文件目录**

**4、创建主static目录用于收集静态文件**
**5、setting文件设置**
- 安装App
- STATICFILES_DIRS = (
os.path.join(BASE_DIR,'static'),
)
- MEDIA_URL = '/media/'
- MEDIA_ROOT = os.path.join(BASE_DIR,'static')
- STATIC_ROOT = os.path.join(BASE_DIR,'static')
收集静态文件时注释STATICFILES_DIRS、MEDIA_URL、MEDIA_ROOT
收集完成取消上述三个注释,再注释STATIC_ROOT
## 二、创建数据模型类
**1、创建模型**
```python
from django.db import models
# Create your models here.
# 定义卖家模型类
class Seller(models.Model):
username = models.CharField(max_length=32,verbose_name="用户名")
password = models.CharField(max_length=32,verbose_name="密码")
nickname = models.CharField(max_length=32,verbose_name="昵称",null=True,blank=True)
phone = models.CharField(max_length=32,verbose_name="手机",null=True,blank=True)
email = models.EmailField(verbose_name="邮箱",null=True,blank=True)
picture = models.ImageField(upload_to="store/images",verbose_name="头像",null=True,blank=True)
address = models.CharField(max_length=32,verbose_name="地址",null=True,blank=True)
card_id = models.CharField(max_length=32,verbose_name="身份证",null=True,blank=True)
# 定义店铺类型类
class StoreType(models.Model):
store_type = models.CharField(max_length=32,verbose_name="类型名称")
type_description = models.TextField(verbose_name="类型描述")
# 定义店铺类
class Store(models.Model):
store_name = models.CharField(max_length=32,verbose_name="店铺名称")
store_address = models.CharField(max_length=32,verbose_name="店铺地址")
store_description = models.TextField(verbose_name="店铺描述")
store_logo = models.ImageField(upload_to="store/images",verbose_name="店铺logo")
store_phone = models.CharField(max_length=32,verbose_name="店铺电话")
store_money = models.FloatField(verbose_name="店铺注册资金")
user_id = models.IntegerField(verbose_name="店铺主人")
type = models.ManyToManyField(to=StoreType,verbose_name="店铺类型")
# 定义商品类
class Goods(models.Model):
goods_name = models.CharField(max_length=32,verbose_name="商品名称")
goods_price = models.FloatField(verbose_name="商品价格")
goods_image = models.ImageField(upload_to="store/images",verbose_name="商品图片")
goods_number = models.IntegerField(verbose_name="商品库存")
goods_description = models.TextField(verbose_name="商品描述")
goods_date = models.DateField(verbose_name="出厂日期")
goods_safeDate = models.IntegerField(verbose_name="保质期")
store_id = models.ManyToManyField(to=Store,verbose_name="商品店铺")
# 定义商品图片类
class GoodsImg(models.Model):
img_address = models.ImageField(upload_to="store/images",verbose_name="图片地址")
img_description = models.TextField(verbose_name="图片描述")
goods_id = models.ForeignKey(to=Goods,on_delete=models.CASCADE,verbose_name="商品id")
```
**2、同步数据库**
校验配置
python manage.py check
生成数据库语句
python manage.py makemigrations
同步数据库
python manage.py migrate
收集静态文件
python manage.py collectstatic(收集完成注意恢复配置,并注释STATIC_ROOT)
## 三、创建登录注册功能
**1、编写注册登录功能**
将模板中的注册HTML文件复制到App下templates-store下

编写简单视图显示页面

创建独立url

填写主url

更改原html文档,将所有css,js导入路径更改为/static/store/...,在访问网页

改动HTML布局
```html
SB Admin 2 - Register
```
效果:

复制register文档做登录页面并编写完整注册视图函数:
```python
import hashlib
from django.shortcuts import render
from django.http import HttpResponseRedirect
from Store.models import *
# Create your views here.
# 密码加密功能
def setPassword(password):
md5 = hashlib.md5()
md5.update(password.encode())
result = md5.hexdigest()
return result
# 注册功能
def register(request):
if request.method == "POST":
username = request.POST.get("username")
password = request.POST.get("password")
if username and password:
seller = Seller()
seller.username = username
seller.password = setPassword(password)
seller.nickname = username
seller.save()
return HttpResponseRedirect("/Store/login/")
return render(request,"store/register.html")
# 登录功能
def login(request):
return render(request,"store/login.html")
```
测试注册功能

**2、完善登录功能**
在登录页面下发cookie,在登录请求校验cookie。如果登录成功,则跳转到首页;否则跳转登录页面
先布局index首页(复制模板当中的index页面并修改样式路径)

完善登录视图:
```python
# 登录功能
def login(request):
"""
登录功能:进入登录页面是下发cookie,验证是正常方式请求登录
登录成功再次下发一个cookie,验证用户
"""
# 进入登录页面下发来源合法的cookie
response = render(request,"store/login.html")
response.set_cookie("login_from","legitimate")
# 判断用户请求的方式
if request.method == "POST":
username = request.POST.get("username")
password = request.POST.get("password")
if username and password: # 用户和密码都存在
seller = Seller.objects.filter(username=username).first() # 数据库查询该用户
if seller:
# 将前端获取到的密码加密,同数据库进行验证
web_password = setPassword(password)
# 校验登录页面的cookie
cookies = request.COOKIES.get("login_from")
if web_password == seller.password and cookies == "legitimate":
# 登录成功,则跳转到首页并下发cookie和session
response = HttpResponseRedirect('/Store/index/')
response.set_cookie("username",seller.username)
request.session["username"] = seller.username
return response
return response
# 首页
def index(request):
return render(request,"store/index.html",locals())
```
效果图:

做个装饰器,给首页添加校验功能,没有cookie不能直接登录首页,只有用户登录成功才能进入首页
```python
# 用户登录校验装饰器
def loginValid(fun):
def inner(request,*args,**kwargs):
# 获取成功登录后的cookie和session
c_user = request.COOKIES.get("username")
s_user = request.session.get("username")
# 如果cookie和session都存在并且值都相同
if c_user and s_user and c_user == s_user:
# 通过c_user查询数据库
seller = Seller.objects.filter(username=c_user).first()
# 如果有这个用户,则返回函数,这里只index
if seller:
return fun(request,*args,**kwargs)
# 否则重定向到登录页面
return HttpResponseRedirect("/Store/login/")
return inner
# 首页
@loginValid
def index(request):
return render(request,"store/index.html",locals())
```
现在直接进入index首页会自动跳转至登录页面

**3、前后端用户名重复校验**
后端(针对登录功能)
```python
# 登录功能
def login(request):
"""
登录功能:进入登录页面是下发cookie,验证是正常方式请求登录
登录成功再次下发一个cookie,验证用户
"""
# 后端校验
result = {"status":"error","data":""}
# 进入登录页面下发来源合法的cookie
response = render(request,"store/login.html")
response.set_cookie("login_from","legitimate")
# 判断用户请求的方式
if request.method == "POST":
username = request.POST.get("username")
password = request.POST.get("password")
if username and password: # 用户和密码都存在
seller = Seller.objects.filter(username=username).first() # 数据库查询该用户
if seller:
# 将前端获取到的密码加密,同数据库进行验证
web_password = setPassword(password)
# 校验登录页面的cookie
cookies = request.COOKIES.get("login_from")
if web_password == seller.password and cookies == "legitimate":
# 登录成功,则跳转到首页并下发cookie和session
response = HttpResponseRedirect('/Store/index/')
response.set_cookie("username",seller.username)
request.session["username"] = seller.username
result["status"] = "success"
result["data"] = "登录成功"
return response
else:
result["data"] = "密码错误"
response = render(request, "store/login.html", locals())
else:
result["data"] = "用户名不存在"
response = render(request, "store/login.html", locals())
else:
result["data"] = "用户名或密码不能为空"
response = render(request, "store/login.html",locals())
return response
```
效果图:

前端校验(针对注册功能)
```python
def ajax_regValid(request):
# ajax前端注册校验
result = {"status": "error", "data": ""}
username = request.POST.get("username")
if username:
user = Seller.objects.filter(username=username).first() # 数据库查询该用户
if user:
result["data"] = "用户名已存在"
else:
result["status"] = "success"
result["data"] = "用户名可以使用"
else:
result["data"] = "用户名不能为空"
return JsonResponse(result)
```
注册页面添加如下内容:
```html
```
效果图:

## 三、编写退出功能(删除cookie)
定义视图函数:
```python
# 退出功能(删除cookie)
def exit(request):
response = HttpResponseRedirect("/Store/login/")
response.delete_cookie("username")
del request.session["username"]
return response
```
更改首页logout超链接的href为/Store/exit/

## 四、编写base模板页,其他页面继承模板页
```html
{% block title %}{% endblock %}
{% block style %}{% endblock %}
{% block content %}
{% endblock %}
{% block script %}
{% endblock %}
```
## 五、店铺注册
在首页功能视图函数中判断用户是否有店铺,通过Store类中user_id
```python
# 首页
@loginValid
def index(request):
"""
v1.5 添加检测账号是否有店铺的逻辑
"""
# 查询当前用户
user_id = request.COOKIES.get("user_id")
if user_id:
user_id = int(user_id)
else:
user_id = 0
# 通过用户查询店铺是否存在(店铺和用户通过用户的id进行关联)
store = Store.objects.filter(user_id=user_id).first()
if store:
is_store = 1
else:
is_store = 0
return render(request,"store/index.html",{"is_store":is_store})
# 登录功能
def login(request):
"""
登录功能:进入登录页面是下发cookie,验证是正常方式请求登录
登录成功再次下发一个cookie,验证用户
"""
# 后端校验
result = {"status":"error","data":""}
# 进入登录页面下发来源合法的cookie
response = render(request,"store/login.html")
response.set_cookie("login_from","legitimate")
# 判断用户请求的方式
if request.method == "POST":
username = request.POST.get("username")
password = request.POST.get("password")
if username and password: # 用户和密码都存在
seller = Seller.objects.filter(username=username).first() # 数据库查询该用户
if seller:
# 将前端获取到的密码加密,同数据库进行验证
web_password = setPassword(password)
# 校验登录页面的cookie
cookies = request.COOKIES.get("login_from")
if web_password == seller.password and cookies == "legitimate":
# 登录成功,则跳转到首页并下发cookie和session
response = HttpResponseRedirect('/Store/index/')
response.set_cookie("username",seller.username)
# v1.5 添加下发user_id的cookie
response.set_cookie("user_id", seller.id)
request.session["username"] = seller.username
result["status"] = "success"
result["data"] = "登录成功"
return response
else:
result["data"] = "密码错误"
response = render(request, "store/login.html", locals())
else:
result["data"] = "用户名不存在"
response = render(request, "store/login.html", locals())
else:
result["data"] = "用户名或密码不能为空"
response = render(request, "store/login.html",locals())
return response
```
前端base也添加判断
```html
{% if is_store == 1 %}
店铺管理
{% else %}
没有店铺,注册一个
{% endif %}
```
效果:

指定一个注册店铺的前端页
```html
{% extends "store/base.html" %}
{% block title %}
后台管理首页
{% endblock %}
{% block content %}
{% endblock %}
```
效果:

完善注册店铺视图函数:
```python
def store_register(request):
# v1.5 新增店铺注册
# 查询所有的店铺类型
type_list = StoreType.objects.all()
if request.method == "POST":
post_data = request.POST #接收post数据
# print(request.FILES)
# print(post_data)
store_name = post_data.get("store_name")
store_address = post_data.get("store_address")
store_description = post_data.get("store_description")
store_phone = post_data.get("store_phone")
store_money = post_data.get("store_money")
# 通过cookie来获得user_id
user_id = int(request.COOKIES.get("user_id"))
# 通过request.post得到类型数据,是个列表
type_lsts = post_data.getlist("type")
# 通过request.FILES获取上传的图片
store_logo = request.FILES.get("store_logo")
# 保存正常数据
store = Store()
store.store_name = store_name
store.store_address = store_address
store.store_description = store_description
store.store_phone = store_phone
store.store_money = store_money
store.user_id = user_id
store.store_logo = store_logo
store.save()
# 在生成数据中添加多对多关系字段
for i in type_lsts:# 循环type列表,得到类型id
store_type = StoreType.objects.get(id=i)#查询类型数据
store.type.add(store_type)# 添加到类型字段,多对多的映射表
store.save()
return render(request,"store/store_register.html",locals())
```


## 六、添加商品
前端页面:
```html
{% extends "store/base.html" %}
{% block title %}
后台管理首页
{% endblock %}
{% block content %}
{% endblock %}
```
后端业务视图:
```python
# v1.6添加商品
def add_goods(request):
if request.method == "POST":
# v1.6 获取前端post请求数据
good_postData = request.POST
# v1.6 通过前端name字段获取实际的值存储起来
goods_name = good_postData.get("goods_name")
goods_price = good_postData.get("goods_price")
goods_number = good_postData.get("goods_number")
goods_description = good_postData.get("goods_description")
goods_date = good_postData.get("goods_date")
goods_safeDate = good_postData.get("goods_safeDate")
# v1.6 注意图片是通过FILES方式获取
goods_image = request.FILES.get("goods_image")
# v1.6 多对多关系字段,前端使用隐藏域
store_id = good_postData.get("store_id")
# 保存正常数据
goods = Goods() # 实例化一个商品对象
goods.goods_name = goods_name
goods.goods_price = goods_price
goods.goods_number = goods_number
goods.goods_description = goods_description
goods.goods_date = goods_date
goods.goods_safeDate = goods_safeDate
goods.goods_image = goods_image
goods.save() # 保存一条记录
# 保存多对多数据(注意get方式获取到的数据为字符串)
goods.store_id.add(
Store.objects.get(id=int(store_id))
)
goods.save()
return render(request,"store/add_goods.html")
```

## 七、商品列表
前端:
```html
{% extends "store/base.html" %}
{% block title %}
商品列表页面
{% endblock %}
{% block label %}
添加商品
{% endblock %}
{% block content %}
商品名称 |
商品价格 |
商品数量 |
出厂日期 |
保质期 |
操作 |
{% for goods in goods_list %}
{{ goods.goods_name }} |
{{ goods.goods_price }} |
{{ goods.goods_number }} |
{{ goods.goods_date }} |
{{ goods.goods_safeDate }} |
下架
销毁
|
{% endfor %}
{% endblock %}
```
后端:
```python
# v1.7 展示商品列表
def list_goods(request):
# v1.7 查询所有商品信息(提前添加了商品数据)
goods_list = Goods.objects.all()
return render(request,"store/goods_list.html",locals())
```
效果:

## 八、添加商品列表搜索功能
前端:

后端:
```python
# v1.7 展示商品列表
def list_goods(request):
# v1.8 添加keywords关键字字段,用户前端搜索
keywords = request.GET.get("keywords","")
if keywords:
# v1.8 对关键字进行模糊查询
goods_list = Goods.objects.filter(goods_name__contains=keywords)
else:
# v1.7 查询所有商品信息(提前添加了商品数据)
goods_list = Goods.objects.all()
return render(request,"store/goods_list.html",locals())
```
效果:

## 九、商品列表页搜索分页功能
后端:
```python
# v1.7 展示商品列表
def list_goods(request):
# v1.8 添加keywords关键字字段,用户前端搜索
keywords = request.GET.get("keywords","")
# v1.9 获取前端页码,默认页码1
page_num = request.GET.get("page_num",1)
if keywords:
# v1.8 对关键字进行模糊查询
goods_list = Goods.objects.filter(goods_name__contains=keywords)
else:
# v1.7 查询所有商品信息(提前添加了商品数据)
goods_list = Goods.objects.all()
# v1.9 新增列表分页功能,创建分页器,针对good_list中的数据,每页3条数据
paginator = Paginator(goods_list,3)
# v1.9 获取具体页的数据
page = paginator.page(int(page_num))
# v1.9 返回页码列表
page_range = paginator.page_range
# 返回分页数据
return render(request,"store/goods_list.html",{"page":page,"page_range":page_range,"keywords":keywords})
```
前端:
```html
{% extends "store/base.html" %}
{% block title %}
商品列表页面
{% endblock %}
{% block label %}
添加商品
{% endblock %}
{% block content %}
{% endblock %}
```
效果:

## 九、商品详情页添加
**1、新建一个商品详情页面goods.html**
```html
{% extends "store/base.html" %}
{% block title %}
{{ goods_data.goods_name }} 详情
{% endblock %}
{% block content %}
|
商品名称
|
{{ goods_data.goods_name }}
|
商品价格
|
{{ goods_data.goods_price }}
|
商品库存
|
{{ goods_data.goods_number }}
|
生产日期
|
{{ goods_data.goods_date }}
|
保质期
|
{{ goods_data.goods_safeDate }}
|
商品描述 |
{{ goods_data.goods_description }} |
修改商品信息
{% endblock %}
```
**2、视图,路由**

**3、在列表页绑定路由**

**4、通过点击商品列表中的商品跳转到商品详情页**
效果:

## 十、商品信息修改功能实现
**1、新建一个前端修改商品信息页面update_goods.html**
```html
{% extends "store/base.html" %}
{% block title %}
{{ goods_data.goods_name }} 详情
{% endblock %}
{% block content %}
{% endblock %}
```
**2、后端简单视图及路由添加和商品详细类似,展示效果**

**3、使用富文本编辑器修改商品描述**
修改setting配置


修改主url

**4、收集静态文件(注意注释配置,收集后,在回复配置)**

**5、前端导入富文本js文件,并应用到商品描述**
```html
{# v2.1 导入ckeditor js文件,并应用到商品描述#}
{% block script %}
{% endblock %}
```
效果:

**6、视图添加数据修改保存操作**

修改界面:

修改信息后跳转商品详情:

## 十一、校验用户是否有商铺功能
**1、在用户登录时下发校验店铺的cookie**

**2、在base页重新进行前端店铺校验**
```html
{% if request.COOKIES.is_store %}
{% else %}
{% endif %}
```
**3、后端更新装饰器店铺cookie下发并装饰到店铺注册函数视图上**

效果展示:



## 十二、业务逻辑实现:查看商品列表是针对当前用户的店铺
**1、更改添加商品页面的店铺id为自动获取cookie,每次添加商品的时候就会自动关联当前页面cookie中的店铺id**
```html
```
**2、修改后端商品列表视图,使用多对多关系反向查询**


## 十三、商品下架功能
**1、商品模型类添加商品状态字段,并同步数据库**
```python
# v2.4 新增商品状态字段;1 待售 0 下架
goods_under = models.IntegerField(verbose_name="商品状态",default=1)
```
**2、更改商品列表视图,是查询上架商品**

**3、新增商品下架视图函数**
```python
# v2.4 新增商品上架功能
def under_goods(request):
id = request.GET.get("id")
# v2.4 返回当前请求的来源地址
referer = request.META.get("HTTP_REFERER")
if id:
# v2.4 获取指定id的商品
goods = Goods.objects.filter(id=id).first()
# v2.4 修改商品状态
goods.goods_under = 0
goods.save()
return HttpResponseRedirect(referer)
```
**4、更新商品列表前端页面下架标签**
```html
下架
```
## 十三、商品上架及销毁功能
**1、base页新增下架商品的列表**
```html
```
**2、为了使上下架商品列表不出现代码冗余,对商品列表视图及前端进行状态判断**

```html
{% ifequal state 'up' %}
下架
{% else %}
上架
{% endifequal %}
销毁
```
**3、将前面的under_goods视图该为set_goods,对页面上下架按钮提交做判断进行相应的处理**
```
python
# v2.4 新增商品上架功能
def set_goods(request,state):
# v2.5 使该视图同时具备上下架及销毁的功能
if state == "up":
state_num = 1
else:
state_num = 0
id = request.GET.get("id")
# v2.4 返回当前请求的来源地址
referer = request.META.get("HTTP_REFERER")
if id:
# v2.4 获取指定id的商品
goods = Goods.objects.filter(id=id).first()
# v2.5 判断前端路由中的字段参数来进行相应的操作,这里如果为delete,删除该商品
if state == "delete":
goods.delete()
else:
# v2.4 修改商品状态
goods.goods_under = state_num
goods.save()
return HttpResponseRedirect(referer)
```

## 十三、前台模型创建
**1、创建买家App,并创建模型类**

**2、安装App,同步模型类**
**3、将前台模板静态文件拷贝到App的子url,然后配置url,收集静态文件**
**4、编写base页**

**5、编写注册登录功能**
注册视图:
```python
# v2.6 前台用户注册
def register(request):
if request.method == "POST":
# 获取前端post请求的数据
username = request.POST.get("user_name")
password = request.POST.get("pwd")
email = request.POST.get("email")
# 实例化一个前台用户
buyer = Buyer()
# 保存数据
buyer.username = username
buyer.password = setPassword(password)
buyer.email = email
buyer.save()
return HttpResponseRedirect('/Buyer/login/')
return render(request,"buyer/register.html")
```
效果:

登录视图:
```python
# v2.6 前台用户登录
def login(request):
if request.method == "POST":
# 获取前端登录页面文本框数据
username = request.POST.get("username")
password = request.POST.get("pwd")
# 判断用户名密码是否存在
if username and password:
# 判断用户是否存在
buyer = Buyer.objects.filter(username=username).first()
if buyer:
# 密码加密比对
web_password = setPassword(password)
if web_password == buyer.password:
response = HttpResponseRedirect('/Buyer/index/')
response.set_cookie("username",buyer.username)
request.session["username"] = buyer.username
# 在下发一个user_id的信息方便其他功能查询
response.set_cookie("user_id",buyer.id)
return response
return render(request,"buyer/login.html")
```
效果:

将后端的装饰器拿过来并应用首页视图
```python
# v2.6 登录校验装饰器
def loginValid(fun):
def inner(request,*args,**kwargs):
c_user = request.COOKIES.get("username")
s_user = request.session.get("username")
if c_user and s_user and c_user == s_user:
return fun(request, *args, **kwargs)
else:
return HttpResponseRedirect("/Buyer/login/")
return inner
# v2.6 前台用户首页
@loginValid
def index(request):
return render(request,"buyer/index.html",locals())
```
退出功能:
```python
# v2.6 前台用户注销
def logout(request):
response = HttpResponseRedirect('/Buyer/login/')
for key in request.COOKIES:
response.delete_cookie(key)
del request.session["username"]
return response
```
base页cookie功能校验顶部功能,如果用户登录,就显示退出,否则显示登录和注册
```html
{% if request.COOKIES.username %}
{% else %}
{% endif %}
```
## 十四、后台新增新增商品类型模型,并在商品表中添加关联外键商品类型字段**

为了避免后续大数据出现差错,我先手动去添加一个商品类型,网上选取4张商品图片作为一个类型的演示


前台index视图添加查询商品类型的语句,先导入后端模型类
```python
def index(request):
goods_type_list = GoodsType.objects.all()
return render(request,"buyer/index.html",locals())
```
然后在index前端页面进行类型及类型中商品的循环展示
```html
{% block content %}
{% for goods_type in goods_type_list %}
{% endfor %}
{% endblock %}
```
效果:

## 十五、后台新增添加商品类型的功能(使用模态框)
**1、在后台base模板页新增“商品类型管理”选项**
```html
商品类型管理
```
**2、新建一个商品类型管理html文件**
这里的使用bootstrap模态框进行实现,模板样式基本固定
```html
{% extends "store/base.html" %}
{% block title %}
商品类型管理
{% endblock %}
{% block label %}
{% endblock %}
{% block content %}
商品类型名称 |
商品类型描述 |
操作 |
{% for goods_type in goods_type_lst%}
{{ goods_type.name }} |
{{ goods_type.description }} |
删除
|
{% endfor %}
{# #}
{# #}
{#
#}
{% endblock %}
```


**3、商品类型管理视图、路由配置**
```python
# v2.8 后台新增商品类型管理,进行商品类型的添加
def goods_type_list(request):
# v2.8 查询现有的商品类型,渲染到前端
goods_type_lst = GoodsType.objects.all()
# 判断请求方式,并获取从模态框传过来的商品类型数据
if request.method == "POST":
name = request.POST.get("name")
description = request.POST.get("description")
picture = request.FILES.get("picture")
# 实例化商品类型,开始添加类型保存
goods_type = GoodsType()
goods_type.name = name
goods_type.description = description
goods_type.picture = picture
goods_type.save()
return render(request,"store/goods_type_list.html",locals())
```
删除商品类型视图及前端路径
```python
# v2.8 删除商品类型
def delete_goods_type(request):
# 前端通过参数id={{goods_type.id}来获取要删除类型对象
id = int(request.GET.get("id"))
goods_type = GoodsType.objects.get(id=id)
goods_type.delete()
return HttpResponseRedirect('/Store/list_goods_type/')
```
```html
删除
```
## 十六、后台添加商品功能的前后端添加商品类型字段
后端:


前端:

效果:

## 十七、前台商品首页列表展示优化
查询所有有数据的商品类型,并且只返回4种商品展示在首页
后台首页视图业务处理:

前台首页循环:
```html
{# {% for goods_type in goods_type_list %}#}
{% for goods_type in result_list %}
{% endfor %}
{% endblock %}
```
效果:

## 十八、前台商品列表展示
新建列表html文档,继承模板,并使用后端数据渲染
```html
{% for goods in goodsList%}
-
¥{{ goods.goods_price }}
{{ goods.goods_price }}/500g
{% endfor %}
```
编写前台商品列表视图,通过“首页查看更多链接”跳转列表页面,查询当前商品类型下的所有商品
```python
# v3.1 前台商品列表展示
def goods_list(request):
"""
前台列表页
:param request:
:return:
"""
goodsList = []
type_id = request.GET.get("type_id")
# v3.1 获取商品类型
goods_type = GoodsType.objects.filter(id = type_id).first()
if goods_type:
# v3.1 查询所有上架的产品
goodsList = goods_type.goods_set.filter(goods_under=1)
return render(request,"buyer/goods_list.html",locals())
```
首页查看更多链接添加商品列表路由
```html
查看更多 >
```
效果:


## 十九、支付宝沙箱环境部署并测试
**1、打开支付宝开发平台地址并扫码登录**
https://open.alipay.com/platform/home.htm
**2、确定入驻身份填写信息,完成入驻**

**3、进入开发中心,开发阶段先用沙箱环境进行测试**

**4、进入沙箱环境,查看沙箱应用**

**5、下载沙箱版支付宝(仅安卓版本)**


**6、打开支付宝开发文档然后查看开发手册,进行开发**
https://docs.open.alipay.com/

生成RSA公钥,下载生成工具

下载完成进行解压并打开RSA签名验签工具生成公私钥

同时在生成工具的目录下RSA秘钥目录下也有文件版本

然后将公钥设置到服务端


查看接入手册,接入当前开发业务类型接口

使用快速接入,下载服务端SDK

**7、下载支付宝SDK开发的python模块**
支付流程:
- 接收订单
- 跳转请求支付宝
- 接收,并将支付页面发给买家
- 买家完成付款
pip install pycryptodome 安装依赖包

pip install python-alipay-sdk --upgrade 安装支付宝SDK

**8、安装完成编写脚本测试支付功能**
```python
from alipay import AliPay
alipay_public_key_string = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv66JqpsopyoXYMiWiIgyV4O/nc4ptXjRgZO9dkKRzsh1LusdILASoXlZ65nx/4ONCDpFn5QEQQNerVtmCW+Y9N/GmNnQOsEeX5tCxfNlg7vHS5Hk8QDCgIbEzVC3K+9wwYiCc8aQRjSM+Czb/Tq3kI+XJpDIGE6lPtp2zkwZaPt3y8yt88MpYcqPllNn3acEW8U5LnQmMHosohqlXu5iPK57OC7a0oC5AwUPlZcMizO2EqmxonWpfqk+scOhVdyVUwuX6siye76OkUuhO8M1758hhhNOUhmzhurEEW20toqA2eoMP63GweyTt5kWmWcqc30YU0FAN8Aq3QG03wF3xQIDAQAB
-----END PUBLIC KEY-----"""
app_private_key_string = """-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAv66JqpsopyoXYMiWiIgyV4O/nc4ptXjRgZO9dkKRzsh1LusdILASoXlZ65nx/4ONCDpFn5QEQQNerVtmCW+Y9N/GmNnQOsEeX5tCxfNlg7vHS5Hk8QDCgIbEzVC3K+9wwYiCc8aQRjSM+Czb/Tq3kI+XJpDIGE6lPtp2zkwZaPt3y8yt88MpYcqPllNn3acEW8U5LnQmMHosohqlXu5iPK57OC7a0oC5AwUPlZcMizO2EqmxonWpfqk+scOhVdyVUwuX6siye76OkUuhO8M1758hhhNOUhmzhurEEW20toqA2eoMP63GweyTt5kWmWcqc30YU0FAN8Aq3QG03wF3xQIDAQABAoIBAHSkuL+qJcX79jf+OKSjBMd+s/dKwtTczdklV5EEl4gXMkA38QS4QM4kc5TMnJgZrJQKKd4fC6uoak/iI6iwUYsKNedD/NQUOvCBIdQl9muAtJmHEaObC8F8wXwTlzPURHBxKrlbZuZiCjrnyYNC3PvKdXeReUJZcXNbLBsD8h6QgVOPaKXnLCSTZf98gHXkDN0IWlNp08143b4Xp6DO+CiaUHUbUrAcHdB+gHQJnN3+YP3dnVGzvyRisjJ8B8mTS1zPk4fpciViA6EMCYLHFCrS+8LZ+WEpJN8o/0o9Mgpd8YPKg1q4WDlYp1ulnRUVqOkJaVZVOvecTtbPbyaJWUECgYEA9fj7peww3VmPNYS3WAAtm/M5YMgsuFNRWC61/o+FKDSVlH57dVRRTnnGF7NE5dUHldloAa+yBw5Zbi/s5LjYV36zvga4dYZpA8Lt4uglzg3QAXwQ+X5YRqkde1gGolOdU/MsnhvehQpdOoZ5XWzJHe/2kA8RNraZyYsER0PQ2hECgYEAx373L2VZZdhPT8EIrgrAU93izxZAS9Cp5A/whogxmVhaKfGHcJa4YygajzyKd3DRoSmYtbXqysGXZyY+RmolCyMeZEBZ1s/2DTj2vtdh3ngV6UQY4vbgpCBgDCOnoiAq/5whcFcTDjma79BmsdHng/ZXgo+5U6v4TrKdZ7ay7nUCgYEArEZnkk175/xLFjPO6d6uExTmMgfhcnRAe9+zbgh9PayeuzNfKs0UaT9W49CWR9bNikGL2+p/aPu+3TLJ22QvehBuuYAhf4bVVGIZlRv9JnV8Ix4PEX9ROqRF1tbPRrADeAHQVSi10D5zD4ORy0JfFg20hi9XYhfAXG12YKd5xtECgYB8t3A6ziZsWCWFG42cmJYSGDYx9pwtiX6cWCarRDuVvTlo3Vkp1t/hBXJNN7Ds6Lf1A/c3KkplhU9sqejmxnbwFn1qeRxxAcO2EnWXazkBBpvUH8FbKrHXiXHiROwInAmlkOsKuzTrgLHO2L9KzYnp4rhkpAtdNrZeJKXo77u+/QKBgF4DmigjHYR32dmKXZQYp2DkM7lFxnuL6McR0r/5b/wkMUPSInCK4n4e8vBH611dkaNNhNSTR5aVsrMZuBGzQrFOGnXawa+PxpGPaVwR/jf/tPCLmsq2KPenQPF15tc+4dosMp726+f+4Klg71qMK8yjGN+fkrHo3Er1y+35Letj
-----END RSA PRIVATE KEY-----"""
# v3.2 实例化支付应用
alipay = AliPay(
appid = "2016101000652510",
app_notify_url = None,
app_private_key_string = app_private_key_string,
alipay_public_key_string = alipay_public_key_string,
sign_type= "RSA2"
)
# v3.2 发起支付请求
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no="33456", #订单号
total_amount=str(1000.01),#支付金额
subject="生鲜交易", #交易主题
return_url=None,
notify_url=None
)
print("https://openapi.alipaydev.com/gateway.do?"+order_string)
```
运行该脚本,会生成一个链接点击它,效果如下:

使用沙箱版支付宝支付:

现在开放平台中的沙箱环境的商家账号已经有金额入账了,表示测试成功
## 二十、将支付宝接口应用到前台商品付款上
**1、基于上述支付宝接口测试代码将代码封装为付款视图函数,并且用get请求发送了订单id和订单金额**
```python
# v3.3 将支付宝接口应用到前台付款,并用get请求发送支付金额和订单id
def pay_order(request):
# v3.3 获取订单金额
money = request.GET.get("money")
# v3.3 获取订单id
order_id = request.GET.get("order_id")
# v3.2 定义变量存储支付宝应用公钥
alipay_public_key_string = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv66JqpsopyoXYMiWiIgyV4O/nc4ptXjRgZO9dkKRzsh1LusdILASoXlZ65nx/4ONCDpFn5QEQQNerVtmCW+Y9N/GmNnQOsEeX5tCxfNlg7vHS5Hk8QDCgIbEzVC3K+9wwYiCc8aQRjSM+Czb/Tq3kI+XJpDIGE6lPtp2zkwZaPt3y8yt88MpYcqPllNn3acEW8U5LnQmMHosohqlXu5iPK57OC7a0oC5AwUPlZcMizO2EqmxonWpfqk+scOhVdyVUwuX6siye76OkUuhO8M1758hhhNOUhmzhurEEW20toqA2eoMP63GweyTt5kWmWcqc30YU0FAN8Aq3QG03wF3xQIDAQAB
-----END PUBLIC KEY-----"""
# v3.2 定义变量存储支付宝应用私钥
app_private_key_string = """-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAv66JqpsopyoXYMiWiIgyV4O/nc4ptXjRgZO9dkKRzsh1LusdILASoXlZ65nx/4ONCDpFn5QEQQNerVtmCW+Y9N/GmNnQOsEeX5tCxfNlg7vHS5Hk8QDCgIbEzVC3K+9wwYiCc8aQRjSM+Czb/Tq3kI+XJpDIGE6lPtp2zkwZaPt3y8yt88MpYcqPllNn3acEW8U5LnQmMHosohqlXu5iPK57OC7a0oC5AwUPlZcMizO2EqmxonWpfqk+scOhVdyVUwuX6siye76OkUuhO8M1758hhhNOUhmzhurEEW20toqA2eoMP63GweyTt5kWmWcqc30YU0FAN8Aq3QG03wF3xQIDAQABAoIBAHSkuL+qJcX79jf+OKSjBMd+s/dKwtTczdklV5EEl4gXMkA38QS4QM4kc5TMnJgZrJQKKd4fC6uoak/iI6iwUYsKNedD/NQUOvCBIdQl9muAtJmHEaObC8F8wXwTlzPURHBxKrlbZuZiCjrnyYNC3PvKdXeReUJZcXNbLBsD8h6QgVOPaKXnLCSTZf98gHXkDN0IWlNp08143b4Xp6DO+CiaUHUbUrAcHdB+gHQJnN3+YP3dnVGzvyRisjJ8B8mTS1zPk4fpciViA6EMCYLHFCrS+8LZ+WEpJN8o/0o9Mgpd8YPKg1q4WDlYp1ulnRUVqOkJaVZVOvecTtbPbyaJWUECgYEA9fj7peww3VmPNYS3WAAtm/M5YMgsuFNRWC61/o+FKDSVlH57dVRRTnnGF7NE5dUHldloAa+yBw5Zbi/s5LjYV36zvga4dYZpA8Lt4uglzg3QAXwQ+X5YRqkde1gGolOdU/MsnhvehQpdOoZ5XWzJHe/2kA8RNraZyYsER0PQ2hECgYEAx373L2VZZdhPT8EIrgrAU93izxZAS9Cp5A/whogxmVhaKfGHcJa4YygajzyKd3DRoSmYtbXqysGXZyY+RmolCyMeZEBZ1s/2DTj2vtdh3ngV6UQY4vbgpCBgDCOnoiAq/5whcFcTDjma79BmsdHng/ZXgo+5U6v4TrKdZ7ay7nUCgYEArEZnkk175/xLFjPO6d6uExTmMgfhcnRAe9+zbgh9PayeuzNfKs0UaT9W49CWR9bNikGL2+p/aPu+3TLJ22QvehBuuYAhf4bVVGIZlRv9JnV8Ix4PEX9ROqRF1tbPRrADeAHQVSi10D5zD4ORy0JfFg20hi9XYhfAXG12YKd5xtECgYB8t3A6ziZsWCWFG42cmJYSGDYx9pwtiX6cWCarRDuVvTlo3Vkp1t/hBXJNN7Ds6Lf1A/c3KkplhU9sqejmxnbwFn1qeRxxAcO2EnWXazkBBpvUH8FbKrHXiXHiROwInAmlkOsKuzTrgLHO2L9KzYnp4rhkpAtdNrZeJKXo77u+/QKBgF4DmigjHYR32dmKXZQYp2DkM7lFxnuL6McR0r/5b/wkMUPSInCK4n4e8vBH611dkaNNhNSTR5aVsrMZuBGzQrFOGnXawa+PxpGPaVwR/jf/tPCLmsq2KPenQPF15tc+4dosMp726+f+4Klg71qMK8yjGN+fkrHo3Er1y+35Letj
-----END RSA PRIVATE KEY-----"""
# v3.2 实例化支付应用
alipay = AliPay(
appid="2016101000652510",
app_notify_url=None,
app_private_key_string=app_private_key_string,
alipay_public_key_string=alipay_public_key_string,
sign_type="RSA2"
)
# v3.2 发起支付请求
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=order_id, # v3.3 订单号
total_amount=str(money), # v3.3 支付金额
subject="生鲜交易", # 交易主题暂时固定成“"生鲜交易"
# v3.3 支付完成要跳转的本地路由
return_url="http://127.0.0.1:8000/Buyer/pay_result/",
# v3.3 支付完成要跳转的异步路由
notify_url="http://127.0.0.1:8000/Buyer/pay_result/"
)
# v3.3 发起购买商品跳转支付路由
return HttpResponseRedirect("https://openapi.alipaydev.com/gateway.do?" + order_string)
# v3.3 编写接收支付结果的视图,用于支付结束后前台网站给用户的响应页面
def pay_result(request):
return HttpResponse("支付成功")
```
**2、直接在浏览器输入栏上输入商品金额和订单号,测试能否成功,后续版本在将其添加到具体商品上**
http://127.0.0.1:8000/Buyer/pay_order/?money=1200&order_id=10001



**3、从地址栏中可以发现支付宝给我们返回的数据也是通过get方式发送的,来分析一下哪些参数我们可以利用**
```python
# 编码
charset=utf-8
# 订单号
out_trade_no=10001
# 订单类型
method=alipay.trade.page.pay.return
# 订单金额
total_amount=1200.00
# 校验值
sign=OqHkG0OypYgrTL%2Fu%2FJbZi1DaSHEhxOPEu1KRVWOF9IrDoUYpTw7P4mVZni2SOgsXWOxiET%2BaODHBOwk1qpu0T8P58OK41Ajv6Scy6KoEpi5USDL1Q0765i5EMTD4ei3x9tUzG07Wl9EWqKNk4Y3gFLfMdcsKJy%2BOF2aFdOyuO1UXJSRlSzg2HIz9ozR40BkHEHy7TH1Updnc7nOWDH4NCkowPQN0g8HSfX6shdfImvaSGHLC5rzMfmfFPlyEjy7HGuQ7u3APP2SZNCGTQKzE58NXD3w4tLGCJBl5nfmwgdWz3kUA0J9wWAzpZ7J0btdtBV7h1kOQAx9pWYJK%2BVvDAA%3D%3D
# 系统订单号
trade_no=2019072622001414251000021352
# 用户的应用id
auth_app_id=2016101000652510
# 版本
version=1.0
# 商家的应用id
app_id=2016101000652510
# 加密方式
sign_type=RSA2
# 商家id
seller_id=2088102178922754
# 时间
timestamp=2019-07-26+19%3A59%3A37
```
通过筛选其中一些参数,来优化一下我们支付结果响应页面,新建一个响应页面,用于响应支付成功后给用户的反馈
```html
{% extends "buyer/base.html" %}
{% block title %}
支付结果
{% endblock %}
{% block content %}
恭喜,支付成功
支付订单:{{ request.GET.out_trade_no }}
支付时间:{{ request.GET.timestamp }}
支付金额:{{ request.GET.total_amount }}
{% endblock %}
```
支付成功响应效果:

## 二十一、商品详情页功能完善
**1、新建商品详情页html文件,继承base,并用块标签填充中间内容**

**2、商品详情页视图函数**
```python
# v3.4 商品详情页功能视图,通过get参数获取
def goods_detail(request):
goods_id = request.GET.get("goods_id")
if goods_id:
goods = Goods.objects.filter(id=int(goods_id)).first()
if goods:
return render(request, "buyer/detail.html",locals())
return HttpResponse("没有您指定的商品")
```
**3、前端数据渲染,并使用js处理商品数量,总价的问题**
```html
{% extends "buyer/base.html" %}
{% block title %}
商品详情
{% endblock %}
{% block content %}
{{ goods.goods_name }}
{{ goods.goods_description }}
¥{{ goods.goods_price }}
单 位:500g
总价:{{ goods.goods_price }}元
新品推荐
-
¥3.90
-
¥16.80
- 商品详情:
- 草莓采摘园位于北京大兴区 庞各庄镇四各庄村 ,每年1月-6月面向北京以及周围城市提供新鲜草莓采摘和精品礼盒装草莓,草莓品种多样丰富,个大香甜。所有草莓均严格按照有机标准培育,不使用任何化肥和农药。草莓在采摘期间免洗可以直接食用。欢迎喜欢草莓的市民前来采摘,也欢迎各大单位选购精品有机草莓礼盒,有机草莓礼盒是亲朋馈赠、福利送礼的最佳选择。
{% endblock %}
{% block script %}
{% endblock %}
```
效果:

## 二十二、商品订单模型创建
订单和地址有多对一关系
订单单纯的用一个表定义不够
订单里面可以有多种商品
所以订单需要两个表
订单表
- order id订单编号
- goods_count商品数量
- order_user订单用户 (多对一)
- order_address订单地址(多对一)
- order_price订单总价
订单详情表 (详情和订单是多对一)
- order_id 订单编号(多对一)
- goods_id 商品id
- goods_name 商品名称
- goods_price 商品价格(单价)
- goods_number 商品的购买数量
- goods_total 商品总价(单个商品)
- goods_store 商品的店铺
**1、创建订单模型类并同步数据库**

**2、新建订单详情页,继承base页,并依据原生模板添加块标签内容**
```html
{% extends "buyer/base.html" %}
{% block title %}
订单详情
{% endblock %}
{% block header %}{% endblock %}
{% block order %}
| 提交订单
{% endblock %}
{% block car %}{% endblock %}
{% block search %}
{% endblock %}
{% block content %}
确认收货地址
支付方式
商品列表
- 1

- 奇异果
- 500g
- 25.80元
- 1
- 25.80元
- 2

- 大兴大棚草莓
- 500g
- 16.80元
- 1
- 16.80元
总金额结算
共2件商品,总金额42.60元
运费:10元
实付款:52.60元
{% endblock %}
```
**3、编写订单详情列表视图**
```python
# v3.5 订单详情
def place_order(request):
# 判断商品详情页加入购买后的提交方式
if request.method == "POST":
# 商品详情页添加了两个input
# post数据
count = int(request.POST.get("count"))
goods_id = request.POST.get("goods_id")
# cookie数据
user_id = request.COOKIES.get("user_id")
# 数据库数据
goods = Goods.objects.get(id=int(goods_id))
store_id = goods.store_id.get(id = 7).id
price = goods.goods_price
# 创建一个订单
order = Order()
order.order_id = setOrder(str(user_id),str(goods_id),str(store_id))
order.goods_count = count
order.order_user = Buyer.objects.get(id=user_id)
order.order_price = count * price
order.save()
# 创建订单详情
order_detail = OrderDetail()
order_detail.order_id = order
order_detail.goods_id = goods_id
order_detail.goods_name = goods.goods_name
order_detail.goods_price = goods.goods_price
order_detail.goods_number = count
order_detail.goods_total = count * goods.goods_price
order_detail.goods_store = store_id
order_detail.goods_image = goods.goods_image
order_detail.save()
detail = [order_detail]
return render(request,"buyer/place_order.html",locals())
else:
return HttpResponse("非法请求")
```
**4、前端详情页数据渲染**
```html
{% extends "buyer/base.html" %}
{% block title %}
订单详情
{% endblock %}
{% block header %}{% endblock %}
{% block order %}
| 提交订单
{% endblock %}
{% block car %}{% endblock %}
{% block search %}
{% endblock %}
{% block content %}
确认收货地址
支付方式
商品列表
{% for d in detail %}
- {{ forloop.counter }}

- {{ d.goods_name }}
- 500g
- {{ d.goods_price }}元
- {{ d.goods_number }}
- {{ d.goods_total }}元
{% endfor %}
总金额结算
共{{ order.goods_count }}件商品,总金额{{ order.order_price }}元
运费:0元
实付款:{{ order.order_price }}元
{% endblock %}
```
效果:
点击立即购买跳转到订单详情:

订单详情,提交订单,跳转支付页面:


## 二十三、优化商品与商铺的关系
由于之前商品与商铺是多对多关系,所以进行商品购买时,店铺可能为多个店铺,必须指定一个店铺才能实现商品的购买。
所以现在优化商品商铺关系为多对一
**1、修改商品表字段**

**2、同步数据库**

**3、修改之前商品业务逻辑**
首先是添加商品视图,将多对多关系数据保存删除掉,直接使用一对多关系添加商品的店铺id

修改商品数据正常,不需要修改
前台购买商品视图需要更新

然后从后台到前台重新测试一遍,无误
## 二十四、后台订单管理系统
**1、后台base页添加订单管理选项**


**2、参照后台商品列表页构建订单列表页**
```html
{% extends "store/base.html" %}
{% block title %}
订单列表
{% endblock %}
{% block content %}
订单编号 |
订单商品 |
订单金额 |
操作 |
{% for order in page %}
{{ order.order_id.order_id }} |
{{ order.goods_name }} |
{{ order.goods_total }} |
确认发货
拒绝发货
|
{% endfor %}
{% endblock %}
```
**3、后台订单列表视图**
```python
# v3.7 新增订单管理
def order_list(request):
# 获取前端页码,默认页码1
page_num = request.GET.get("page_num", 1)
# 获取当前商铺id
store_id = request.COOKIES.get("is_store")
order_list = OrderDetail.objects.filter(goods_store=store_id)
# 创建分页器
paginator = Paginator(order_list, 5)
# 获取具体页的数据
page = paginator.page(int(page_num))
# 返回页码列表
page_range = paginator.page_range
return render(request,"store/order_list.html",locals())
```

**4、前台订单模型添加“订单状态”字段并同步数据库**
```python
# v3.7 添加订单状态:未支付1;待发货2;已发货3;已收货4;已退货0
order_status = models.IntegerField(default=1,verbose_name="订单状态")
```
**5、后台订单管理视图修改,查询当前未处理(未发货)的订单列表**
这里的使用Django ORM 双下划线跨表查询
```python
# 查询当前店铺待发货(2)的订单
order_list = OrderDetail.objects.filter(goods_store=store_id,order_id__order_status=2)
```
**6、补充:前台生成订单的时候订单状态字段**
生成订单时未支付状态:
order.order_status = 1
**7、支付之后订单状态变为待发货(2)**
支付视图:
```python
# v3.7 添加支付成功后订单状态置为2待发货
order = Order.objects.get(order_id=order_id)
order.order_status = 2
order.save()
```
gif展示商品购买支付效果:


## 二十五、购物车建模及ajax实现数据添加
**1、购物车建模并同步数据库**

**2、根据原生模板新建购物车列表页面**
```html
{% extends "buyer/base.html" %}
{% block title %}
购物车
{% endblock %}
{% block header %}{% endblock %}
{% block order %}
| 购物车
{% endblock %}
{% block car %}{% endblock %}
{% block search %}
{% endblock %}
{% block content %}
全部商品2件
{% for goods in goods_list %}
{% endfor %}
{% endblock %}
```
**3、购物车展示视图**
```python
# v3.8 购物车列表页展示
def cart(request):
# 根据cookie获取user_id
user_id = request.COOKIES.get("user_id")
# 查询购物车中的商品
goods_list = Cart.objects.filter(user_id = user_id)
return render(request,"buyer/cart.html",locals())
```
**4、在商品详情页对添加购物车做ajax_post方式添加**
```html
$("#add_cart").click(
function () {
var count = $("#count").val();
var goods_id = $("#goods_id").val();
var send_data = {
"count" : count,
"goods_id" : goods_id,
"csrfmiddlewaretoken" : '{{ csrf_token }}'
};
var url = "/Buyer/add_cart/";
$.ajax(
{
url : url,
type : "post",
data : send_data,
success : function (data) {
alert(data.data)
},
error : function (error) {
console.log(error)
}
}
)
}
)
```
**4、前台新增添加购物车视图函数**
```python
# v3.8 添加购物车
def add_cart(request):
# 定义json数据状态
result = {"state":"error","data":""}
if request.method == "POST":
# 获取ajax_post请求数据
count = int(request.POST.get("count"))
goods_id = request.POST.get("goods_id")
# 数据库查询商品
goods = Goods.objects.get(id=int(goods_id))
# 根据cookie查询当前用户
user_id = request.COOKIES.get("user_id")
# 创建一个购物车,用于添加数据
cart = Cart()
cart.goods_name = goods.goods_name
cart.goods_price = goods.goods_price
cart.goods_total = goods.goods_price * count
cart.goods_number = count
cart.goods_picture = goods.goods_image
cart.goods_id = goods.id
cart.goods_store = goods.store_id.id
cart.user_id = user_id
cart.save()
result["state"] = "success"
result["data"] = "商品添加成功"
else:
result["data"] = "请求错误"
return JsonResponse(result)
```
## 二十六、购物车页面全选,全不选前端功能实现
```html
{% for goods in goods_list %}
{% endfor %}
{% endblock %}
{% block script %}
{% endblock %}
```
效果:

## 二十七、购物车至订单、支付功能完善
**1、购物车结算应该是跳转到订单页面,是属于向后台提交数据的过程,应该使用post请求,所以将购物车页面添加form表单及submit**
**2、购物车商品提交,保存订单**
```python
# v3.8 购物车列表页展示
def cart(request):
# 根据cookie获取user_id
user_id = request.COOKIES.get("user_id")
# 查询购物车中的商品
goods_list = Cart.objects.filter(user_id = user_id)
# v4.0 购物车商品添加至订单列表
if request.method == "POST":
# 获取购物车页面请求的post数据
post_data = request.POST
# 此列表用于收集前端传过来的商品
cart_data = []
# 遍历post数据,将购物车列表商品信息取出来
for k,v in post_data.items():
# 前端input选择框定义了name和value为购物车对应id
if k.startswith("goods_"):
cart_data.append(Cart.objects.get(id=int(v)))
# 提交过来的购物车数据总数(不是商品数量)
goods_count = len(cart_data)
# 订单总价
goods_total = sum([int(i.goods_total) for i in cart_data])
# 保存订单
order = Order()
# 购物车中生成订单号时,订单中可能有多个商品或多个商铺;使用goods_count代替商品id,使用一个数字代替商铺id
order.order_id = setOrder(user_id,goods_count,"2")
order.goods_count = goods_count
order.order_user = Buyer.objects.get(id=user_id)
order.order_price = goods_total
order.order_status = 1
# 保存订单详情,这里的cart是购物车里的数据实例,不是商品的实例
for cart in cart_data:
orderdetail = OrderDetail()
orderdetail.order_id = order
orderdetail.goods_id = cart.goods_id
orderdetail.goods_name = cart.goods_name
orderdetail.goods_price = cart.goods_price
orderdetail.goods_number = cart.goods_price
orderdetail.goods_total = cart.goods_total
orderdetail.goods_store = cart.goods_store
orderdetail.goods_image = cart.goods_picture
orderdetail.save()
# 当在购物车中点击“"去结算"时跳转到订单列表进行支付
url = "/Buyer/place_order/?order_id=%s"%order.id
return HttpResponseRedirect(url)
return render(request,"buyer/cart.html",locals())
```
**3、修改订单提交的视图,允许通过订单id进行获取**
```python
else:
order_id = request.GET.get("order_id")
if order_id:
order = Order.objects.get(id=order_id)
detail = order.orderdetail_set.all()
return render(request, "buyer/place_order.html", locals())
return HttpResponse("非法请求")
```

## 二十八、搭建django rest接口
**1、安装rest接口框架**
```python
pip install djangorestframework
pip install django-filter
pip install Markdown
```
**2、配置setting**
安装app
```python
'rest_framework'
```
接口配置
```python
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
```
**3、创建serializers文件,用来存放接口过滤器**

**4、在视图当中查询接口要返回的数据,并指定过滤器**
```python
from rest_framework import viewsets
from Store.serializers import *
# v4.2 查询指定接口返回数据
class GoodsViewSet(viewsets.ModelViewSet):
# 具体返回的数据
queryset = Goods.objects.all()
# 指定过滤的类
serializer_class = GoodsSerializer
class GoodsTypeViewSet(viewsets.ModelViewSet):
# 具体返回的数据
queryset = GoodsType.objects.all()
# 指定过滤的类
serializer_class = GoodsTypeSerializer
```
**5、在路由当中注册接口**

效果:


## 二十九、使用搭建的django rest接口配合前端vue-resource渲染数据
就拿后台的商品列表为例,复制一份good_list.html验证,使用vue-resource绑定接口中的数据
```html
{% extends "store/base.html" %}
{% block title %}
{{ store_name }}-商品列表页面
{% endblock %}
{% block label %}
添加商品
{% endblock %}
{% block content %}
{% endblock %}
{% block script %}
{% endblock %}
```
效果:

## 三十、使用搭建的django rest接口分页功能对前端进行数据分页
在django restframework框架及数据接口搭建配置好的基础上,在setting中配置分页参数:
可以通过设置DEFAULT_PAGINATION_CLASS和PAGE_SIZE,设置全局变量
```python
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
```

使用vue进行前端分页数据绑定及分页列表点击事件
```html
```

## 三十一、重写restframework框架renderer方法,自定义接口返回数据
**1、在项目的根目录创建utils包用来存放要编写的renderer文件**

**2、在这个包下面创建一个py文件**

**3、编写renderer类**
```python
from rest_framework.renderers import JSONRenderer
class Customrenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
:param data: 返回的数据
:param accepted_media_type: 接收的类型
:param renderer_context: 渲染呈现的内容
"""
# 如果有请求数据过来:类似之前的if request.method == "POST"
if renderer_context:
# 判断返回的数据是否为字典
if isinstance(data,dict):
msg = data.pop("msg","请求成功") # 如果是字典,获取字典当中的msg键的值;若没有这个键,则给出一个回应
code = data.pop("code",0) # 如果是字典,获取字典当中的code键的值;若没有这个键,则给出一个回应
else: # 非字典类型
msg = "请求成功"
code = 0
# 重新构建返回数据的格式
ret = {
"msg":msg,
"code":code,
"author":"zhang",
"data":data
}
# 根据父类方式返回数据格式
return super().render(ret,accepted_media_type,renderer_context)
else: # 如果没有发生修改则返回原格式数据
return super().render(data,accepted_media_type,renderer_context)
```
**4、setting中安装renderer**
```python
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE':5,
'DEFAULT_RENDERER_CLASSES':(
'utils.rendererresponse.Customrenderer',
)
}
```
效果:
