系列文章目录
第一章——AI人机对战版五子棋游戏
第二章——在线商城系统
第三章——在线生鲜商城系统
文章目录
前言
Django+Vue+新浪微博账号登录+支付宝支付
1.系统背景介绍
近几年生鲜市场一直是创投者们格外关注的焦点,而应用市场上也涌现了许多生鲜配送APP和在线生鲜商城系统。本章节通过一个综合实例的实现过程,详细讲解使用Django开发一个在线生鲜商城系统的方法。
2. 功能需求分析
作为一个在线生鲜商城系统,必须具备如下所示的功能模块。
- 会员系统
包括会员注册、登录验证、个人信息管理子模块,并且可以使用新浪微博账号登录和手机登录系统。 - 热门生鲜商品
- 生鲜商品分类
- 生鲜商品介绍
- 购物车
- 在线支付功能
- 订单管理
- 后台管理
以上模块的具体说明如图
3.准备工作
3.1 用到的库
- Django:著名的企业级Web开发库,本项目的后端主要是基于Django实现的
- Vue:这是一套构建用户界面的渐进式库,本项目的前端主要是基于Vue实现的
- Django Rest Framework:这是基于Django实现的一个RESFUL风格的API库,能够帮助我们快速开发RESTFUL风格的API。本项目使用Django Rest Framework实现前后端分离功能。
- drf-extensions:用于处理Django Rest Framework的缓存
- social-auth-app-django:可以实现基于QQ、微信和微博的第三方账号登录
- django-redis:使用redis在Django Web项目中实现缓存处理。
- django-ckeditor:在Django Web项目中实现富文本编辑器功能
- django-cors-headers:在Django Web项目中解决跨域问题。
- django-crispy-forms:对Django的form表单在HTML页面中的呈现方式进行管理。
上面介绍了本项目需要的库文件,在requirements.txt中保存了本项目所用到的所有库的名称和版本信息。
3.2 准备Vue环境
下载安装Webstorm、nodejs 和cnpm
安装命令如下:
npm install -g cnpm --registry=https://registry.npm.taobao.org
安装vue
cnpm install vue
3.3 创建应用
本在线生鲜商城系统功能比较强大,规模也比较庞大。为了便于系统的设计、实现和后期维护,将整个功能通过几个模块应用来实现。在Django Web项目中,不同的模块被称作App。分别用如下命令创建users、goods、trade和user_operation共4个app。
python manage.py startapp users
python manage.py startapp goods
python manage.py startapp trade
python manage.py startapp user_operation
在settings.py中添加4个app
INSTALLED_APPS = [
......
"users.apps.UsersConfig",
"goods.apps.GoodsConfig",
"trade.apps.TradeConfig",
"user_operation.apps.UserOperationConfig"
]
3.4 系统配置
在文件setting.py中配置Django项目,具体实现流程如下:
- 设置后端认证方式,本项目不但支持使用自己的注册验证系统,还可以使用微博、QQ认证、微信认证等方式。代码如下:
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend', # 自定义认证后端
'social_core.backends.weibo.WeiboOAuth2', # 微博认证后端
'social_core.backends.qq.QQOAuth2', # qq认证后端
'social_core.backends.weixin.WeixinOAuth2', # 微信认证后端
'django.contrib.auth.backends.ModelBackend', # 支持账号密码登录
)
- 修改INSTALLED_APPS代码
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
# 添加drf应用
"rest_framework",
"rest_framework.authentication",
"django_filters",
# 添加django联合登录
"social_django",
# Django跨域解决
'corsheaders',
# 注册富文本编辑器
'ckeditor',
# 注册富文本上传图片
'ckeditor_uploader',
"users.apps.UsersConfig",
"goods.apps.GoodsConfig",
"trade.apps.TradeConfig",
"user_operation.apps.UserOperationConfig"
]
- 本项目使用默认的SQLite3数据库
- 设置保存媒体文件和上传文件的路径,代码如下:
# 配置媒体文件
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 配置富文本上传路径
CKEDITOR_UPLOAD_PATH = 'upload/'
- 设置跨域请求,在Django设置中配置中间件。必须将允许执行跨站点请求的主机添加到CORS_ORIGIN_WHITELIST。
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
'corsheaders.middleware.CorsMiddleware', # 必须放在这个位置
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
CORS_ORIGIN_ALLOW_ALL = True
#允许所有的请求头
CORS_ALLOW_HEADERS = ('*')
- 本项目中使用JWT在用户和服务器之间传递安全可靠的信息,下面是自定义配置JWT的代码
- 下面是支付宝的相关配置信息,app_id、app_private_key、alipay_public_key等信息需要去支付宝开发者中心申请。
- 使用socal_django配置认证密钥,将本项目上传到网络服务器后,将涉密信息保存在配置文件中,分别设置自己的微博账号登录信息,包括weibo_key和weibo_secret。代码如下:
4.设计数据库
4.1 为users应用创建model模型
在user应用的数据库模型中,主要保存系统用户信息,包括会员和管理员。
- 首先在文件 setting.py 中设置认证模型,添加如下代码:
AUTH_USER_MODEL = 'users.UserProfile' # 使用自定义的model做认证
- 在users目录下编写文件 models.py, 分别创建模型类UserProfile 和 VerifyCode,对于代码如下:
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class UserProfile(AbstractUser):
"""
扩展用户,需要在settings设置认证model
"""
name = models.CharField(max_length=30, blank=True, null=True, verbose_name="姓名", help_text="姓名")
birthday = models.DateField(null=True, blank=True, verbose_name="出生年月", help_text="出生年月")
mobile = models.CharField(max_length=11, blank=True, null=True, verbose_name="电话", help_text="电话")
gender = models.CharField(max_length=6, choices=(('male', "男"), ('female', '女')), default="male", verbose_name="性别",
help_text="性别")
class Meta:
verbose_name_plural = verbose_name = '用户'
def __str__(self):
# 要判断name是否有值,如果没有,返回username,
# 否则使用createsuperuser创建用户访问与用户关联会报错
if self.name:
return self.name
else:
return self.username
class VerifyCode(models.Model):
"""
短信验证码,跨域保存在redis中
"""
code = models.CharField(max_length=20,verbose_name="验证码",help_text="验证码")
mobile = models.CharField(max_length=11, verbose_name="电话", help_text="电话")
add_time = models.DateTimeField(auto_now_add=True,verbose_name="添加时间")
class Meta:
verbose_name_plural = verbose_name = "短信验证码"
def __str__(self):
return self.code
- 在user目录下编写apps.py 设置在后台将应用名显示为中文,代码如下:
class UsersConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "users"
verbose_name = "用户"
- 在users目录下编写admin.py,功能是采用批量注册方式将应用users关联到admin后台。对应代码如下:
from django.contrib import admin
from .models import UserProfile, VerifyCode
from django.apps import apps
all_models = apps.get_app_config('users').get_models()
for model in all_models:
try:
admin.site.register(model)
except:
pass
4.2 为goods应用创建model模型
在goods应用的数据库模型中,主要是用于保存和商品有关的信息,包括类别、品牌、商品详情、图片、首页轮播图、首页广告等信息。
- 在向数据库中添加商品信息时用到了富文本编辑器,所以首先在文件setting.py中添加富文本编辑器应用对应INSTALLED_APPS,如下:
# 注册富文本编辑器
'ckeditor',
# 注册富文本上传图片
'ckeditor_uploader',
并在setting.py中设置文件上传路径:
# 配置媒体文件
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 配置富文本上传路径
CKEDITOR_UPLOAD_PATH = 'upload/'
- 在路径导航文件url.py中添加富文本编辑器的路由,代码如下:
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
# 上传的文件可以直接通过url打开,以及setting中设置
from django.conf import settings
urlpatterns = [
path("admin/", admin.site.urls),
path("api-auth/", include('rest_framework.urls')),
path("ckeditor/", include('ckeditor_uploader.urls')), # 配置富文本编辑器url
]
# 上传的文件可以直接通过url打开
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
- 在goods目录下编写文件models.py,分别创建模型类GoodsCategory、GoodsCategoryBrand、Goods、GoodsImage、Banner和IndexCategoryAd,对应代码如下:
from django.db import models
# Create your models here.
class GoodsCategory(models.Model):
"""
商品类别
"""
CATEGORY_TYPE = (
(1, "一级类目"),
(2, "二级类目"),
(3, "三级类目"),
)
name = models.CharField(max_length=30, default='', verbose_name='类别名称', help_text='商品类别名称')
code = models.CharField(max_length=30, default='', verbose_name='类别编码', help_text='商品类别编码')
desc = models.TextField(default='', verbose_name="类别描述", help_text="商品类别描述")
category_type = models.SmallIntegerField(choices=CATEGORY_TYPE, default=1, verbose_name="商品类目", help_text="商品类目级别")
is_tab = models.BooleanField(default=False, verbose_name="是否导航", help_text="类别是否导航")
add_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
parent_category = models.ForeignKey('self', null=True, blank=True, verbose_name="父级目录", help_text="父级目录",
on_delete=models.CASCADE, related_name="sub_category")
class Meta:
verbose_name_plural = verbose_name = "商品类别"
def __str__(self):
return self.name
class GoodsCategoryBrand(models.Model):
"""
品牌
"""
category = models.ForeignKey(GoodsCategory, null=True, blank=True, on_delete=models.CASCADE, verbose_name="商品类别",
help_text="商品类别", related_name='brands')
name = models.CharField(max_length=30, default='', verbose_name="品牌名称", help_text="品牌名称")
desc = models.TextField(default='', max_length=200, verbose_name="品牌描述", help_text="品牌描述")
image = models.ImageField(max_length=200, upload_to='brand/images/', verbose_name="品牌图片", help_text="品牌图片")
add_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
class Meta:
verbose_name_plural = verbose_name = "品牌"
def __str__(self):
return self.name
class Goods(models.Model):
"""
商品
"""
category = models.ForeignKey(GoodsCategory, null=True, blank=True, on_delete=models.CASCADE, verbose_name="商品类别",
help_text="商品类别", related_name='goods')
goods_sn = models.CharField(max_length=100, default='', verbose_name="商品编码", help_text="商品唯一货号")
name = models.CharField(max_length=300, verbose_name="商品名称", help_text="商品名称")
click_num = models.IntegerField(default=0, verbose_name="点击数", help_text='点击数')
sold_num = models.IntegerField(default=0, verbose_name="销售量", help_text="销售量")
fav_num = models.IntegerField(default=0, verbose_name="收藏量", help_text="收藏量")
goods_num = models.IntegerField(default=0, verbose_name="库存量", help_text="库存量")
market_price = models.FloatField(default=0, verbose_name="市场价格", help_text="市场价格")
shop_price = models.FloatField(default=0, verbose_name="本店价格", help_text="本店价格")
goods_brief = models.TextField(max_length=500, verbose_name="简短描述", help_text="商品简短描述")
ship_free = models.BooleanField(default=True, verbose_name="是否免运费", help_text="是否免运费")
goods_front_image = models.ImageField(upload_to='goods/front/', null=True, blank=True, verbose_name="封面图",
help_text="商品封面图")
is_new = models.BooleanField(default=False, verbose_name="是否新品", help_text="是否新品")
is_hot = models.BooleanField(default=False, verbose_name="是否热销", help_text="是否热销")
add_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
class Meta:
verbose_name_plural = verbose_name = "商品"
def __str__(self):
return self.name
class GoodsImage(models.Model):
"""
商品图片
"""
goods = models.ForeignKey(Goods, verbose_name="商品", help_text="商品", on_delete=models.CASCADE, related_name='images')
image = models.ImageField(upload_to='goods/images/', verbose_name="图片", help_text="图片")
add_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
class Meta:
verbose_name_plural = verbose_name = "商品图片"
def __str__(self):
return self.goods.name
class Banner(models.Model):
"""
首页轮播图
"""
goods = models.ForeignKey(Goods, verbose_name='商品', help_text='商品', on_delete=models.CASCADE,
related_name='banners')
image = models.ImageField(upload_to='goods/banners/', verbose_name='图片', help_text='图片')
index = models.IntegerField(default=0, verbose_name='轮播顺序', help_text='轮播顺序')
add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
class Meta:
verbose_name_plural = verbose_name = '首页轮播图'
def __str__(self):
return self.goods.name
class IndexCategoryAd(models.Model):
"""
首页广告
"""
category = models.ForeignKey(GoodsCategory, null=True, blank=True, on_delete=models.CASCADE, verbose_name='商品类别',
help_text='商品类别', related_name='ads')
goods = models.ForeignKey(Goods, verbose_name='商品', help_text='商品', on_delete=models.CASCADE, related_name='ads')
add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
class Meta:
verbose_name_plural = verbose_name = '首页类别广告'
def __str__(self):
return '{}:{}'.format(self.category.name, self.goods.name)
- 在goods目录下编写apps.py文件,设置后台应用名为中文:
from django.apps import AppConfig
class GoodsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.goods"
verbose_name = "商品"
- 在goods目录下编写admin.py,功能是采用批量注册方式将应用goods关联到admin后台。
from django.contrib import admin
from .models import GoodsCategory, Goods, GoodsImage, IndexCategoryAd
from django.apps import apps
@admin.register(GoodsCategory)
class GoodsCategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'category_type', 'is_tab', 'parent_category'] # 列表页显示
list_display_links = ('name', 'parent_category',) # 列表页外键链接,字段需在list_display中
list_editable = ('is_tab',) # 列表页可编辑
list_filter = ('category_type',) # 列表页可筛选
search_fields = ('name', 'desc') # 列表页可搜索
class GoodsImageInline(admin.TabularInline):
model = GoodsImage
@admin.register(Goods)
class GoodsAdmin(admin.ModelAdmin):
list_display = ['name']
inlines = [
GoodsImageInline
]
@admin.register(IndexCategoryAd)
class IndexCategoryAdAdmin(admin.ModelAdmin):
list_display = ['category', 'goods']
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'category':
# 外键下拉框添加过滤
kwargs['queryset'] = GoodsCategory.objects.filter(category_type=1)
return super(IndexCategoryAdAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
all_models = apps.get_app_config('goods').get_models()
for model in all_models:
try:
admin.site.register(model)
except:
pass
4.3 为trade应用创建Model模型
在trade应用的数据库模型中,主要用于保存系统交易信息,包括购物车、订单和订单商品等信息。
- 在trade目录下编写models.py,分别创建模型类ShoppingCart、OrderInfo和OrderGoods,代码如下:
from django.db import models
from apps.goods.models import Goods
# from users.models import UserProfile # 但是某些情况下我们不知道用户的模型,可以直接使用下方的方法获取用户model
from django.contrib.auth import get_user_model
User = get_user_model()
class ShoppingCart(models.Model):
"""
购物车
"""
user = models.ForeignKey(User, verbose_name='用户', help_text='用户', on_delete=models.CASCADE, related_name='shopping_carts')
goods = models.ForeignKey(Goods, verbose_name='商品', help_text='商品', on_delete=models.CASCADE)
nums = models.IntegerField(default=0, verbose_name='购买数量', help_text='购买数量')
add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
class Meta:
verbose_name_plural = verbose_name = '购物车'
unique_together = ['user', 'goods'] # 用户和商品联合唯一
def __str__(self):
return "{}({})".format(self.goods.name, self.nums)
class OrderInfo(models.Model):
"""
订单
"""
ORDER_STATUS = (
('TRADE_FINISHED', '交易完成'),
('TRADE_SUCCESS', '支付成功'),
('WAIT_BUYER_PAY', '交易创建'),
('TRADE_CLOSE', '交易关闭')
)
user = models.ForeignKey(User, verbose_name='用户', help_text='用户', on_delete=models.CASCADE, related_name='order_infos')
order_sn = models.CharField(max_length=30, unique=True, blank=True, null=True, verbose_name='订单号', help_text='订单号')
trade_no = models.CharField(max_length=100, unique=True, null=True, blank=True, verbose_name='支付交易号', help_text='支付交易号')
pay_status = models.CharField(choices=ORDER_STATUS, default='WAIT_BUYER_PAY', max_length=20, verbose_name='订单状态', help_text='订单状态')
post_script = models.CharField(max_length=50, blank=True, null=True, verbose_name='订单留言', help_text='订单留言')
order_amount = models.FloatField(default=0.0, verbose_name='订单金额', help_text='订单金额')
pay_time = models.DateTimeField(null=True, blank=True, verbose_name='支付时间', help_text='支付时间')
# 用户信息
address = models.CharField(max_length=200, default='', verbose_name='收货地址', help_text='收货地址')
signer_name = models.CharField(max_length=20, default='', verbose_name='签收人', help_text='签收人')
signer_mobile = models.CharField(max_length=11, verbose_name='联系电话', help_text='联系电话')
add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
class Meta:
verbose_name_plural = verbose_name = '订单'
ordering = ['-add_time']
def __str__(self):
return "{}".format(self.order_sn)
class OrderGoods(models.Model):
"""
订单商品详情
"""
order = models.ForeignKey(OrderInfo, on_delete=models.CASCADE, verbose_name='订单信息', help_text='订单信息', related_name='order_goods')
goods = models.ForeignKey(Goods, verbose_name='商品', help_text='商品', blank=True, null=True, on_delete=models.SET_NULL)
goods_nums = models.IntegerField(default=0, verbose_name='购买数量', help_text='购买数量')
add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
class Meta:
verbose_name_plural = verbose_name = '订单商品'
def __str__(self):
return str(self.order.order_sn)
- 在trade目录下编写apps.py,设置后台将应用名显示为中文
from django.apps import AppConfig
class TradeConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.trade"
verbose_name = "交易"
- 在’'trade’目录下编写admin.py,功能是采用批量注册将trade应用关联到admin后台
from django.contrib import admin
from django.apps import apps
all_models = apps.get_app_config('trade').get_models()
for model in all_models:
try:
admin.site.register(model)
except:
pass
4.4 为user_operation 应用创建Model模型
在user_operation应用的数据库模型中,主要用于保存会员用户的资料信息,包括收藏、留言和收获地址等
- 在user_operation 目录编写文件model.py,分别创建模型类ShoppingCart、OrderInfo和OrderGoods,对应代码如下:
from django.db import models
from apps.goods.models import Goods
from django.contrib.auth import get_user_model
User = get_user_model()
class UserFav(models.Model):
"""
用户收藏
"""
user = models.ForeignKey(User, verbose_name='用户', help_text='用户', on_delete=models.CASCADE, related_name='favs')
goods = models.ForeignKey(Goods, on_delete=models.CASCADE, verbose_name='商品', help_text='商品', related_name='favs')
add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
class Meta:
verbose_name_plural = verbose_name = '用户收藏'
unique_together = ['user', 'goods']
def __str__(self):
return "{} 收藏 {}".format(self.user.name if self.user.name else self.user.username, self.goods.name)
class UserLeavingMessage(models.Model):
"""
用户留言
"""
MESSAGE_TYPE = (
(1, '留言'),
(2, '投诉'),
(3, '询问'),
(4, '售后'),
(5, '求购')
)
user = models.ForeignKey(User, verbose_name='用户', help_text='用户', on_delete=models.CASCADE, related_name='leaving_msgs')
message_type = models.IntegerField(default=1, choices=MESSAGE_TYPE, verbose_name='留言类型', help_text='留言类型:1-留言,2-投诉, 3-询问, 4-售后, 5-求购')
subject = models.CharField(max_length=100, default='', verbose_name='主题', help_text='主题')
message = models.TextField(default='', verbose_name='留言内容', help_text='留言内容')
file = models.FileField(upload_to='upload/leaving_msg/', blank=True, null=True, verbose_name='上传文件', help_text='上传文件')
add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
class Meta:
verbose_name_plural = verbose_name = '用户留言'
def __str__(self):
return '{} {}:{}'.format(self.user.name if self.user.name else self.user.username, self.get_message_type_display(), self.subject)
class UserAddress(models.Model):
"""
用户收货地址
"""
user = models.ForeignKey(User, verbose_name='用户', help_text='用户', on_delete=models.CASCADE, related_name='addresses')
province = models.CharField(max_length=100, default='', verbose_name='省份', help_text='省份')
city = models.CharField(max_length=100, default='', verbose_name='城市', help_text='城市')
district = models.CharField(max_length=100, default='', verbose_name='区域', help_text='区域')
address = models.CharField(max_length=200, default='', verbose_name='收货地址', help_text='收货地址')
signer_name = models.CharField(max_length=20, default='', verbose_name='签收人', help_text='签收人')
signer_mobile = models.CharField(max_length=11, verbose_name='联系电话', help_text='联系电话')
add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
class Meta:
verbose_name_plural = verbose_name = '收货地址'
def __str__(self):
return self.address
- 在user_operation目录下编写文件apps.py, 设置在后台将应用名显示为中文,对应代码如下:
from django.apps import AppConfig
class UserOperationConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.user_operation"
verbose_name = "操作"
- 在user_operation目录下编写admin.py,功能是采用批量注册方式将user_operation应用关联到admin后台,对应代码如下:
from django.contrib import admin
from django.apps import apps
all_models = apps.get_app_config('user_operation').get_models()
for model in all_models:
try:
admin.site.register(model)
except:
pass
4.5 生成数据库表
通过如下命令生成数据库表
python manage.py makemigrations
python manage.py migrate
使用 python manage.py createsuperuser 创建管理员账号登录后台系统。
5. 使用RestfulApi
为了便于系统开发和维护,实现前端资源和后端资源的分离,本项目使用RestfulApi实现后台view视图和前台Vue的关联。RestfulApi是当今被公认的实现Django前后端分离的最佳工具库,在Django Web中使用Restful API后,这个web可以直接通过http协议拥有post、get、put、delete等操作方法,而且不需要额外的协议。
5.1 商品列表序列化
商品列表页面是https://localhost/goods/,页面对应的视图文件是views_base.py和view.py
- 在文件views_base.py 中通过Django的view获取商品列表页
from django.views.generic.base import View
from django.views.generic import ListView
from goods.models import Goods
class GoodsListView(View):
def get(self, request):
"""
通过Django的View获取商品列表页
:param request:
:return:
"""
json_list = list()
all_goods = Goods.objects.all()[:5]
from django.core import serializers
json_data = serializers.serialize('json', all_goods) # 序列化
from django.http import HttpResponse, JsonResponse
import json
json_data = json.loads(json_data) # 转换为数组
return JsonResponse(json_data, safe=False)
- 在文件views.py中使用DRF实现商品视图功能,Django+DRF将后端变成一种声明式工作流,只要按照Models->serializer->views->urls的流程去实现一个个python文件,即可生成一个很全面的后端。文件views.py具体实现流程如下:
- 通过GoodsPagination实现自定义分页功能,代码如下:
class GoodsPagination(PageNumberPagination):
page_size = 12 # 每一页个数,由于前段
page_query_description = _('使用分页后的页码') # 分页文档中文描述
page_size_query_param = 'page_size'
page_size_query_description = _('每页返回的结果数')
page_query_param = 'page' # 参数?p=xx,将其修改为page,适应前端,也方便识别
max_page_size = 36 # 最大指定每页个数
Django的分页API支持以下两种方式
①作为相应内容的一部分提供的分页链接
②包含在响应头中的分页链接,如内容范围或链接
在上述代码的类GoodsPagination中,我们使用了REST_FRAMEWORK中的模块PageNumberPagination实现了分页功能。在使用上述分页功能后,相应取消文件setting.py中的默认分页,防止影响后续商品分类的结果,代码如下
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 'PAGE_SIZE': 5,
- 定义类GoodsListView,使用分页样式显示商品信息,代码如下;
class GoodsListView(generics.ListAPIView):
"""
显示所有的商品列表
"""
queryset = Goods.objects.all()
serializer_class = GoodsSerializer
pagination_class = GoodsPagination
- 通过多个ViewSet类显示商品列表信息。ViewSet类几乎与视图类相同,只是它提供了read和update之类的操作,而不是get或put之类的方法处理程序。ViewSet类只在最后时刻绑定到一组方法处理程序,当它被实例化为一组视图时,通常通过使用一个Router类来定义URL Conf的复杂性。代码如下:
class GoodsListViewSet(CacheResponseMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
"""
list:
显示商品列表,分页、过滤、搜索、排序
retrieve:
显示商品详情
"""
queryset = Goods.objects.all() # 使用get_queryset函数,依赖queryset的值
serializer_class = GoodsSerializer
pagination_class = GoodsPagination
filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,) # 将过滤器后端添加到单个视图或视图集
filterset_class = GoodsFilter
# authentication_classes = (TokenAuthentication, ) # 只在本视图中验证Token
search_fields = ('name', 'goods_desc', 'category__name') # 搜索字段
ordering_fields = ('click_num', 'sold_num', 'shop_price') # 排序
# throttle_classes = [UserRateThrottle, AnonRateThrottle] # DRF默认限速类,可以仿照写自己的限速类
throttle_scope = 'goods_list'
def retrieve(self, request, *args, **kwargs):
# 增加点击数
instance = self.get_object()
instance.click_num += 1
instance.save()
serializer = self.get_serializer(instance)
return Response(serializer.data)
def get_queryset(self):
keyword = self.request.query_params.get('search')
if keyword:
from utils.hotsearch import HotSearch
hot_search = HotSearch()
hot_search.save_keyword(keyword)
return self.queryset
class CategoryViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
# 注释很有用,在drf文档中
"""
list:
商品分类列表
retrieve:
商品分类详情
"""
# queryset = GoodsCategory.objects.all() # 取出所有分类,没必要分页,因为分类数据量不大
queryset = GoodsCategory.objects.filter(category_type=1) # 只获取一级分类数据
serializer_class = CategorySerializer # 使用商品类别序列化类,写商品的分类外键已有,直接调用
class ParentCategoryViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
"""
list:
根据子类别查询父类别
retrieve:
根据子类别查询父类别详情
"""
queryset = GoodsCategory.objects.all()
serializer_class = ParentCategorySerializer
class BannerViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
"""
list:
获取轮播图列表
"""
queryset = Banner.objects.all()
serializer_class = BannerSerializer
class IndexCategoryGoodsViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
"""
list:
首页分类、商品数据
"""
queryset = GoodsCategory.objects.filter(category_type=1)
serializer_class = IndexCategoryGoodsSerializer
def get_queryset(self):
# 随机取出几个分类
import random
category_id_list = self.queryset.values_list('id', flat=True)
selected_ids = random.sample(list(category_id_list), 3)
qs = self.queryset.filter(id__in=selected_ids)
return qs
在默认情况下,GenericViewSet类不需要提供任何操作,但是它包含了基本的通用视图行为集,例如get_object和get_queryset方法,也就是之前继承的viewsets.GenericViewSet没有定义get、post方法,处理程序方法只在定义URL Conf时绑定到操作。因为使用的时ViewSet类而不是view类,所以实际上不需要自己设计URL。可以使用Rounter类自动处理将资源链接到视图和URL的约定,需要做的是用路由器注册适当的视图集,然后让它完成剩下的工作,所以在urls.py文件中,APIurl现在由路由器自动确定:
# 创建一个路由器并注册我们的视图集
router = DefaultRouter()
router.register(r'goods', GoodsListViewSet, basename='goods') # 配置goods的url
urlpatterns = [
path("admin/", admin.site.urls),
path("api-auth/", include('rest_framework.urls')),
path("ckeditor/", include('ckeditor_uploader.urls')), # 配置富文本编辑器url
path("",include(router.urls)),
]
- 在上述文件views.py中基于Serializer实现了DRF,通过代码GoodsSerializer实现了Serializer功能。编写文件Serializer.py实现GoodsSerializer,通过DRF的Serializer可以将数据保存到数据库中。Serializer.py代码如下:
from rest_framework import serializers
from .models import Goods, GoodsCategory, GoodsImage, Banner, GoodsCategoryBrand
from django.db.models import Q
class CategorySerializer3(serializers.ModelSerializer):
class Meta:
model = GoodsCategory
fields = '__all__'
class CategorySerializer2(serializers.ModelSerializer):
sub_category = CategorySerializer3(many=True) # 通过二级分类获取三级分类
class Meta:
model = GoodsCategory
fields = '__all__'
class CategorySerializer(serializers.ModelSerializer):
sub_category = CategorySerializer2(many=True) # 通过一级分类获取到二级分类,由于一级分类下有多个二级分类,需要设置many=True
class Meta:
model = GoodsCategory
fields = '__all__'
# 商品图片序列化
class GoodsImageSerializer(serializers.ModelSerializer):
class Meta:
model = GoodsImage
fields = ['image'] # 需要的字段只需要image
class GoodsSerializer(serializers.ModelSerializer):
category = CategorySerializer() # 自定义字段覆盖原有的字段,实例化
images = GoodsImageSerializer(many=True) # 字段名和外键名称一样,商品轮播图,需要加many=True,因为一个商品有多个图片
class Meta:
model = Goods
fields = '__all__'
# 获取父级分类
class ParentCategorySerializer3(serializers.ModelSerializer):
class Meta:
model = GoodsCategory
fields = '__all__'
class ParentCategorySerializer2(serializers.ModelSerializer):
parent_category = ParentCategorySerializer3()
class Meta:
model = GoodsCategory
fields = '__all__'
class ParentCategorySerializer(serializers.ModelSerializer):
parent_category = ParentCategorySerializer2()
class Meta:
model = GoodsCategory
fields = '__all__'
class BannerSerializer(serializers.ModelSerializer):
class Meta:
model = Banner
fields = "__all__"
# 品牌图片
class BrandsSerializer(serializers.ModelSerializer):
class Meta:
model = GoodsCategoryBrand
fields = "__all__"
# 首页分类商品序列化
class IndexCategoryGoodsSerializer(serializers.ModelSerializer):
brands = BrandsSerializer(many=True) # 分类下的品牌图片
# goods = GoodsSerializer(many=True) # 不能这样用,因为现在需要的是一级分类,而大多数商品是放在三级分类中的,所以很多商品是取不到的,所以到自己查询一级分类子类别下的所有商品
goods = serializers.SerializerMethodField()
sub_category = CategorySerializer2(many=True) # 序列化二级分类
ad_goods = serializers.SerializerMethodField() # 广告商品可能加了很多,取每个分类第一个
def get_ad_goods(self, obj):
all_ads = obj.ads.all()
if all_ads:
ad = all_ads.first().goods # 获取到商品分类对应的商品
ad_serializer = GoodsSerializer(ad, context={'request': self.context['request']}) # 序列化该广告商品,嵌套的序列化类中添加context参数,可在序列化时添加域名
return ad_serializer.data
else:
# 在该分类没有广告商品时,必须要返回空字典,否则Vue中取obj.id会报错
return {}
def get_goods(self, obj):
# 查询每级分类下的所有商品
all_goods = Goods.objects.filter(Q(category_id=obj.id) | Q(category__parent_category_id=obj.id) | Q(category__parent_category__parent_category_id=obj.id))
# 将查询的商品集进行序列化
goods_serializer = GoodsSerializer(all_goods, many=True, context={'request': self.context['request']})
# 返回json对象
return goods_serializer.data
class Meta:
model = GoodsCategory
fields = '__all__'
访问http://127.0.0.1:8000/goods/,就会显示DRF格式的商品列表
- 编写一个filter.py功能是使用库django-filter实现商品过滤功能。django-filter包含一个DjangoFilterBackend类,支持REST框架的高度可定制字段过滤。使用django-filter首先安装django-filter,然后将django-filter添加到Django的INSTALLED_APPS
,再在views.py的GoodsListViewSet中通过如下代码增加商品列表过滤器:
filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,) # 将过滤器后端添加到单个视图或视图集
filter.py具体代码如下:
from django_filters import rest_framework as filters
from django.db.models import Q
from .models import Goods
class GoodsFilter(filters.FilterSet):
"""
商品的过滤类
"""
name = filters.CharFilter(field_name='name', lookup_expr='contains', help_text='分类名模糊匹配') # 包含关系,模糊匹配
goods_desc = filters.CharFilter(field_name='name', lookup_expr='contains', help_text='商品描述模糊匹配')
min_price = filters.NumberFilter(field_name="shop_price", lookup_expr='gte', help_text='最低价格') # 自定义字段
max_price = filters.NumberFilter(field_name="shop_price", lookup_expr='lte', help_text='最高价格')
top_category = filters.NumberFilter(method='top_category_filter', field_name='category_id', lookup_expr='=',
help_text='自定义过滤某个一级分类') # 自定义过滤,过滤某个一级分类
def top_category_filter(self, queryset, field_name, value):
"""
自定义过滤内容
这儿是传递一个分类的id,在已有商品查询集基础上获取分类id,一级一级往上找,直到将三级类别找完
:param queryset:
:param field_name:
:param value: 需要过滤的值
:return:
"""
queryset = queryset.filter(Q(category_id=value) | Q(category__parent_category_id=value) | Q(
category__parent_category__parent_category_id=value))
return queryset
class Meta:
model = Goods
fields = ['name', 'goods_desc', 'min_price', 'max_price', 'is_hot', 'is_new']
5.2 在前端展示左侧分类、排序、商品列表和分页
略,等学完vue再来补充这块