学位课表、老师表和价格策略表

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


class DegreeCourse(models.Model):
    """学位课程"""
    name = models.CharField(max_length=128, unique=True)
    course_img = models.CharField(max_length=255, verbose_name="缩略图")
    brief = models.TextField(verbose_name="学位课程简介", )
    total_scholarship = models.PositiveIntegerField(verbose_name="总奖学金(贝里)", default=40000)
    mentor_compensation_bonus = models.PositiveIntegerField(verbose_name="本课程的导师辅导费用(贝里)", default=15000)
    period = models.PositiveIntegerField(verbose_name="建议学习周期(days)", default=150, help_text='为了计算学位奖学金')
    prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024)
    teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师")

    # 用于GenericForeignKey反向查询,不会生成表字段,切勿删除
    degreecourse_price_policy = GenericRelation("PricePolicy")


class Teacher(models.Model):
    """讲师、导师表"""
    name = models.CharField(max_length=32, verbose_name='姓名')
    role_choices = ((1, '讲师'), (2, '导师'))
    role = models.SmallIntegerField(choices=role_choices, default=1)

    title = models.CharField(max_length=64, verbose_name="职位、职称")
    signature = models.CharField(max_length=255, verbose_name="导师签名", blank=True, null=True)
    image = models.CharField(max_length=128, verbose_name='头像')
    brief = models.TextField(max_length=1024, verbose_name='简介')


class PricePolicy(models.Model):
    """价格与有课程效期表"""
    content_type = models.ForeignKey(ContentType, verbose_name='关联普通课或者学位课表',related_name='x1')
    object_id = models.PositiveIntegerField(verbose_name='关联普通课或者学位课中的课程ID')
    content_object = GenericForeignKey('content_type', 'object_id')

    valid_period_choices = (
        (1, '1天'),
        (3, '3天'),
        (7, '1周'),
        (14, '2周'),
        (30, '1个月'),
        (60, '2个月'),
        (90, '3个月'),
        (180, '6个月'),
        (210, '12个月'),
        (540, '18个月'),
        (720, '24个月'),
    )
    valid_period = models.SmallIntegerField(choices=valid_period_choices, verbose_name='课程周期')
    price = models.FloatField(verbose_name='价格')

我们先通过这三张表来实现一些简单的查询api

查询课程列表

在rest_framework中我们的视图类何以继承多种类,我们可以不同的需求进行选择

我们现在的需求是查询课程列表,首先我们需要定义url,这里我们的url使用 api/xxx/的形式,需要做一次分发,所以在项目的url中

from django.conf.urls import url,include
from django.contrib import admin

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

然后在我们的app中定义分发的url

from django.conf.urls import url
from .views import course
from .views import price

urlpatterns = [
    url(r'^course/$', course.CourseView.as_view({'get':'list'})),
    url(r'^course/(?P<pk>\d+)$', course.CourseView.as_view({'get':'retrieve'})),

    url(r'^price/$', price.PriceView.as_view()),
]

原本我们的视图都是写在app的views.py文件中,但是由于可能涉及的逻辑较多,我们可以将视图分开,不同的表对应不同的视图文件

视图内容

from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
from django.core.exceptions import ObjectDoesNotExist
from api import models
from api.response.base import BaseResponse
from api.serializers.course import CourseSerializer,CourseDetailSerializer
from api.pagination.page import LuffyPageNumberPagination


class MyException(Exception):
    def __init__(self,msg):
        self.msg = msg


class CourseView(GenericViewSet):
    
    def list(self,request,*args,**kwargs):
        """
        获取课程列表
        :return:
        """
        # queryset = [{id:,,name:...},{id:,,name:...},]
        # 方式一:
        # select id,name from course
        # course_list = queryset类型[{id:,,name:...},{id:,,name:...},]
        # course_list = models.DegreeCourse.objects.all().values('id','name')
        # course_list = list(course_list)
        # val = json.dumps(course_list,ensure_ascii=False)
        # return  HttpResponse(val)
        # 方式二:
        # course_list = models.DegreeCourse.objects.all().values('id','name')
        # course_list = list(course_list)
        # return JsonResponse(course_list,safe=False,json_dumps_params={'ensure_ascii':False})

        # 方法三:
        # select id,name from course
        # course_list=Queryset=[obj,obj,obj,obj, obj,obj,obj,obj,obj,obj, ]
        # course_list = models.DegreeCourse.objects.all().defer('name')

        """
        ret = {'code':1000,'data':None,'error':None}
        try:
            # QuerySet,是django的类型
            # 1. 获取数据
            course_list = models.DegreeCourse.objects.all().only('id','name').order_by('-id')
            # 2. 对数据进行分页
            page = LuffyPageNumberPagination()
            page_course_list = page.paginate_queryset(course_list,request,self)
            # 3. 对数据序列化
            ser = CourseSerializer(instance=page_course_list,many=True)
            ret['data'] = ser.data
        except Exception as e:
            ret['code'] = 1001
            ret['error'] = '获取数据失败'
        return Response(ret)
        """
        ret = BaseResponse()
        try:
            # QuerySet,是django的类型
            # 1. 获取数据
            course_list = models.DegreeCourse.objects.all().only('id', 'name').order_by('-id')
            # 2. 对数据进行分页
            page = LuffyPageNumberPagination()
            page_course_list = page.paginate_queryset(course_list, request, self)

            # 3. 对数据序列化
            ser = CourseSerializer(instance=page_course_list, many=True)
            ret.data = ser.data
        except Exception as e:
            ret.code = 1001
            ret.error = '获取数据失败'
        return Response(ret.dict)

