Django組件-ContentType

ContentType組件

ContentType是Django的內置的一個應用,能夠追蹤項目中全部的APP和model的對應關係,並記錄在ContentType表中。
當項目作數據遷移後,會有不少django自帶的表,其中就有django_content_type表前端

ContentType組件應用mysql

  • 在model中定義ForeignKey字段,並關聯到ContentType表,一般這個字段命名爲content-type
  • 在model中定義PositiveIntergerField字段, 用來存儲關聯表中的主鍵,一般用object_id
  • 在model中定義GenericForeignKey字段,傳入上面兩個字段的名字
  • 方便反向查詢能夠定義GenericRelation字段
  • postman
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation

# Create your models here.


class Food(models.Model):
    """
    id      title
    1       麪包
    2       牛奶
    """
    title = models.CharField(max_length=32)
    # 不會生成字段 只用於反向查詢
    coupons = GenericRelation(to="Coupon")


class Fruit(models.Model):
    """
    id      title
    1       蘋果
    2       香蕉
    """
    title = models.CharField(max_length=32)


# 若是有40張表
# class Coupon(models.Model):
#     """
#     id      title          food_id    fruit_id
#     1       麪包九五折         1         null
#     2       香蕉滿10元減5元    null       2
#     """
#     title = models.CharField(max_length=32)
#     food = models.ForeignKey(to="Food")
#     fruit = models.ForeignKey(to="Fruit")


# class Coupon(models.Model):
#     """
#     id      title        table_id      object_id
#     1       麪包九五折       1             1
#     2       香蕉滿10元減5元  2             2
#     """
#     title = models.CharField(max_length=32)
#     table = models.ForeignKey(to="Table")
#     object_id = models.IntegerField()
#
#
# class Table(models.Model):
#     """
#     id      app_name       table_name
#     1       demo            food
#     2       demo            fruit
#     """
#     app_name = models.CharField(max_length=32)
#     table_name = models.CharField(max_length=32)


class Coupon(models.Model):
    title = models.CharField(max_length=32)
    # 第一步:注意沒有引號由於是導入的
    content_type = models.ForeignKey(to=ContentType, on_delete=None)
    # 第二步
    object_id = models.IntegerField()
    # 第三步 不會生成字段,用來操做增刪改查
    content_object = GenericForeignKey("content_type", "object_id")
models.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Food, Coupon
from django.contrib.contenttypes.models import ContentType

# Create your views here.


class DemoView(APIView):

    def get(self, request):
        # 給麪包建立一個優惠券
        food_obj = Food.objects.filter(id=1).first()
        # Coupon.objects.create(title="麪包九五折", content_type_id=8, object_id=1)
        # Coupon.objects.create(title="雙十一面包九折促銷", content_object=food_obj)

        #查詢食物都有哪些優惠券
        #定義了反向查詢
        coupons = food_obj.coupons.all()
        print(coupons)

        # 若是沒定義反向查詢
        content = ContentType.objects.filter(app_label="app01", model="food").first()
        coupons = Coupon.objects.filter(content_type=content, object_id=1).all()
        print(coupons)

        # 優惠券查對象
        # 查詢優惠券id=1綁定了哪一個商品
        coupon_obj = Coupon.objects.filter(id=1).first()
        content_obj = coupon_obj.content_object
        print(coupon_obj.title,content_obj.title)

        # 經過ContentType表找表模型
        content = ContentType.objects.filter(app_label="app01", model="food").first()
        # content=food  獲取food表的表模型用model_class() 
        model_class = content.model_class()
        ret = model_class.objects.all()
        print(ret)

        return Response("ContentType測試")
views.py

media的配置

#靜態文件
STATIC_URL = '/static/'  
STATICFILES_DIRS=(
    os.path.join(BASE_DIR,'static'),  
)
# Media配置
MEDIA_URL = "media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
settings.py
from django.conf.urls import url, include
from django.contrib import admin
from django.views.static import serve
from new_luffy import settings


urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/course/', include("course.urls")),

    # media路徑配置
    url(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT})
]
urls.py

項目路由配置

from django.contrib import admin
from django.urls import path, include, re_path
from django.views.static import serve
from LuffyCity import settings
from Login.views import GeetestView


urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/course/', include("Course.urls")),
    path('api/shop/', include("shopping.urls")),
    path('api/', include("Login.urls")),
    path('pc-geetest/register', GeetestView.as_view()),
    path('pc-geetest/ajax_validate', GeetestView.as_view()),


    # media路徑配置
    # path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
    re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
]
項目urls.py

1、課程模塊

  1. 課程模塊,包括了免費課程以及專題課程
  2. 主要是課程的展現,點擊課程進入課程詳細頁面
  3. 課程詳細頁面展現,課程的概述,課程的價格策略,課程章節,評價以及常見問題

一、設計表結構

from django.db import models
# Create your models here.

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType

# Create your models here.
__all__ = ["Category", "Course", "CourseDetail", "Teacher", "DegreeCourse", "CourseChapter",
           "CourseSection", "PricePolicy", "OftenAskedQuestion", "Comment", "Account", "CourseOutline"]


