当前位置: 首页 > news >正文

【项目实战开发】第三章——在线生鲜商城系统

系列文章目录

第一章——AI人机对战版五子棋游戏
第二章——在线商城系统
第三章——在线生鲜商城系统


文章目录

  • 系列文章目录
  • 前言
  • 1.系统背景介绍
  • 2. 功能需求分析
  • 3.准备工作
    • 3.1 用到的库
    • 3.2 准备Vue环境
    • 3.3 创建应用
    • 3.4 系统配置
  • 4.设计数据库
    • 4.1 为users应用创建model模型
    • 4.2 为goods应用创建model模型
    • 4.3 为trade应用创建Model模型
    • 4.4 为user_operation 应用创建Model模型
    • 4.5 生成数据库表
  • 5. 使用RestfulApi
    • 5.1 商品列表序列化
    • 5.2 在前端展示左侧分类、排序、商品列表和分页


前言

Django+Vue+新浪微博账号登录+支付宝支付


1.系统背景介绍

近几年生鲜市场一直是创投者们格外关注的焦点,而应用市场上也涌现了许多生鲜配送APP和在线生鲜商城系统。本章节通过一个综合实例的实现过程,详细讲解使用Django开发一个在线生鲜商城系统的方法。

2. 功能需求分析

作为一个在线生鲜商城系统,必须具备如下所示的功能模块。

  1. 会员系统
    包括会员注册、登录验证、个人信息管理子模块,并且可以使用新浪微博账号登录和手机登录系统。
  2. 热门生鲜商品
  3. 生鲜商品分类
  4. 生鲜商品介绍
  5. 购物车
  6. 在线支付功能
  7. 订单管理
  8. 后台管理

以上模块的具体说明如图
在这里插入图片描述

3.准备工作

3.1 用到的库

  1. Django:著名的企业级Web开发库,本项目的后端主要是基于Django实现的
  2. Vue:这是一套构建用户界面的渐进式库,本项目的前端主要是基于Vue实现的
  3. Django Rest Framework:这是基于Django实现的一个RESFUL风格的API库,能够帮助我们快速开发RESTFUL风格的API。本项目使用Django Rest Framework实现前后端分离功能。
  4. drf-extensions:用于处理Django Rest Framework的缓存
  5. social-auth-app-django:可以实现基于QQ、微信和微博的第三方账号登录
  6. django-redis:使用redis在Django Web项目中实现缓存处理。
  7. django-ckeditor:在Django Web项目中实现富文本编辑器功能
  8. django-cors-headers:在Django Web项目中解决跨域问题。
  9. 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项目,具体实现流程如下:

  1. 设置后端认证方式,本项目不但支持使用自己的注册验证系统,还可以使用微博、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',  # 支持账号密码登录
)
  1. 修改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"
]
  1. 本项目使用默认的SQLite3数据库
  2. 设置保存媒体文件和上传文件的路径,代码如下:
# 配置媒体文件
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# 配置富文本上传路径
CKEDITOR_UPLOAD_PATH = 'upload/'
  1. 设置跨域请求,在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 = ('*')
  1. 本项目中使用JWT在用户和服务器之间传递安全可靠的信息,下面是自定义配置JWT的代码
  2. 下面是支付宝的相关配置信息,app_id、app_private_key、alipay_public_key等信息需要去支付宝开发者中心申请。
  3. 使用socal_django配置认证密钥,将本项目上传到网络服务器后,将涉密信息保存在配置文件中,分别设置自己的微博账号登录信息,包括weibo_key和weibo_secret。代码如下:

4.设计数据库

4.1 为users应用创建model模型

在user应用的数据库模型中,主要保存系统用户信息,包括会员和管理员。

  1. 首先在文件 setting.py 中设置认证模型,添加如下代码:
AUTH_USER_MODEL = 'users.UserProfile' # 使用自定义的model做认证
  1. 在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

  1. 在user目录下编写apps.py 设置在后台将应用名显示为中文,代码如下:
class UsersConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "users"
    verbose_name = "用户"
  1. 在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应用的数据库模型中,主要是用于保存和商品有关的信息,包括类别、品牌、商品详情、图片、首页轮播图、首页广告等信息。

  1. 在向数据库中添加商品信息时用到了富文本编辑器,所以首先在文件setting.py中添加富文本编辑器应用对应INSTALLED_APPS,如下:
# 注册富文本编辑器
'ckeditor',
# 注册富文本上传图片
'ckeditor_uploader',

并在setting.py中设置文件上传路径:

# 配置媒体文件
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# 配置富文本上传路径
CKEDITOR_UPLOAD_PATH = 'upload/'
  1. 在路径导航文件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)
  1. 在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)

  1. 在goods目录下编写apps.py文件,设置后台应用名为中文:
from django.apps import AppConfig


class GoodsConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "apps.goods"
    verbose_name = "商品"

  1. 在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应用的数据库模型中,主要用于保存系统交易信息,包括购物车、订单和订单商品等信息。

  1. 在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)

  1. 在trade目录下编写apps.py,设置后台将应用名显示为中文
from django.apps import AppConfig


class TradeConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "apps.trade"
    verbose_name = "交易"
  1. 在’'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应用的数据库模型中,主要用于保存会员用户的资料信息,包括收藏、留言和收获地址等

  1. 在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

  1. 在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 = "操作"

  1. 在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

  1. 在文件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)

  1. 在文件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再来补充这块

相关文章:

  • 论如何参与一个开源项目(中)
  • java中对jvm参数的调整进行调优
  • MySQL并发事务访问相同记录
  • 利用UART串口实现数据的收发
  • 虚幻引擎5 C++游戏开发教程
  • 【GNN报告】GNN可解释性 基于几何与拓扑特性的图学习
  • Hadoop3 - HDFS 介绍及 Shell Cli 操作
  • Java~数据结构(三)~栈和队列(Stack\Queue\Deque的常用方法和模拟实现一个栈和队列等)
  • 股票API下单接口是怎样传入交易数据的?
  • 【C++初阶】C++入门篇(二)
  • 点云LAS格式分析
  • 关于我的家乡html网页设计完整版,10个以家乡为主题的网页设计与实现
  • 有营养的算法笔记(二)
  • 10.5 - 每日一题 - 408
  • 递归、分治算法刷题笔记
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • EventListener原理
  • iOS仿今日头条、壁纸应用、筛选分类、三方微博、颜色填充等源码
  • Java-详解HashMap
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • PermissionScope Swift4 兼容问题
  • PHP 使用 Swoole - TaskWorker 实现异步操作 Mysql
  • Promise面试题,控制异步流程
  • Sequelize 中文文档 v4 - Getting started - 入门
  • tweak 支持第三方库
  • v-if和v-for连用出现的问题
  • vue中实现单选
  • windows下mongoDB的环境配置
  • 安装python包到指定虚拟环境
  • 半理解系列--Promise的进化史
  • 读懂package.json -- 依赖管理
  • 你真的知道 == 和 equals 的区别吗?
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • Play Store发现SimBad恶意软件,1.5亿Android用户成受害者 ...
  • Salesforce和SAP Netweaver里数据库表的元数据设计
  • 阿里云移动端播放器高级功能介绍
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • 如何正确理解,内页权重高于首页?
  • ​520就是要宠粉,你的心头书我买单
  • #、%和$符号在OGNL表达式中经常出现
  • #define 用法
  • #预处理和函数的对比以及条件编译
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (二十四)Flask之flask-session组件
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (牛客腾讯思维编程题)编码编码分组打印下标(java 版本+ C版本)
  • (十八)SpringBoot之发送QQ邮件
  • (十六)Flask之蓝图
  • (未解决)macOS matplotlib 中文是方框
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • .\OBJ\test1.axf: Error: L6230W: Ignoring --entry command. Cannot find argumen 'Reset_Handler'
  • .halo勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复