由于我们查询的是课程列表,所以应该对应的是list函数

而在查询的过程中我们用到了3种方式,前两种方式我们没有用到serializers序列化

方式1

# queryset = [{id:,,name:...},{id:,,name:...},]
# 方式一:
# select id,name from course
# course_list = queryset类型[{id:,,name:...},{id:,,name:...},]

course_list = models.DegreeCourse.objects.all().values('id','name')
course_list = list(course_list)
val = json.dumps(course_list,ensure_ascii=False)
return  HttpResponse(val)

这里我们用values方法从数据库中拿到[{id:,,name:...},{id:,,name:...},]形式的queryset数据,然后直接用json进行序列化,这里要注意,由于我们name中包含中文,在序列化时会将中文转化为ascii的编码,加上参数ensure_ascii=False就可以让它正常显示中文了

方式2

course_list = models.DegreeCourse.objects.all().values('id','name')
course_list = list(course_list)
return JsonResponse(course_list,safe=False,json_dumps_params={'ensure_ascii':False})

我们使用django的JsonResponse进行序列化并返回数据,但是由于JsonResponse有一个特点,如果传的不是字典会报错,所以需要加safe=False参数防止它报错,同样为了正常显示中文,我们还需要加上方式1中的参数json_dumps_params={'ensure_ascii':False}

 

 方式3

ret = {'code':1000,'data':None,'error':None}
try:
    # QuerySet,是django的类型
    # 1. 获取数据
    course_list = models.DegreeCourse.objects.all().only('id','name').order_by('-id')
    # 2. 对数据进行分页
    page = LuffyPageNumberPagination()
    page_course_list = page.paginate_queryset(course_list,request,self)
    # 3. 对数据序列化
    ser = CourseSerializer(instance=page_course_list,many=True)
    ret['data'] = ser.data
except Exception as e:
    ret['code'] = 1001
    ret['error'] = '获取数据失败'
return Response(ret)

这里我们使用serializers进行序列化,首先要查询到包含数据对象的queryset,在查询时我们用到了only参数,这个参数的意思是查询到的对象中只包含我们only中的字段,如果我们依然用对象.其它字段,那么会再进行一次数据库查询,与only对应的是defer,它的意思

是查询到的对象中不包含某些字段,查询到数据后我们对数据进行分页并序列化

分页组件

from rest_framework.pagination import PageNumberPagination


class LuffyPageNumberPagination(PageNumberPagination):
    page_size = 1
    max_page_size = 20
    page_size_query_param = 'size'

序列化组件

from rest_framework import serializers
from rest_framework.serializers import Serializer,ModelSerializer
from api import models

class CourseSerializer(ModelSerializer):
    class Meta:
        model = models.DegreeCourse
        fields = ['id','name']

这些组件我们都写在单独的文件中,使用时导入使用

在之前的使用中,我们通过序列化类实例出对象ser后,都是直接将ser.data返回,这样的方式不是很好,我们需要返回更多的信息,所以在这里我们定义了ret = {'code':1000,'data':None,'error':None},用于返回更多的信息,当数据取成功后我们将数据放到ret["data"]中

同时取数据时我们还在用try捕捉异常,如果取数据时出错,则将错误信息放到ret["error"]中,最后返回ret

我们也可以将上面ret这个字典的内容封装到一个类中使用

class BaseResponse(object):
    def __init__(self,code=1000,data=None,error=None):
        self.code = code
        self.data = data
        self.error = error
    @property
    def dict(self):
        return self.__dict__

在视图中,可以直接使用这个类实例化一个对象进行使用

ret = BaseResponse()
try:
    # QuerySet,是django的类型
    # 1. 获取数据
    course_list = models.DegreeCourse.objects.all().only('id', 'name').order_by('-id')
    # 2. 对数据进行分页
    page = LuffyPageNumberPagination()
    page_course_list = page.paginate_queryset(course_list, request, self)
    # 3. 对数据序列化
    ser = CourseSerializer(instance=page_course_list, many=True)
    ret.data = ser.data
except Exception as e:
    ret.code = 1001
    ret.error = '获取数据失败'
return Response(ret.dict)

获取课程详细

拿到课程列表后我们就应该能通过点击获取每个课程的详细信息了,首先还是定义url

from django.conf.urls import url
from .views import course
from .views import price

urlpatterns = [
    url(r'^course/$', course.CourseView.as_view({'get':'list'})),
    url(r'^course/(?P<pk>\d+)$', course.CourseView.as_view({'get':'retrieve'})),

    url(r'^price/$', price.PriceView.as_view()),
]

这里我们和课程列表使用了同样的视图类,由于都是get请求所以我们要在as_view中增加参数来进行区分

课程详细的serializers类

"""
class CourseDetailSerializer(ModelSerializer):
    class Meta:
        model = models.DegreeCourse
        fields = '__all__'
        depth = 1
"""
class CourseDetailSerializer(ModelSerializer):
    show_teachers = serializers.SerializerMethodField()
    price_policy = serializers.SerializerMethodField()
    class Meta:
        model = models.DegreeCourse
        fields = ['id','name','show_teachers','price_policy']
    def get_show_teachers(self,obj):
        teacher_list = obj.teachers.all()
        return [{'id':row.id,'name':row.name} for row in teacher_list]
    def get_price_policy(self,obj):
        price_policy_list = obj.degreecourse_price_policy.all()
        return [{'id': row.id, 'price': row.price,'period':row.get_valid_period_display()} for row in price_policy_list]

由于课程和老师还有价格策略表有关联,默认情况下对于老师表这种多对多的字段会展示对应老师的id列表,这里我们使用depth参数,当为1时,与课程关联的老师的信息也会详细显示,如果老师也有关联的字段,那么可以设置depth=2来展示

当然,我们这里也可以通过serializers.SerializerMethodField()添加两个自定义的字段,再通过get_字段名的方式获取了与课程相关的老师的信息和价格策略信息,这里返回的都是列表中加字典的数据形式

在视图类中我们定义retrieve方法

def retrieve(self,request,pk,*args,**kwargs):
    """
        示例1:
            ret = BaseResponse()
            try:
                obj = models.DegreeCourse.objects.filter(id=pk).first()
                if not obj:
                    raise MyException('数据不存在')
            except MyException as e:
                ret.code = 1001
                ret.error = e.msg
            except Exception as e:
                ret.code = 1002
                ret.error = str(e)
            return Response(ret.dict)
    """
    # 示例2:
    ret = BaseResponse()
    try:
        obj = models.DegreeCourse.objects.get(id=pk)
        # obj = CourseDetailSerializer.__new__
        # obj.__init__
        # many=False,
        #       obj= CourseDetailSerializer的对象来进行序列化数据。
        #       obj.__init__
        # many=True
        #       obj = ListSerializer() 的对象来进行序列化数据。
        #       obj.__init__
        ser = CourseDetailSerializer(instance=obj, many=False)
        ret.data = ser.data
    except ObjectDoesNotExist as e:
        ret.code = 1001
        ret.error = '查询数据不存在'
    except Exception as e:
        ret.code = 1002
        ret.error = "查询失败"
    return Response(ret.dict)

在这个方法中我们获取pk值对应的课程数据对象,在通过序列化类进行序列化,这里需要注意的是序列化单个对象时,参数many=False,其实many参数决定了我们用来序列化的类,True和False对应的__new__方法中生成的对象是不同的,序列化完成后同样是使用

ret来返回数据信息,这里我们同样进行异常捕获,只是示例1中我们自己定义了一个错误类

class MyException(Exception):
    def __init__(self,msg):
        self.msg = msg

用户认证

在做用户认证时,我们还是先创建一个认证类

from rest_framework.authentication import BaseAuthentication
from rest_framework.request import Request
from rest_framework.exceptions import AuthenticationFailed

class LuffyAuthentication(BaseAuthentication):

    def authenticate(self,request):
        # 去请求体中获取POST值: request.data -> request._request.POST
        # 去URL中获取GET传值:request.query_params -> request._request.GET
        # token = request.query_params.get('token')
        # 去数据库中检查


        # 认证失败,抛出异常
        # raise AuthenticationFailed('认证失败')
        # if not token:
        #     raise AuthenticationFailed({'code':9999,'error':'认证失败'})
        # # 认证成功,(request.user,request.auth)
        # return ('alex',None)

        pass

这里要注意的是由于rest_freamwork对request又进行了一次封装,所以我没呢可以从request.data中取到请求体中的数据,也可以从request.query_params中取到url中GET的传值

认证是我们可以先从request.query_params中取到token的值,再根据token判断是否登录,如果验证成功了那么我们可以返回一个元组,元组的值会分别赋给request.user和request.auth,验证失败了则可以抛出异常AuthenticationFailed,在之前我们都只是抛出

异常的信息,在返回时会自动给我们返回一个字典{"detail":异常信息},但是现在我们需要返回更多的信息,所以我们可以在异性信息中放入一个字典AuthenticationFailed({'code':9999,'error':'认证失败'})

如果我们不返回元组,也不抛出异常,而是返回None,那么默认会是一个匿名用户,在request.user中会取到Anonymous,我们也可以在settings中配相关参数,来决定返回None时request.user和request.auth的值

REST_FRAMEWORK = {
    'UNAUTHENTICATED_USER':None,
    'UNAUTHENTICATED_TOKEN':None
    # 'UNAUTHENTICATED_USER': lambda : "匿名用户",
    # 'UNAUTHENTICATED_TOKEN': lambda : '匿名Token',
}

可以是None,也可以是我们自己定义的匿名用户

 

posted on 2018-04-18 19:00  Py行僧  阅读(181)  评论(0编辑  收藏  举报