Django组件

ContentType组件

ContentType是Django的内置的一个应用,可以追踪项目中所有的APP和model的对应关系,并记录在ContentType表中。
当项目做数据迁移后,会有很多django自带的表,其中就有django_content_type表

ContentType组件应用

  • 在model中定义ForeignKey字段,并关联到ContentType表,通常这个字段命名为content-type
  • 在model中定义PositiveIntergerField字段, 用来存储关联表中的主键,通常用object_id
  • 在model中定义GenericForeignKey字段,传入上面两个字段的名字
  • 方便反向查询可以定义GenericRelation字段
  • postman

1from django.db import models 2from django.contrib.contenttypes.models import ContentType 3from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation 4 5# Create your models here. 6 7 8class Food(models.Model): 9 """ 10 id title 11 1 面包 12 2 牛奶 13 """ 14 title = models.CharField(max_length=32) 15 # 不会生成字段 只用于反向查询 16 coupons = GenericRelation(to="Coupon") 17 18 19class Fruit(models.Model): 20 """ 21 id title 22 1 苹果 23 2 香蕉 24 """ 25 title = models.CharField(max_length=32) 26 27 28# 如果有40张表 29# class Coupon(models.Model): 30# """ 31# id title food_id fruit_id 32# 1 面包九五折 1 null 33# 2 香蕉满10元减5null 2 34# """ 35# title = models.CharField(max_length=32) 36# food = models.ForeignKey(to="Food") 37# fruit = models.ForeignKey(to="Fruit") 38 39 40# class Coupon(models.Model): 41# """ 42# id title table_id object_id 43# 1 面包九五折 1 1 44# 2 香蕉满10元减52 2 45# """ 46# title = models.CharField(max_length=32) 47# table = models.ForeignKey(to="Table") 48# object_id = models.IntegerField() 49# 50# 51# class Table(models.Model): 52# """ 53# id app_name table_name 54# 1 demo food 55# 2 demo fruit 56# """ 57# app_name = models.CharField(max_length=32) 58# table_name = models.CharField(max_length=32) 59 60 61class Coupon(models.Model): 62 title = models.CharField(max_length=32) 63 # 第一步:注意没有引号因为是导入的 64 content_type = models.ForeignKey(to=ContentType, on_delete=None) 65 # 第二步 66 object_id = models.IntegerField() 67 # 第三步 不会生成字段,用来操作增删改查 68 content_object = GenericForeignKey("content_type", "object_id")

models.py

1from django.shortcuts import render 2from rest_framework.views import APIView 3from rest_framework.response import Response 4from .models import Food, Coupon 5from django.contrib.contenttypes.models import ContentType 6 7# Create your views here. 8 9 10class DemoView(APIView): 11 12 def get(self, request): 13 # 给面包创建一个优惠券 14 food_obj = Food.objects.filter(id=1).first() 15 # Coupon.objects.create(title="面包九五折", content_type_id=8, object_id=1) 16 # Coupon.objects.create(title="双十一面包九折促销", content_object=food_obj) 17 18 #查询食物都有哪些优惠券 19 #定义了反向查询 20 coupons = food_obj.coupons.all() 21 print(coupons) 22 23 # 如果没定义反向查询 24 content = ContentType.objects.filter(app_label="app01", model="food").first() 25 coupons = Coupon.objects.filter(content_type=content, object_id=1).all() 26 print(coupons) 27 28 # 优惠券查对象 29 # 查询优惠券id=1绑定了哪个商品 30 coupon_obj = Coupon.objects.filter(id=1).first() 31 content_obj = coupon_obj.content_object 32 print(coupon_obj.title,content_obj.title) 33 34 # 通过ContentType表找表模型 35 content = ContentType.objects.filter(app_label="app01", model="food").first() 36 # content=food 获取food表的表模型用model_class() 37 model_class = content.model_class() 38 ret = model_class.objects.all() 39 print(ret) 40 41 return Response("ContentType测试")

views.py

media的配置

1#静态文件 2STATIC_URL = '/static/' 3STATICFILES_DIRS=( 4 os.path.join(BASE_DIR,'static'), 5) 6# Media配置 7MEDIA_URL = "media/" 8MEDIA_ROOT = os.path.join(BASE_DIR, "media")

settings.py

