商城后台项目

2021年11月20日 阅读数:6
这篇文章主要向大家介绍商城后台项目,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

商城项目DRF框架编写

一: 项目初始化

1.1: 建立新的子应用:

  • 执行命令:python3 …/manage.py startapp meiduo_adminhtml

  • 修改配置文件:前端

    INSTALL_APPS = [
      # ...
      'apps.meiduo_admin'
    ]
    
  • 子应用下建立urls.pypython

    urlpatterns = []
    
  • 总路由中注册子路由:mysql

    urlpatterns = [
      # ....
      re_path(r'^meiduo_admin/', include('apps.meiduo_admin.urls')),
    ]
    

1.2: 在跨域白名单中增长静态服务器域名:

修改dev.py:redis

# CORS跨域请求白名单设置
CORS_ORIGIN_WHITELIST = (
    'http://127.0.0.1:8080',
    'http://localhost:8080',
    'http://www.meiduo.site:8080',

    # 管理站点跨域请求的源
    'http://127.0.0.1:8081',
    'http://localhost:8081',
    'http://www.meiduo.site:8081'
)

1.3: 将meiduo_mall_admin移动到项目中:

  • 进入meiduo_admin/dist
  • 执行命令: python3 -m http.server 8081 启动项目。

二:JWT单点登陆:

2.1: 安装JWT单点登陆的拓展组件:

  • pip install djangorestframework-jwtsql

  • 配置文件中配置拓展组件:数据库

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
          	# 追加Token认证后端 —— 用于验证token有效期识别用户身份
            'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
            'rest_framework.authentication.SessionAuthentication',
            'rest_framework.authentication.BasicAuthentication',
        ),
    }
    
    JWT_AUTH = {
      	# 有效期设置为10天
        'JWT_EXPIRATION_DELTA': datetime.timedelta(days=10),
    }
    

2.2: 使用拓展组件完成JWT登陆:

  • 拓展组件已经帮咱们写好了obtain_jwt_token,做为JWT登陆的视图,因此须要直接路由映射:django

    from django.urls import re_path
    # obtain_jwt_token为拓展插件提供的用于验证用户名和密码并签发token的视图
    from rest_framework_jwt.views import obtain_jwt_token
    
    urlpatterns = [
        re_path(r'^authorizations/$', obtain_jwt_token),
    ]
    
  • 可是这个拓展组件返回的是他本身写的内容,咱们须要指定视图的返回内容怎么办?json

    • 咱们须要在工具类中定义一个函数,这个函数须要返回咱们拓展组件指定的返回格式,而后在配置文件中指定就能够了。后端

    • 新建:meiduo_mall/utils/jwt_response_handlers.py模块

    • def jwt_response_payload_handler(token, user=None, request=None):
          return {
            	# 补充返回username和user_id字段
              'username': user.username,
              'user_id': user.id,
              'token': token
          }
      
    • JWT_AUTH = {
          # 设置签发的token的有效期
          'JWT_EXPIRATION_DELTA': datetime.timedelta(days=100),
      
          # 来指定拓展插件默认视图返回的响应参数构造函数
          'JWT_RESPONSE_PAYLOAD_HANDLER': 'meiduo_mall.utils.jwt_response_handlers.jwt_response_payload_handler'
      }
      
  • 权限限定:要求,不是管理员的不能进入后台管理界面。

    """
    思路: 不管是前台页面登陆仍是后台页面登陆都须要通过Django后台,然后台确定是调用authenticate函数实现用户名和密码的验证, 对于前台咱们调用authenticate 传入了request对象,因此咱们自定义认证后端获得的request对象是一个对象,然后端登陆,咱们使用的是第三方拓展组件,这个拓展组件在调用authenticate函数的时候直接传入的是用户名和密码,没有传入request对象,因此在自定义认证后端,若是request对象是None,便是后端登陆,而且登陆的时候User对象is_staff是False,即不是管理员身份,则认证后端不进行认证了,直接返回None.
    """
    
    # 继承Django默认的传统认证后端
    class UsernameMobileAuthBackend(ModelBackend):
    
        # 重写authenticate方法
        # 缘由:默认的authenticate方法,只会根据username字段去过滤查找用户
        def authenticate(self, request, username=None, password=None, **kwargs):
            # 容许多帐号登录的状况下,前端传来的"username"有多是用户名也有多是手机号
    
            try:
                # 一、先按用户名查找
                user = User.objects.get(
                    # username=="18588269037" or mobile=="18588269037"
                    Q(username=username) |  Q(mobile=username) | Q(email=username)
                )
            except User.DoesNotExist as e:
                return None # 用户名找不到,返回None表示认证失败
    
    
            # TODO:判断是否为管理站点页面登录,若是是须要进一步校验is_staff=True
            # 若是是商城页面登录,request是一个请求对象
            # 若是是管理站点页面登录,request是一个None
            if request is None and not user.is_staff:
                return None
    
            # 三、某一个找到了,再校验密码
            if user.check_password(password):
                return user
    

商城后台项目_django

三:主页数据统计:

3.1:用户总数的统计:

  • 1: 配置文件修改时区:

    LANGUAGE_CODE = 'zh-hans'  #中文支持,django1.8之后支持;1.8之前是zh-cn
    TIME_ZONE = 'Asia/Shanghai
    
  • 2: 编辑apps/meiduo_admin/views/home_views.py

    """
    主页接口视图
    """
    from django.utils import timezone # Django提供用于处理时间的一个模块
    
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from apps.users.models import User
    
    # 一、用户总数
    class UserTotalCountView(APIView):
    
        def get(self, request):
            # 一、提取参数
            # 二、校验参数
            # 三、数据处理 —— 统计用户总数量
            count = User.objects.count()
            # 四、构建响应
            # cur_date = datetime.now() # 系统本地时间
            cur_date = timezone.localtime() # 获取配置参数TIME_ZONE指定时区的时间;返回datetime对象
            return Response({
                'count': count,
                'date': cur_date.date() # 年月日; datetime().date() --> 把datetime类型转化为date类型(只取年月日)
            })
    
  • 2: 路由:

    from django.urls import re_path
    # obtain_jwt_token为拓展插件提供的用于验证用户名和密码并签发token的视图
    from rest_framework_jwt.views import obtain_jwt_token
    
    from meiduo_admin.views.home_views import UserTotalCountView
    
    urlpatterns = [
        # 1: 身份认证
        re_path(r'^authorizations/$', obtain_jwt_token),
        # 2: 用户总数统计
        re_path(r'^statistical/total_count/$', UserTotalCountView.as_view()),
        
    ]
    
    

3.2: 新增用户统计:

  • 配置文件检查配置:

    USE_TZ = True
    
    
  • 视图:

    # 二、日增用户数量统计
    class UserDayCountView(APIView):
    
        def get(self, request):
            # 当日新增用户数量统计:过滤出"当日"新建的用户数量 date_joined>=当日的零时刻
            # 思考:如何获取"当日"的零时刻?!
            cur_time = timezone.localtime() # datetime(2020, 9, 26, 17, 8, 56, 612345, tz=<Asia/Shanghai>)
            # replace函数把原datetime对象的年月日是分秒微妙和时区属性替换,返回替换后的一个新的datetime对象
            cur_0_time = cur_time.replace(hour=0, minute=0, second=0, microsecond=0)
            # 这里过滤的时候,Django会所有将时间转换成UTC时间去比较,无需本身手动转换。
            count = User.objects.filter(
                date_joined__gte=cur_0_time
            ).count()
    
            return Response({
                'count': count,
                'date': cur_time.date()
            })
    
  • 路由:

    urlpatterns = [
        # 1: 身份认证
        re_path(r'^authorizations/$', obtain_jwt_token),
        # 2: 用户总数统计
        re_path(r'^statistical/total_count/$', UserTotalCountView.as_view()),
        # 3:统计当日新增用户
        re_path(r'^statistical/day_increment/$', UserDayCountView.as_view()),
    ]
    

