学位课表、老师表和价格策略表
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,也可以是我们自己定义的匿名用户
浙公网安备 33010602011771号