商城后台项目

╰+攻爆jí腚メ 2022-11-11 06:10 298阅读 0赞

商城项目DRF框架编写

    • 一: 项目初始化
      • 1.1: 创建新的子应用:
      • 1.2: 在跨域白名单中增加静态服务器域名:
      • 1.3: 将meiduo_mall_admin移动到项目中:
    • 二:JWT单点登录:
      • 2.1: 安装JWT单点登录的拓展组件:
      • 2.2: 使用拓展组件完成JWT登录:
    • 三:主页数据统计:
      • 3.1:用户总数的统计:
      • 3.2: 新增用户统计:
      • 3.3:日活跃用户统计:
      • 3.4:日下单用户统计:
      • 3.5:月增用户统计:
      • 3.6:日分类商品访问量统计:
    • 四:用户管理:
      • 4.1:获取用户列表(分页)
    • 五:商品管理:
      • 5.1:SKU管理:
        • 1: 获取所有的商品:
        • 2:因此需要定义两个序列化器:
        • 2:新增SKU的三级可选分类:
        • 3:新增SKU单一资源
        • 4:获取SKU单一资源:
        • 5:更新SKU单一资源:
        • 6:删除SKU单一资源:
        • 7: 异步任务与事务的封装:
      • 5.2:SPU管理:
        • 1: 获取SPU列表数据:
        • 2:新增中SPU的可选品牌:
        • 3:新增中SPU的可选分类:
        • 4:新建SPU,获取单个SPU,更新SPU,删除SPU :
      • 5.3:规格管理:
      • 5.4:规格选项管理:
      • 5.5:品牌管理:
      • 5.6:图片管理:
        • 1:获取图片列表数据:
    • 六:订单管理:
      • 1:获取订单的列表信息:
      • 2:获取订单的详细信息:
      • 3:修改订单的状态:
    • 七:系统管理:
      • 7.1:权限管理:
          • 1、新建编辑`apps/meiduo_admin/serializers/perm_serialziers.py`
          • 2、新建编辑`apps/meiduo_admin/views/perm_views.py`
          • 3、新建编辑`apps/meiduo_admin/urls.py`
      • 7.2:用户组管理:
          • 1、新建编辑`apps/meiduo_admin/serializers/group_serialziers.py`
          • 2、新建编辑`apps/meiduo_admin/views/goup_views.py`
          • 3、新建编辑`apps/meiduo_admin/urls.py`
      • 7.3:管理员管理:
          • 1、新建编辑`apps/meiduo_admin/serializers/admin_serialziers.py`
          • 2、新建编辑`apps/meiduo_admin/views/admin_views.py`
          • 3、新建编辑`apps/meiduo_admin/urls.py`
    • 八:频道管理:
      • 1:序列化返回所有的频道信息:
      • 2: 获取单一频道信息:
      • 3:创建频道
        • 3.1: 获取所有的频道分组:
        • 3.2:获取所有频道的一级分类:
        • 3.3:创建频道:
      • 4:更新频道
      • 5:删除频道:
    • 九:品牌管理:
      • 1:获取品牌列表:
      • 2:新建单一品牌数据:
      • 3:获取单一品牌数据,更新单一品牌数据,删除单一品牌数据:

一: 项目初始化

1.1: 创建新的子应用:

  • 执行命令:python3 …/manage.py startapp meiduo_admin
  • 修改配置文件:

    1. INSTALL_APPS = [
    2. # ...
    3. 'apps.meiduo_admin'
    4. ]
  • 子应用下创建urls.py

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

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

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

修改dev.py:

  1. # CORS跨域请求白名单设置
  2. CORS_ORIGIN_WHITELIST = (
  3. 'http://127.0.0.1:8080',
  4. 'http://localhost:8080',
  5. 'http://www.meiduo.site:8080',
  6. # 管理站点跨域请求的源
  7. 'http://127.0.0.1:8081',
  8. 'http://localhost:8081',
  9. 'http://www.meiduo.site:8081'
  10. )

1.3: 将meiduo_mall_admin移动到项目中:

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