class Category(models.Model):
    """課程分類表"""
    title = models.CharField(max_length=32, unique=True, verbose_name="課程的分類")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "01-課程分類表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class Course(models.Model):
    """課程表"""
    title = models.CharField(max_length=128, unique=True, verbose_name="課程的名稱")
    course_img = models.ImageField(upload_to="course/%Y-%m", verbose_name='課程的圖片')
    category = models.ForeignKey(to="Category", verbose_name="課程的分類", on_delete=None)


    COURSE_TYPE_CHOICES = ((0, "付費"), (1, "vip專享"), (2, "學位課程"))
    course_type = models.SmallIntegerField(choices=COURSE_TYPE_CHOICES)
    degree_course = models.ForeignKey(to="DegreeCourse", blank=True, null=True, help_text="若是是學位課程,必須關聯學位表", on_delete=None)


    brief = models.CharField(verbose_name="課程簡介", max_length=1024)
    level_choices = ((0, '初級'), (1, '中級'), (2, '高級'))
    level = models.SmallIntegerField(choices=level_choices, default=1)

    status_choices = ((0, '上線'), (1, '下線'), (2, '預上線'))
    status = models.SmallIntegerField(choices=status_choices, default=0)
    pub_date = models.DateField(verbose_name="發佈日期", blank=True, null=True)

    order = models.IntegerField("課程順序", help_text="從上一個課程數字日後排")
    study_num = models.IntegerField(verbose_name="學習人數", help_text="只要有人買課程,訂單表加入數據的同時給這個字段+1")

    # order_details = GenericRelation("OrderDetail", related_query_name="course")
    # coupon = GenericRelation("Coupon")
    # 只用於反向查詢不生成字段
    price_policy = GenericRelation("PricePolicy")
    often_ask_questions = GenericRelation("OftenAskedQuestion")
    course_comments = GenericRelation("Comment")

    def save(self, *args, **kwargs):
        if self.course_type == 2:
            if not self.degree_course:
                raise ValueError("學位課必須關聯學位課程表")
        super(Course, self).save(*args, **kwargs)

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "02-課程表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class CourseDetail(models.Model):
    """課程詳細表"""
    course = models.OneToOneField(to="Course", on_delete=None)
    hours = models.IntegerField(verbose_name="課時", default=7)
    course_slogan = models.CharField(max_length=125, blank=True, null=True, verbose_name="課程口號")
    video_brief_link = models.CharField(max_length=255, blank=True, null=True)
    summary = models.TextField(max_length=2048, verbose_name="課程概述")
    why_study = models.TextField(verbose_name="爲何學習這門課程")
    what_to_study_brief = models.TextField(verbose_name="我將學到哪些內容")
    career_improvement = models.TextField(verbose_name="此項目如何有助於個人職業生涯")
    prerequisite = models.TextField(verbose_name="課程先修要求", max_length=1024)
    recommend_courses = models.ManyToManyField("Course", related_name="recommend_by", blank=True)

    teachers = models.ManyToManyField("Teacher", verbose_name="課程講師")

    def __str__(self):
        return self.course.title

    class Meta:
        verbose_name = "03-課程詳細表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class Teacher(models.Model):
    """講師表"""
    name = models.CharField(max_length=32, verbose_name="講師名字")
    brief = models.TextField(max_length=1024, verbose_name="講師介紹")

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "04-教師表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class DegreeCourse(models.Model):
    """
    字段大致跟課程表相同,哪些不一樣根據業務邏輯去區分
    """
    title = models.CharField(max_length=32, verbose_name="學位課程名字")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "05-學位課程表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class CourseChapter(models.Model):
    """課程章節表"""
    course = models.ForeignKey(to="Course", related_name="course_chapters", on_delete=None)
    chapter = models.SmallIntegerField(default=1, verbose_name="第幾章")
    title = models.CharField(max_length=32, verbose_name="課程章節名稱")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "06-課程章節表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ("course", "chapter")


class CourseSection(models.Model):
    """課時表"""
    chapter = models.ForeignKey(to="CourseChapter", related_name="course_sections", on_delete=None)
    title = models.CharField(max_length=32, verbose_name="課時")
    section_order = models.SmallIntegerField(verbose_name="課時排序", help_text="建議每一個課時之間空1至2個值,以備後續插入課時")
    section_type_choices = ((0, '文檔'), (1, '練習'), (2, '視頻'))
    free_trail = models.BooleanField("是否可試看", default=False)
    section_type = models.SmallIntegerField(default=2, choices=section_type_choices)
    section_link = models.CharField(max_length=255, blank=True, null=True, help_text="如果video,填vid,如果文檔,填link")

    def course_chapter(self):
        return self.chapter.chapter

    def course_name(self):
        return self.chapter.course.title

    def __str__(self):
        return "%s-%s" % (self.chapter, self.title)

    class Meta:
        verbose_name = "07-課程課時表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ('chapter', 'section_link')


class PricePolicy(models.Model):
    """價格策略表"""
    content_type = models.ForeignKey(ContentType, on_delete=None)  # 關聯course or degree_course
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    valid_period_choices = ((1, '1天'), (3, '3天'),
                            (7, '1周'), (14, '2周'),
                            (30, '1個月'),
                            (60, '2個月'),
                            (90, '3個月'),
                            (120, '4個月'),
                            (180, '6個月'), (210, '12個月'),
                            (540, '18個月'), (720, '24個月'),
                            (722, '24個月'), (723, '24個月'),
                            )
    valid_period = models.SmallIntegerField(choices=valid_period_choices)
    price = models.FloatField()

    def __str__(self):
        return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price)

    class Meta:
        verbose_name = "08-價格策略表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ("content_type", 'object_id', "valid_period")


class OftenAskedQuestion(models.Model):
    """常見問題"""
    content_type = models.ForeignKey(ContentType, on_delete=None)  # 關聯course or degree_course
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    question = models.CharField(max_length=255)
    answer = models.TextField(max_length=1024)

    def __str__(self):
        return "%s-%s" % (self.content_object, self.question)

    class Meta:
        verbose_name = "09-常見問題表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ('content_type', 'object_id', 'question')


class Comment(models.Model):
    """通用的評論表"""
    content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=None)
    object_id = models.PositiveIntegerField(blank=True, null=True)
    content_object = GenericForeignKey('content_type', 'object_id')

    content = models.TextField(max_length=1024, verbose_name="評論內容")
    account = models.ForeignKey("Account", verbose_name="會員名", on_delete=None)
    date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.content

    class Meta:
        verbose_name = "10-評價表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class Account(models.Model):
    username = models.CharField(max_length=32, verbose_name="用戶姓名")
    pwd = models.CharField(max_length=32, verbose_name="密文密碼")
    # head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png',
    #                             verbose_name="我的頭像")
    balance = models.IntegerField(verbose_name="貝里餘額", default=0)

    def __str__(self):
        return self.username

    class Meta:
        verbose_name = "11-用戶表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class CourseOutline(models.Model):
    """課程大綱"""
    course_detail = models.ForeignKey(to="CourseDetail", related_name="course_outline", on_delete=None)
    title = models.CharField(max_length=128)
    order = models.PositiveSmallIntegerField(default=1)
    # 前端顯示順序

    content = models.TextField("內容", max_length=2048)

    def __str__(self):
        return "%s" % self.title

    class Meta:
        verbose_name = "12-課程大綱表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ('course_detail', 'title')
Course中models.py

二、接口的編寫

  • 課程這個模塊,全部的功能都是展現,基於數據展現的,一般稱爲數據接口
  • 課程頁面:有課程全部分類這個接口,有展現課程的接口
  • 課程詳情頁面:詳情頁面的數據接口
  • 詳情頁面下的子路由對應子組件的數據接口:課程章節課時、課程的評論、課程的常見問題
from django.urls import path
from .views import CategoryView, CourseView, CourseDetailView, CourseChapterView, CourseCommentView, QuestionView
from .video_view import PolyvView