3.3:日活跃用户统计:

  • 视图:

    # 3: 日活跃用户统计
    class UserActiveCount(APIView):
    
        def get(self, request):
            """
            思路: 获取当日零时区的时间,而后统计最后一次登录时间大于这个时间的人数的数量
            :param request:
            :return:
            """
            # 1:获取当日零时刻的时间
            cur_0_time = timezone.localtime().replace(hour=0, minute=0,second=0)
            # 2: 统计最后登录时间大于这个时间的人数
            count = User.objects.filter(last_login__gte=cur_0_time).count()
            # 3: 构建响应返回数据
            return Response({'count': count, 'date': cur_0_time.date()})
    
    
  • 路由:

    urlpatterns = [
        # 4:统计日活跃用户的数量
        re_path(r'^statistical/day_active/$', UserActiveCount.as_view()),
    ]
    
    
  • 修改登陆后端:

    class LoginBackend(ModelBackend):
        def authenticate(self, request, username=None, password=None, **kwargs):
            try:
                user = User.objects.get(Q(username=username)|Q(mobile=username))
    
            except Exception as e:
                return None
    
            if request is None and not user.is_staff:
                return None
    
            if user.check_password(password):
                # 登录的时候修改登录的时间
                user.last_login = timezone.localtime()
                user.save()
                return user
    
    

3.4:日下单用户统计:

  • 1: 视图:

    # 日下单用户统计
    class UserOrderCountView(APIView):
    
        def get(self, request):
            """
            统计当日下单的用户的数量:
            思路: 先查询出下的全部的订单, 而后再在订单中筛选出用户来放入一个集合中,最后统计集合的数量
            :param request:
            :return:
            """
            # 1: 查询出今天全部的订单
            cur_day = timezone.localtime().replace(hour=0,  minute=0, second=0)
            orders = OrderInfo.objects.filter(create_time__gte=cur_day)
    
            # 2: 根据用户进行去重
            my_set = set()
            for order in orders:
                my_set.add(order.user)
    
            # 3: 统计集合的数量
            count = len(my_set)
    
            # 4: 返回响应信息
            return Response({'count': count, 'data': cur_day.date()} )
    
    
  • 2: 路由:

     # 5: 日下单用户统计
        re_path(r'^statistical/day_orders/$', UserOrderCountView.as_view()),
    
    

3.5:月增用户统计:

  • 需求:统计最近30天的用户,每日的新增用户数量。

  • 路由:

    urlpatterns = [
        # 6: 月增用户统计
        re_path(r'^statistical/month_increment/$', UserMonthCountView.as_view()),
    
    ]
    
    
  • 视图:

    # 月新增用户统计
    class UserMonthCountView(APIView):
        def get(self, request):
            """
            思路: 
            1:先获取当日零时的时刻,而后再获取29天前的0时刻日期
            2:使用列表套字典的形式保存数据
            3:循环遍历30次,将天天的次数和日期加入到一个字典中,而后再追加到字典中。
            :param request:
            :return:
            """
    
            # 1: 获取当日的零时
            end_0_time = timezone.localtime().replace(hour=0, second=0, minute=0)
            # 2: 获取29日以前的零时
            start_0_time = end_0_time - timedelta(days=29)
    
            # 3: 准备列表构建返回值
            res_list = []
            for i in range(30):
                # 当日的零时
                cur_0_time = start_0_time + timedelta(days=i)
                next_0_time = start_0_time + timedelta(days=i+1)
                # 统计数量
                count = User.objects.filter(date_joined__gte=cur_0_time, date_joined__lt=next_0_time).count()
                # 构造字典追加进入列表
                res_list.append({'count': count, 'date': cur_0_time.date()})
            # 4:返回列表
            return Response(res_list)
    
    

3.6:日分类商品访问量统计:

  • 1: 更新商品的模型类:

    class GoodsVisitCount(BaseModel):
        """统计分类商品访问量模型类"""
        category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE, verbose_name='商品分类')
        count = models.IntegerField(verbose_name='访问量', default=0)
        date = models.DateField(auto_now_add=True, verbose_name='统计日期')
    
        class Meta:
            db_table = 'tb_goods_visit'
            verbose_name = '统计分类商品访问量'
            verbose_name_plural = verbose_name
    
    

    无需迁移建表

  • 2:在浏览历史记录接口中补充代码实现记录分类商品访问量

    # 用户浏览历史记录
    class UserBrowseHistory(LoginRequiredJSONMixin, View):
    
        # 添加历史
        def post(self, request):
    
            user = request.user
            # 一、提取参数
            # 二、校验参数
            data = json.loads(request.body.decode())
            sku_id = data.get('sku_id')
            if not sku_id:
                return JsonResponse({
                    'code': 400,
                    'errmsg': '缺乏参数'
                }, status=400)
    
            try:
                sku = SKU.objects.get(pk=sku_id, is_launched=True)
            except SKU.DoesNotExist as e:
                return JsonResponse({
                    'code': 404,
                    'errmsg': '商品已下架/不存在'
                }, status=404)
    
            # 三、数据/业务处理 —— 把访问的sku的id写入redis表示记录一条浏览历史
            # 3.一、获取"history"缓存配置的redis连接
            conn = get_redis_connection('history')
            p = conn.pipeline()
            # 3.二、历史记录写入缓存
            # 3.2.一、去重
            p.lrem(
                'history_%d' % user.id,
                0, # 删除全部指定成员
                sku_id
            )
            # 3.2.二、插入列表头
            p.lpush(
                'history_%d' % user.id,
                sku_id
            )
            # 3.2.三、截断保留5个记录
            p.ltrim(
                'history_%d' % user.id,
                0,
                4
            )
            p.execute() # 批量执行redis指令
    
    
            # TODO: 记录该sku商品的分类访问量
            # 分类id:sku.category_id
            # 当日零时刻:
            cur_0_time = timezone.localtime().replace(hour=0, minute=0, second=0)
            # (1)、判断当前sku商品的分类,和当日的数据存不存在;
            try:
                visit_obj = GoodsVisitCount.objects.get(
                    category_id=sku.category_id,
                    create_time__gte=cur_0_time
                )
            except GoodsVisitCount.DoesNotExist as e:
                # 记录不存在则新建
                GoodsVisitCount.objects.create(
                    category_id=sku.category_id,
                    count=1
                )
            else:
                # 记录存在则累加
                visit_obj.count += 1
                visit_obj.save()
    
            # 四、构建响应
            return JsonResponse({
                'code': 0,
                'errmsg': 'ok'
            })
    
  • 3: 序列化器:apps/meiduo_admin/serializers/home_serializers.py

    """
    主页接口序列化器
    """
    from rest_framework import serializers
    from apps.goods.models import GoodsVisitCount
    
    # 定义一个序列还器,用于序列化GoodsVisitCount模型类数据
    class GoodsVisitModelSerializer(serializers.ModelSerializer):
        # category = serializers.PrimaryKeyRelatedField() --> 序列化的结果是关联分类对象的主键值
        category = serializers.StringRelatedField()
        class Meta:
            model = GoodsVisitCount
            fields = ['category', 'count']
    
    
  • 4:视图:编辑apps/meiduo_admin/views/home_views.py

    from ..serializers.home_serializers import *
    # 六、获取分类访问量列表数据
    class GoodsDayView(ListAPIView):
        # 若是在类属性中获取0时刻,这个cur_0_time记录的永远都是服务器启动的那一天的0时刻了;
        # cur_0_time = timezone.localtime().replace(hour=0, minute=0, second=0)
        # queryset = GoodsVisitCount.objects.filter(
        #     create_time__gte=cur_0_time
        # )
    
        queryset = GoodsVisitCount.objects.all()
        serializer_class = GoodsVisitModelSerializer
    
        # 在每一次请求的时候,都是经过get_queryset方法来获取查询集
        def get_queryset(self):
            cur_0_time = timezone.localtime().replace(hour=0, minute=0, second=0)
            return self.queryset.filter(
                create_time__gte=cur_0_time
            )
    
    
  • 5:路由:

    urlpatterns = [
      	# ......
        # 六、日分类商品访问量统计
        re_path(r'^statistical/goods_day_views/$', GoodsDayView.as_view()),
    ]
    
    