二:JWT单点登录:

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

  • pip install djangorestframework-jwt
  • 配置文件中配置拓展组件:

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

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

  • 拓展组件已经帮我们写好了obtain_jwt_token,作为JWT登录的视图,所以需要直接路由映射:

    1. from django.urls import re_path
    2. # obtain_jwt_token为拓展插件提供的用于验证用户名和密码并签发token的视图
    3. from rest_framework_jwt.views import obtain_jwt_token
    4. urlpatterns = [
    5. re_path(r'^authorizations/$', obtain_jwt_token),
    6. ]
  • 但是这个拓展组件返回的是他自己写的内容,我们需要指定视图的返回内容怎么办?

    • 我们需要在工具类中定义一个函数,这个函数需要返回我们拓展组件指定的返回格式,然后在配置文件中指定就可以了。
    • 新建:meiduo_mall/utils/jwt_response_handlers.py模块
    • def jwt_response_payload_handler(token, user=None, request=None):

      1. return {
      2. # 补充返回username和user_id字段
      3. 'username': user.username,
      4. 'user_id': user.id,
      5. 'token': token
      6. }
    • JWT_AUTH = {

      1. # 设置签发的token的有效期
      2. 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=100),
      3. # 来指定拓展插件默认视图返回的响应参数构造函数
      4. 'JWT_RESPONSE_PAYLOAD_HANDLER': 'meiduo_mall.utils.jwt_response_handlers.jwt_response_payload_handler'

      }

  • 权限限定:要求,不是管理员的不能进入后台管理界面。

    1. """
    2. 思路: 无论是前台页面登录还是后台页面登录都需要经过Django后台,而后台肯定是调用authenticate函数实现用户名和密码的验证, 对于前台我们调用authenticate 传入了request对象,所以我们自定义认证后端得到的request对象是一个对象,而后端登录,我们使用的是第三方拓展组件,这个拓展组件在调用authenticate函数的时候直接传入的是用户名和密码,没有传入request对象,所以在自定义认证后端,如果request对象是None,即是后端登录,并且登录的时候User对象is_staff是False,即不是管理员身份,则认证后端不进行认证了,直接返回None.
    3. """
    4. # 继承Django默认的传统认证后端
    5. class UsernameMobileAuthBackend(ModelBackend):
    6. # 重写authenticate方法
    7. # 原因:默认的authenticate方法,只会根据username字段去过滤查找用户
    8. def authenticate(self, request, username=None, password=None, **kwargs):
    9. # 允许多账号登陆的情况下,前端传来的"username"有可能是用户名也有可能是手机号
    10. try:
    11. # 1、先按用户名查找
    12. user = User.objects.get(
    13. # username=="18588269037" or mobile=="18588269037"
    14. Q(username=username) | Q(mobile=username) | Q(email=username)
    15. )
    16. except User.DoesNotExist as e:
    17. return None # 用户名找不到,返回None表示认证失败
  1. # TODO:判断是否为管理站点页面登陆,如果是需要进一步校验is_staff=True
  2. # 如果是商城页面登陆,request是一个请求对象
  3. # 如果是管理站点页面登陆,request是一个None
  4. if request is None and not user.is_staff:
  5. return None
  6. # 3、某一个找到了,再校验密码
  7. if user.check_password(password):
  8. return user

在这里插入图片描述

三:主页数据统计:

3.1:用户总数的统计:

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

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

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

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

3.2: 新增用户统计:

  • 配置文件检查配置:

    1. USE_TZ = True
  • 视图:

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

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

3.3:日活跃用户统计:

  • 视图:

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

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

    1. class LoginBackend(ModelBackend):
    2. def authenticate(self, request, username=None, password=None, **kwargs):
    3. try:
    4. user = User.objects.get(Q(username=username)|Q(mobile=username))
    5. except Exception as e:
    6. return None
    7. if request is None and not user.is_staff:
    8. return None
    9. if user.check_password(password):
    10. # 登陆的时候修改登陆的时间
    11. user.last_login = timezone.localtime()
    12. user.save()
    13. return user

3.4:日下单用户统计:

  • 1: 视图:

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

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