urlpatterns = [
    path('category', CategoryView.as_view()),
    path('list', CourseView.as_view()),
    path('detail/<int:pk>', CourseDetailView.as_view()),
    path('chapter/<int:pk>', CourseChapterView.as_view()),
    path('comment/<int:pk>', CourseCommentView.as_view()),
    path('question/<int:pk>', QuestionView.as_view()),
    path('polyv', PolyvView.as_view()),

]
Course中urls.py
from rest_framework import serializers
from . import models


class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Category
        fields = "__all__"


class CourseSerializer(serializers.ModelSerializer):
    level = serializers.CharField(source="get_level_display")
    price = serializers.SerializerMethodField()

    def get_price(self, obj):
        print(obj.price_policy.all())
        return obj.price_policy.all().order_by("price").first().price

    class Meta:
        model = models.Course
        fields = ["id", "title", "course_img", "brief", "level", "study_num", "price"]


class CourseDetailSerializer(serializers.ModelSerializer):
    level = serializers.CharField(source="course.get_level_display")
    study_num = serializers.IntegerField(source="course.study_num")
    recommend_courses = serializers.SerializerMethodField()
    teachers = serializers.SerializerMethodField()
    price_policy = serializers.SerializerMethodField()
    course_outline = serializers.SerializerMethodField()


    def get_course_outline(self, obj):
        return [{"id": outline.id, "title": outline.title, "content": outline.content} for outline in obj.course_outline.all().order_by("order")]


    def get_price_policy(self, obj):
        return [{"id": price.id, "valid_price_display": price.get_valid_period_display(), "price": price.price} for price in obj.course.price_policy.all()]


    def get_teachers(self, obj):
        return [{"id": teacher.id, "name": teacher.name} for teacher in obj.teachers.all()]

    def get_recommend_courses(self, obj):
        return [{"id": course.id, "title": course.title} for course in obj.recommend_courses.all()]

    class Meta:
        model = models.CourseDetail
        fields = ["id", "hours", "summary", "level", "study_num", "recommend_courses", "teachers",
                  "price_policy", "course_outline"]


class CourseChapterSerializer(serializers.ModelSerializer):
    sections = serializers.SerializerMethodField()

    def get_sections(self, obj):
        return [{"id": section.id, "title": section.title, "free_trail": section.free_trail} for section in obj.course_sections.all().order_by("section_order")]

    class Meta:
        model = models.CourseChapter
        fields = ["id", "title", "sections"]


class CourseCommentSerializer(serializers.ModelSerializer):
    account = serializers.CharField(source="account.username")

    class Meta:
        model = models.Comment
        fields = ["id", "account", "content", "date"]


class QuestionSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.OftenAskedQuestion
        fields = ["id", "question", "answer"]
Course中serializers.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from . import models
from .serializers import CategorySerializer, CourseSerializer, CourseDetailSerializer, CourseChapterSerializer
from .serializers import CourseCommentSerializer, QuestionSerializer

# Create your views here.


class CategoryView(APIView):
    """課程分類接口"""
    def get(self, request):
        # 經過ORM操做獲取全部分類數據
        queryset = models.Category.objects.all()
        # 利用序列化器去序列化咱們的數據
        ser_obj = CategorySerializer(queryset, many=True)
        # 返回
        return Response(ser_obj.data)


class CourseView(APIView):
    """查看全部免費課程的接口"""
    def get(self, request):
        # 獲取過濾條件中的分類ID
        category_id = request.query_params.get("category", 0)
        # 根據分類獲取課程
        if category_id == 0:
            # 證實沒有分類,能夠拿全部的課程數據
            queryset = models.Course.objects.all().order_by("order")
        else:
            queryset = models.Course.objects.filter(category_id=category_id).all().order_by("order")
        # 序列化課程數據
        ser_obj = CourseSerializer(queryset, many=True)
        # 返回
        return Response(ser_obj.data)


class CourseDetailView(APIView):
    """課程詳情頁面"""
    def get(self, request, pk):
        # 根據pk獲取到課程詳情對象
        course_detail_obj = models.CourseDetail.objects.filter(course__id=pk).first()
        if not course_detail_obj:
            return Response({"code": 1001, "error": "查詢的課程詳情不存在"})
        # 序列化課程詳情
        ser_obj = CourseDetailSerializer(course_detail_obj)
        # 返回
        return Response(ser_obj.data)


class CourseChapterView(APIView):
    """課程章節接口"""
    def get(self, request, pk):
        # 數據結構["第一章": {課時一, 課時二}]
        queryset = models.CourseChapter.objects.filter(course_id=pk).all().order_by("chapter")
        # 序列化章節對象
        ser_obj = CourseChapterSerializer(queryset, many=True)
        # 返回
        return Response(ser_obj.data)


class CourseCommentView(APIView):
    def get(self, request, pk):
        # 經過課程id找到課程全部的評論
        queryset = models.Course.objects.filter(id=pk).first().course_comments.all()
        # 序列化
        ser_obj = CourseCommentSerializer(queryset, many=True)
        # 返回
        return Response(ser_obj.data)


class QuestionView(APIView):
    def get(self, request, pk):
        queryset = models.Course.objects.filter(id=pk).first().often_ask_questions.all()
        ser_obj = QuestionSerializer(queryset, many=True)
        return Response(ser_obj.data)
Course中views.py
from django.contrib import admin

# Register your models here.
from . import models

for table in models.__all__:
    admin.site.register(getattr(models, table))
Course中admin.py

2、登陸認證模塊(token存Redis)

  • 之前先後端不分離用cookie,session解決,如今先後端分離使用token令牌。
  • 用戶登陸成功後,生成一個隨機字符串token給前端返回
  • 前端之後都攜帶這個token來訪問,這樣後端只須要鑑別這個token就能夠作認證
import redis
POOL = redis.ConnectionPool(host="127.0.0.1", port=6379, decode_responses=True, max_connections=10)
utils中redis_pool.py
class BaseResponse(object):

    def __init__(self):
        self.code = 1000
        self.data = None
        self.error = None

    @property
    def dict(self):
        return self.__dict__
utils中base_response.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .redis_pool import POOL
from Course.models import Account
import redis



CONN = redis.Redis(connection_pool=POOL)


class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # 從請求頭中獲取前端帶過來的token
        token = request.META.get("HTTP_AUTHENTICATION", "")
        if not token:
            raise AuthenticationFailed("沒有攜帶token")
        # 去redis比對
        user_id = CONN.get(str(token))
        if user_id == None:
            raise AuthenticationFailed("token過時")
        user_obj = Account.objects.filter(id=user_id).first()
        return user_obj, token
