DRF

课程接口的编写

"""
Django settings for LuffyBoy project.

Generated by 'django-admin startproject' using Django 1.11.11.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '#v&kmj7$b3veb*36@ha^=#!f&psro#tw_3nt03vg+9+x$oqa&r'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'course.apps.CourseConfig',
    "rest_framework",
    'login',
    'pay'
]
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'utils.middlewares.MyCors',

]

ROOT_URLCONF = 'LuffyBoy.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'LuffyBoy.wsgi.application'

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = False

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'
# media配置
MEDIA_URL = "media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
settings.py
from django.urls import path, re_path
from .views import CourseCategoryView, CourseView,CourseDetailView

urlpatterns = [
    re_path(r'category$', CourseCategoryView.as_view()),
    re_path(r'^$', CourseView.as_view()),
    re_path(r'detail/(?P<id>d+)$',CourseDetailView.as_view()),

]
urls.py
from django.contrib import admin
from . import models

# Register your models here.

for table in models.__all__:
    admin.site.register(getattr(models, table))
admin.py
from rest_framework import serializers
from . import models


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


class CourseSerializer(serializers.ModelSerializer):
    # 重写choise字段
    level = serializers.CharField(source='get_level_display')
    price = serializers.SerializerMethodField()
    # 课程图片
    course_img = serializers.SerializerMethodField()

    def get_price(self, obj):
        price_policy_obj = obj.price_policy.all().order_by('price').first()
        return price_policy_obj.price

    def get_course_img(self, obj):
        return 'http://127.0.0.1:8001/media/' + str(obj.course_img)

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


class CourseDetailSerializer(serializers.ModelSerializer):
    recommend_courses = serializers.SerializerMethodField()
    teachers = serializers.SerializerMethodField()
    # 课程大纲
    outline = serializers.SerializerMethodField()
    # 价格策略
    price_policy = serializers.SerializerMethodField()
    # 标题
    title = serializers.SerializerMethodField()
    # 难度
    level = serializers.SerializerMethodField()
    # 学习人数
    study_num = serializers.SerializerMethodField()

    def get_recommend_courses(self, obj):
        # 多对多
        return [{'id': item.id, 'title': item.title} for item in obj.recommend_courses.all()]

    def get_teachers(self, obj):
        # 多对多
        return [{'id': item.id, 'name': item.name, 'brief': item.brief} for item in obj.teachers.all()]

    #  课程大纲
    def get_outline(self, obj):
        # course_outline 反向查询字段,一对多
        return [{'id': item.id, 'title': item.title, 'content': item.content} for item in
                obj.course_outline.all().order_by('order')]

    # 价格策略
    def get_price_policy(self, obj):
        # Content_type
        # 跨表到Course表
        return [{'id': item.id, 'price': item.price, 'valid_period': item.get_valid_period_display()} for item in
                obj.course.price_policy.all()]

    def get_title(self, obj):
        return obj.course.title

    # 难度
    def get_level(self, obj):
        return obj.course.get_level_display()

    # 学习人数
    def get_study_num(self, obj):
        return obj.course.study_num

    class Meta:
        model = models.CourseDetail
        exclude = ['course']
serializers.py
from django.shortcuts import render
from rest_framework.views import APIView
from . import models
from . import serializers
from rest_framework.response import Response
import json

# Create your views here.


class CourseCategoryView(APIView):
    def get(self, request):
        # 从数据库中拿出所有的分类
        queryset = models.Category.objects.all()
        # 序列化所有的分类
        ser_obj = serializers.CategorySerializer(queryset, many=True)
        # 返回序列化好的数据
        return Response(ser_obj.data)


class CourseView(APIView):
    def get(self, request, ):
        # 判断category_id
        category_id = request.query_params.get('category_id', 0)
        category_id = int(category_id)
        if category_id == 0:
            queryset = models.Course.objects.all().order_by('order')
            # print(queryset)
        else:
            # 获取响应的分类数据
            queryset = models.Course.objects.filter(category_id=category_id).order_by('order')
            print(queryset)
        # 序列化数据
        ser_obj = serializers.CourseSerializer(queryset, many=True)
        # 返回序列化好的数据
        print(type(ser_obj.data))
        return Response(ser_obj.data)


class CourseDetailView(APIView):
    def get(self, request, id):
        # 获取这个课程id找到课程详情对象
        queryset = models.CourseDetail.objects.filter(course_id=id).first()
        # 序列化这个课程详情对象
        ser_obj = serializers.CourseDetailSerializer(queryset)
        # 返回序列化数据
        return Response(ser_obj.data)
views.py
from django.db import models
from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey
from django.contrib.contenttypes.models import ContentType

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


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='课程的图片')
    # media/course/2018-11/xxx.png
    category = models.ForeignKey(to="Category", verbose_name="课程的分类", on_delete=models.CASCADE)
    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=models.CASCADE)
    # course_type    degree_course_id
    #  0                null
    #  1                null
    #  2                2

    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(verbose_name="课程顺序", help_text="从上一个课程数字往后排, 建议中间空几个数字")
    study_num = models.IntegerField(verbose_name="学习人数", help_text="只要有人买课程,订单表加入数据的同时给这个字段+1")
    is_free = models.BooleanField(default=False)

    # 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=models.CASCADE)
    hours = models.IntegerField(verbose_name="课时")
    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="为什么学习这门课程")
    service = 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=models.CASCADE)
    # 排序用的
    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=models.CASCADE)
    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 __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=models.CASCADE)  # 关联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个月')
                            )
    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=models.CASCADE)  # 关联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=models.CASCADE)
    # 定位对象的id
    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=models.CASCADE)
    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=128, verbose_name='密码')
    token = models.UUIDField(null=True, blank=True)
    create_token_time = models.DateTimeField(auto_now=True)
    beli = models.IntegerField(default=1000)

    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=models.CASCADE)
    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')


#######################################优惠券表
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='等值货币', blank=True, null=True)
    off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只针对折扣券,例7.9折,写79", blank=True, null=True)
    minimun_consume = models.PositiveIntegerField('最低消费', default=0, help_text='仅在满减券时填写此字段')

    content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField('绑定课程', blank=True, null=True, help_text='可以把优惠券跟课程绑定')
    content_object = GenericForeignKey('content_type', 'object_id')

    quantity = models.PositiveIntegerField('数量(张)', default=1)
    open_data = models.DateField('优惠券领取的开始时间')
    close_data = models.DateField('优惠券领取结束时间')
    vaild_begin_data = models.DateTimeField(verbose_name='有效期开始时间', blank=True, null=True)
    vaild_end_data = models.DateTimeField(verbose_name='有效期结束时间', blank=True, null=True)
    coupon_valid_days = models.PositiveIntegerField(verbose_name='优惠劵的有效期(天)', blank=True, null=True,
                                                    help_text='自券被领时开始算起')

    data = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name = "13-优惠券生成规则"
        db_table = verbose_name
        verbose_name_plural = "13-优惠券生成规则"

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


class CouponRecord(models.Model):
    """优惠券发放、消费纪录"""
    coupon = models.ForeignKey("Coupon", on_delete=models.CASCADE)
    user = models.ForeignKey("Account", verbose_name="拥有者", on_delete=models.CASCADE)
    status_choices = ((0, '未使用'), (1, '已使用'), (2, '已过期'))
    status = models.SmallIntegerField(choices=status_choices, default=0)
    get_time = models.DateTimeField(verbose_name="领取时间", help_text="用户领取时间", null=True, blank=True)
    used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用时间")

    class Meta:
        verbose_name = "14-优惠券发放、消费纪录"
        db_table = verbose_name
        verbose_name_plural = verbose_name

    def __str__(self):
        return '%s-%s-%s' % (self.user, self.coupon, self.get_status_display())


######################################  订单表


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)  # 考虑到订单合并支付的问题
    user = models.ForeignKey("Account", on_delete=models.CASCADE)
    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 = "15-订单表"
        db_table = verbose_name
        verbose_name_plural = verbose_name

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


class OrderDetail(models.Model):
    """订单详情"""
    order = models.ForeignKey("Order", on_delete=models.CASCADE)

    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)  # 可关联普通课程或学位
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    original_price = models.FloatField("课程原价")
    price = models.FloatField("折后价格")
    content = models.CharField(max_length=255, blank=True, null=True)  #
    valid_period_display = models.CharField("有效期显示", max_length=32)  # 在订单页显示
    valid_period = models.PositiveIntegerField("有效期(days)")  # 课程有效期
    memo = models.CharField(max_length=255, blank=True, null=True)

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

    class Meta:
        verbose_name = "16-订单详细"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ("order", 'content_type', 'object_id')
models.py

登录,注册接口的编写

from django.urls import path, include
from .views import RegisterView, LoginView


urlpatterns = [
    path('register/', RegisterView.as_view()),
    path('login/', LoginView.as_view()),

]
urls.py
from rest_framework import serializers
from course.models import Account
import hashlib

# 后端加密
class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ["username", "pwd"]
        
    def create(self, validated_data):
        username = validated_data["username"]
        pwd = validated_data["pwd"]
        hash_pwd = hashlib.md5(pwd.encode()).hexdigest()
        user_obj = Account.objects.create(username=username, pwd=hash_pwd)
        return user_obj
serializers.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import AccountSerializers
from utils.base_response import BaseResponse
from course.models import Account
import uuid
import hashlib


class RegisterView(APIView):
    def post(self, request):
        # 获取用户名和密码
        # 拿序列化器做验证
        ser_obj = AccountSerializers(data=request.data)
        if ser_obj.is_valid():
            ser_obj.save()
            return Response('注册成功')
        return Response(ser_obj.errors)


class LoginView(APIView):
    def post(self, request):
        ret = BaseResponse()
        # 获取用户名和密码
        username = request.data.get('username', '')

        if not username:
            ret.code = 1010
            ret.error = '用户名不能为空'
        pwd = request.data.get('pwd', '')
        if pwd:
            pwd = hashlib.md5(pwd.encode()).hexdigest()
        if not pwd:
            ret.code = 1011
            ret.error = '密码不能为空'
        try:
            # 判断是否有这个对象
            user_obj = Account.objects.filter(username=username, pwd=pwd).first()
            if not user_obj:
                ret.code = 1012
                ret.error = '用户名或密码错误'
            # 有这个对象,生成token
            user_obj.token = uuid.uuid4()
            user_obj.save()
            ret.data = '登录成功'
        except Exception as e:
            ret.code = 1013
            ret.error = '登录失败'
        return Response(ret.dict)
views.py

认证

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from course.models import Account
import datetime
from django.utils.timezone import now


class MyAuth(BaseAuthentication):
    def authenticate(self, request):
        # 过滤前端传来的复杂请求
        if request.method == 'OPTIONS':
            return None
        # 拿到前端带过来的token
        # print(request.META)
        token = request.META.get('HTTP_AUTHENTICATE', "")
        # 判断是否有这个token
        if not token:
            raise AuthenticationFailed({"code": 1020, "error": "没有携带token"})
        user_obj = Account.objects.filter(token=token).first()
        if not user_obj:
            raise AuthenticationFailed({"code": 1021, "error": "token不合法"})
        # 判断token是否过期
        old_time = user_obj.create_token_time
        # print(old_time)
        # print(type(old_time))
        now_time = now()
        # print(now)
        # print(type(now))
        # print((now_time-old_time).days)
        if (now_time - old_time).days > 7:
            raise AuthenticationFailed({"code": 1022, "error": "token过期请重新登录"})
        return (user_obj, token)
authentication.py

自定义状态码

class BaseResponse(object):
    def __init__(self):
        self.code = 1000
        self.error = ''
        self.data = ''

    @property
    def dict(self):
        return self.__dict__
状态码

自定义中间件

from django.utils.deprecation import MiddlewareMixin


class MyCors(MiddlewareMixin):
    def process_response(self, request, response):
        response["Access-Control-Allow-Origin"] = "*"
        if request.method == "OPTIONS":
            # 复杂请求会先发预检
            response["Access-Control-Allow-Headers"] = "Content-Type,AUTHENTICATE"  # AUTHENTICATE 把token加入进来防止前端报跨域
            response["Access-Control-Allow-Methods"] = "PUT,PATCH,DELETE"
        return response
# 中间件添加相应头
中间件解决跨域

自定义异常

class CommonException(Exception):
    def __init__(self, msg, code):
        self.msg = msg
        self.code = code
自定义异常

redis

import redis

# redis的连接池
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, decode_responses=True)
# decode_responses=True 不用转为bytes
conn = redis.Redis(connection_pool=pool)
redis的连接池

购物车,结算,支付接口的编写

from django.urls import path, include
from .views import ShoppingCarView,AccountView,PaymenyView

urlpatterns = [
    path('shopping_car', ShoppingCarView.as_view()),
    path('account', AccountView.as_view()),
    path('payment', PaymenyView.as_view())

]
urls.py
from rest_framework.views import APIView
from rest_framework.response import Response
from utils.authentication import MyAuth
from utils.radis_pool import pool
import redis
from utils.base_response import BaseResponse
from course import models
import json
from utils.exceptions import CommonException
import datetime
from django.utils.timezone import now
from utils.pay import AliPay
from django.core.exceptions import ObjectDoesNotExist

SHOPPING_CAR_KEY = 'shopping_car_%s_%s'
REDIS_CONN = redis.Redis(connection_pool=pool)
ACCOUNT_KEY = 'account_%s_%s'


class ShoppingCarView(APIView):
    """
    shopping_car_%s_%s: {
    id: 1,
    title: CMDB,
    course_img: xxxx,
    price_policy_dict: {
        1: {有效期1个月, 99}
    },
    default_price_policy_id :3
}
    """
    authentication_classes = [MyAuth, ]

    def post(self, request):
        res = BaseResponse()
        try:
            # 1 获取前端传过来的course_id 以及price_policy_id user_id
            course_id = request.data.get("course_id", "")
            price_policy_id = request.data.get("price_policy_id", "")
            user_id = request.user.id
            # 2 验证数据的合法性
            # 2.1 验证course_id是否合法
            course_obj = models.Course.objects.filter(id=course_id).first()
            if not course_obj:
                res.code = 1031
                res.error = "课程不存在"
                return Response(res.dict)
            # 2.2 验证价格策略是否合法
            # 该课程的所有价格策略对象
            price_policy_queryset = course_obj.price_policy.all()
            # 循环获得每个价格策略的详细信息
            price_policy_dict = {}
            for price_policy_obj in price_policy_queryset:
                price_policy_dict[price_policy_obj.id] = {
                    "valid_period_text": price_policy_obj.get_valid_period_display(),
                    "price": price_policy_obj.price
                }
            # 判断价格策略是否在价格策略的字典里
            if price_policy_id not in price_policy_dict:
                res.code = 1032
                res.error = "价格策略不存在"
                return Response(res.dict)
            # 3 构建我们想要的数据结构
            course_info = {
                "id": course_id,
                "title": course_obj.title,
                "course_img": course_obj.course_img,
                "price_policy_dict": json.dumps(price_policy_dict, ensure_ascii=False),
                "default_policy_id": price_policy_id
            }
            # 4 写入redis
            # 4.1 先拼接购物车的key
            shopping_car_key = SHOPPING_CAR_KEY % (user_id, course_id)
            # 4.2 写入redis
            REDIS_CONN.hmset(shopping_car_key, course_info)
            res.data = "加入购物车成功"
        except Exception as e:
            res.code = 1030
            res.error = "加入购物车失败"
        return Response(res.dict)

    def get(self, request):
        res = BaseResponse()
        try:
            # 1 取到user_id
            user_id = request.user.id
            # 2 拼接购物车的key,redis的key支持模糊匹配
            shopping_car_key = SHOPPING_CAR_KEY % (user_id, "*")
            # shopping_car_1_*
            # shopping_car_1_asdgnlaksdj
            # 3 去redis读取该用户的所有加入购物车的课程
            # 3.1 先去模糊匹配出所有符合要求的key
            all_keys = REDIS_CONN.scan_iter(shopping_car_key)
            print(all_keys)
            # 3.2 循环所有的keys 得到每个可以
            shopping_car_list = []
            for key in all_keys:
                course_info = REDIS_CONN.hgetall(key)
                course_info["price_policy_dict"] = json.loads(course_info["price_policy_dict"])
                shopping_car_list.append(course_info)
            res.data = shopping_car_list
        except Exception as e:
            res.code = 1033
            res.error = "获取购物车失败"
        return Response(res.dict)

    def put(self, request):
        res = BaseResponse()
        try:
            course_id = request.data.get("course_id", "")
            price_policy_id = request.data.get("price_policy_id", "")
            user_id = request.user.id
            shopping_car_key = SHOPPING_CAR_KEY % (user_id, course_id)
            if not REDIS_CONN.exists(shopping_car_key):
                res.code = 1035
                res.error = '课程不不存在'
                return Response(res.dict)
            course_info = REDIS_CONN.hgetall(shopping_car_key)
            price_policy_dict = json.loads(course_info['price_policy_dict'])
            if str(price_policy_id) not in price_policy_dict:
                res.code = 1036
                res.error = '价格策略不合法'
                return Response(res.dict)
            course_info['default_price_policy_id'] = price_policy_id
            REDIS_CONN.hmset(shopping_car_key, course_info)
            res.data = '更新成功'
            return Response(res.dict)




        except Exception as  e:
            res.code = 1034
            res.error = '更新价格策略失败'
            return Response(res.dict)

    def delete(self, request):
        res = BaseResponse()
        try:
            course_id = request.data.get('course_id', '')
            user_id = request.user.id
            shopping_car_key = SHOPPING_CAR_KEY % (user_id, course_id)
            if not REDIS_CONN.exists(shopping_car_key):
                res.code = 1039
                res.error = '删除的课程不存在'
                return Response(res.dict)
            REDIS_CONN.delete(shopping_car_key)
            res.data = '删除成功'
        except Exception as e:
            res.code = 1037
            res.error = '删除失败'
        return Response(res.dict)


class AccountView(APIView):
    '''
        结算接口

                shopping_car_ 1_ 1: {
                    id: 1,
                    title: CMDB,
                    course_img: xxxxx,
                    price_policy_dict: {
                        1: {有效期1个月, 99}

                      },
                    default_price_policy_id: 3

                }


                account_%s_%s:{
                    "course_info":{
                                        id: 1,
                                        title: CMDB,
                                        course_img: xxxxx,
                                        price_policy_dict: {
                                            1: {有效期1个月, 99}

                                          },
                                        default_price_policy_id: 3

                                    },

                    "coupons":{   # 课程优惠卷
                           1:{},
                           3:{},
                    }
                }


                global_coupon_1:{} # 通用优惠卷
        '''
    authentication_classes = [MyAuth, ]

    def post(self, request, *args, **kwargs):
        res = BaseResponse()
        #  1. 获取数据
        user = request.user
        course_id_list = request.data.get('course_id_list')

        try:
            # 2 创建数据结构
            # 清空操作
            # 找到所有以account_userid_*,全部清空
            del_list = REDIS_CONN.keys(ACCOUNT_KEY % (user.pk, '*'))
            REDIS_CONN.delete(*del_list)  # 删除需要加*,

            for course_id in course_id_list:
                shopping_car_key = SHOPPING_CAR_KEY % (user.pk, course_id)
                account_key = ACCOUNT_KEY % (user.pk, course_id)
                account_dict = {}
                # 判断课程是否存在购物车中
                if not REDIS_CONN.exists(shopping_car_key):
                    raise CommonException('课程不存在', 1040)
                # 将课程信息加入到每一个课程结算字典中
                course_info = REDIS_CONN.hgetall(shopping_car_key)
                account_dict['course_info'] = course_info
                # 将课程优惠卷加入结算的每一个字典里
                account_dict['course_coupons'] = self.get_coupon_dict(request, course_id)
                # print(account_dict)
                # 存储结算信息
                REDIS_CONN.set(account_key, json.dumps(account_dict))  # 使用redis字符串方法
            # 获取通用优惠卷,不传课程id即可,存入redis中
            REDIS_CONN.set('global_coupon_%s' % (user.pk), json.dumps(self.get_coupon_dict(request)))
            res.data = '结算成功'

        except CommonException as e:
            res.code = e.code
            res.error = e.msg

        except Exception as e:
            res.code = 600
            res.error = str(e)

        return Response(res.dict)

    def get_coupon_dict(self, request, course_id=None):
        now = datetime.datetime.utcnow()  # 当前时间转为同一时区
        # 将课程优惠卷加入结算的每一个字典里
        coupon_record_list = models.CouponRecord.objects.filter(
            user=request.user,
            status=0,
            coupon__vaild_begin_data__lte=now,
            coupon__vaild_end_data__gt=now,
            coupon__content_type_id=9,
            # 9对应content_type表绑定课程表的id,如果优惠卷绑定不是课程表,那就会查不到
            coupon__object_id=course_id,

        )
        print('coupon_record_list', coupon_record_list)
        coupon_dict = {}
        for coupon_record in coupon_record_list:
            coupon_dict[coupon_record.pk] = {
                'name': coupon_record.coupon.name,
                'coupon_type': coupon_record.coupon.get_coupon_type_display(),
                'money_equivalent_value': coupon_record.coupon.money_equivalent_value,
                'off_percent': coupon_record.coupon.off_percent,
                'minimun_consume': coupon_record.coupon.minimun_consume,
                'vaild_begin_data': coupon_record.coupon.vaild_begin_data.strftime('%Y-%m-%d'),
                # 这里需要转一下,不然json无法序列化这个格式
                'vaild_end_data': coupon_record.coupon.vaild_end_data.strftime('%Y-%m-%d'),

            }
        return coupon_dict

    def get(self, request, *args, **kwargs):
        pass


# 支付接口
class PaymenyView(APIView):
    authentication_classes = [MyAuth]

    def cal_coupun_price(self, price, coupon_record):

        coupon_type = coupon_record.coupon.coupon_type
        money_equivalent_value = coupon_record.coupon.money_equivalent_value
        off_percent = coupon_record.coupon.off_percent
        minimun_consume = coupon_record.coupon.minimun_consume

        rebate_price = 0
        if coupon_type == 0:  # 立减券
            rebate_price = price - money_equivalent_value
            if rebate_price < 0:
                rebate_price = 0
        elif coupon_type == 1:  # 满减券
            if price < minimun_consume:  # 不满足最低消费
                raise CommonException("不满足最低消费", 1060)
            rebate_price = price - money_equivalent_value

        else:  # 折扣

            rebate_price = price * off_percent / 100

        return rebate_price

    def post(self, request):
        '''
        {

        "courses_info":[
                     {
                      course_id:1,
                      price_policy_id:1,
                      coupon_record_id:2
                     },
                      {
                      course_id:2,
                      price_policy_id:5,
                      coupon_record_id:3
                     }
                     ]

        global_coupon_id:1,
        beli:1000"pay_money":268,

        }

        :param request:
        :return:
        '''

        # 1 获取数据
        user = request.user
        courses_info = request.data.get("courses_info")
        global_coupon_id = request.data.get("global_coupon_id")
        beli = request.data.get("beli")
        pay_money = request.data.get("pay_money")
        now = datetime.datetime.utcnow()

        response = BaseResponse()

        # 2 循环课程信息
        try:

            course_price_list = []
            for course_info in courses_info:
                course_id = course_info.get("course_id")
                price_policy_id = course_info.get("price_policy_id")
                coupon_record_id = course_info.get("coupon_record_id")

                # 3 校验数据
                # 3.1判断课程是否存在
                course_obj = models.Course.objects.get(pk=course_id)

                # 3.2 价格策略是否合法
                if price_policy_id not in [obj.pk for obj in course_obj.price_policy.all()]:
                    raise CommonException("价格策略不存在", 1051)

                # 3.3 课程优惠券是否合法

                couponrecord = models.CouponRecord.objects.filter(
                    pk=coupon_record_id,
                    user=request.user,
                    status=0,
                    coupon__vaild_begin_data__lte=now,
                    coupon__vaild_end_data__gt=now,
                    coupon__content_type_id=9,
                    coupon__object_id=course_id
                ).first()
                if not couponrecord:
                    raise CommonException("课程优惠券有问题", 1052)

                # 3.4 计算课程优惠券惠后价格
                course_price = models.PricePolicy.objects.get(pk=price_policy_id).price
                coupon_price = self.cal_coupun_price(course_price, couponrecord)
                course_price_list.append(coupon_price)

            # 4 通用优惠券处理

            # 4.1 通用优惠券是否能合法
            global_couponrecord = models.CouponRecord.objects.filter(
                pk=global_coupon_id,
                user=request.user,
                status=0,
                coupon__vaild_begin_data__lte=now,
                coupon__vaild_end_data__gt=now,
                coupon__content_type_id=9,
                coupon__object_id=None
            ).first()
            if not global_couponrecord:
                raise CommonException("通用优惠券有问题", 1053)

            # 4.2 计算通用优惠券惠后价格
            global_coupon_price = self.cal_coupun_price(sum(course_price_list), global_couponrecord)

            # 5 处理贝里

            # 5.1 校验贝里是否充足
            if beli > request.user.beli:
                raise CommonException("贝里数不够", 1054)

            # 5.2 计算贝里后的价格
            final_price = global_coupon_price - beli / 10
            if final_price < 0:
                final_price = 0
                # 返还贝里数
            print("final", final_price)  # 计算优惠后的价格
            # 6 比较参数pay_money与实际支付价格是否一致
            if final_price != pay_money:
                raise CommonException("实际支付价格与参数价格并不一致", 1055)

            # 7  生成订单
            # Order记录
            # OrderDetail
            # OrderDetail
            # OrderDetail

            # 8 构建支付宝的二维码页面的url
            import time
            alipay = self.get_alipay()
            # 生成支付的url
            query_params = alipay.direct_pay(
                subject="Django课程",  # 商品简单描述
                out_trade_no="x2" + str(time.time()),  # 商户订单号
                total_amount=pay_money,  # 交易金额(单位: 元 保留俩位小数)
            )

            pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)

            response.data = "订单创建成功!"
            response.url = pay_url


        except ObjectDoesNotExist as e:
            response.code = 1050
            response.error = "课程不存在"

        except CommonException as e:
            response.code = e.code
            response.error = e.msg

        # except Exception as e:
        #     response.code = 500
        #     response.error = str(e)

        return Response(response.dict)

    def get_alipay(self):
        # 沙箱环境地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info
        app_id = "2016091100486897"
        # POST请求,用于最后的检测
        notify_url = "http://47.94.172.250:8804/page2/"
        # notify_url = "http://www.wupeiqi.com:8804/page2/"
        # GET请求,用于页面的跳转展示
        return_url = "http://47.94.172.250:8804/page2/"
        # return_url = "http://www.wupeiqi.com:8804/page2/"
        merchant_private_key_path = "utils/keys/app_private_2048.txt"
        alipay_public_key_path = "utils/keys/alipay_public_2048.txt"

        alipay = AliPay(
            appid=app_id,
            app_notify_url=notify_url,
            return_url=return_url,
            app_private_key_path=merchant_private_key_path,
            alipay_public_key_path=alipay_public_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥
            debug=True,  # 默认False,
        )
        return alipay
views.py

支付宝接口

from datetime import datetime
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from base64 import decodebytes, encodebytes
import json



class AliPay(object):
    """
    支付宝支付接口(PC端支付接口)
    """

    def __init__(self, appid, app_notify_url, app_private_key_path,
                 alipay_public_key_path, return_url, debug=False):
        self.appid = appid
        self.app_notify_url = app_notify_url
        self.app_private_key_path = app_private_key_path
        self.app_private_key = None
        self.return_url = return_url
        with open(self.app_private_key_path) as fp:
            self.app_private_key = RSA.importKey(fp.read())
        self.alipay_public_key_path = alipay_public_key_path
        with open(self.alipay_public_key_path) as fp:
            self.alipay_public_key = RSA.importKey(fp.read())

        if debug is True:
            self.__gateway = "https://openapi.alipaydev.com/gateway.do"
        else:
            self.__gateway = "https://openapi.alipay.com/gateway.do"

    def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
        biz_content = {
            "subject": subject,
            "out_trade_no": out_trade_no,
            "total_amount": total_amount,
            "product_code": "FAST_INSTANT_TRADE_PAY",
            # "qr_pay_mode":4
        }

        biz_content.update(kwargs)
        data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
        return self.sign_data(data)

    def build_body(self, method, biz_content, return_url=None):
        data = {
            "app_id": self.appid,
            "method": method,
            "charset": "utf-8",
            "sign_type": "RSA2",
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "version": "1.0",
            "biz_content": biz_content
        }

        if return_url is not None:
            data["notify_url"] = self.app_notify_url
            data["return_url"] = self.return_url

        return data

    def sign_data(self, data):
        data.pop("sign", None)
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
        sign = self.sign(unsigned_string.encode("utf-8"))
        # ordered_items = self.ordered_data(data)
        quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)

        # 获得最终的订单信息字符串
        signed_string = quoted_string + "&sign=" + quote_plus(sign)
        return signed_string

    def ordered_data(self, data):
        complex_keys = []
        for key, value in data.items():
            if isinstance(value, dict):
                complex_keys.append(key)

        # 将字典类型的数据dump出来
        for key in complex_keys:
            data[key] = json.dumps(data[key], separators=(',', ':'))

        return sorted([(k, v) for k, v in data.items()])

    def sign(self, unsigned_string):
        # 开始计算签名
        key = self.app_private_key
        signer = PKCS1_v1_5.new(key)
        signature = signer.sign(SHA256.new(unsigned_string))
        # base64 编码,转换为unicode表示并移除回车
        sign = encodebytes(signature).decode("utf8").replace("
", "")
        return sign

    def _verify(self, raw_content, signature):
        # 开始计算签名
        key = self.alipay_public_key
        signer = PKCS1_v1_5.new(key)
        digest = SHA256.new()
        digest.update(raw_content.encode("utf8"))
        if signer.verify(digest, decodebytes(signature.encode("utf8"))):
            return True
        return False

    def verify(self, data, signature):
        if "sign_type" in data:
            sign_type = data.pop("sign_type")
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
        return self._verify(message, signature)
Pay.py

Django的MEDIA配置

 
# settings.py

STATIC_URL = '/static/'
# Media配置
MEDIA_URL = "media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
 
 
# urls.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})
]
 

这样我们上传的图片~数据库存的是路径地址~~我们前端向后端的media路径发送请求~~

拿到我们想要的图片,视频等资源~~

幻想毫无价值,计划渺如尘埃,目标不可能达到。这一切的一切毫无意义——除非我们付诸行动。
原文地址:https://www.cnblogs.com/TodayWind/p/13903918.html