3.5:月增用户统计:

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

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

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

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

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

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

    无需迁移建表

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

    1. # 用户浏览历史记录
    2. class UserBrowseHistory(LoginRequiredJSONMixin, View):
    3. # 添加历史
    4. def post(self, request):
    5. user = request.user
    6. # 1、提取参数
    7. # 2、校验参数
    8. data = json.loads(request.body.decode())
    9. sku_id = data.get('sku_id')
    10. if not sku_id:
    11. return JsonResponse({
    12. 'code': 400,
    13. 'errmsg': '缺少参数'
    14. }, status=400)
    15. try:
    16. sku = SKU.objects.get(pk=sku_id, is_launched=True)
    17. except SKU.DoesNotExist as e:
    18. return JsonResponse({
    19. 'code': 404,
    20. 'errmsg': '商品已下架/不存在'
    21. }, status=404)
    22. # 3、数据/业务处理 —— 把访问的sku的id写入redis表示记录一条浏览历史
    23. # 3.1、获取"history"缓存配置的redis链接
    24. conn = get_redis_connection('history')
    25. p = conn.pipeline()
    26. # 3.2、历史记录写入缓存
    27. # 3.2.1、去重
    28. p.lrem(
    29. 'history_%d' % user.id,
    30. 0, # 删除所有指定成员
    31. sku_id
    32. )
    33. # 3.2.2、插入列表头
    34. p.lpush(
    35. 'history_%d' % user.id,
    36. sku_id
    37. )
    38. # 3.2.3、截断保留5个记录
    39. p.ltrim(
    40. 'history_%d' % user.id,
    41. 0,
    42. 4
    43. )
    44. p.execute() # 批量执行redis指令
  1. # TODO: 记录该sku商品的分类访问量
  2. # 分类id:sku.category_id
  3. # 当日零时刻:
  4. cur_0_time = timezone.localtime().replace(hour=0, minute=0, second=0)
  5. # (1)、判断当前sku商品的分类,和当日的数据存不存在;
  6. try:
  7. visit_obj = GoodsVisitCount.objects.get(
  8. category_id=sku.category_id,
  9. create_time__gte=cur_0_time
  10. )
  11. except GoodsVisitCount.DoesNotExist as e:
  12. # 记录不存在则新建
  13. GoodsVisitCount.objects.create(
  14. category_id=sku.category_id,
  15. count=1
  16. )
  17. else:
  18. # 记录存在则累加
  19. visit_obj.count += 1
  20. visit_obj.save()
  21. # 4、构建响应
  22. return JsonResponse({
  23. 'code': 0,
  24. 'errmsg': 'ok'
  25. })
  • 3: 序列化器:apps/meiduo_admin/serializers/home_serializers.py

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

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

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