utils中my_auth.py
urlpatterns = [
    path('register', RegisterView.as_view()),
    path('login', LoginView.as_view()),
    path('test_auth', TestView.as_view()),

]
Login中urls.py
from rest_framework import serializers
from Course.models import Account
import hashlib


class RegisterSerializer(serializers.ModelSerializer):

    class Meta:
        model = Account
        fields = "__all__"

    def create(self, validated_data):
        pwd = validated_data["pwd"]
        pwd_salt = "luffy_password" + pwd
        md5_str = hashlib.md5(pwd_salt.encode()).hexdigest()
        user_obj = Account.objects.create(username=validated_data["username"], pwd=md5_str)
        return user_obj
Login中serializers.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import RegisterSerializer
from utils.base_response import BaseResponse
from Course.models import Account
from utils.redis_pool import POOL
import redis
import uuid
from utils.my_auth import LoginAuth
from utils.geetest import GeetestLib
from django.http import HttpResponse
import json

# Create your views here.


class RegisterView(APIView):

    def post(self, request):
        res = BaseResponse()
        # 用序列化器作校驗
        ser_obj = RegisterSerializer(data=request.data)
        if ser_obj.is_valid():
            ser_obj.save()
            res.data = ser_obj.data
        else:
            res.code = 1020
            res.error = ser_obj.errors
        return Response(res.dict)


class LoginView(APIView):

    def post(self, request):
        res = BaseResponse()
        username = request.data.get("username", "")
        pwd = request.data.get("pwd", "")
        user_obj = Account.objects.filter(username=username, pwd=pwd).first()
        if not user_obj:
            res.code = 1030
            res.error = "用戶名或密碼錯誤"
            return Response(res.dict)

        # 用戶登陸成功生成一個token寫入redis
        # 寫入redis  token : user_id
        conn = redis.Redis(connection_pool=POOL)
        try:
            token = uuid.uuid4()
            # conn.set(str(token), user_obj.id, ex=10)
            conn.set(str(token), user_obj.id)
            res.data = token
        except Exception as e:
            print(e)
            res.code = 1031
            res.error = "建立令牌失敗"
        return Response(res.dict)


class TestView(APIView):
    authentication_classes = [LoginAuth, ]

    def get(self, request):
        return Response("認證測試")


pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c"
pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4"
REDIS_CONN = redis.Redis(connection_pool=POOL)


class GeetestView(APIView):

    def get(self, request):
        user_id = 'test'
        gt = GeetestLib(pc_geetest_id, pc_geetest_key)
        status = gt.pre_process(user_id)
        # request.session[gt.GT_STATUS_SESSION_KEY] = status
        REDIS_CONN.set(gt.GT_STATUS_SESSION_KEY, status)
        # request.session["user_id"] = user_id
        REDIS_CONN.set("gt_user_id", user_id)
        response_str = gt.get_response_str()
        return HttpResponse(response_str)

    def post(self, request):
        # print(request.session.get("user_id"))
        print(request.META.get("HTTP_AUTHENTICATION"))
        print(request.data)
        gt = GeetestLib(pc_geetest_id, pc_geetest_key)
        challenge = request.data.get(gt.FN_CHALLENGE, '')
        validate = request.data.get(gt.FN_VALIDATE, '')
        seccode = request.data.get(gt.FN_SECCODE, '')
        # username
        # pwd
        # status = request.session.get(gt.GT_STATUS_SESSION_KEY)
        # print(status)
        # user_id = request.session.get("user_id")
        # print(user_id)
        status = REDIS_CONN.get(gt.GT_STATUS_SESSION_KEY)
        user_id = REDIS_CONN.get("gt_user_id")
        if status:
            result = gt.success_validate(challenge, validate, seccode, user_id)
        else:
            result = gt.failback_validate(challenge, validate, seccode)
        result = {"status": "success"} if result else {"status": "fail"}
        # if result:
        #     # 證實驗證碼經過
        #     # 判斷用戶名和密碼
        # else:
        #     #  返回驗證碼錯誤
        return HttpResponse(json.dumps(result))
Login中views.py

3、登陸認證模塊(token存mysql)

# 拓展以前課程模塊下的用戶表
class Account(models.Model):
    username = models.CharField(max_length=32, verbose_name="用戶姓名", unique=True)
    password = models.CharField(max_length=32, verbose_name="用戶密碼")
    # head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png',
    #                             verbose_name="我的頭像")
    token = models.UUIDField(null=True, blank=True)

    def __str__(self):
        return self.username

    class Meta:
        verbose_name = "11-用戶表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
models.py 擴展以前功能模塊的用戶表
class BaseResponse(object):

    def __init__(self):
        self.code = 1000
        self.data = None
        self.error = None

    @property
    def dict(self):
        return self.__dict__
utils中base_response.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from Course.models import Account
# django 提供的拿時間的接口 提供的是根據django配置的時區拿到的當前時間
from django.utils.timezone import now


class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # 從請求頭中獲取前端帶過來的token
        token = request.META.get("HTTP_AUTHENTICATION", "")
        if not token:
            raise AuthenticationFailed("沒有攜帶token")
        # 去redis比對
        user_obj = Account.objects.filter(token=token).first()
        if not user_obj:
            raise AuthenticationFailed("token過時")
        else:
            old_time = user_obj.create_token_time
            if (now() - old_time).days > 7:
                raise AuthenticationFailed({"code": 1020, "error": "無效的token"})
            return user_obj, token
utils中my_auth.py
urlpatterns = [
    path('register', RegisterView.as_view()),
    path('login', LoginView.as_view()),
    path('test_auth', TestView.as_view()),

]
Login中urls.py
from rest_framework import serializers
from Course.models import Account
import hashlib


class RegisterSerializer(serializers.ModelSerializer):

    class Meta:
        model = Account
        fields = "__all__"

    def create(self, validated_data):
        pwd = validated_data["pwd"]
        pwd_salt = "luffy_password" + pwd
        md5_str = hashlib.md5(pwd_salt.encode()).hexdigest()
        user_obj = Account.objects.create(username=validated_data["username"], pwd=md5_str)
        return user_obj
Login中serializers.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import RegisterSerializer
from utils.base_response import BaseResponse
from Course.models import Account
import uuid



# Create your views here.