四:用户管理:

4.1:获取用户列表(分页)

  • 1:定义一个用户的序列化器:

    from rest_framework import serializers
    from apps.users.models import User
    
    # 1:定义用户的序列化器:使用ModelSerializer模型类序列化器
    class UserModelSerializer(serializers.ModelSerializer):
        
        class Meta:
            # 1: 指定模型类
            model = User
            # 2: 制定哪些须要映射
            fields = [
                'id',
                'username',
                'mobile',
                'email'
            ]
    
    
  • 2:自定义分页器:

    from rest_framework.pagination import PageNumberPagination
    from rest_framework.response import Response
    
    class MyPage(PageNumberPagination):
        page_query_param = 'page' # ?page=1
        page_size_query_param = 'pagesize' # ?pagesize=5
        max_page_size = 10
        page_size = 5
    
        def get_paginated_response(self, data):
            return Response({
                'counts': self.page.paginator.count, # 总数量
                'lists': data, # 查询集分页的子集(当前页数据)
                'page': self.page.number, # 当前页
                'pages': self.page.paginator.num_pages, # 总页数
                'pagesize': self.page_size
            })
    
    
  • 3:定义视图:

    from rest_framework.generics import ListAPIView
    
    from apps.meiduo_admin.paginations import MyPage
    from apps.meiduo_admin.serializers.user_serializers import UserModelSerializer
    from apps.users.models import User
    
    class UserView(ListAPIView):
        queryset = User.objects.filter(is_staff=True).order_by('id')
        serializer_class = UserModelSerializer
        pagination_class = MyPage
    
        def get_queryset(self):
            # 根据查询字符串参数keyword过滤
            # 一、提取keyword
            # 问题:如何在非视图函数中获取请求对象?!
            # 答:在django/drf中,每次请求的时候,框架除了把请求对象request传入视图函数之外
            # 还会把请求对象封装到self.request中(self指的是视图对象)
            keyword = self.request.query_params.get('keyword')
            # 二、根据keyword过滤
            if keyword:
                return self.queryset.filter(username__contains=keyword)
            return self.queryset.all() # 获取最新数据
    
    
  • 4:路由:

     # 8: 用户的列表查询
        re_path(r"^users/$", UserView.as_view()),
    
    
  
  ### 4.2:新增用户(管理员):

- 问题: 咱们在新建用户对象的时候, `ModelSerializer`默认提供的`create`方法没法帮助咱们进行:
  - (1)密码加密;
  - (2)设置is_staff=True

- 解决方案?

  - 咱们重写序列化器的`create`方法,来实现密码加密和添加`is_staff=True`!
  - 咱们还能够在校验过程当中,对有效数据中的明文密码加密,以及在返回的有效数据中添加`is_staff=True` —— 后续`create`方法完成新建对象就是使用调整以后的有效数据!

  

