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