class RegisterView(APIView):

    def post(self, request):
        res = BaseResponse()
        # 用序列化器作校驗
        ser_obj = RegisterSerializer(data=request.data)
        if ser_obj.is_valid():
            ser_obj.save()
            res.data = ser_obj.data
        else:
            res.code = 1020
            res.error = ser_obj.errors
        return Response(res.dict)


class LoginView(APIView):

    def post(self, request):
        res = BaseResponse()
        username = request.data.get("username", "")
        pwd = request.data.get("pwd", "")
        user_obj = Account.objects.filter(username=username, pwd=pwd).first()
        if not user_obj:
            res.code = 1030
            res.error = "用戶名或密碼錯誤"
            return Response(res.dict)

        try:
            token = uuid.uuid4()
            ###
            user_obj.update(token=token)
            res.data = token
        except Exception as e:
            print(e)
            res.code = 1031
            res.error = "建立令牌失敗"
        return Response(res.dict)

# 全部這是一個須要認證的接口
class TestView(APIView):
    authentication_classes = [LoginAuth, ]

    def get(self, request):
        return Response("認證測試")
Login中views.py

4、購物車模塊

  1. 用戶點擊商品加入購物車,我的中心能夠查看本身全部購物車中數據
  2. 在購物車中能夠刪除課程,還能夠更新購物車中課程的價格策略
  3. 因此接口應該有四種請求方式, get,post,patch,delete
  4. 由於購物車是屬於中間狀態數據並且不少時候須要過時時間因此選擇存儲到redis
from django.urls import path
from .views import ShoppingCarView
from .settlement_view import SettlementView
from .payment_view import PaymentView

urlpatterns = [
    path('shopping_car', ShoppingCarView.as_view()),
    path('settlement', SettlementView.as_view()),
    path('payment', PaymentView.as_view()),
]
shopping中urls.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .redis_pool import POOL
from Course.models import Account
import redis



CONN = redis.Redis(connection_pool=POOL)


class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # 從請求頭中獲取前端帶過來的token
        token = request.META.get("HTTP_AUTHENTICATION", "")
        if not token:
            raise AuthenticationFailed("沒有攜帶token")
        # 去redis比對
        user_id = CONN.get(str(token))
        if user_id == None:
            raise AuthenticationFailed("token過時")
        user_obj = Account.objects.filter(id=user_id).first()
        return user_obj, token
utils中my_auth.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from utils.base_response import BaseResponse
from utils.my_auth import LoginAuth
from utils.redis_pool import POOL
from Course.models import Course
import json
import redis

# Create your views here.

# 前端傳過來 course_id  price_policy_id
# 把購物車數據放入redis
"""
{
    SHOPPINGCAR_USERID_COURSE_ID: {
        "id",   課程id
        "title",  課程標題
        "course_img", 
        "price_policy_dict": {
            price_policy_id: "{valid_period,  price, valid_period_display}"
            price_policy_id2: "{valid_period,  price, valid_period_display}"
            price_policy_id3: "{valid_period,  price, valid_period_display}"
        
        },
        "default_price_policy_id": 1  默認選中的價格id
        
    
    }


}
"""

SHOPPINGCAR_KEY = "SHOPPINGCAR_%s_%s"
CONN = redis.Redis(connection_pool=POOL)


class ShoppingCarView(APIView):
    authentication_classes = [LoginAuth, ]
    # 給購物車增長商品
    def post(self, request):
        res = BaseResponse()
        try:
            # 1, 獲取前端傳過來的數據以及user_id
            course_id = request.data.get("course_id", "")
            price_policy_id = request.data.get("price_policy_id", "")
            user_id = request.user.pk
            # 2, 校驗數據的合法性
            # 2.1 校驗課程id合法性
            course_obj = Course.objects.filter(id=course_id).first()
            if not course_obj:
                res.code = 1040
                res.error = "課程id不合法"
                return Response(res.dict)
            # 2.2 校驗價格策略id是否合法
            price_policy_queryset = course_obj.price_policy.all()
            price_policy_dict = {}
            for price_policy in price_policy_queryset:
                price_policy_dict[price_policy.id] = {
                    "price": price_policy.price,
                    "valid_period": price_policy.valid_period,
                    "valid_period_display": price_policy.get_valid_period_display()
                }
            if price_policy_id not in price_policy_dict:
                res.code = 1041
                res.error = "價格策略id不合法"
                return Response(res.dict)
            # 3,構建redisKEY
            key = SHOPPINGCAR_KEY % (user_id, course_id)
            # 4,構建數據結構
            course_info = {
                "id": course_obj.id,
                "title": course_obj.title,
                "course_img": str(course_obj.course_img),
                "price_policy_dict": json.dumps(price_policy_dict, ensure_ascii=False),
                "default_price_policy_id": price_policy_id
            }
            # 5  寫入redis
            CONN.hmset(key, course_info)
            res.data = "加入購物車成功"
        except Exception as e:
            res.code = 1012
            res.error = "加入購物車失敗"
        return Response(res.dict)

    def get(self, request):
        res = BaseResponse()
        try:
            # 1, 拼接redis key
            user_id = request.user.pk
            shopping_car_key = SHOPPINGCAR_KEY % (user_id, "*")
            # 2, 去redis中讀取數據
            # 2.1 模糊匹配全部的keys
            # 3,構建數據結構展現
            all_keys = CONN.scan_iter(shopping_car_key)
            ret = []
            for key in all_keys:
                ret.append(CONN.hgetall(key))
            res.data = ret
        except Exception as e:
            res.code = 1013
            res.error = "獲取購物車失敗"
        return Response(res.dict)

    def put(self, request):
        # 前端 course_id  price_policy_id
        res = BaseResponse()
        try:
            # 1, 獲取前端傳過來的數據以及user_id
            course_id = request.data.get("course_id", "")
            price_policy_id = request.data.get("price_policy_id", "")
            user_id = request.user.pk
            # 2, 校驗數據的合法性
            # 2.1 course_id是否合法
            key = SHOPPINGCAR_KEY % (user_id, course_id)
            if not CONN.exists(key):
                res.code = 1043
                res.error = "課程id不合法"
                return Response(res.dict)
            # 2,2 price_policy_id是否合法
            price_policy_dict = json.loads(CONN.hget(key, "price_policy_dict"))
            if str(price_policy_id) not in price_policy_dict:
                res.code = 1044
                res.error = "價格策略不合法"
                return Response(res.dict)
            # 3, 更新redis  default_price_policy_id
            CONN.hset(key, "default_price_policy_id", price_policy_id)
            res.data = "更新成功"
        except Exception as e:
            res.code = 1014
            res.error = "更新購物車失敗"
        return Response(res.dict)

    def delete(self, request):
        # course_list = [course_id, ]
        res = BaseResponse()
        try:
            # 1 獲取前端傳來的數據以及user_id
            course_list = request.data.get("course_list", "")
            user_id = request.user.pk
            # 2 校驗course_id是否合法
            for course_id in course_list:
                key = SHOPPINGCAR_KEY % (user_id, course_id)
                if not CONN.exists(key):
                    res.code = 1045
                    res.error = "課程ID不合法"
                    return Response(res.dict)
                # 3, 刪除redis數據
                CONN.delete(key)
            res.data = "刪除成功"
        except Exception as e:
            res.code = 1014
            res.error = "刪除購物車失敗"
        return Response(res.dict)
shopping中views.py

5、結算中心模塊

結算中心要開始選擇優惠券了,有單獨的課程優惠券還有全局優惠券。ajax

from django.db import models

# Create your models here.

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from Course.models import Account

# Create your models here.
__all__ = ["Coupon", "CouponRecord", "Order", "OrderDetail", "TransactionRecord"]


class Coupon(models.Model):
    """優惠券生成規則"""
    name = models.CharField(max_length=64, verbose_name="活動名稱")
    brief = models.TextField(blank=True, null=True, verbose_name="優惠券介紹")
    coupon_type_choices = ((0, '通用券'), (1, '滿減券'), (2, '折扣券'))
    coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券類型")

    money_equivalent_value = models.IntegerField(verbose_name="等值貨幣", null=True, blank=True, default=0)
    off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只針對折扣券,例7.9折,寫79", blank=True, null=True, default=100)
    minimum_consume = models.PositiveIntegerField("最低消費", default=0, help_text="僅在滿減券時填寫此字段", null=True, blank=True)

    content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=None)
    object_id = models.PositiveIntegerField("綁定課程", blank=True, null=True, help_text="能夠把優惠券跟課程綁定")
    # 不綁定表明全局優惠券
    content_object = GenericForeignKey('content_type', 'object_id')

    open_date = models.DateField("優惠券領取開始時間")
    close_date = models.DateField("優惠券領取結束時間")
    valid_begin_date = models.DateField(verbose_name="有效期開始時間", blank=True, null=True)
    valid_end_date = models.DateField(verbose_name="有效結束時間", blank=True, null=True)
    coupon_valid_days = models.PositiveIntegerField(verbose_name="優惠券有效期(天)", blank=True, null=True,
                                                    help_text="自券被領時開始算起")
    date = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = "13. 優惠券生成規則記錄"
        db_table = verbose_name_plural
        verbose_name = verbose_name_plural

    def __str__(self):
        return "%s(%s)" % (self.get_coupon_type_display(), self.name)

    def save(self, *args, **kwargs):
        if not self.coupon_valid_days or (self.valid_begin_date and self.valid_end_date):
            if self.valid_begin_date and self.valid_end_date:
                if self.valid_end_date <= self.valid_begin_date:
                    raise ValueError("valid_end_date 有效期結束日期必須晚於 valid_begin_date ")
            if self.coupon_valid_days == 0:
                raise ValueError("coupon_valid_days 有效期不能爲0")
        if self.close_date < self.open_date:
            raise ValueError("close_date 優惠券領取結束時間必須晚於 open_date優惠券領取開始時間 ")

        super(Coupon, self).save(*args, **kwargs)


class CouponRecord(models.Model):
    """優惠券發放、消費紀錄"""
    coupon = models.ForeignKey("Coupon", on_delete=None)
    number = models.CharField(max_length=64, unique=True, verbose_name="用戶優惠券記錄的流水號")
    account = models.ForeignKey(to=Account, verbose_name="擁有者", on_delete=None)
    status_choices = ((0, '未使用'), (1, '已使用'), (2, '已過時'))
    status = models.SmallIntegerField(choices=status_choices, default=0)
    get_time = models.DateTimeField(verbose_name="領取時間", help_text="用戶領取時間")
    used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用時間")
    order = models.ForeignKey("Order", blank=True, null=True, verbose_name="關聯訂單", on_delete=None)  # 一個訂單能夠有多個優惠券

    class Meta:
        verbose_name_plural = "14. 用戶優惠券領取使用記錄表"
        db_table = verbose_name_plural
        verbose_name = verbose_name_plural

    def __str__(self):
        return '%s-%s-%s' % (self.account, self.number, self.status)


class Order(models.Model):
    """訂單"""
    payment_type_choices = ((0, '微信'), (1, '支付寶'), (2, '優惠碼'), (3, '貝里'))
    payment_type = models.SmallIntegerField(choices=payment_type_choices)

    payment_number = models.CharField(max_length=128, verbose_name="支付第3方訂單號", null=True, blank=True)
    order_number = models.CharField(max_length=128, verbose_name="訂單號", unique=True)  # 考慮到訂單合併支付的問題
    account = models.ForeignKey(to=Account, on_delete=None)
    actual_amount = models.FloatField(verbose_name="實付金額")

    status_choices = ((0, '交易成功'), (1, '待支付'), (2, '退費申請中'), (3, '已退費'), (4, '主動取消'), (5, '超時取消'))
    status = models.SmallIntegerField(choices=status_choices, verbose_name="狀態")
    date = models.DateTimeField(auto_now_add=True, verbose_name="訂單生成時間")
    pay_time = models.DateTimeField(blank=True, null=True, verbose_name="付款時間")
    cancel_time = models.DateTimeField(blank=True, null=True, verbose_name="訂單取消時間")

    class Meta:
        verbose_name_plural = "15. 訂單表"
        db_table = verbose_name_plural
        verbose_name = verbose_name_plural

    def __str__(self):
        return "%s" % self.order_number


class OrderDetail(models.Model):
    """訂單詳情"""
    order = models.ForeignKey("Order", on_delete=None)

    content_type = models.ForeignKey(ContentType, on_delete=None)  # 可關聯普通課程或學位
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    original_price = models.FloatField("課程原價")
    price = models.FloatField("折後價格")
    valid_period_display = models.CharField("有效期顯示", max_length=32)  # 在訂單頁顯示
    valid_period = models.PositiveIntegerField("有效期(days)")  # 課程有效期
    memo = models.CharField(max_length=255, blank=True, null=True, verbose_name="備忘錄")

    def __str__(self):
        return "%s - %s - %s" % (self.order, self.content_type, self.price)

    class Meta:
        verbose_name_plural = "16. 訂單詳細"
        db_table = verbose_name_plural
        verbose_name = verbose_name_plural


class TransactionRecord(models.Model):
    """貝里交易紀錄"""
    account = models.ForeignKey(to=Account, on_delete=None)
    amount = models.IntegerField("金額")
    balance = models.IntegerField("帳戶餘額")
    transaction_type_choices = ((0, '收入'), (1, '支出'), (2, '退款'), (3, "提現"))  # 2 爲了處理 訂單過時未支付時,鎖按期貝里的回退
    transaction_type = models.SmallIntegerField(choices=transaction_type_choices)
    transaction_number = models.CharField(unique=True, verbose_name="流水號", max_length=128)
    date = models.DateTimeField(auto_now_add=True)
    memo = models.CharField(max_length=128, blank=True, null=True, verbose_name="備忘錄")

    class Meta:
        verbose_name_plural = "17. 貝里交易記錄"
        db_table = verbose_name_plural
        verbose_name = verbose_name_plural

    def __str__(self):
        return "%s" % self.transaction_number
shopping中models.py
from rest_framework.views import APIView
from rest_framework.response import Response
from utils.base_response import BaseResponse
from utils.redis_pool import POOL
from django.utils.timezone import now
from utils.my_auth import LoginAuth
import redis
from .views import SHOPPINGCAR_KEY
from .models import CouponRecord
import json


CONN = redis.Redis(connection_pool=POOL)
SETTLEMENT_KEY = "SETTLEMENT_%s_%s"
GLOBAL_COUPON_KEY = "GLOBAL_COUPON_%s"
"""
結算中心
在購物車裏選擇了商品以及價格策略點擊結算 才進入結算中心
在結算中心用戶能夠選擇優惠券

前端傳過來數據 course_list
redis = {
    settlement_userid_courseid: {
            id, 課程id,
            title,
            course_img,
            valid_period_display,
            price,
            course_coupon_dict: {
                coupon_id: {優惠券信息}
                coupon_id2: {優惠券信息}
                coupon_id3: {優惠券信息}
            }
            # 默認不給你選  這個字段只有更新的時候才添加
            default_coupon_id: 1  
    }
    
    global_coupon_userid: {
        coupon_id: {優惠券信息}
        coupon_id2: {優惠券信息}
        coupon_id3: {優惠券信息},
        # 這個字段只有更新的時候才添加
        # 在用戶進入結算中心選擇優惠券的時候 也就是更新請求的時候更改 
        default_global_coupon_id: 1
    
    }

}
"""


class SettlementView(APIView):
    authentication_classes = [LoginAuth, ]

    def post(self, request):
        res = BaseResponse()
        try:
            # 1 獲取前端的數據以及user_id
            course_list = request.data.get("course_list", "")
            user_id = request.user.pk
            # 2 校驗數據的合法性
            for course_id in course_list:
                # 2.1 判斷course_id 是否在購物車中
                shopping_car_key = SHOPPINGCAR_KEY % (user_id, course_id)
                if not CONN.exists(shopping_car_key):
                    res.code = 1050
                    res.error = "課程ID不合法"
                    return Response(res.dict)
                # 3 構建數據結構
                # 3.1 獲取用戶的全部合法優惠券
                user_all_coupons = CouponRecord.objects.filter(
                    account_id=user_id,
                    status=0,
                    coupon__valid_begin_date__lte=now(),
                    coupon__valid_end_date__gte=now(),
                ).all()
                print(user_all_coupons)
                # 3.2 構建優惠券dict
                course_coupon_dict = {}
                global_coupon_dict = {}
                for coupon_record in user_all_coupons:
                    coupon = coupon_record.coupon
                    if coupon.object_id == course_id:
                        course_coupon_dict[coupon.id] = {
                            "id": coupon.id,
                            "name": coupon.name,
                            "coupon_type": coupon.get_coupon_type_display(),
                            "object_id": coupon.object_id,
                            "money_equivalent_value": coupon.money_equivalent_value,
                            "off_percent": coupon.off_percent,
                            "minimum_consume": coupon.minimum_consume
                        }
                    elif coupon.object_id == "":
                        global_coupon_dict[coupon.id] = {
                            "id": coupon.id,
                            "name": coupon.name,
                            "coupon_type": coupon.get_coupon_type_display(),
                            "money_equivalent_value": coupon.money_equivalent_value,
                            "off_percent": coupon.off_percent,
                            "minimum_consume": coupon.minimum_consume
                        }
                # 3.3 構建寫入redis的數據結構
                course_info = CONN.hgetall(shopping_car_key)
                price_policy_dict = json.loads(course_info["price_policy_dict"])
                default_policy_id = course_info["default_price_policy_id"]
                valid_period = price_policy_dict[default_policy_id]["valid_period_display"]
                price = price_policy_dict[default_policy_id]["price"]

                settlement_info = {
                    "id": course_info["id"],
                    "title": course_info["title"],
                    "course_img": course_info["course_img"],
                    "valid_period": valid_period,
                    "price": price,
                    "course_coupon_dict": json.dumps(course_coupon_dict, ensure_ascii=False)
                }
                # 4 寫入redis
                settlement_key = SETTLEMENT_KEY % (user_id, course_id)
                global_coupon_key = GLOBAL_COUPON_KEY % user_id
                CONN.hmset(settlement_key, settlement_info)
                if global_coupon_dict:
                    CONN.hmset(global_coupon_key, global_coupon_dict)
                # 5 刪除購物車中的數據
                CONN.delete(shopping_car_key)
            res.data = "加入結算中心成功"
        except Exception as e:
            res.code = 1020
            res.error = "結算失敗"
        return Response(res.dict)

    def get(self, request):
        res = BaseResponse()
        try:
            # 1, 獲取user_id
            user_id = request.user.pk
            # 2,  拼接全部key
            # 3, 去redis取數據
            settlement_key = SETTLEMENT_KEY % (user_id, "*")
            global_coupon_key = GLOBAL_COUPON_KEY % user_id
            all_keys = CONN.scan_iter(settlement_key)
            ret = []
            for key in all_keys:
                ret.append(CONN.hgetall(key))
            global_coupon_info = CONN.hgetall(global_coupon_key)
            res.data = {
                "settlement_info": ret,
                "global_coupon_dict": global_coupon_info
            }
        except Exception as e:
            res.code = 1024
            res.error = "獲取結算中心失敗"
        return Response(res.dict)

    def put(self, request):
        # course_id  course_coupon_id  global_coupon_id
        res = BaseResponse()
        try:
            # 1, 獲取前端傳過來數據
            course_id = request.data.get("course_id", "")
            course_coupon_id = request.data.get("course_coupon_id", "")
            global_coupon_id = request.data.get("global_coupon_id", "")
            user_id = request.user.pk
            # 2, 校驗數據合法性
            # 2.1 校驗course_id
            key = SETTLEMENT_KEY % (user_id, course_id)
            if course_id:
                if not CONN.exists(key):
                    res.code = 1060
                    res.error = "課程ID不合法"
                    return Response(res.dict)
            # 2.2 校驗 course_coupon_id
            if course_coupon_id:
                course_coupon_dict = json.loads(CONN.hget(key, "course_coupon_dict"))
                if str(course_coupon_id) not in course_coupon_dict:
                    res.code = 1061
                    res.error = "課程優惠券ID不合法"
                    return Response(res.dict)
            # 2.3 校驗global_coupon_id
            if global_coupon_id:
                global_coupon_key = GLOBAL_COUPON_KEY % user_id
                if not CONN.exists(global_coupon_key):
                    res.code = 1062
                    res.error = "全局優惠券ID不合法"
                    return Response(res.dict)
                CONN.hset(global_coupon_key, "default_global_coupon_id", global_coupon_id)
            # 3,修改redis中數據
            CONN.hset(key, "default_coupon_id", course_coupon_id)
            res.data = "更新優惠券成功"
        except Exception as e:
            res.code = 1026
            res.error = "更改優惠券失敗"
        return Response(res.dict)