四:用户管理:

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

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

    1. from rest_framework import serializers
    2. from apps.users.models import User
    3. # 1:定义用户的序列化器:使用ModelSerializer模型类序列化器
    4. class UserModelSerializer(serializers.ModelSerializer):
    5. class Meta:
    6. # 1: 指定模型类
    7. model = User
    8. # 2: 制定哪些需要映射
    9. fields = [
    10. 'id',
    11. 'username',
    12. 'mobile',
    13. 'email'
    14. ]
  • 2:自定义分页器:

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

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

    1. # 8: 用户的列表查询
    2. re_path(r"^users/$", UserView.as_view()),

    4.2:新增用户(管理员):

    • 问题: 我们在新建用户对象的时候, ModelSerializer默认提供的create方法无法帮助我们进行:

      • (1)密码加密;
      • (2)设置is_staff=True
    • 解决方案?

      • 我们重写序列化器的create方法,来实现密码加密和添加is_staff=True
      • 我们还可以在校验过程中,对有效数据中的明文密码加密,以及在返回的有效数据中添加is_staff=True —— 后续create方法完成新建对象就是使用调整之后的有效数据!
  1. - 方案一: 重写序列化器的create方法实现密码加密和添加属性。
  2. 修改序列化器:
  3. ```python
  4. from rest_framework import serializers
  5. from apps.users.models import User
  6. # 1:定义用户的序列化器:使用ModelSerializer模型类序列化器
  7. class UserModelSerializer(serializers.ModelSerializer):
  8. class Meta:
  9. # 1: 指定模型类
  10. model = User
  11. # 2: 制定哪些需要映射
  12. fields = [
  13. 'id',
  14. 'username',
  15. 'mobile',
  16. 'email'
  17. ]
  18. # 重写create方法实现加密和设置属性:
  19. def create(self, validated_data):
  20. # 1: 设置is_staff 属性是True
  21. validated_data['is_staff'] = True
  22. # 2: 密码加密
  23. user = User.objects.create_user(**validated_data)
  24. # 3: 返回
  25. return user

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

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

    1. # 1:定义用户的序列化器:使用ModelSerializer模型类序列化器
    2. class UserModelSerializer(serializers.ModelSerializer):
    3. class Meta:
    4. # 1: 指定模型类
    5. model = User
    6. # 2: 制定哪些需要映射
    7. fields = [
    8. 'id',
    9. 'username',
    10. 'mobile',
    11. 'email'
    12. ]
    13. def validate(self, attrs):
    14. # 1、密码加密
    15. raw_password = attrs.pop('password') # 明文
    16. secret_password = make_password(raw_password) # make_password传入明文密码,加密返回密文码
    17. attrs['password'] = secret_password
    18. # 2、返回有效数据中添加is_staff=True
    19. attrs['is_staff'] = True
    20. return attrs

五:商品管理:

5.1:SKU管理:

1: 获取所有的商品:

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

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

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

    2:因此需要定义两个序列化器:

    1. from rest_framework import serializers
    2. from apps.goods.models import SKUSpecification, SKU
  1. class SKUSpecOptModelSerializer(serializers.ModelSerializer):
  2. # 因为这2个字段必须参与反序列化,所有我们只能手动映射覆盖原有的默认映射(默认只作用于序列化)
  3. spec_id = serializers.IntegerField()
  4. option_id = serializers.IntegerField()
  5. class Meta:
  6. model = SKUSpecification
  7. # 如果关联对象主键隐藏字段,我们在fields中显示声明,则会自动映射类型为ReadOnlyField —— 只读(只参与序列化)
  8. fields = [
  9. 'spec_id',
  10. 'option_id'
  11. ]
  12. class SKUModelSerializer(serializers.ModelSerializer):
  13. # 三个隐藏字段:
  14. # 1、本表主键id,会自动映射
  15. # 2、外间关联对象的主键隐藏字段(如:spu_id), 不会自动映射
  16. spu = serializers.StringRelatedField()
  17. spu_id = serializers.IntegerField()
  18. category = serializers.StringRelatedField()
  19. category_id = serializers.IntegerField()
  20. # 3、从表related_name指定的主表隐藏字段,不会自动映射
  21. # 表示当前SKU对象(主表)关联的"多个"SKUSpecification对象(从表)
  22. specs = SKUSpecOptModelSerializer(many=True)
  23. class Meta:
  24. model = SKU
  25. fields = "__all__"
  • 3:定义路由:

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

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

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

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

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

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

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

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

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

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

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

      在这里插入图片描述

  • SKU的可选规格和选项:

    • 1: 序列化器分析:

      分析可以得出,需要对SPUSpecification生成序列化器,但是要同时序列化SKUSpecification。

    • 2:编写序列化器:

      1. class SpecOptSimpleSerializer(serializers.ModelSerializer):
      2. class Meta:
      3. model = SpecificationOption
      4. fields = [
      5. 'id',
      6. 'value'
      7. ]
      8. class SPUSpecModelSerializer(serializers.ModelSerializer):
      9. spu = serializers.StringRelatedField()
      10. spu_id = serializers.IntegerField()
      11. # 关联从表SpecficationOption多条数据
      12. options = SpecOptSimpleSerializer(many=True)
      13. class Meta:
      14. model = SPUSpecification
      15. fields = [
      16. 'id',
      17. 'name',
      18. 'spu',
      19. 'spu_id',
      20. 'options'
      21. ]
    • 3: 路由:

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

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

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

    3:新增SKU单一资源

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

      分析表结构可以得到, spu和category是外键关联字段,需要手动映射,specs是主表隐藏字段,需要关联反序列化。
      在这里插入图片描述

    • 2:构建序列化器:由于我们之前有了这两个序列化器,我们看看原来的序列化器能不能完成任务。
      在这里插入图片描述

      在上图我们可以发现在进行反序列化的时候,我的SKU序列化器不能完成新建SKU规格和选项的操作。怎么办呢?需要重写SKU序列化器的create方法才可以。

      思路: 重写create方法然后,将校验的数据分成SKU的和SKU规格数据的两部分。使用SKU的 创建SKU对象,然后循环遍历SKU规格和选项,将SKU规格和选项中再增加上SKU的ID,然后创建规格和选项就可以了,注意最后一定要返回sku。

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

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

4:获取SKU单一资源:

  • 1:路由:

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

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

5:更新SKU单一资源:

  • 1: 路由:

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

    我们的序列化器, 不能完成更新操作,因此需要重写update方法。

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

6:删除SKU单一资源:

  • 1:路由:

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

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

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

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

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

    class SKUModelSerializer(serializers.ModelSerializer):

    1. # 三个隐藏字段:
    2. # 1、本表主键id,会自动映射
    3. # 2、外间关联对象的主键隐藏字段(如:spu_id), 不会自动映射
    4. spu = serializers.StringRelatedField()
    5. spu_id = serializers.IntegerField()
    6. category = serializers.StringRelatedField()
    7. category_id = serializers.IntegerField()
    8. # 3、从表related_name指定的主表隐藏字段,不会自动映射
    9. # 表示当前SKU对象(主表)关联的"多个"SKUSpecification对象(从表)
    10. specs = SKUSpecOptModelSerializer(many=True)
    11. class Meta:
    12. model = SKU
    13. fields = "__all__"
    14. # 默认create方法,无法帮助我们插入中间表数据来记录新增sku商品的规格和选项信息
    15. def create(self, validated_data):
    16. # [{spec_id: "4", option_id: 8}, {spec_id: "5", option_id: 11}]
    17. specs = validated_data.pop('specs')
    18. # 设计默认图片
    19. validated_data['default_image'] = 'group1/M00/00/02/CtM3BVrPB4GAWkTlAAGuN6wB9fU4220429'
    20. # TODO: 下面两步操作,是数据库两张表的插入动作,必须保证"事务"特性
    21. with transaction.atomic():
    22. save_id = transaction.savepoint()
    23. try:
    24. # 1、新建sku模型类对象(主表)
    25. sku = SKU.objects.create(**validated_data)
    26. # 2、新建规格选项中间表数据,来记录新增sku的规格和选项信息
    27. for temp in specs:
    28. # temp : {spec_id: "4", option_id: 8}
    29. temp['sku_id'] = sku.id
    30. SKUSpecification.objects.create(**temp)
    31. except Exception as e:
    32. transaction.savepoint_rollback(save_id)
    33. raise serializers.ValidationError('数据库新建失败!')
    34. transaction.savepoint_commit(save_id)
    35. # TODO: 使用异步任务方式,静态化生成当前新建sku商品的详情页
    36. generate_static_sku_detail_html.delay(sku.id)
    37. return sku
  • 新建文件celery_tasks/html/tasks.py,编辑如下:

    1. import os
    2. from django.template import loader
    3. from django.conf import settings
    4. from apps.goods.utils import get_categories,get_goods_and_spec
    5. from celery_tasks.main import celery_app
    6. @celery_app.task(name='generate_static_sku_detail_html')
    7. def generate_static_sku_detail_html(sku_id):
    8. """
    9. 功能:生成指定sku商品的详情页面
    10. sku_id: SKU商品的id
    11. :return:
    12. """
    13. categories = get_categories()
    14. goods, sku, specs = get_goods_and_spec(sku_id)
    15. # =========模版渲染========
    16. template = loader.get_template('detail.html')
    17. context = {
    18. 'categories': categories,
    19. 'goods': goods, # 当前sku从属的spu
    20. 'specs': specs, # 规格和选项信息
    21. 'sku': sku # 当前sku商品对象
    22. }
    23. page = template.render(context)
    24. # settings.STATIC_FILE_PATH --> 是front_end_pc文件夹路径
    25. file_path = os.path.join(
    26. settings.STATIC_FILE_PATH,
    27. 'goods/%d.html' % sku_id, # 'goods/1.html'
    28. )
    29. with open(file_path, 'w') as f:
    30. f.write(page)

5.2:SPU管理:

1: 获取SPU列表数据:

  • 1: 路由:

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

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

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

    1. from apps.goods.models import SPU
    2. class SPUModelSerializer(serializers.ModelSerializer):
    3. brand = serializers.StringRelatedField()
    4. brand_id = serializers.IntegerField()
    5. category1_id = serializers.IntegerField()
    6. category2_id = serializers.IntegerField()
    7. category3_id = serializers.IntegerField()
    8. class Meta:
    9. model = SPU
    10. fields = [
    11. 'id',
    12. 'name',
    13. 'brand',
    14. 'brand_id',
    15. 'category1_id',
    16. 'category2_id',
    17. 'category3_id',
    18. 'sales',
    19. 'comments',
    20. 'desc_detail',
    21. 'desc_pack',
    22. 'desc_service'
    23. ]
  • 4: 视图:

    1. from rest_framework.generics import ListAPIView
    2. from rest_framework.viewsets import ModelViewSet
    3. from ..serializers.spu_serializers import *
    4. from ..paginations import MyPage
    5. class SPUView(ModelViewSet):
    6. queryset = SPU.objects.all().order_by('id')
    7. serializer_class = SPUModelSerializer
    8. pagination_class = MyPage
    9. def get_queryset(self):
    10. keyword = self.request.query_params.get('keyword')
    11. if keyword:
    12. return self.queryset.filter(name__contains=keyword)
    13. return self.queryset.all()

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

  • 路由:

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

    1. class BrandSimpleSerializer(serializers.ModelSerializer):
    2. class Meta:
    3. model = Brand
    4. fields = [
    5. 'id',
    6. 'name'
    7. ]
  • 3:视图:

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

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

  • 1:路由:

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

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

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

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

  • 1:路由:

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

5.3:规格管理:

  • 1:序列化器:
  • 新建编辑apps/meiduo_admin/serializers/spec_serializers.py

    1. from rest_framework import serializers
    2. from apps.goods.models import SPUSpecification
    3. class SpecModelSerializer(serializers.ModelSerializer):
    4. spu = serializers.StringRelatedField()
    5. spu_id = serializers.IntegerField()
    6. class Meta:
    7. model = SPUSpecification
    8. fields = [
    9. 'id',
    10. 'name',
    11. 'spu',
    12. 'spu_id'
    13. ]
  • 2:视图:

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

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

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

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

    1. class Meta:
    2. model = SPUSpecification
    3. fields = [
    4. 'id',
    5. 'name'
    6. ]
  1. class OptionModelSerializer(serializers.ModelSerializer):
  2. spec = serializers.StringRelatedField()
  3. spec_id = serializers.IntegerField()
  4. class Meta:
  5. model = SpecificationOption
  6. fields = [
  7. 'id',
  8. 'value',
  9. 'spec',
  10. 'spec_id'
  11. ]
  • 视图:
  • 新建编辑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

  1. class OptSpecSimpleView(ListAPIView):
  2. queryset = SPUSpecification.objects.all()
  3. serializer_class = OptSpecSimpleSerializer
  4. class OptionView(ModelViewSet):
  5. queryset = SpecificationOption.objects.all()
  6. serializer_class = OptionModelSerializer
  7. pagination_class = MyPage
  • 路由:
  • 编辑apps/meiduo_admin/urls.py

    urlpatterns = [

    1. # ......
    2. # 选项管理
    3. re_path(r'^specs/options/$', OptionView.as_view({
    4. 'get': 'list', 'post': 'create'})),
    5. re_path(r'^specs/options/(?P<pk>\d+)/$', OptionView.as_view({
    6. 'get': 'retrieve',
    7. 'put': 'update',
    8. 'delete': 'destroy'
    9. })),
    10. # 新增选项可选规格
    11. re_path(r'^goods/specs/simple/$', OptSpecSimpleView.as_view()),

    ]

5.5:品牌管理:

5.6:图片管理:

1:获取图片列表数据:

  • 1: 定义序列化器:

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

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

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

    1. # Storage是默认的Django的存储后端
    2. from django.core.files.storage import Storage
    3. from django.conf import settings
    4. from fdfs_client.client import Fdfs_client
    5. from rest_framework import serializers
    6. class FastDFSStorage(Storage):
    7. def _open(self, name, mode='rb'):
    8. # 打开django本地文件
    9. pass
    10. def _save(self, name, image_obj, max_length=None):
    11. # 如果SKUImage(image=<文件对象>) --> 当前存储后端的_save方法,完成图片的保存动作
    12. # 此处我们需要把文件保存到fdfs(上传到fdfs)
    13. # name: 文件名称,同时也是保存到Django本地的文件名称 ---> 无需使用
    14. # image_obj: 传来的文件对象 --> 就是ImageField字段在新建或者更新的时候被赋值的文件对象
    15. conn = Fdfs_client('./meiduo_mall/settings/client.conf')
    16. res = conn.upload_by_buffer(image_obj.read())
    17. if res is None:
    18. raise serializers.ValidationError('上传fdfs失败!')
    19. file_id = res['Remote file_id']
    20. # 注意:_save方法返回值就是当前字段,存储在mysql中的文件id
    21. return file_id
    22. def exists(self, name):
    23. # 功能:判断上传的文件在Django本地是否重复
    24. # True:表示重复
    25. # Fales:表示不重复
    26. return False
    27. def url(self, name):
    28. """
    29. 功能:返回值就是ImageField.url属性 ---> 构建完整的图片(文件)链接
    30. :param name: ImageField类型字段在mysql中存储的值 ---> 文件索引标识"group1/M00/00/02/CtM3BVrPB5CALKn6AADq-Afr0eE1672090"
    31. :return: 图片链接
    32. """
    33. # return "group1/M00/00/02/CtM3BVrPB5CALKn6AADq-Afr0eE1672090"
    34. # return name
    35. # return "http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrPB5CALKn6AADq-Afr0eE1672090"
    36. # return "http://image.meiduo.site:8888/" + name
    37. return settings.FDFS_BASE_URL + name
  • 4:路由:

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

六:订单管理:

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

  • 1:路由:

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

    1. from rest_framework import serializers
    2. from apps.orders.models import OrderInfo,OrderGoods
    3. from apps.goods.models import SKU
    4. class OrderSimpleSerializer(serializers.ModelSerializer):
    5. class Meta:
    6. model = OrderInfo
    7. fields = [
    8. 'order_id',
    9. 'create_time'
    10. ]
  • 3:视图:

    1. from meiduo_admin.paginations import MyPage
    2. from meiduo_admin.serializers.orders_serializers import OrderSimpleSerializer
    3. from apps.orders.models import OrderInfo
    4. class OrderView(UpdateModelMixin, ReadOnlyModelViewSet):
    5. queryset = OrderInfo.objects.all().order_by('create_time')
    6. serializer_class = OrderSimpleSerializer
    7. pagination_class = MyPage
    8. def get_queryset(self):
    9. keyword = self.request.query_params.get('keyword')
    10. if keyword:
    11. return self.queryset.filter(order_id__contains=keyword)
    12. return self.queryset.all()

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

  • 1:路由:

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

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

    class SKUSimpleSerializer(serializers.ModelSerializer):

    1. class Meta:
    2. model = SKU
    3. fields = [
    4. 'name',
    5. 'default_image'
    6. ]

    class OrderGoodsModelSerializer(serializers.ModelSerializer):

    1. # 当前订单商品对象关联的"单一的"SKU对象
    2. sku = SKUSimpleSerializer()
    3. class Meta:
    4. model = OrderGoods
    5. fields = [
    6. 'count',
    7. 'price',
    8. 'sku'
    9. ]

    class OrderDetailSerializer(serializers.ModelSerializer):

    1. user = serializers.StringRelatedField()
    2. # 当前订单对象关联的多个订单商品(OrderGoods)对象
    3. skus = OrderGoodsModelSerializer(many=True)
    4. class Meta:
    5. model = OrderInfo
    6. fields = "__all__"
  • 3:视图:对于同一个视图,我们如何指定不同的方式用不同的序列化器呢?

    重写get_serializer_class方法。

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

3:修改订单的状态:

  • 路由:

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

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

七:系统管理:

7.1:权限管理:

1、新建编辑apps/meiduo_admin/serializers/perm_serialziers.py
  1. from django.contrib.auth.models import Permission,ContentType
  2. from rest_framework import serializers
  3. class PermContentTypeSerializer(serializers.ModelSerializer):
  4. class Meta:
  5. model = ContentType
  6. fields = [
  7. 'id',
  8. 'name'
  9. ]
  10. class PermModelSerializer(serializers.ModelSerializer):
  11. # content_type = serializers.StringRelatedField()
  12. class Meta:
  13. model = Permission
  14. fields = [
  15. 'id',
  16. 'name',
  17. 'codename',
  18. 'content_type'
  19. ]
2、新建编辑apps/meiduo_admin/views/perm_views.py
  1. from rest_framework.generics import ListAPIView
  2. from rest_framework.viewsets import ModelViewSet
  3. from ..serializers.perm_serializers import *
  4. from ..paginations import MyPage
  5. class PermContentTypeView(ListAPIView):
  6. queryset = ContentType.objects.all()
  7. serializer_class = PermContentTypeSerializer
  8. class PermView(ModelViewSet):
  9. queryset = Permission.objects.all()
  10. serializer_class = PermModelSerializer
  11. pagination_class = MyPage
  12. def get_queryset(self):
  13. return self.queryset.order_by('pk')
3、新建编辑apps/meiduo_admin/urls.py
  1. urlpatterns = [
  2. # ....
  3. # 权限管理
  4. re_path(r'^permission/perms/$', PermView.as_view({
  5. 'get': 'list', 'post': 'create'})),
  6. re_path(r'^permission/perms/(?P<pk>\d+)/$', PermView.as_view({
  7. 'get': 'retrieve',
  8. 'put': 'update',
  9. 'delete': 'destroy'
  10. })),
  11. # 新增权限可选类型
  12. re_path(r'^permission/content_types/$', PermContentTypeView.as_view()),
  13. ]

7.2:用户组管理:

1、新建编辑apps/meiduo_admin/serializers/group_serialziers.py
  1. from django.contrib.auth.models import Group,Permission
  2. from rest_framework import serializers
  3. class GroupPermSimpleSerializer(serializers.ModelSerializer):
  4. class Meta:
  5. model = Permission
  6. fields = [
  7. 'id',
  8. 'name'
  9. ]
  10. class GroupModelSerializer(serializers.ModelSerializer):
  11. class Meta:
  12. model = Group
  13. fields = [
  14. 'id',
  15. 'name',
  16. # 假设模型类序列化器可以完成该字段的校验和新建中间表数据;
  17. 'permissions'
  18. ]
  19. # 模型类序列化器的create方法,会帮助我们根据ManyToManyField字段来构建中间表数据
  20. # 接下来手动使用ManyToManyField字段来构建中间表数据
  21. # def create(self, validated_data):
  22. # permissions = validated_data.pop('permissions')
  23. # # 1、新建主表分组对象
  24. # group = Group.objects.create(**validated_data)
  25. # # 2、根据前端传来的permisssions列表数据新增中间表
  26. # group.permissions.set(permissions)
  27. # # group.permissions = permissions
  28. #
  29. # return group
2、新建编辑apps/meiduo_admin/views/goup_views.py
  1. from rest_framework.generics import ListAPIView
  2. from rest_framework.viewsets import ModelViewSet
  3. from ..serializers.group_serializers import *
  4. from ..paginations import MyPage
  5. class GroupPermView(ListAPIView):
  6. queryset = Permission.objects.all()
  7. serializer_class = GroupPermSimpleSerializer
  8. class GroupView(ModelViewSet):
  9. queryset = Group.objects.all()
  10. serializer_class = GroupModelSerializer
  11. pagination_class = MyPage
3、新建编辑apps/meiduo_admin/urls.py
  1. urlpatterns = [
  2. # ....
  3. # 分组管理
  4. re_path(r'^permission/groups/$', GroupView.as_view({
  5. 'get': 'list', 'post': 'create'})),
  6. re_path(r'^permission/groups/(?P<pk>\d+)/$', GroupView.as_view({
  7. 'get': 'retrieve',
  8. 'put': 'update',
  9. 'delete': 'destroy'
  10. })),
  11. # 新增分组可选权限
  12. re_path(r'^permission/simple/$', GroupPermView.as_view()),
  13. ]

7.3:管理员管理:

1、新建编辑apps/meiduo_admin/serializers/admin_serialziers.py
  1. from django.contrib.auth.models import Group
  2. from django.contrib.auth.hashers import make_password
  3. from rest_framework import serializers
  4. from apps.users.models import User
  5. class AdminGroupSerializer(serializers.ModelSerializer):
  6. class Meta:
  7. model = Group
  8. fields = [
  9. 'id',
  10. 'name'
  11. ]
  12. class AdminUserSerializer(serializers.ModelSerializer):
  13. class Meta:
  14. model = User
  15. fields = [
  16. 'id',
  17. 'username',
  18. 'email',
  19. 'mobile',
  20. 'password',
  21. 'groups',
  22. 'user_permissions'
  23. ]
  24. extra_kwargs = {
  25. 'password': {
  26. 'write_only': True}
  27. }
  28. def validate(self, attrs):
  29. raw_password = attrs.get('password')
  30. secret_password = make_password(raw_password)
  31. attrs['password'] = secret_password
  32. attrs['is_staff'] = True
  33. return attrs
2、新建编辑apps/meiduo_admin/views/admin_views.py
  1. from rest_framework.generics import ListAPIView
  2. from rest_framework.viewsets import ModelViewSet
  3. from ..serializers.admin_serializers import *
  4. from ..paginations import MyPage
  5. class AdminGroupView(ListAPIView):
  6. queryset = Group.objects.all()
  7. serializer_class = AdminGroupSerializer
  8. class AdminUserView(ModelViewSet):
  9. queryset = User.objects.filter(is_staff=True)
  10. serializer_class = AdminUserSerializer
  11. pagination_class = MyPage
3、新建编辑apps/meiduo_admin/urls.py
  1. urlpatterns = [
  2. # ....
  3. # 管理员用户
  4. re_path(r'^permission/admins/$', AdminUserView.as_view({
  5. 'get': 'list', 'post': 'create'})),
  6. re_path(r'^permission/admins/(?P<pk>\d+)/$', AdminUserView.as_view({
  7. 'get': 'retrieve',
  8. 'put': 'update',
  9. 'delete': 'destroy'
  10. })),
  11. # 新增管理员可选权限
  12. re_path(r'^permission/groups/simple/$', AdminGroupView.as_view()),
  13. ]

八:频道管理:

1:序列化返回所有的频道信息:

  • 1: 路由:

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

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

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

2: 获取单一频道信息:

  • 1:路由:

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

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

3:创建频道

3.1: 获取所有的频道分组:

  • 1: 路由:

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

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

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

3.2:获取所有频道的一级分类:

  • 1:路由:

    29: 获取频道所有一级类目:

    1. re_path(r"^goods/categories/$", Channels_first_View.as_view({
    2. 'get': 'list'})),
  • 2:序列化器:

    3: 获取频道所有一级类目

    class GoodsCategory_serializers(serializers.ModelSerializer):

    1. class Meta:
    2. model = GoodsCategory
    3. fields = "__all__"
  • 3:视图:

    3: 获取频道所有一级类目

    class Channels_first_View(ModelViewSet):

    1. queryset = GoodsCategory.objects.all().order_by('id').filter(id__lte=37)
    2. serializer_class = GoodsCategory_serializers

3.3:创建频道:

  • 1: 路由:

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

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

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

4:更新频道

  • 1:路由:

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

5:删除频道:

  • 1: 路由:

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

九:品牌管理:

1:获取品牌列表:

  • 1:序列化器:

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

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

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

2:新建单一品牌数据:

  • 1: 路由:

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

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

  • 路由:

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

发表评论

表情:
评论列表 (有 0 条评论,298人围观)

还没有评论,来说两句吧...

相关阅读

    相关 谈谈商城项目

    很多同学在学完SSM框架之后不知道该做一个什么样的项目来提升自己和巩固自己,那么老师今天就给大家带来一个商城项目。 很多同学会说,现在很多培训机构都在做电商这个项目,那我们做

    相关 商城项目_

    搭建前台 思路分析 通过当前的商品的分类的id: 先找自己,再通过pid找到父级,再找父子的所有自己,除去自己 通过path找到当