- 方案一: 重写序列化器的create方法实现密码加密和添加属性。

  修改序列化器:

  ```python
  from rest_framework import serializers
  from apps.users.models import User
  
  # 1:定义用户的序列化器:使用ModelSerializer模型类序列化器
  class UserModelSerializer(serializers.ModelSerializer):
  
      class Meta:
          # 1: 指定模型类
          model = User
          # 2: 制定哪些须要映射
          fields = [
              'id',
              'username',
              'mobile',
              'email'
          ]
      # 重写create方法实现加密和设置属性:
      def create(self, validated_data):
          # 1: 设置is_staff 属性是True
          validated_data['is_staff'] = True
          # 2: 密码加密
          user = User.objects.create_user(**validated_data)
          # 3: 返回
          return user

视图增长继承: CreateAPIView,路由保持不变。

  • 方案二:校验的过程当中进行加密

    # 1:定义用户的序列化器:使用ModelSerializer模型类序列化器
    class UserModelSerializer(serializers.ModelSerializer):
    
        class Meta:
            # 1: 指定模型类
            model = User
            # 2: 制定哪些须要映射
            fields = [
                'id',
                'username',
                'mobile',
                'email'
            ]
    
        def validate(self, attrs):
            # 一、密码加密
            raw_password = attrs.pop('password')  # 明文
            secret_password = make_password(raw_password)  # make_password传入明文密码,加密返回密文码
            attrs['password'] = secret_password
            # 二、返回有效数据中添加is_staff=True
            attrs['is_staff'] = True
    
            return attrs
    

五:商品管理:

5.1:SKU管理:

1: 获取全部的商品:

  • 1: 序列化器如何定义?

    经过分析得知: 外键字段,外键的主键字段,主表的隐藏字段都不会自动映射,须要手动映射。

    商城后台项目_django_02

    2:所以须要定义两个序列化器:

    from rest_framework import serializers
    
    from apps.goods.models import SKUSpecification, SKU
    
    
    class SKUSpecOptModelSerializer(serializers.ModelSerializer):
        # 由于这2个字段必须参与反序列化,全部咱们只能手动映射覆盖原有的默认映射(默认只做用于序列化)
        spec_id = serializers.IntegerField()
        option_id = serializers.IntegerField()
    
        class Meta:
            model = SKUSpecification
            # 若是关联对象主键隐藏字段,咱们在fields中显示声明,则会自动映射类型为ReadOnlyField —— 只读(只参与序列化)
            fields = [
                'spec_id',
                'option_id'
            ]
    
    
    class SKUModelSerializer(serializers.ModelSerializer):
        # 三个隐藏字段:
        # 一、本表主键id,会自动映射
        # 二、外间关联对象的主键隐藏字段(如:spu_id), 不会自动映射
        spu = serializers.StringRelatedField()
        spu_id = serializers.IntegerField()
        category = serializers.StringRelatedField()
        category_id = serializers.IntegerField()
    
        # 三、从表related_name指定的主表隐藏字段,不会自动映射
        # 表示当前SKU对象(主表)关联的"多个"SKUSpecification对象(从表)
        specs = SKUSpecOptModelSerializer(many=True)
    
        class Meta:
            model = SKU
            fields = "__all__"
    
    
  • 3:定义路由:

      re_path(r'^skus/$', SKUGoodsView.as_view({'get': 'list'})),
    
    
  • 4:定义视图:

    from rest_framework.viewsets import ModelViewSet
    from apps.goods.models import SKU
    from apps.meiduo_admin.paginations import MyPage
    from apps.meiduo_admin.serializers.sku_serializers import SKUModelSerializer
    
    
    class SKUGoodsView(ModelViewSet):
        queryset = SKU.objects.all()
        serializer_class = SKUModelSerializer
        pagination_class = MyPage
    
        # 进行过滤
        def get_queryset(self):
            keyword = self.request.query_params.get('keyword')
            if keyword:
                return self.queryset.filter(name__contains=keyword)
            return self.queryset.all()
    
    

2:新增SKU的三级可选分类:

  • 1:序列化器怎么写?接口定义: 只须要返回SKU的名字和id

    # SKU三级分类的序列化器:
    class SKUCategorySimpleSerializer(serializers.ModelSerializer):
        class Meta:
            model = GoodsCategory # 商品分类表
            fields = [
                'id',
                'name'
            ]
    
    
  • 2:路由:

    # 10: SKU商品的三级分类
        re_path(r'^skus/categories/$', SKUCategoryView.as_view()),
    
    
  • 3:视图: 选择哪个?ListAPIView

    # 返回SKU商品分类的三级分类
    class SKUCategoryView(ListAPIView):
        queryset = GoodsCategory.objects.all()
        serializer_class = SKUCategorySimpleSerializer
    
        def get_queryset(self):
            # 过滤三级分类
            return self.queryset.filter(parent_id__gte=37)
    
    
  • 3: 获取SPU商品的表名称数据:

    • 1: 建立SPU的序列化器:

      # SPU的序列化器:
      class SPUSimpleSerializer(serializers.ModelSerializer):
          class Meta:
              model = SPU
              fields = [
                  'id',
                  'name'
              ]
      
      
    • 2:路由:

       # 11: SPU的路由:
          re_path(r'^goods/simple/$', SPUSimpleView.as_view()),
      
      
    • 3:视图:

      # 获取SPU的信息
      class SPUSimpleView(ListAPIView):
          queryset = SPU.objects.all()
          serializer_class = SPUSimpleSerializer
      
      

      商城后台项目_django_03

  • SKU的可选规格和选项:

    • 1: 序列化器分析:

      分析能够得出,须要对SPUSpecification生成序列化器,可是要同时序列化SKUSpecification。

    • 2:编写序列化器:

      class SpecOptSimpleSerializer(serializers.ModelSerializer):
          class Meta:
              model = SpecificationOption
              fields = [
                  'id',
                  'value'
              ]
      
      class SPUSpecModelSerializer(serializers.ModelSerializer):
          spu = serializers.StringRelatedField()
          spu_id = serializers.IntegerField()
      
          # 关联从表SpecficationOption多条数据
          options = SpecOptSimpleSerializer(many=True)
      
          class Meta:
              model = SPUSpecification
              fields = [
                  'id',
                  'name',
                  'spu',
                  'spu_id',
                  'options'
              ]
      
    • 3: 路由:

      # 12: sku的可选规格和选项
          re_path(r'^goods/(?P<pk>\d+)/specs/$', SPUSpecView.as_view()),
      
    • 4:视图:

      分析: 首先是序列化返回多个,选择ListAPIView, 而须要根据路径中的spu_id过滤出关联的多个规格数据,所以须要重写get_queryset方法进行过滤。

      class SPUSpecView(ListAPIView):
          queryset = SPUSpecification.objects.all()
          serializer_class = SPUSpecModelSerializer
      
          def get_queryset(self):
              # 根据路径中的spu_id过滤出其关联的多个规格数据
              # 命名分组正则提取出来的参数,封装在self.kwargs中(是一个字典,分组名做为key,提取的传值做为value)
              # 非命名分组正则提取出来的参数,封装在self.args中(是一个列表,按照分组顺序提取参数)
              spu_id = self.kwargs.get('pk')
              return self.queryset.filter(spu_id=spu_id)
      

    3:新增SKU单一资源

    • 1:分析序列化器如何构建?

      分析表结构能够获得, spu和category是外键关联字段,须要手动映射,specs是主表隐藏字段,须要关联反序列化。
      商城后台项目_数据_04

    • 2:构建序列化器:因为咱们以前有了这两个序列化器,咱们看看原来的序列化器能不能完成任务。
      商城后台项目_django_05

      在上图咱们能够发如今进行反序列化的时候,个人SKU序列化器不能完成新建SKU规格和选项的操做。怎么办呢?须要重写SKU序列化器的create方法才能够。

      思路: 重写create方法而后,将校验的数据分红SKU的和SKU规格数据的两部分。使用SKU的 建立SKU对象,而后循环遍历SKU规格和选项,将SKU规格和选项中再增长上SKU的ID,而后建立规格和选项就能够了,注意最后必定要返回sku。

       # 重写模型类序列化器的create方法的缘由
          # 默认create方法,没法帮助咱们插入中间表数据来记录新增sku商品的规格和选项信息
          def create(self, validated_data):
              #  [{spec_id: "4", option_id: 8}, {spec_id: "5", option_id: 11}]
              specs = validated_data.pop('specs')
              # 一、新建sku模型类对象(主表)
              sku = SKU.objects.create(**validated_data)
              # 二、新建规格选项中间表数据,来记录新增sku的规格和选项信息
              for temp in specs:
                  # temp : {spec_id: "4", option_id: 8}
                  temp['sku_id'] = sku.id
                  SKUSpecification.objects.create(**temp)
             return sku
      
    • 3:新增SKU路由:

      re_path(r'^skus/$', SKUGoodsView.as_view({'get': 'list', 'post': 'create'})),
      
      
    • 4:视图:咱们发现咱们的视图不用改变。

4:获取SKU单一资源:

  • 1:路由:

     re_path(r'^skus/(?P<pk>\d+)/$', SKUGoodsView.as_view({
            'get': 'retrieve'
        })),
    
    
  • 2:视图,之前的还能用。

    • 3: 序列化器: 无需修改。

5:更新SKU单一资源:

  • 1: 路由:

    re_path(r'^skus/(?P<pk>\d+)/$', SKUGoodsView.as_view({
            'put': 'update'
        })),
    
    
  • 2:视图:无需修改

  • 3:序列化器:

    咱们的序列化器, 不能完成更新操做,所以须要重写update方法。

    # 默认update方法,没法完成中间表数据的更新
        def update(self, instance, validated_data):
            # [{spec_id: "1", option_id: 1}, {spec_id: "2", option_id: 4}, {spec_id: "3", option_id: 6}]
            specs = validated_data.pop('specs')
    
            # TODO: 两张表动做必须保证事务特性
    
            # 0、更新主表数据
            sku = super().update(instance, validated_data)
            # 一、删除原有中间表数据
            SKUSpecification.objects.filter(
                sku_id=sku.id
            ).delete()
            # 二、插入新的规格和选项中间表数据
            for temp in specs:
                # temp: {spec_id: "1", option_id: 1}
                temp['sku_id'] = sku.id
                SKUSpecification.objects.create(**temp)
    
            # TODO: 从新静态化生成新的详情页
            return sku
    
    

6:删除SKU单一资源:

  • 1:路由:

    re_path(r'^skus/(?P<pk>\d+)/$', SKUGoodsView.as_view({
            'delete': 'destroy'
        })),
    
    
  • 2:视图:无需修改

  • 3:序列化器:无需修改

7: 异步任务与事务的封装:

  • 需求:一旦咱们增长了某个SKU,则咱们的前台页面确定要增长一个SKU商品的页面。

    如何去作呢?思路: 采用异步的方式,静态化的生成当前SKU商品的详情页。

  • 增长异步方式生成页面,另外设置默认的图片。

class SKUModelSerializer(serializers.ModelSerializer):
    # 三个隐藏字段:
    # 一、本表主键id,会自动映射
    # 二、外间关联对象的主键隐藏字段(如:spu_id), 不会自动映射
    spu = serializers.StringRelatedField()
    spu_id = serializers.IntegerField()
    category = serializers.StringRelatedField()
    category_id = serializers.IntegerField()

    # 三、从表related_name指定的主表隐藏字段,不会自动映射
    # 表示当前SKU对象(主表)关联的"多个"SKUSpecification对象(从表)
    specs = SKUSpecOptModelSerializer(many=True)

    class Meta:
        model = SKU
        fields = "__all__"

		# 默认create方法,没法帮助咱们插入中间表数据来记录新增sku商品的规格和选项信息
    def create(self, validated_data):
        #  [{spec_id: "4", option_id: 8}, {spec_id: "5", option_id: 11}]
        specs = validated_data.pop('specs')
        # 设计默认图片
        validated_data['default_image'] = 'group1/M00/00/02/CtM3BVrPB4GAWkTlAAGuN6wB9fU4220429'
        
        # TODO: 下面两步操做,是数据库两张表的插入动做,必须保证"事务"特性
        with transaction.atomic():
            save_id = transaction.savepoint()
            try:
                # 一、新建sku模型类对象(主表)
                sku = SKU.objects.create(**validated_data)
                # 二、新建规格选项中间表数据,来记录新增sku的规格和选项信息
                for temp in specs:
                    # temp : {spec_id: "4", option_id: 8}
                    temp['sku_id'] = sku.id
                    SKUSpecification.objects.create(**temp)
            except Exception as e:
                transaction.savepoint_rollback(save_id)
                raise serializers.ValidationError('数据库新建失败!')

            transaction.savepoint_commit(save_id)

        # TODO: 使用异步任务方式,静态化生成当前新建sku商品的详情页
        generate_static_sku_detail_html.delay(sku.id)

        return sku

  • 新建文件celery_tasks/html/tasks.py,编辑以下:

    import os
    from django.template import loader
    from django.conf import settings
    from apps.goods.utils import get_categories,get_goods_and_spec
    from celery_tasks.main import celery_app
    
    @celery_app.task(name='generate_static_sku_detail_html')
    def generate_static_sku_detail_html(sku_id):
    
        """
        功能:生成指定sku商品的详情页面
        sku_id: SKU商品的id
        :return: 无
        """
    
        categories = get_categories()
        goods, sku, specs = get_goods_and_spec(sku_id)
    
        # =========模版渲染========
        template = loader.get_template('detail.html')
        context = {
            'categories': categories,
            'goods': goods,  # 当前sku从属的spu
            'specs': specs,  # 规格和选项信息
            'sku': sku  # 当前sku商品对象
        }
        page = template.render(context)
    
        # settings.STATIC_FILE_PATH --> 是front_end_pc文件夹路径
        file_path = os.path.join(
            settings.STATIC_FILE_PATH,
            'goods/%d.html' % sku_id, # 'goods/1.html'
        )
    
        with open(file_path, 'w') as f:
            f.write(page)
    
    

5.2:SPU管理:

1: 获取SPU列表数据:

  • 1: 路由:

     # 15: SPU获取列表数据:
        re_path(r'^goods/$', SPUView.as_view({'get': 'list'})),
    
    
  • 2:序列化器如何写?

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JUFdd3KO-1616158242025)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1616073711080.png)]

  • 3:序列化器的代码编写:

    from apps.goods.models import SPU
    
    class SPUModelSerializer(serializers.ModelSerializer):
    
        brand = serializers.StringRelatedField()
        brand_id = serializers.IntegerField()
        category1_id = serializers.IntegerField()
        category2_id = serializers.IntegerField()
        category3_id = serializers.IntegerField()
    
        class Meta:
            model = SPU
            fields = [
                'id',
                'name',
                'brand',
                'brand_id',
                'category1_id',
                'category2_id',
                'category3_id',
                'sales',
                'comments',
                'desc_detail',
                'desc_pack',
                'desc_service'
            ]
    
    
  • 4: 视图:

    from rest_framework.generics import ListAPIView
    from rest_framework.viewsets import ModelViewSet
    from ..serializers.spu_serializers import *
    from ..paginations import MyPage
    
    class SPUView(ModelViewSet):
        queryset = SPU.objects.all().order_by('id')
        serializer_class = SPUModelSerializer
        pagination_class = MyPage
    
        def get_queryset(self):
            keyword = self.request.query_params.get('keyword')
            if keyword:
                return self.queryset.filter(name__contains=keyword)
            return self.queryset.all()
    
    

2:新增中SPU的可选品牌:

  • 路由:

     re_path(r'^goods/brands/simple/$', BrandSimpleView.as_view()),
    
    
  • 序列化器:

    class BrandSimpleSerializer(serializers.ModelSerializer):
        class Meta:
            model = Brand
            fields = [
                'id',
                'name'
            ]
    
  • 3:视图:

    class BrandSimpleView(ListAPIView):
        queryset = Brand.objects.all()
        serializer_class = BrandSimpleSerializer
    

3:新增中SPU的可选分类:

  • 1:路由:

    # 17: 新增SPU的可选分类
        # 新增SPU可选一级分类
        re_path(r'^goods/channel/categories/$', SPUCateSimpleView.as_view()),
        # 新增SPU可选二级或三级份额里
        re_path(r'^goods/channel/categories/(?P<pk>\d+)/$', SPUCateSimpleView.as_view()),
    
  • 2: 序列化器:

    # SPU可选分类的序列化器
    class SPUCateSimpleSerializer(serializers.ModelSerializer):
        class Meta:
            model = GoodsCategory
            fields = ['id', 'name']
    
  • 3:视图:

    class SPUCateSimpleView(ListAPIView):
        queryset = GoodsCategory.objects.all()
      serializer_class = SPUCateSimpleSerializer
    
        def get_queryset(self):
            parent_id = self.kwargs.get('pk')
            # 若是是二级、三级分类接口调用,根据路径pk过滤出子集返回
            if parent_id:
                return self.queryset.filter(parent_id=parent_id)
    
            # 若是是一级分类接口调用
            return self.queryset.filter(parent=None)
    

4:新建SPU,获取单个SPU,更新SPU,删除SPU :

  • 1:路由:

    # 15: SPU获取列表数据:
        re_path(r'^goods/$', SPUView.as_view({'get': 'list', 'post': 'create'})),
        re_path(r'^goods/(?P<pk>\d+)/$', SPUView.as_view({
            'get': 'retrieve',
            'put': 'update',
            'delete': 'destroy'
        })),
    
    
  • 序列化器和视图发现都无需改变。

5.3:规格管理:

  • 1:序列化器:

  • 新建编辑apps/meiduo_admin/serializers/spec_serializers.py

    from rest_framework import serializers
    from apps.goods.models import SPUSpecification
    
    class SpecModelSerializer(serializers.ModelSerializer):
        spu = serializers.StringRelatedField()
        spu_id = serializers.IntegerField()
    
        class Meta:
            model = SPUSpecification
            fields = [
                'id',
                'name',
                'spu',
                'spu_id'
            ]
    
    
  • 2:视图:

    新建编辑apps/meiduo_admin/views/spec_views.py

    from rest_framework.viewsets import ModelViewSet
    from ..serializers.spec_serializers import *
    from ..paginations import MyPage
    
    class SpecView(ModelViewSet):
        queryset = SPUSpecification.objects.all()
        serializer_class = SpecModelSerializer
        pagination_class = MyPage
    
    
  • 3:路由:

     # 规格管理
        re_path(r'^goods/specs/$', SpecView.as_view({'get': 'list', 'post': 'create'})),
        re_path(r'^goods/specs/(?P<pk>\d+)/$', SpecView.as_view({
            'get': 'retrieve',
            'put': 'update',
            'delete': 'destroy'
        })),
    
    

5.4:规格选项管理:

  • 序列化器:
  • 新建编辑apps/meiduo_admin/serializers/spec_serializers.py
from rest_framework import serializers
from apps.goods.models import SpecificationOption,SPUSpecification

class OptSpecSimpleSerializer(serializers.ModelSerializer):
    class Meta:
        model = SPUSpecification
        fields = [
            'id',
            'name'
        ]


class OptionModelSerializer(serializers.ModelSerializer):
    spec = serializers.StringRelatedField()
    spec_id = serializers.IntegerField()

    class Meta:
        model = SpecificationOption
        fields = [
            'id',
            'value',
            'spec',
            'spec_id'
        ]

  • 视图:

  • 新建编辑apps/meiduo_admin/views/spec_views.py

from rest_framework.viewsets import ModelViewSet
from rest_framework.generics import ListAPIView
from ..serializers.option_serializers import *
from ..paginations import MyPage


class OptSpecSimpleView(ListAPIView):
    queryset = SPUSpecification.objects.all()
    serializer_class = OptSpecSimpleSerializer

class OptionView(ModelViewSet):
    queryset = SpecificationOption.objects.all()
    serializer_class = OptionModelSerializer
    pagination_class = MyPage

  • 路由:

  • 编辑apps/meiduo_admin/urls.py

urlpatterns = [
    # ......
    # 选项管理
    re_path(r'^specs/options/$', OptionView.as_view({'get': 'list', 'post': 'create'})),
    re_path(r'^specs/options/(?P<pk>\d+)/$', OptionView.as_view({
        'get': 'retrieve',
        'put': 'update',
        'delete': 'destroy'
    })),
    # 新增选项可选规格
    re_path(r'^goods/specs/simple/$', OptSpecSimpleView.as_view()),
]

5.5:品牌管理:

5.6:图片管理:

1:获取图片列表数据:

  • 1: 定义序列化器:

    from rest_framework import serializers
    from apps.goods.models import SKUImage, SKU
    from fdfs_client.client import Fdfs_client
    
    
    class SKUSimpleSerializer(serializers.ModelSerializer):
        class Meta:
            model = SKU
            fields = [
                'id',
                'name'
            ]
    
    
    class ImageModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = SKUImage
            fields = [
                'id',
                'sku',
                'image'
            ]
    
        def validate(self, attrs):
            # sku类型是PrimaryKeyRelatedField;反序列化前端传来主键值,通过类型校验以后成了对应的对象
            # image字段类型是ImageField;发序列化前端传来文件数据,通过类型校验以后成了文件对象
    
            # 规则1:SKUImage(sku=<SKU对象>, image=<文件对象>) ---> image赋值为一个文件对象的话,会触发文件存储后端来完成;
            # 规则2:SKUImage(sku=<SKU对象>, image="图片标示,图片id") ---> image赋值为一个字符串,那么就不会触发文件存储后端,直接将该字符串存入mysql;
    
            # 手动实现上传图片
            image_obj = attrs.get('image')  # 图片文件对象
            # (1)、在校验过程当中,手动从文件对象中读取图片数据,上传fdfs
            conn = Fdfs_client('./meiduo_mall/settings/client.conf')
            res = conn.upload_by_buffer(image_obj.read())
            if res is None:
                raise serializers.ValidationError("fdfs上传失败!")
            # (2)、再把fdfs返回的文件标示(文件id)做为有效数据中image字段的值
            attrs['image'] = res['Remote file_id']
            return attrs
    
  • 2: 定义图片的视图:

    from rest_framework.generics import ListAPIView
    from rest_framework.viewsets import ModelViewSet
    
    from goods.models import SKU, SKUImage
    from meiduo_admin.paginations import MyPage
    from meiduo_admin.serializers.image_serialisers import SKUSimpleSerializer, ImageModelSerializer
    
    
    class SKUSimpleView(ListAPIView):
        queryset = SKU.objects.all()
        serializer_class = SKUSimpleSerializer
    
    class ImageView(ModelViewSet):
        queryset = SKUImage.objects.all()
        serializer_class = ImageModelSerializer
        pagination_class = MyPage
    
    
  • 3:修改FSATDFS文件存储后端:

    client.conf配置文件放入settings目录中;

    # Storage是默认的Django的存储后端
    from django.core.files.storage import Storage
    from django.conf import settings
    from fdfs_client.client import Fdfs_client
    from rest_framework import serializers
    
    class FastDFSStorage(Storage):
    
        def _open(self, name, mode='rb'):
            # 打开django本地文件
            pass
    
        def _save(self, name, image_obj, max_length=None):
            # 若是SKUImage(image=<文件对象>) --> 当前存储后端的_save方法,完成图片的保存动做
            # 此处咱们须要把文件保存到fdfs(上传到fdfs)
            # name: 文件名称,同时也是保存到Django本地的文件名称 ---> 无需使用
            # image_obj: 传来的文件对象 --> 就是ImageField字段在新建或者更新的时候被赋值的文件对象
    
            conn = Fdfs_client('./meiduo_mall/settings/client.conf')
            res = conn.upload_by_buffer(image_obj.read())
            if res is None:
                raise serializers.ValidationError('上传fdfs失败!')
            file_id = res['Remote file_id']
    
            # 注意:_save方法返回值就是当前字段,存储在mysql中的文件id
            return file_id
    
        def exists(self, name):
            # 功能:判断上传的文件在Django本地是否重复
            # True:表示重复
            # Fales:表示不重复
            return False
    
        def url(self, name):
            """
            功能:返回值就是ImageField.url属性 ---> 构建完整的图片(文件)连接
            :param name: ImageField类型字段在mysql中存储的值 ---> 文件索引标识"group1/M00/00/02/CtM3BVrPB5CALKn6AADq-Afr0eE1672090"
            :return: 图片连接
            """
    
            # return "group1/M00/00/02/CtM3BVrPB5CALKn6AADq-Afr0eE1672090"
            # return name
            # return "http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrPB5CALKn6AADq-Afr0eE1672090"
            # return "http://image.meiduo.site:8888/" + name
            return settings.FDFS_BASE_URL + name
    
  • 4:路由:

        # 20:图片管理
        re_path(r"^skus/images/$", ImageView.as_view({'get': 'list', 'post': 'create'})),
        re_path(r'^specs/options/(?P<pk>\d+)/$', ImageView.as_view({
            'get': 'retrieve',
            'put': 'update',
            'delete': 'destroy'
        })),
    

六:订单管理:

1:获取订单的列表信息:

  • 1:路由:

    urlpatterns = [
        # ......
        # 订单列表
        re_path(r'^orders/$', OrderView.as_view({'get':'list'})),
    ]
    
    
  • 2:序列化器:

    from rest_framework import serializers
    from apps.orders.models import OrderInfo,OrderGoods
    from apps.goods.models import SKU
    
    class OrderSimpleSerializer(serializers.ModelSerializer):
        class Meta:
            model = OrderInfo
            fields = [
                'order_id',
                'create_time'
            ]
    
    
  • 3:视图:

    from meiduo_admin.paginations import MyPage
    from meiduo_admin.serializers.orders_serializers import OrderSimpleSerializer
    from apps.orders.models import OrderInfo
    class OrderView(UpdateModelMixin, ReadOnlyModelViewSet):
        queryset = OrderInfo.objects.all().order_by('create_time')
        serializer_class = OrderSimpleSerializer
        pagination_class = MyPage
    
        def get_queryset(self):
            keyword = self.request.query_params.get('keyword')
            if keyword:
                return self.queryset.filter(order_id__contains=keyword)
            return self.queryset.all()
    

2:获取订单的详细信息:

  • 1:路由:

     re_path(r'^orders/(?P<pk>\d+)/$', OrderView.as_view({'get':'retrieve'})),
    
  • 2:序列化器:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VwfHGUBM-1616158242026)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1616138743367.png)]

class SKUSimpleSerializer(serializers.ModelSerializer):
    class Meta:
        model = SKU
        fields = [
            'name',
            'default_image'
        ]

class OrderGoodsModelSerializer(serializers.ModelSerializer):
    # 当前订单商品对象关联的"单一的"SKU对象
    sku = SKUSimpleSerializer()

    class Meta:
        model = OrderGoods
        fields = [
            'count',
            'price',
            'sku'
        ]

class OrderDetailSerializer(serializers.ModelSerializer):
    user = serializers.StringRelatedField()

    # 当前订单对象关联的多个订单商品(OrderGoods)对象
    skus = OrderGoodsModelSerializer(many=True)

    class Meta:
        model = OrderInfo
        fields = "__all__"
  • 3:视图:对于同一个视图,咱们如何指定不一样的方式用不一样的序列化器呢?

    重写get_serializer_class方法。

    class OrderView(UpdateModelMixin, ReadOnlyModelViewSet):
        queryset = OrderInfo.objects.all().order_by('create_time')
        serializer_class = OrderSimpleSerializer
        pagination_class = MyPage
    
        def get_queryset(self):
            keyword = self.request.query_params.get('keyword')
            if keyword:
                return self.queryset.filter(order_id__contains=keyword)
            return self.queryset.all()
    
        def get_serializer_class(self):
            # 功能:获取操做数据使用序列化器类;默认返回类属性serializer_class
            # 区分2个逻辑
            # (1)、若是调用的是获取列表数据接口self.list, 使用OrderSimpleSerializer
            # (2)、若是调用的是获取单一详情数据接口self.retrieve, 使用OrderDetailSerializer
            # 知识点:self.action属性就是当前请求的视图函数的名称!
            if self.action == "list":
                return OrderSimpleSerializer
            elif self.action == "retrieve":
                return OrderDetailSerializer
    
            return self.get_serializer_class
    

3:修改订单的状态:

  • 路由:

    # 23: 修改订单的状态
        re_path(r'^orders/(?P<pk>\d+)/status/$', OrderView.as_view({'patch': 'partial_update'})),
    
  • 修改视图:

    def get_serializer_class(self):
            # 功能:获取操做数据使用序列化器类;默认返回类属性serializer_class
            # 区分2个逻辑
            # (1)、若是调用的是获取列表数据接口self.list, 使用OrderSimpleSerializer
            # (2)、若是调用的是获取单一详情数据接口self.retrieve, 使用OrderDetailSerializer
            # 知识点:self.action属性就是当前请求的视图函数的名称!
            if self.action == "list":
                return OrderSimpleSerializer
            elif self.action == "retrieve":
                return OrderDetailSerializer
            elif self.action == 'partial_update':
                return OrderDetailSerializer
    
            return self.get_serializer_class
    

七:系统管理:

7.1:权限管理:

一、新建编辑apps/meiduo_admin/serializers/perm_serialziers.py
from django.contrib.auth.models import Permission,ContentType
from rest_framework import serializers

class PermContentTypeSerializer(serializers.ModelSerializer):
    class Meta:
        model = ContentType
        fields = [
            'id',
            'name'
        ]


class PermModelSerializer(serializers.ModelSerializer):
    # content_type = serializers.StringRelatedField()

    class Meta:
        model = Permission
        fields = [
            'id',
            'name',
            'codename',
            'content_type'
        ]
二、新建编辑apps/meiduo_admin/views/perm_views.py
from rest_framework.generics import ListAPIView
from rest_framework.viewsets import ModelViewSet
from ..serializers.perm_serializers import *
from ..paginations import MyPage

class PermContentTypeView(ListAPIView):
    queryset = ContentType.objects.all()
    serializer_class = PermContentTypeSerializer

class PermView(ModelViewSet):
    queryset = Permission.objects.all()
    serializer_class = PermModelSerializer
    pagination_class  = MyPage

    def get_queryset(self):
        return self.queryset.order_by('pk')

三、新建编辑apps/meiduo_admin/urls.py
urlpatterns = [
    # ....
    # 权限管理
    re_path(r'^permission/perms/$', PermView.as_view({'get': 'list', 'post': 'create'})),
    re_path(r'^permission/perms/(?P<pk>\d+)/$', PermView.as_view({
        'get': 'retrieve',
        'put': 'update',
        'delete': 'destroy'
    })),
    # 新增权限可选类型
    re_path(r'^permission/content_types/$', PermContentTypeView.as_view()),
]

7.2:用户组管理:

一、新建编辑apps/meiduo_admin/serializers/group_serialziers.py
from django.contrib.auth.models import Group,Permission
from rest_framework import serializers

class GroupPermSimpleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Permission
        fields = [
            'id',
            'name'
        ]

class GroupModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Group
        fields = [
            'id',
            'name',

            # 假设模型类序列化器能够完成该字段的校验和新建中间表数据;
            'permissions'
        ]

    # 模型类序列化器的create方法,会帮助咱们根据ManyToManyField字段来构建中间表数据
    # 接下来手动使用ManyToManyField字段来构建中间表数据
    # def create(self, validated_data):
    #     permissions = validated_data.pop('permissions')
    #     # 一、新建主表分组对象
    #     group = Group.objects.create(**validated_data)
    #     # 二、根据前端传来的permisssions列表数据新增中间表
    #     group.permissions.set(permissions)
    #     # group.permissions = permissions
    #
    #     return group
二、新建编辑apps/meiduo_admin/views/goup_views.py
from rest_framework.generics import ListAPIView
from rest_framework.viewsets import ModelViewSet
from ..serializers.group_serializers import *
from ..paginations import MyPage

class GroupPermView(ListAPIView):
    queryset = Permission.objects.all()
    serializer_class = GroupPermSimpleSerializer

class GroupView(ModelViewSet):
    queryset = Group.objects.all()
    serializer_class = GroupModelSerializer
    pagination_class = MyPage

三、新建编辑apps/meiduo_admin/urls.py
urlpatterns = [
    # ....
    # 分组管理
    re_path(r'^permission/groups/$', GroupView.as_view({'get': 'list', 'post': 'create'})),
    re_path(r'^permission/groups/(?P<pk>\d+)/$', GroupView.as_view({
        'get': 'retrieve',
        'put': 'update',
        'delete': 'destroy'
    })),
    # 新增分组可选权限
    re_path(r'^permission/simple/$', GroupPermView.as_view()),
]

7.3:管理员管理:

一、新建编辑apps/meiduo_admin/serializers/admin_serialziers.py
from django.contrib.auth.models import Group
from django.contrib.auth.hashers import make_password
from rest_framework import serializers
from apps.users.models import User

class AdminGroupSerializer(serializers.ModelSerializer):
    class Meta:
        model = Group
        fields = [
            'id',
            'name'
        ]

class AdminUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = [
            'id',
            'username',
            'email',
            'mobile',

            'password',
            'groups',
            'user_permissions'
        ]

        extra_kwargs = {
            'password': {'write_only': True}
        }


    def validate(self, attrs):
        raw_password = attrs.get('password')
        secret_password = make_password(raw_password)
        attrs['password'] = secret_password
        attrs['is_staff'] = True
        return attrs
二、新建编辑apps/meiduo_admin/views/admin_views.py
from rest_framework.generics import ListAPIView
from rest_framework.viewsets import ModelViewSet
from ..serializers.admin_serializers import *
from ..paginations import MyPage

class AdminGroupView(ListAPIView):
    queryset = Group.objects.all()
    serializer_class = AdminGroupSerializer

class AdminUserView(ModelViewSet):
    queryset = User.objects.filter(is_staff=True)
    serializer_class = AdminUserSerializer
    pagination_class = MyPage

三、新建编辑apps/meiduo_admin/urls.py
urlpatterns = [
    # ....
  	# 管理员用户
    re_path(r'^permission/admins/$', AdminUserView.as_view({'get': 'list', 'post': 'create'})),
    re_path(r'^permission/admins/(?P<pk>\d+)/$', AdminUserView.as_view({
        'get': 'retrieve',
        'put': 'update',
        'delete': 'destroy'
    })),
		# 新增管理员可选权限
    re_path(r'^permission/groups/simple/$', AdminGroupView.as_view()),
]

八:频道管理:

1:序列化返回全部的频道信息:

  • 1: 路由:

        # 27:序列化返回全部的频道信息
        re_path(r'^goods/channels/$', ChannelsView.as_view({'get': 'list'})),
    
    
  • 2:视图:

    from rest_framework import serializers
    from apps.goods.models import GoodsChannel
    # 1:频道的序列化器
    class ChannelsSerializer(serializers.ModelSerializer):
        group_id = serializers.IntegerField()
        category_id = serializers.IntegerField()
        class Meta:
            model = GoodsChannel
            fields = "__all__"
    
    
  • 3: 序列化器:

    from rest_framework import serializers
    from apps.goods.models import GoodsChannel
    
    # 1:频道的序列化器
    class ChannelsSerializer(serializers.ModelSerializer):
        class Meta:
            model = GoodsChannel
            fields = "__all__"
    

2: 获取单一频道信息:

  • 1:路由:

        re_path(r'^goods/channels/(?P<pk>\d+)/$', ChannelsView.as_view({'get': 'retrieve'})),
    
  • 2:视图:增长过滤

    def get_queryset(self):
            keyword = self.request.query_params.get('keyword')
            if keyword:
                return self.queryset.filter(order_id__contains=keyword)
            return self.queryset.all()
    
  • 3:序列化器无需改变。

3:建立频道

3.1: 获取全部的频道分组:

  • 1: 路由:

     # 28: 获取全部的频道组信息
        re_path(r"^goods/channel_types/$", Channels_Group_View.as_view({'get': 'list'})),
    
  • 2:序列化器:

    # 2: 序列化返回频道分组序列化器:
    class Channels_Group_Serializer(serializers.ModelSerializer):
        class Meta:
            model = GoodsChannelGroup
            fields = "__all__"
    
    
  • 3:视图:

    # 2: 序列化返回全部的频道分组列表
    class Channels_Group_View(ModelViewSet):
        queryset = GoodsChannelGroup.objects.all().order_by('id')
        serializer_class = Channels_Group_Serializer
    
    

3.2:获取全部频道的一级分类:

  • 1:路由:
    # 29: 获取频道全部一级类目:
    re_path(r"^goods/categories/$", Channels_first_View.as_view({'get': 'list'})),

  • 2:序列化器:
# 3: 获取频道全部一级类目
class GoodsCategory_serializers(serializers.ModelSerializer):
    class Meta:
        model = GoodsCategory
        fields = "__all__"

  • 3:视图:
# 3: 获取频道全部一级类目
class Channels_first_View(ModelViewSet):
    queryset = GoodsCategory.objects.all().order_by('id').filter(id__lte=37)
    serializer_class = GoodsCategory_serializers

3.3:建立频道:

  • 1: 路由:

      re_path(r'^goods/channels/$', ChannelsView.as_view({'get': 'list', 'post': 'create'})),
    
    
  • 2: 序列化器:

    # 1:频道的序列化器
    class ChannelsSerializer(serializers.ModelSerializer):
        group = serializers.StringRelatedField()
        group_id = serializers.IntegerField()
        category = serializers.StringRelatedField()
        category_id = serializers.IntegerField()
    
        class Meta:
            model = GoodsChannel
            fields = "__all__"
    
    
  • 3:视图:

    # 1: 序列化返回频道列表
    class ChannelsView(ModelViewSet):
        queryset = GoodsChannel.objects.all().order_by('id')
        serializer_class = ChannelsSerializer
        pagination_class = MyPage
    
        def get_queryset(self):
            keyword = self.request.query_params.get('keyword')
            if keyword:
                return self.queryset.filter(order_id__contains=keyword)
            return self.queryset.all()
    
    

4:更新频道

  • 1:路由:

        re_path(r'^goods/channels/(?P<pk>\d+)/$', ChannelsView.as_view({'get': 'retrieve', 'put': 'update'})),
    
    
  • 2: 序列化器和视图不用改变。

5:删除频道:

  • 1: 路由:

        re_path(r'^goods/channels/(?P<pk>\d+)/$', ChannelsView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
    
    
  • 3:其他不用改变。

九:品牌管理:

1:获取品牌列表:

  • 1:序列化器:

    from rest_framework import serializers
    from apps.goods.models import Brand
    
    
    class Brands_Serializer(serializers.ModelSerializer):
        class Meta:
            model = Brand
            fields = "__all__"
    
  • 2:路由:

    # 30: 获取品牌列表
        re_path(r"^goods/brands/$", Brands_views.as_view({'get': 'list'})),
    
  • 3:视图:

    from rest_framework.viewsets import ModelViewSet
    
    from apps.goods.models import Brand
    from meiduo_admin.paginations import MyPage
    from meiduo_admin.serializers.brands_serializers import Brands_Serializer
    
    
    class Brands_views(ModelViewSet):
        queryset = Brand.objects.all().order_by('id')
        serializer_class = Brands_Serializer
        pagination_class = MyPage
    
        def get_queryset(self):
            keyword = self.request.query_params.get('keyword')
            if keyword:
                return self.queryset.filter(order_id__contains=keyword)
            return self.queryset.all()
    

2:新建单一品牌数据:

  • 1: 路由:

     re_path(r"^goods/brands/$", Brands_views.as_view({'get': 'list', 'post': 'create'})),
    

3:获取单一品牌数据,更新单一品牌数据,删除单一品牌数据:

  • 路由:

     re_path(r"^goods/brands/(?P<pk>\d+)/$", Brands_views.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),