shopping中settlement_view.py

6、支付中心模塊

from rest_framework.views import APIView
from rest_framework.response import Response
from utils.my_auth import LoginAuth
from utils.base_response import BaseResponse
from .settlement_view import SETTLEMENT_KEY, GLOBAL_COUPON_KEY
from utils.redis_pool import POOL
import redis
from Course.models import Course
from .models import Coupon
from django.utils.timezone import now


COON = redis.Redis(connection_pool=POOL)


#  price  balance
class PaymentView(APIView):
    authentication_classes = [LoginAuth, ]

    def post(self, request):
        res = BaseResponse()
        # 1 獲取數據
        balance = request.data.get("balance", 0)
        price = request.data.get("price", "")
        user_id = request.user.pk
        # 2 校驗數據的合法性
        # 2.1 校驗貝里數是否合法
        if int(balance) > request.user.balance:
            res.code = 1070
            res.error = "抵扣的貝里錯誤"
            return Response(res.dict)
        # 2.2 從用戶的結算中心拿數據 跟數據庫比對是否合法
        settlement_key = SETTLEMENT_KEY % (user_id, "*")
        all_keys = COON.scan_iter(settlement_key)
        # 課程id是否合法
        course_rebate_total_price = 0
        for key in all_keys:
            settlement_info = COON.hgetall(key)
            course_id = settlement_info["id"]
            course_obj = Course.objects.filter(id=course_id).first()
            if not course_obj or course_obj.status == 1:
                res.code = 1071
                res.error = "課程id不合法"
                return Response(res.dict)
            # 課程優惠券是否過時
            course_coupon_id = settlement_info.get("default_coupon_id", 0)
            if course_coupon_id:
                coupon_dict = Coupon.objects.filter(
                            id=course_coupon_id,
                            couponrecord__status=0,
                            couponrecord__account_id=user_id,
                            object_id=course_id,
                            valid_begin_date__lte=now(),
                            valid_end_date__gte=now(),
                            ).values("coupon_type", "money_equivalent_value", "off_percent", "minimum_consume")
            if not coupon_dict:
                res.code = 1072
                res.error = "優惠券不合法"
                return Response(res.dict)
            # 2.3 校驗price
            # 獲得全部的課程的折後價格和
            course_pirce = settlement_info["price"]
            course_rebate_price = self.account_price(coupon_dict, course_pirce)
            if course_rebate_price == -1:
                res.code = 1074
                res.error = "課程優惠券不符合要求"
                return Response(res.dict)
            course_rebate_total_price += course_rebate_price
        # 跟全局優惠券作折扣
        # 校驗全局優惠券是否合法
        global_coupon_key = GLOBAL_COUPON_KEY % user_id
        global_coupon_id = int(COON.hget(global_coupon_key, "default_global_coupon_id"))
        if global_coupon_id:
            global_coupon_dict = Coupon.objects.filter(
                id=global_coupon_id,
                couponrecord__status=0,
                couponrecord__account_id=user_id,
                valid_begin_date__lte=now(),
                valid_end_date__gte=now(),
            ).values("coupon_type", "money_equivalent_value", "off_percent", "minimum_consume")
        if not global_coupon_dict:
            res.code = 1073
            res.error = "全局優惠券id不合法"
            return Response(res.dict)
        global_rebate_price = self.account_price(global_coupon_dict, course_rebate_total_price)
        if global_rebate_price == -1:
            res.code = 1076
            res.error = "全局優惠券不符合要求"
            return Response(res.dict)
        # 抵扣貝里
        balance_money = balance / 100
        balance_rebate_price = global_rebate_price - balance
        if balance_rebate_price < 0:
            balance_rebate_price = 0
        # 終極校驗price
        if balance_rebate_price != price:
            res.code = 1078
            res.error = "價格不合法"
            return Response(res.dict)
        # 先去建立訂單  訂單狀態未支付狀態
        # 3 調用支付寶接口支付
            # 若是成功支付支付寶會給咱們發回調
            # 改變訂單的狀態
            # 注意訂單詳情表有多個記錄
            # 更改優惠券的使用狀態
            # 更改用戶表裏的貝里 貝里要添加交易記錄







    def account_price(self, coupon_dict, price):
        coupon_type = coupon_dict["coupon_type"]
        if coupon_type == 0:
            # 通用優惠券
            money_equivalent_value = coupon_dict["money_equivalent_value"]
            if price - money_equivalent_value >=0:
                rebate_price = price - money_equivalent_value
            else:
                rebate_price = 0
        elif coupon_type == 1:
            # 滿減
            money_equivalent_value = coupon_dict["money_equivalent_value"]
            minimum_consume = coupon_dict["minimum_consume"]
            if price >= minimum_consume:
                rebate_price = price - money_equivalent_value
            else:
                return -1
        elif coupon_type == 2:
            # 折扣
            minimum_consume = coupon_dict["minimum_consume"]
            off_percent = coupon_dict["off_percent"]
            if price >= minimum_consume:
                rebate_price = price * (off_percent / 100)
            else:
                return -1
        return rebate_price
shopping中payment_view.py
相關文章
相關標籤/搜索