1from django.conf.urls import url, include 2from django.contrib import admin 3from django.views.static import serve 4from new_luffy import settings 5 6 7urlpatterns = [ 8 url(r'^admin/', admin.site.urls), 9 url(r'^api/course/', include("course.urls")), 10 11 # media路径配置 12 url(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}) 13]

urls.py

项目路由配置

1from django.contrib import admin 2from django.urls import path, include, re_path 3from django.views.static import serve 4from LuffyCity import settings 5from Login.views import GeetestView 6 7 8urlpatterns = [ 9 path('admin/', admin.site.urls), 10 path('api/course/', include("Course.urls")), 11 path('api/shop/', include("shopping.urls")), 12 path('api/', include("Login.urls")), 13 path('pc-geetest/register', GeetestView.as_view()), 14 path('pc-geetest/ajax_validate', GeetestView.as_view()), 15 16 17 # media路径配置 18 # path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}) 19 re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}) 20]

项目urls.py

一、课程模块

  1. 课程模块,包括了免费课程以及专题课程
  2. 主要是课程的展示,点击课程进入课程详细页面
  3. 课程详细页面展示,课程的概述,课程的价格策略,课程章节,评价以及常见问题

1、设计表结构

1from django.db import models 2# Create your models here. 3 4from django.db import models 5from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation 6from django.contrib.contenttypes.models import ContentType 7 8# Create your models here. 9__all__ = ["Category", "Course", "CourseDetail", "Teacher", "DegreeCourse", "CourseChapter", 10 "CourseSection", "PricePolicy", "OftenAskedQuestion", "Comment", "Account", "CourseOutline"] 11 12 13class Category(models.Model): 14 """课程分类表""" 15 title = models.CharField(max_length=32, unique=True, verbose_name="课程的分类") 16 17 def __str__(self): 18 return self.title 19 20 class Meta: 21 verbose_name = "01-课程分类表" 22 db_table = verbose_name 23 verbose_name_plural = verbose_name 24 25 26class Course(models.Model): 27 """课程表""" 28 title = models.CharField(max_length=128, unique=True, verbose_name="课程的名称") 29 course_img = models.ImageField(upload_to="course/%Y-%m", verbose_name='课程的图片') 30 category = models.ForeignKey(to="Category", verbose_name="课程的分类", on_delete=None) 31 32 33 COURSE_TYPE_CHOICES = ((0, "付费"), (1, "vip专享"), (2, "学位课程")) 34 course_type = models.SmallIntegerField(choices=COURSE_TYPE_CHOICES) 35 degree_course = models.ForeignKey(to="DegreeCourse", blank=True, null=True, help_text="如果是学位课程,必须关联学位表", on_delete=None) 36 37 38 brief = models.CharField(verbose_name="课程简介", max_length=1024) 39 level_choices = ((0, '初级'), (1, '中级'), (2, '高级')) 40 level = models.SmallIntegerField(choices=level_choices, default=1) 41 42 status_choices = ((0, '上线'), (1, '下线'), (2, '预上线')) 43 status = models.SmallIntegerField(choices=status_choices, default=0) 44 pub_date = models.DateField(verbose_name="发布日期", blank=True, null=True) 45 46 order = models.IntegerField("课程顺序", help_text="从上一个课程数字往后排") 47 study_num = models.IntegerField(verbose_name="学习人数", help_text="只要有人买课程,订单表加入数据的同时给这个字段+1") 48 49 # order_details = GenericRelation("OrderDetail", related_query_name="course") 50 # coupon = GenericRelation("Coupon") 51 # 只用于反向查询不生成字段 52 price_policy = GenericRelation("PricePolicy") 53 often_ask_questions = GenericRelation("OftenAskedQuestion") 54 course_comments = GenericRelation("Comment") 55 56 def save(self, *args, **kwargs): 57 if self.course_type == 2: 58 if not self.degree_course: 59 raise ValueError("学位课必须关联学位课程表") 60 super(Course, self).save(*args, **kwargs) 61 62 def __str__(self): 63 return self.title 64 65 class Meta: 66 verbose_name = "02-课程表" 67 db_table = verbose_name 68 verbose_name_plural = verbose_name 69 70 71class CourseDetail(models.Model): 72 """课程详细表""" 73 course = models.OneToOneField(to="Course", on_delete=None) 74 hours = models.IntegerField(verbose_name="课时", default=7) 75 course_slogan = models.CharField(max_length=125, blank=True, null=True, verbose_name="课程口号") 76 video_brief_link = models.CharField(max_length=255, blank=True, null=True) 77 summary = models.TextField(max_length=2048, verbose_name="课程概述") 78 why_study = models.TextField(verbose_name="为什么学习这门课程") 79 what_to_study_brief = models.TextField(verbose_name="我将学到哪些内容") 80 career_improvement = models.TextField(verbose_name="此项目如何有助于我的职业生涯") 81 prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024) 82 recommend_courses = models.ManyToManyField("Course", related_name="recommend_by", blank=True) 83 84 teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师") 85 86 def __str__(self): 87 return self.course.title 88 89 class Meta: 90 verbose_name = "03-课程详细表" 91 db_table = verbose_name 92 verbose_name_plural = verbose_name 93 94 95class Teacher(models.Model): 96 """讲师表""" 97 name = models.CharField(max_length=32, verbose_name="讲师名字") 98 brief = models.TextField(max_length=1024, verbose_name="讲师介绍") 99 100 def __str__(self): 101 return self.name 102 103 class Meta: 104 verbose_name = "04-教师表" 105 db_table = verbose_name 106 verbose_name_plural = verbose_name 107 108 109class DegreeCourse(models.Model): 110 """ 111 字段大体跟课程表相同,哪些不同根据业务逻辑去区分 112 """ 113 title = models.CharField(max_length=32, verbose_name="学位课程名字") 114 115 def __str__(self): 116 return self.title 117 118 class Meta: 119 verbose_name = "05-学位课程表" 120 db_table = verbose_name 121 verbose_name_plural = verbose_name 122 123 124class CourseChapter(models.Model): 125 """课程章节表""" 126 course = models.ForeignKey(to="Course", related_name="course_chapters", on_delete=None) 127 chapter = models.SmallIntegerField(default=1, verbose_name="第几章") 128 title = models.CharField(max_length=32, verbose_name="课程章节名称") 129 130 def __str__(self): 131 return self.title 132 133 class Meta: 134 verbose_name = "06-课程章节表" 135 db_table = verbose_name 136 verbose_name_plural = verbose_name 137 unique_together = ("course", "chapter") 138 139 140class CourseSection(models.Model): 141 """课时表""" 142 chapter = models.ForeignKey(to="CourseChapter", related_name="course_sections", on_delete=None) 143 title = models.CharField(max_length=32, verbose_name="课时") 144 section_order = models.SmallIntegerField(verbose_name="课时排序", help_text="建议每个课时之间空1至2个值,以备后续插入课时") 145 section_type_choices = ((0, '文档'), (1, '练习'), (2, '视频')) 146 free_trail = models.BooleanField("是否可试看", default=False) 147 section_type = models.SmallIntegerField(default=2, choices=section_type_choices) 148 section_link = models.CharField(max_length=255, blank=True, null=True, help_text="若是video,填vid,若是文档,填link") 149 150 def course_chapter(self): 151 return self.chapter.chapter 152 153 def course_name(self): 154 return self.chapter.course.title 155 156 def __str__(self): 157 return "%s-%s" % (self.chapter, self.title) 158 159 class Meta: 160 verbose_name = "07-课程课时表" 161 db_table = verbose_name 162 verbose_name_plural = verbose_name 163 unique_together = ('chapter', 'section_link') 164 165 166class PricePolicy(models.Model): 167 """价格策略表""" 168 content_type = models.ForeignKey(ContentType, on_delete=None) # 关联course or degree_course 169 object_id = models.PositiveIntegerField() 170 content_object = GenericForeignKey('content_type', 'object_id') 171 172 valid_period_choices = ((1, '1天'), (3, '3天'), 173 (7, '1周'), (14, '2周'), 174 (30, '1个月'), 175 (60, '2个月'), 176 (90, '3个月'), 177 (120, '4个月'), 178 (180, '6个月'), (210, '12个月'), 179 (540, '18个月'), (720, '24个月'), 180 (722, '24个月'), (723, '24个月'), 181 ) 182 valid_period = models.SmallIntegerField(choices=valid_period_choices) 183 price = models.FloatField() 184 185 def __str__(self): 186 return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price) 187 188 class Meta: 189 verbose_name = "08-价格策略表" 190 db_table = verbose_name 191 verbose_name_plural = verbose_name 192 unique_together = ("content_type", 'object_id', "valid_period") 193 194 195class OftenAskedQuestion(models.Model): 196 """常见问题""" 197 content_type = models.ForeignKey(ContentType, on_delete=None) # 关联course or degree_course 198 object_id = models.PositiveIntegerField() 199 content_object = GenericForeignKey('content_type', 'object_id') 200 201 question = models.CharField(max_length=255) 202 answer = models.TextField(max_length=1024) 203 204 def __str__(self): 205 return "%s-%s" % (self.content_object, self.question) 206 207 class Meta: 208 verbose_name = "09-常见问题表" 209 db_table = verbose_name 210 verbose_name_plural = verbose_name 211 unique_together = ('content_type', 'object_id', 'question') 212 213 214class Comment(models.Model): 215 """通用的评论表""" 216 content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=None) 217 object_id = models.PositiveIntegerField(blank=True, null=True) 218 content_object = GenericForeignKey('content_type', 'object_id') 219 220 content = models.TextField(max_length=1024, verbose_name="评论内容") 221 account = models.ForeignKey("Account", verbose_name="会员名", on_delete=None) 222 date = models.DateTimeField(auto_now_add=True) 223 224 def __str__(self): 225 return self.content 226 227 class Meta: 228 verbose_name = "10-评价表" 229 db_table = verbose_name 230 verbose_name_plural = verbose_name 231 232 233class Account(models.Model): 234 username = models.CharField(max_length=32, verbose_name="用户姓名") 235 pwd = models.CharField(max_length=32, verbose_name="密文密码") 236 # head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png', 237 # verbose_name="个人头像") 238 balance = models.IntegerField(verbose_name="贝里余额", default=0) 239 240 def __str__(self): 241 return self.username 242 243 class Meta: 244 verbose_name = "11-用户表" 245 db_table = verbose_name 246 verbose_name_plural = verbose_name 247 248 249class CourseOutline(models.Model): 250 """课程大纲""" 251 course_detail = models.ForeignKey(to="CourseDetail", related_name="course_outline", on_delete=None) 252 title = models.CharField(max_length=128) 253 order = models.PositiveSmallIntegerField(default=1) 254 # 前端显示顺序 255 256 content = models.TextField("内容", max_length=2048) 257 258 def __str__(self): 259 return "%s" % self.title 260 261 class Meta: 262 verbose_name = "12-课程大纲表" 263 db_table = verbose_name 264 verbose_name_plural = verbose_name 265 unique_together = ('course_detail', 'title')

Course中models.py

2、接口的编写

  • 课程这个模块,所有的功能都是展示,基于数据展示的,通常称为数据接口
  • 课程页面:有课程所有分类这个接口,有展示课程的接口
  • 课程详情页面:详情页面的数据接口
  • 详情页面下的子路由对应子组件的数据接口:课程章节课时、课程的评论、课程的常见问题

1from django.urls import path 2from .views import CategoryView, CourseView, CourseDetailView, CourseChapterView, CourseCommentView, QuestionView 3from .video_view import PolyvView 4 5 6urlpatterns = [ 7 path('category', CategoryView.as_view()), 8 path('list', CourseView.as_view()), 9 path('detail/<int:pk>', CourseDetailView.as_view()), 10 path('chapter/<int:pk>', CourseChapterView.as_view()), 11 path('comment/<int:pk>', CourseCommentView.as_view()), 12 path('question/<int:pk>', QuestionView.as_view()), 13 path('polyv', PolyvView.as_view()), 14 15]

Course中urls.py

1from rest_framework import serializers 2from . import models 3 4 5class CategorySerializer(serializers.ModelSerializer): 6 class Meta: 7 model = models.Category 8 fields = "__all__" 9 10 11class CourseSerializer(serializers.ModelSerializer): 12 level = serializers.CharField(source="get_level_display") 13 price = serializers.SerializerMethodField() 14 15 def get_price(self, obj): 16 print(obj.price_policy.all()) 17 return obj.price_policy.all().order_by("price").first().price 18 19 class Meta: 20 model = models.Course 21 fields = ["id", "title", "course_img", "brief", "level", "study_num", "price"] 22 23 24class CourseDetailSerializer(serializers.ModelSerializer): 25 level = serializers.CharField(source="course.get_level_display") 26 study_num = serializers.IntegerField(source="course.study_num") 27 recommend_courses = serializers.SerializerMethodField() 28 teachers = serializers.SerializerMethodField() 29 price_policy = serializers.SerializerMethodField() 30 course_outline = serializers.SerializerMethodField() 31 32 33 def get_course_outline(self, obj): 34 return [{"id": outline.id, "title": outline.title, "content": outline.content} for outline in obj.course_outline.all().order_by("order")] 35 36 37 def get_price_policy(self, obj): 38 return [{"id": price.id, "valid_price_display": price.get_valid_period_display(), "price": price.price} for price in obj.course.price_policy.all()] 39 40 41 def get_teachers(self, obj): 42 return [{"id": teacher.id, "name": teacher.name} for teacher in obj.teachers.all()] 43 44 def get_recommend_courses(self, obj): 45 return [{"id": course.id, "title": course.title} for course in obj.recommend_courses.all()] 46 47 class Meta: 48 model = models.CourseDetail 49 fields = ["id", "hours", "summary", "level", "study_num", "recommend_courses", "teachers", 50 "price_policy", "course_outline"] 51 52 53class CourseChapterSerializer(serializers.ModelSerializer): 54 sections = serializers.SerializerMethodField() 55 56 def get_sections(self, obj): 57 return [{"id": section.id, "title": section.title, "free_trail": section.free_trail} for section in obj.course_sections.all().order_by("section_order")] 58 59 class Meta: 60 model = models.CourseChapter 61 fields = ["id", "title", "sections"] 62 63 64class CourseCommentSerializer(serializers.ModelSerializer): 65 account = serializers.CharField(source="account.username") 66 67 class Meta: 68 model = models.Comment 69 fields = ["id", "account", "content", "date"] 70 71 72class QuestionSerializer(serializers.ModelSerializer): 73 class Meta: 74 model = models.OftenAskedQuestion 75 fields = ["id", "question", "answer"]

Course中serializers.py

1from django.shortcuts import render 2from rest_framework.views import APIView 3from rest_framework.response import Response 4from . import models 5from .serializers import CategorySerializer, CourseSerializer, CourseDetailSerializer, CourseChapterSerializer 6from .serializers import CourseCommentSerializer, QuestionSerializer 7 8# Create your views here. 9 10 11class CategoryView(APIView): 12 """课程分类接口""" 13 def get(self, request): 14 # 通过ORM操作获取所有分类数据 15 queryset = models.Category.objects.all() 16 # 利用序列化器去序列化我们的数据 17 ser_obj = CategorySerializer(queryset, many=True) 18 # 返回 19 return Response(ser_obj.data) 20 21 22class CourseView(APIView): 23 """查看所有免费课程的接口""" 24 def get(self, request): 25 # 获取过滤条件中的分类ID 26 category_id = request.query_params.get("category", 0) 27 # 根据分类获取课程 28 if category_id == 0: 29 # 证明没有分类,可以拿所有的课程数据 30 queryset = models.Course.objects.all().order_by("order") 31 else: 32 queryset = models.Course.objects.filter(category_id=category_id).all().order_by("order") 33 # 序列化课程数据 34 ser_obj = CourseSerializer(queryset, many=True) 35 # 返回 36 return Response(ser_obj.data) 37 38 39class CourseDetailView(APIView): 40 """课程详情页面""" 41 def get(self, request, pk): 42 # 根据pk获取到课程详情对象 43 course_detail_obj = models.CourseDetail.objects.filter(course__id=pk).first() 44 if not course_detail_obj: 45 return Response({"code": 1001, "error": "查询的课程详情不存在"}) 46 # 序列化课程详情 47 ser_obj = CourseDetailSerializer(course_detail_obj) 48 # 返回 49 return Response(ser_obj.data) 50 51 52class CourseChapterView(APIView): 53 """课程章节接口""" 54 def get(self, request, pk): 55 # 数据结构["第一章": {课时一, 课时二}] 56 queryset = models.CourseChapter.objects.filter(course_id=pk).all().order_by("chapter") 57 # 序列化章节对象 58 ser_obj = CourseChapterSerializer(queryset, many=True) 59 # 返回 60 return Response(ser_obj.data) 61 62 63class CourseCommentView(APIView): 64 def get(self, request, pk): 65 # 通过课程id找到课程所有的评论 66 queryset = models.Course.objects.filter(id=pk).first().course_comments.all() 67 # 序列化 68 ser_obj = CourseCommentSerializer(queryset, many=True) 69 # 返回 70 return Response(ser_obj.data) 71 72 73class QuestionView(APIView): 74 def get(self, request, pk): 75 queryset = models.Course.objects.filter(id=pk).first().often_ask_questions.all() 76 ser_obj = QuestionSerializer(queryset, many=True) 77 return Response(ser_obj.data)

Course中views.py

1from django.contrib import admin 2 3# Register your models here. 4from . import models 5 6for table in models.__all__: 7 admin.site.register(getattr(models, table))

Course中admin.py

二、登录认证模块(token存Redis)

  • 以前前后端不分离用cookie,session解决,现在前后端分离使用token令牌。
  • 用户登录成功后,生成一个随机字符串token给前端返回
  • 前端以后都携带这个token来访问,这样后端只需要鉴别这个token就可以做认证

1import redis 2POOL = redis.ConnectionPool(host="127.0.0.1", port=6379, decode_responses=True, max_connections=10)

utils中redis_pool.py

1class BaseResponse(object): 2 3 def __init__(self): 4 self.code = 1000 5 self.data = None 6 self.error = None 7 8 @property 9 def dict(self): 10 return self.__dict__

utils中base_response.py

1from rest_framework.authentication import BaseAuthentication 2from rest_framework.exceptions import AuthenticationFailed 3from .redis_pool import POOL 4from Course.models import Account 5import redis 6 7 8 9CONN = redis.Redis(connection_pool=POOL) 10 11 12class LoginAuth(BaseAuthentication): 13 def authenticate(self, request): 14 # 从请求头中获取前端带过来的token 15 token = request.META.get("HTTP_AUTHENTICATION", "") 16 if not token: 17 raise AuthenticationFailed("没有携带token") 18 # 去redis比对 19 user_id = CONN.get(str(token)) 20 if user_id == None: 21 raise AuthenticationFailed("token过期") 22 user_obj = Account.objects.filter(id=user_id).first() 23 return user_obj, token

utils中my_auth.py

1urlpatterns = [ 2 path('register', RegisterView.as_view()), 3 path('login', LoginView.as_view()), 4 path('test_auth', TestView.as_view()), 5 6]

Login中urls.py

1from rest_framework import serializers 2from Course.models import Account 3import hashlib 4 5 6class RegisterSerializer(serializers.ModelSerializer): 7 8 class Meta: 9 model = Account 10 fields = "__all__" 11 12 def create(self, validated_data): 13 pwd = validated_data["pwd"] 14 pwd_salt = "luffy_password" + pwd 15 md5_str = hashlib.md5(pwd_salt.encode()).hexdigest() 16 user_obj = Account.objects.create(username=validated_data["username"], pwd=md5_str) 17 return user_obj

Login中serializers.py

1from django.shortcuts import render 2from rest_framework.views import APIView 3from rest_framework.response import Response 4from .serializers import RegisterSerializer 5from utils.base_response import BaseResponse 6from Course.models import Account 7from utils.redis_pool import POOL 8import redis 9import uuid 10from utils.my_auth import LoginAuth 11from utils.geetest import GeetestLib 12from django.http import HttpResponse 13import json 14 15# Create your views here. 16 17 18class RegisterView(APIView): 19 20 def post(self, request): 21 res = BaseResponse() 22 # 用序列化器做校验 23 ser_obj = RegisterSerializer(data=request.data) 24 if ser_obj.is_valid(): 25 ser_obj.save() 26 res.data = ser_obj.data 27 else: 28 res.code = 1020 29 res.error = ser_obj.errors 30 return Response(res.dict) 31 32 33class LoginView(APIView): 34 35 def post(self, request): 36 res = BaseResponse() 37 username = request.data.get("username", "") 38 pwd = request.data.get("pwd", "") 39 user_obj = Account.objects.filter(username=username, pwd=pwd).first() 40 if not user_obj: 41 res.code = 1030 42 res.error = "用户名或密码错误" 43 return Response(res.dict) 44 45 # 用户登录成功生成一个token写入redis 46 # 写入redis token : user_id 47 conn = redis.Redis(connection_pool=POOL) 48 try: 49 token = uuid.uuid4() 50 # conn.set(str(token), user_obj.id, ex=10) 51 conn.set(str(token), user_obj.id) 52 res.data = token 53 except Exception as e: 54 print(e) 55 res.code = 1031 56 res.error = "创建令牌失败" 57 return Response(res.dict) 58 59 60class TestView(APIView): 61 authentication_classes = [LoginAuth, ] 62 63 def get(self, request): 64 return Response("认证测试") 65 66 67pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c" 68pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4" 69REDIS_CONN = redis.Redis(connection_pool=POOL) 70 71 72class GeetestView(APIView): 73 74 def get(self, request): 75 user_id = 'test' 76 gt = GeetestLib(pc_geetest_id, pc_geetest_key) 77 status = gt.pre_process(user_id) 78 # request.session[gt.GT_STATUS_SESSION_KEY] = status 79 REDIS_CONN.set(gt.GT_STATUS_SESSION_KEY, status) 80 # request.session["user_id"] = user_id 81 REDIS_CONN.set("gt_user_id", user_id) 82 response_str = gt.get_response_str() 83 return HttpResponse(response_str) 84 85 def post(self, request): 86 # print(request.session.get("user_id")) 87 print(request.META.get("HTTP_AUTHENTICATION")) 88 print(request.data) 89 gt = GeetestLib(pc_geetest_id, pc_geetest_key) 90 challenge = request.data.get(gt.FN_CHALLENGE, '') 91 validate = request.data.get(gt.FN_VALIDATE, '') 92 seccode = request.data.get(gt.FN_SECCODE, '') 93 # username 94 # pwd 95 # status = request.session.get(gt.GT_STATUS_SESSION_KEY) 96 # print(status) 97 # user_id = request.session.get("user_id") 98 # print(user_id) 99 status = REDIS_CONN.get(gt.GT_STATUS_SESSION_KEY) 100 user_id = REDIS_CONN.get("gt_user_id") 101 if status: 102 result = gt.success_validate(challenge, validate, seccode, user_id) 103 else: 104 result = gt.failback_validate(challenge, validate, seccode) 105 result = {"status": "success"} if result else {"status": "fail"} 106 # if result: 107 # # 证明验证码通过 108 # # 判断用户名和密码 109 # else: 110 # # 返回验证码错误 111 return HttpResponse(json.dumps(result))

Login中views.py

三、登录认证模块(token存mysql)

1# 拓展之前课程模块下的用户表 2class Account(models.Model): 3 username = models.CharField(max_length=32, verbose_name="用户姓名", unique=True) 4 password = models.CharField(max_length=32, verbose_name="用户密码") 5 # head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png', 6 # verbose_name="个人头像") 7 token = models.UUIDField(null=True, blank=True) 8 9 def __str__(self): 10 return self.username 11 12 class Meta: 13 verbose_name = "11-用户表" 14 db_table = verbose_name 15 verbose_name_plural = verbose_name

models.py 扩展之前功能模块的用户表

1class BaseResponse(object): 2 3 def __init__(self): 4 self.code = 1000 5 self.data = None 6 self.error = None 7 8 @property 9 def dict(self): 10 return self.__dict__

utils中base_response.py

1from rest_framework.authentication import BaseAuthentication 2from rest_framework.exceptions import AuthenticationFailed 3from Course.models import Account 4# django 提供的拿时间的接口 提供的是根据django配置的时区拿到的当前时间 5from django.utils.timezone import now 6 7 8class LoginAuth(BaseAuthentication): 9 def authenticate(self, request): 10 # 从请求头中获取前端带过来的token 11 token = request.META.get("HTTP_AUTHENTICATION", "") 12 if not token: 13 raise AuthenticationFailed("没有携带token") 14 # 去redis比对 15 user_obj = Account.objects.filter(token=token).first() 16 if not user_obj: 17 raise AuthenticationFailed("token过期") 18 else: 19 old_time = user_obj.create_token_time 20 if (now() - old_time).days > 7: 21 raise AuthenticationFailed({"code": 1020, "error": "无效的token"}) 22 return user_obj, token

utils中my_auth.py

1urlpatterns = [ 2 path('register', RegisterView.as_view()), 3 path('login', LoginView.as_view()), 4 path('test_auth', TestView.as_view()), 5 6]

Login中urls.py

1from rest_framework import serializers 2from Course.models import Account 3import hashlib 4 5 6class RegisterSerializer(serializers.ModelSerializer): 7 8 class Meta: 9 model = Account 10 fields = "__all__" 11 12 def create(self, validated_data): 13 pwd = validated_data["pwd"] 14 pwd_salt = "luffy_password" + pwd 15 md5_str = hashlib.md5(pwd_salt.encode()).hexdigest() 16 user_obj = Account.objects.create(username=validated_data["username"], pwd=md5_str) 17 return user_obj

Login中serializers.py

1from django.shortcuts import render 2from rest_framework.views import APIView 3from rest_framework.response import Response 4from .serializers import RegisterSerializer 5from utils.base_response import BaseResponse 6from Course.models import Account 7import uuid 8 9 10 11# Create your views here. 12 13class RegisterView(APIView): 14 15 def post(self, request): 16 res = BaseResponse() 17 # 用序列化器做校验 18 ser_obj = RegisterSerializer(data=request.data) 19 if ser_obj.is_valid(): 20 ser_obj.save() 21 res.data = ser_obj.data 22 else: 23 res.code = 1020 24 res.error = ser_obj.errors 25 return Response(res.dict) 26 27 28class LoginView(APIView): 29 30 def post(self, request): 31 res = BaseResponse() 32 username = request.data.get("username", "") 33 pwd = request.data.get("pwd", "") 34 user_obj = Account.objects.filter(username=username, pwd=pwd).first() 35 if not user_obj: 36 res.code = 1030 37 res.error = "用户名或密码错误" 38 return Response(res.dict) 39 40 try: 41 token = uuid.uuid4() 42 ### 43 user_obj.update(token=token) 44 res.data = token 45 except Exception as e: 46 print(e) 47 res.code = 1031 48 res.error = "创建令牌失败" 49 return Response(res.dict) 50 51# 所有这是一个需要认证的接口 52class TestView(APIView): 53 authentication_classes = [LoginAuth, ] 54 55 def get(self, request): 56 return Response("认证测试")

Login中views.py

四、购物车模块

  1. 用户点击商品加入购物车,个人中心可以查看自己所有购物车中数据
  2. 在购物车中可以删除课程,还可以更新购物车中课程的价格策略
  3. 所以接口应该有四种请求方式, get,post,patch,delete
  4. 因为购物车是属于中间状态数据而且很多时候需要过期时间所以选择存储到redis

1from django.urls import path 2from .views import ShoppingCarView 3from .settlement_view import SettlementView 4from .payment_view import PaymentView 5 6urlpatterns = [ 7 path('shopping_car', ShoppingCarView.as_view()), 8 path('settlement', SettlementView.as_view()), 9 path('payment', PaymentView.as_view()), 10]

shopping中urls.py

1from rest_framework.authentication import BaseAuthentication 2from rest_framework.exceptions import AuthenticationFailed 3from .redis_pool import POOL 4from Course.models import Account 5import redis 6 7 8 9CONN = redis.Redis(connection_pool=POOL) 10 11 12class LoginAuth(BaseAuthentication): 13 def authenticate(self, request): 14 # 从请求头中获取前端带过来的token 15 token = request.META.get("HTTP_AUTHENTICATION", "") 16 if not token: 17 raise AuthenticationFailed("没有携带token") 18 # 去redis比对 19 user_id = CONN.get(str(token)) 20 if user_id == None: 21 raise AuthenticationFailed("token过期") 22 user_obj = Account.objects.filter(id=user_id).first() 23 return user_obj, token

utils中my_auth.py

1from django.shortcuts import render 2from rest_framework.views import APIView 3from rest_framework.response import Response 4from utils.base_response import BaseResponse 5from utils.my_auth import LoginAuth 6from utils.redis_pool import POOL 7from Course.models import Course 8import json 9import redis 10 11# Create your views here. 12 13# 前端传过来 course_id price_policy_id 14# 把购物车数据放入redis 15""" 16{ 17 SHOPPINGCAR_USERID_COURSE_ID: { 18 "id", 课程id 19 "title", 课程标题 20 "course_img", 21 "price_policy_dict": { 22 price_policy_id: "{valid_period, price, valid_period_display}" 23 price_policy_id2: "{valid_period, price, valid_period_display}" 24 price_policy_id3: "{valid_period, price, valid_period_display}" 25 26 }, 27 "default_price_policy_id": 1 默认选中的价格id 28 29 30 } 31 32 33} 34""" 35 36SHOPPINGCAR_KEY = "SHOPPINGCAR_%s_%s" 37CONN = redis.Redis(connection_pool=POOL) 38 39 40class ShoppingCarView(APIView): 41 authentication_classes = [LoginAuth, ] 42 # 给购物车增加商品 43 def post(self, request): 44 res = BaseResponse() 45 try: 46 # 1, 获取前端传过来的数据以及user_id 47 course_id = request.data.get("course_id", "") 48 price_policy_id = request.data.get("price_policy_id", "") 49 user_id = request.user.pk 50 # 2, 校验数据的合法性 51 # 2.1 校验课程id合法性 52 course_obj = Course.objects.filter(id=course_id).first() 53 if not course_obj: 54 res.code = 1040 55 res.error = "课程id不合法" 56 return Response(res.dict) 57 # 2.2 校验价格策略id是否合法 58 price_policy_queryset = course_obj.price_policy.all() 59 price_policy_dict = {} 60 for price_policy in price_policy_queryset: 61 price_policy_dict[price_policy.id] = { 62 "price": price_policy.price, 63 "valid_period": price_policy.valid_period, 64 "valid_period_display": price_policy.get_valid_period_display() 65 } 66 if price_policy_id not in price_policy_dict: 67 res.code = 1041 68 res.error = "价格策略id不合法" 69 return Response(res.dict) 70 # 3,构建redisKEY 71 key = SHOPPINGCAR_KEY % (user_id, course_id) 72 # 4,构建数据结构 73 course_info = { 74 "id": course_obj.id, 75 "title": course_obj.title, 76 "course_img": str(course_obj.course_img), 77 "price_policy_dict": json.dumps(price_policy_dict, ensure_ascii=False), 78 "default_price_policy_id": price_policy_id 79 } 80 # 5 写入redis 81 CONN.hmset(key, course_info) 82 res.data = "加入购物车成功" 83 except Exception as e: 84 res.code = 1012 85 res.error = "加入购物车失败" 86 return Response(res.dict) 87 88 def get(self, request): 89 res = BaseResponse() 90 try: 91 # 1, 拼接redis key 92 user_id = request.user.pk 93 shopping_car_key = SHOPPINGCAR_KEY % (user_id, "*") 94 # 2, 去redis中读取数据 95 # 2.1 模糊匹配所有的keys 96 # 3,构建数据结构展示 97 all_keys = CONN.scan_iter(shopping_car_key) 98 ret = [] 99 for key in all_keys: 100 ret.append(CONN.hgetall(key)) 101 res.data = ret 102 except Exception as e: 103 res.code = 1013 104 res.error = "获取购物车失败" 105 return Response(res.dict) 106 107 def put(self, request): 108 # 前端 course_id price_policy_id 109 res = BaseResponse() 110 try: 111 # 1, 获取前端传过来的数据以及user_id 112 course_id = request.data.get("course_id", "") 113 price_policy_id = request.data.get("price_policy_id", "") 114 user_id = request.user.pk 115 # 2, 校验数据的合法性 116 # 2.1 course_id是否合法 117 key = SHOPPINGCAR_KEY % (user_id, course_id) 118 if not CONN.exists(key): 119 res.code = 1043 120 res.error = "课程id不合法" 121 return Response(res.dict) 122 # 2,2 price_policy_id是否合法 123 price_policy_dict = json.loads(CONN.hget(key, "price_policy_dict")) 124 if str(price_policy_id) not in price_policy_dict: 125 res.code = 1044 126 res.error = "价格策略不合法" 127 return Response(res.dict) 128 # 3, 更新redis default_price_policy_id 129 CONN.hset(key, "default_price_policy_id", price_policy_id) 130 res.data = "更新成功" 131 except Exception as e: 132 res.code = 1014 133 res.error = "更新购物车失败" 134 return Response(res.dict) 135 136 def delete(self, request): 137 # course_list = [course_id, ] 138 res = BaseResponse() 139 try: 140 # 1 获取前端传来的数据以及user_id 141 course_list = request.data.get("course_list", "") 142 user_id = request.user.pk 143 # 2 校验course_id是否合法 144 for course_id in course_list: 145 key = SHOPPINGCAR_KEY % (user_id, course_id) 146 if not CONN.exists(key): 147 res.code = 1045 148 res.error = "课程ID不合法" 149 return Response(res.dict) 150 # 3, 删除redis数据 151 CONN.delete(key) 152 res.data = "删除成功" 153 except Exception as e: 154 res.code = 1014 155 res.error = "删除购物车失败" 156 return Response(res.dict)

shopping中views.py

五、结算中心模块

结算中心要开始选择优惠券了,有单独的课程优惠券还有全局优惠券。

1from django.db import models 2 3# Create your models here. 4 5from django.db import models 6from django.contrib.contenttypes.models import ContentType 7from django.contrib.contenttypes.fields import GenericForeignKey 8from Course.models import Account 9 10# Create your models here. 11__all__ = ["Coupon", "CouponRecord", "Order", "OrderDetail", "TransactionRecord"] 12 13 14class Coupon(models.Model): 15 """优惠券生成规则""" 16 name = models.CharField(max_length=64, verbose_name="活动名称") 17 brief = models.TextField(blank=True, null=True, verbose_name="优惠券介绍") 18 coupon_type_choices = ((0, '通用券'), (1, '满减券'), (2, '折扣券')) 19 coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券类型") 20 21 money_equivalent_value = models.IntegerField(verbose_name="等值货币", null=True, blank=True, default=0) 22 off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只针对折扣券,例7.9折,写79", blank=True, null=True, default=100) 23 minimum_consume = models.PositiveIntegerField("最低消费", default=0, help_text="仅在满减券时填写此字段", null=True, blank=True) 24 25 content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=None) 26 object_id = models.PositiveIntegerField("绑定课程", blank=True, null=True, help_text="可以把优惠券跟课程绑定") 27 # 不绑定代表全局优惠券 28 content_object = GenericForeignKey('content_type', 'object_id') 29 30 open_date = models.DateField("优惠券领取开始时间") 31 close_date = models.DateField("优惠券领取结束时间") 32 valid_begin_date = models.DateField(verbose_name="有效期开始时间", blank=True, null=True) 33 valid_end_date = models.DateField(verbose_name="有效结束时间", blank=True, null=True) 34 coupon_valid_days = models.PositiveIntegerField(verbose_name="优惠券有效期(天)", blank=True, null=True, 35 help_text="自券被领时开始算起") 36 date = models.DateTimeField(auto_now_add=True) 37 38 class Meta: 39 verbose_name_plural = "13. 优惠券生成规则记录" 40 db_table = verbose_name_plural 41 verbose_name = verbose_name_plural 42 43 def __str__(self): 44 return "%s(%s)" % (self.get_coupon_type_display(), self.name) 45 46 def save(self, *args, **kwargs): 47 if not self.coupon_valid_days or (self.valid_begin_date and self.valid_end_date): 48 if self.valid_begin_date and self.valid_end_date: 49 if self.valid_end_date <= self.valid_begin_date: 50 raise ValueError("valid_end_date 有效期结束日期必须晚于 valid_begin_date ") 51 if self.coupon_valid_days == 0: 52 raise ValueError("coupon_valid_days 有效期不能为0") 53 if self.close_date < self.open_date: 54 raise ValueError("close_date 优惠券领取结束时间必须晚于 open_date优惠券领取开始时间 ") 55 56 super(Coupon, self).save(*args, **kwargs) 57 58 59class CouponRecord(models.Model): 60 """优惠券发放、消费纪录""" 61 coupon = models.ForeignKey("Coupon", on_delete=None) 62 number = models.CharField(max_length=64, unique=True, verbose_name="用户优惠券记录的流水号") 63 account = models.ForeignKey(to=Account, verbose_name="拥有者", on_delete=None) 64 status_choices = ((0, '未使用'), (1, '已使用'), (2, '已过期')) 65 status = models.SmallIntegerField(choices=status_choices, default=0) 66 get_time = models.DateTimeField(verbose_name="领取时间", help_text="用户领取时间") 67 used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用时间") 68 order = models.ForeignKey("Order", blank=True, null=True, verbose_name="关联订单", on_delete=None) # 一个订单可以有多个优惠券 69 70 class Meta: 71 verbose_name_plural = "14. 用户优惠券领取使用记录表" 72 db_table = verbose_name_plural 73 verbose_name = verbose_name_plural 74 75 def __str__(self): 76 return '%s-%s-%s' % (self.account, self.number, self.status) 77 78 79class Order(models.Model): 80 """订单""" 81 payment_type_choices = ((0, '微信'), (1, '支付宝'), (2, '优惠码'), (3, '贝里')) 82 payment_type = models.SmallIntegerField(choices=payment_type_choices) 83 84 payment_number = models.CharField(max_length=128, verbose_name="支付第3方订单号", null=True, blank=True) 85 order_number = models.CharField(max_length=128, verbose_name="订单号", unique=True) # 考虑到订单合并支付的问题 86 account = models.ForeignKey(to=Account, on_delete=None) 87 actual_amount = models.FloatField(verbose_name="实付金额") 88 89 status_choices = ((0, '交易成功'), (1, '待支付'), (2, '退费申请中'), (3, '已退费'), (4, '主动取消'), (5, '超时取消')) 90 status = models.SmallIntegerField(choices=status_choices, verbose_name="状态") 91 date = models.DateTimeField(auto_now_add=True, verbose_name="订单生成时间") 92 pay_time = models.DateTimeField(blank=True, null=True, verbose_name="付款时间") 93 cancel_time = models.DateTimeField(blank=True, null=True, verbose_name="订单取消时间") 94 95 class Meta: 96 verbose_name_plural = "15. 订单表" 97 db_table = verbose_name_plural 98 verbose_name = verbose_name_plural 99 100 def __str__(self): 101 return "%s" % self.order_number 102 103 104class OrderDetail(models.Model): 105 """订单详情""" 106 order = models.ForeignKey("Order", on_delete=None) 107 108 content_type = models.ForeignKey(ContentType, on_delete=None) # 可关联普通课程或学位 109 object_id = models.PositiveIntegerField() 110 content_object = GenericForeignKey('content_type', 'object_id') 111 112 original_price = models.FloatField("课程原价") 113 price = models.FloatField("折后价格") 114 valid_period_display = models.CharField("有效期显示", max_length=32) # 在订单页显示 115 valid_period = models.PositiveIntegerField("有效期(days)") # 课程有效期 116 memo = models.CharField(max_length=255, blank=True, null=True, verbose_name="备忘录") 117 118 def __str__(self): 119 return "%s - %s - %s" % (self.order, self.content_type, self.price) 120 121 class Meta: 122 verbose_name_plural = "16. 订单详细" 123 db_table = verbose_name_plural 124 verbose_name = verbose_name_plural 125 126 127class TransactionRecord(models.Model): 128 """贝里交易纪录""" 129 account = models.ForeignKey(to=Account, on_delete=None) 130 amount = models.IntegerField("金额") 131 balance = models.IntegerField("账户余额") 132 transaction_type_choices = ((0, '收入'), (1, '支出'), (2, '退款'), (3, "提现")) # 2 为了处理 订单过期未支付时,锁定期贝里的回退 133 transaction_type = models.SmallIntegerField(choices=transaction_type_choices) 134 transaction_number = models.CharField(unique=True, verbose_name="流水号", max_length=128) 135 date = models.DateTimeField(auto_now_add=True) 136 memo = models.CharField(max_length=128, blank=True, null=True, verbose_name="备忘录") 137 138 class Meta: 139 verbose_name_plural = "17. 贝里交易记录" 140 db_table = verbose_name_plural 141 verbose_name = verbose_name_plural 142 143 def __str__(self): 144 return "%s" % self.transaction_number

shopping中models.py

1from rest_framework.views import APIView 2from rest_framework.response import Response 3from utils.base_response import BaseResponse 4from utils.redis_pool import POOL 5from django.utils.timezone import now 6from utils.my_auth import LoginAuth 7import redis 8from .views import SHOPPINGCAR_KEY 9from .models import CouponRecord 10import json 11 12 13CONN = redis.Redis(connection_pool=POOL) 14SETTLEMENT_KEY = "SETTLEMENT_%s_%s" 15GLOBAL_COUPON_KEY = "GLOBAL_COUPON_%s" 16""" 17结算中心 18在购物车里选择了商品以及价格策略点击结算 才进入结算中心 19在结算中心用户可以选择优惠券 20 21前端传过来数据 course_list 22redis = { 23 settlement_userid_courseid: { 24 id, 课程id, 25 title, 26 course_img, 27 valid_period_display, 28 price, 29 course_coupon_dict: { 30 coupon_id: {优惠券信息} 31 coupon_id2: {优惠券信息} 32 coupon_id3: {优惠券信息} 33 } 34 # 默认不给你选 这个字段只有更新的时候才添加 35 default_coupon_id: 1 36 } 37 38 global_coupon_userid: { 39 coupon_id: {优惠券信息} 40 coupon_id2: {优惠券信息} 41 coupon_id3: {优惠券信息}, 42 # 这个字段只有更新的时候才添加 43 # 在用户进入结算中心选择优惠券的时候 也就是更新请求的时候更改 44 default_global_coupon_id: 1 45 46 } 47 48} 49""" 50 51 52class SettlementView(APIView): 53 authentication_classes = [LoginAuth, ] 54 55 def post(self, request): 56 res = BaseResponse() 57 try: 58 # 1 获取前端的数据以及user_id 59 course_list = request.data.get("course_list", "") 60 user_id = request.user.pk 61 # 2 校验数据的合法性 62 for course_id in course_list: 63 # 2.1 判断course_id 是否在购物车中 64 shopping_car_key = SHOPPINGCAR_KEY % (user_id, course_id) 65 if not CONN.exists(shopping_car_key): 66 res.code = 1050 67 res.error = "课程ID不合法" 68 return Response(res.dict) 69 # 3 构建数据结构 70 # 3.1 获取用户的所有合法优惠券 71 user_all_coupons = CouponRecord.objects.filter( 72 account_id=user_id, 73 status=0, 74 coupon__valid_begin_date__lte=now(), 75 coupon__valid_end_date__gte=now(), 76 ).all() 77 print(user_all_coupons) 78 # 3.2 构建优惠券dict 79 course_coupon_dict = {} 80 global_coupon_dict = {} 81 for coupon_record in user_all_coupons: 82 coupon = coupon_record.coupon 83 if coupon.object_id == course_id: 84 course_coupon_dict[coupon.id] = { 85 "id": coupon.id, 86 "name": coupon.name, 87 "coupon_type": coupon.get_coupon_type_display(), 88 "object_id": coupon.object_id, 89 "money_equivalent_value": coupon.money_equivalent_value, 90 "off_percent": coupon.off_percent, 91 "minimum_consume": coupon.minimum_consume 92 } 93 elif coupon.object_id == "": 94 global_coupon_dict[coupon.id] = { 95 "id": coupon.id, 96 "name": coupon.name, 97 "coupon_type": coupon.get_coupon_type_display(), 98 "money_equivalent_value": coupon.money_equivalent_value, 99 "off_percent": coupon.off_percent, 100 "minimum_consume": coupon.minimum_consume 101 } 102 # 3.3 构建写入redis的数据结构 103 course_info = CONN.hgetall(shopping_car_key) 104 price_policy_dict = json.loads(course_info["price_policy_dict"]) 105 default_policy_id = course_info["default_price_policy_id"] 106 valid_period = price_policy_dict[default_policy_id]["valid_period_display"] 107 price = price_policy_dict[default_policy_id]["price"] 108 109 settlement_info = { 110 "id": course_info["id"], 111 "title": course_info["title"], 112 "course_img": course_info["course_img"], 113 "valid_period": valid_period, 114 "price": price, 115 "course_coupon_dict": json.dumps(course_coupon_dict, ensure_ascii=False) 116 } 117 # 4 写入redis 118 settlement_key = SETTLEMENT_KEY % (user_id, course_id) 119 global_coupon_key = GLOBAL_COUPON_KEY % user_id 120 CONN.hmset(settlement_key, settlement_info) 121 if global_coupon_dict: 122 CONN.hmset(global_coupon_key, global_coupon_dict) 123 # 5 删除购物车中的数据 124 CONN.delete(shopping_car_key) 125 res.data = "加入结算中心成功" 126 except Exception as e: 127 res.code = 1020 128 res.error = "结算失败" 129 return Response(res.dict) 130 131 def get(self, request): 132 res = BaseResponse() 133 try: 134 # 1, 获取user_id 135 user_id = request.user.pk 136 # 2, 拼接所有key 137 # 3, 去redis取数据 138 settlement_key = SETTLEMENT_KEY % (user_id, "*") 139 global_coupon_key = GLOBAL_COUPON_KEY % user_id 140 all_keys = CONN.scan_iter(settlement_key) 141 ret = [] 142 for key in all_keys: 143 ret.append(CONN.hgetall(key)) 144 global_coupon_info = CONN.hgetall(global_coupon_key) 145 res.data = { 146 "settlement_info": ret, 147 "global_coupon_dict": global_coupon_info 148 } 149 except Exception as e: 150 res.code = 1024 151 res.error = "获取结算中心失败" 152 return Response(res.dict) 153 154 def put(self, request): 155 # course_id course_coupon_id global_coupon_id 156 res = BaseResponse() 157 try: 158 # 1, 获取前端传过来数据 159 course_id = request.data.get("course_id", "") 160 course_coupon_id = request.data.get("course_coupon_id", "") 161 global_coupon_id = request.data.get("global_coupon_id", "") 162 user_id = request.user.pk 163 # 2, 校验数据合法性 164 # 2.1 校验course_id 165 key = SETTLEMENT_KEY % (user_id, course_id) 166 if course_id: 167 if not CONN.exists(key): 168 res.code = 1060 169 res.error = "课程ID不合法" 170 return Response(res.dict) 171 # 2.2 校验 course_coupon_id 172 if course_coupon_id: 173 course_coupon_dict = json.loads(CONN.hget(key, "course_coupon_dict")) 174 if str(course_coupon_id) not in course_coupon_dict: 175 res.code = 1061 176 res.error = "课程优惠券ID不合法" 177 return Response(res.dict) 178 # 2.3 校验global_coupon_id 179 if global_coupon_id: 180 global_coupon_key = GLOBAL_COUPON_KEY % user_id 181 if not CONN.exists(global_coupon_key): 182 res.code = 1062 183 res.error = "全局优惠券ID不合法" 184 return Response(res.dict) 185 CONN.hset(global_coupon_key, "default_global_coupon_id", global_coupon_id) 186 # 3,修改redis中数据 187 CONN.hset(key, "default_coupon_id", course_coupon_id) 188 res.data = "更新优惠券成功" 189 except Exception as e: 190 res.code = 1026 191 res.error = "更改优惠券失败" 192 return Response(res.dict)

shopping中settlement_view.py

六、支付中心模块

1from rest_framework.views import APIView 2from rest_framework.response import Response 3from utils.my_auth import LoginAuth 4from utils.base_response import BaseResponse 5from .settlement_view import SETTLEMENT_KEY, GLOBAL_COUPON_KEY 6from utils.redis_pool import POOL 7import redis 8from Course.models import Course 9from .models import Coupon 10from django.utils.timezone import now 11 12 13COON = redis.Redis(connection_pool=POOL) 14 15 16# price balance 17class PaymentView(APIView): 18 authentication_classes = [LoginAuth, ] 19 20 def post(self, request): 21 res = BaseResponse() 22 # 1 获取数据 23 balance = request.data.get("balance", 0) 24 price = request.data.get("price", "") 25 user_id = request.user.pk 26 # 2 校验数据的合法性 27 # 2.1 校验贝里数是否合法 28 if int(balance) > request.user.balance: 29 res.code = 1070 30 res.error = "抵扣的贝里错误" 31 return Response(res.dict) 32 # 2.2 从用户的结算中心拿数据 跟数据库比对是否合法 33 settlement_key = SETTLEMENT_KEY % (user_id, "*") 34 all_keys = COON.scan_iter(settlement_key) 35 # 课程id是否合法 36 course_rebate_total_price = 0 37 for key in all_keys: 38 settlement_info = COON.hgetall(key) 39 course_id = settlement_info["id"] 40 course_obj = Course.objects.filter(id=course_id).first() 41 if not course_obj or course_obj.status == 1: 42 res.code = 1071 43 res.error = "课程id不合法" 44 return Response(res.dict) 45 # 课程优惠券是否过期 46 course_coupon_id = settlement_info.get("default_coupon_id", 0) 47 if course_coupon_id: 48 coupon_dict = Coupon.objects.filter( 49 id=course_coupon_id, 50 couponrecord__status=0, 51 couponrecord__account_id=user_id, 52 object_id=course_id, 53 valid_begin_date__lte=now(), 54 valid_end_date__gte=now(), 55 ).values("coupon_type", "money_equivalent_value", "off_percent", "minimum_consume") 56 if not coupon_dict: 57 res.code = 1072 58 res.error = "优惠券不合法" 59 return Response(res.dict) 60 # 2.3 校验price 61 # 得到所有的课程的折后价格和 62 course_pirce = settlement_info["price"] 63 course_rebate_price = self.account_price(coupon_dict, course_pirce) 64 if course_rebate_price == -1: 65 res.code = 1074 66 res.error = "课程优惠券不符合要求" 67 return Response(res.dict) 68 course_rebate_total_price += course_rebate_price 69 # 跟全局优惠券做折扣 70 # 校验全局优惠券是否合法 71 global_coupon_key = GLOBAL_COUPON_KEY % user_id 72 global_coupon_id = int(COON.hget(global_coupon_key, "default_global_coupon_id")) 73 if global_coupon_id: 74 global_coupon_dict = Coupon.objects.filter( 75 id=global_coupon_id, 76 couponrecord__status=0, 77 couponrecord__account_id=user_id, 78 valid_begin_date__lte=now(), 79 valid_end_date__gte=now(), 80 ).values("coupon_type", "money_equivalent_value", "off_percent", "minimum_consume") 81 if not global_coupon_dict: 82 res.code = 1073 83 res.error = "全局优惠券id不合法" 84 return Response(res.dict) 85 global_rebate_price = self.account_price(global_coupon_dict, course_rebate_total_price) 86 if global_rebate_price == -1: 87 res.code = 1076 88 res.error = "全局优惠券不符合要求" 89 return Response(res.dict) 90 # 抵扣贝里 91 balance_money = balance / 100 92 balance_rebate_price = global_rebate_price - balance 93 if balance_rebate_price < 0: 94 balance_rebate_price = 0 95 # 终极校验price 96 if balance_rebate_price != price: 97 res.code = 1078 98 res.error = "价格不合法" 99 return Response(res.dict) 100 # 先去创建订单 订单状态未支付状态 101 # 3 调用支付宝接口支付 102 # 如果成功支付支付宝会给我们发回调 103 # 改变订单的状态 104 # 注意订单详情表有多个记录 105 # 更改优惠券的使用状态 106 # 更改用户表里的贝里 贝里要添加交易记录 107 108 109 110 111 112 113 114 def account_price(self, coupon_dict, price): 115 coupon_type = coupon_dict["coupon_type"] 116 if coupon_type == 0: 117 # 通用优惠券 118 money_equivalent_value = coupon_dict["money_equivalent_value"] 119 if price - money_equivalent_value >=0: 120 rebate_price = price - money_equivalent_value 121 else: 122 rebate_price = 0 123 elif coupon_type == 1: 124 # 满减 125 money_equivalent_value = coupon_dict["money_equivalent_value"] 126 minimum_consume = coupon_dict["minimum_consume"] 127 if price >= minimum_consume: 128 rebate_price = price - money_equivalent_value 129 else: 130 return -1 131 elif coupon_type == 2: 132 # 折扣 133 minimum_consume = coupon_dict["minimum_consume"] 134 off_percent = coupon_dict["off_percent"] 135 if price >= minimum_consume: 136 rebate_price = price * (off_percent / 100) 137 else: 138 return -1 139 return rebate_price

shopping中payment_view.py

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )