• 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路径发送请求~~

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

    幻想毫无价值,计划渺如尘埃,目标不可能达到。这一切的一切毫无意义——除非我们付诸行动。
  • 相关阅读:
    vim删除某一列
    linux下在当前文件夹查找一个字符串信息
    .tar和.tar.gz的区别
    visual studio中调用masm汇编
    iconv转码失败的原因
    终端查看
    kubectl 常用命令总结
    Charles实战之Charles抓取https请求
    Fiddler实战之拟2G、3G、4G网络进行弱网测试
    Fiddler实战之使用Fiddler模拟弱网环境
  • 原文地址:https://www.cnblogs.com/TodayWind/p/13903918.html
Copyright © 2020-2023  润新知