课程视频(免费):https://www.imooc.com/learn/1274
课程代码:https://github.com/liaogx/drf-tutorial
课程介绍
DRF基本配置
INSTALLED_APPS = ['rest_framework','rest_framework.authtoken',# DRF自带的Token认证功能,会生成一张Token表,记得migrate一下]REST_FRAMEWORK = {"DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema","DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination","PAGE_SIZE": 50,"DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S","DEFAULT_RENDERER_CLASSES": [ # response渲染器"rest_framework.renderers.JSONRenderer","rest_framework.renderers.BrowsableAPIRenderer",],"DEFAULT_PARSER_CLASSES": [ # request.data解析器"rest_framework.parsers.JSONParser", # 解析前端传来的JSON"rest_framework.parsers.FormParser", # 解析前端传来的表单"rest_framework.parsers.MultiPartParser", # 解析前端传来的文件],"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated",],"DEFAULT_AUTHENTICATION_CLASSES": ["rest_framework.authentication.BasicAuthentication", # 基本认证:用户名+密码"rest_framework.authentication.SessionAuthentication","rest_framework.authentication.TokenAuthentication", # 如果加了这个,app中就必须要有'rest_framework.authtoken']}# 可以导入相关模块后分析源码from rest_framework import renderers, parsers, permissions, \authentication, pagination, schemas
在settings.py中设置的REST_FRAMEWORK是全局生效的。比如上面设置了DEFAULT_PERMISSION_CLASSES,将来不管访问什么资源都需要登录认证。
学习思路:上面的很多配置对应的DRF中相关的类,均可以导入后查看源码来加深理解。
urlpatterns = [...path('api-auth/', include('rest_framework.urls')) # DRF的登录退出]
DRF学习地图(20个知识点)

实战开始
1.创建模型
创建app: course
编写模型:
from django.conf import settingsfrom django.db import modelsclass Course(models.Model):name = models.CharField(max_length=255, unique=True, help_text="课程名称", verbose_name="名称")introduction = models.TextField(help_text="课程简介", verbose_name="简介")teacher = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,help_text="课程讲师", verbose_name="讲师")price = models.DecimalField(max_digits=6, decimal_places=2, help_text="课程价格", verbose_name="价格")created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")class Meta:verbose_name = "课程信息"verbose_name_plural = verbose_nameordering = ("price",)def __str__(self):return self.name
将模型注册到admin:
from django.contrib import adminfrom .models import Course@admin.register(Course)class CourseAdmin(admin.ModelAdmin):list_display = ("name", "introduction", "teacher", "price")search_fields = list_displaylist_filter = list_display
2.创建序列化器
from django.contrib.auth.models import Userfrom rest_framework import serializersfrom .models import Courseclass UserSerializer(serializers.ModelSerializer):class Meta:model = Userfields = '__all__'class CourseSerializer(serializers.ModelSerializer):teacher = serializers.ReadOnlyField(source='teacher.username') # 外键字段 只读class Meta:model = Course # 写法和上面的CourseForm类似# exclude = ('id', ) # 注意元组中只有1个元素时不能写成("id")# fields = ('id', 'name', 'introduction', 'teacher', 'price', 'created_at', 'updated_at')fields = '__all__'depth = 2 # 【重要!!!】对于外键字段的序列化,可以指定遍历的深度"""超链接API"""# class CourseSerializer(serializers.HyperlinkedModelSerializer):# teacher = serializers.ReadOnlyField(source='teacher.username')## class Meta:# model = Course# # url是默认值,可在settings.py中设置URL_FIELD_NAME使全局生效# fields = ('id', 'url', 'name', 'introduction', 'teacher', 'price', 'created_at', 'updated_at')
3.视图-开发Restful API接口
Django原生视图API的不足:
分页、排序、认证、权限、限流。
这些Restful API的标配Django几乎啥都没有,都需要自己从零到一实现,太难了。。。
于是就有了DRF的视图。
4种开发方式

在开始视图编写之前,先注册一下根urls.py
from django.contrib import adminfrom django.urls import path, includeurlpatterns = [path('admin/', admin.site.urls),path('api-auth/', include('rest_framework.urls')),path("course/", include("course.urls")),]
(1)函数式编程
from rest_framework import statusfrom rest_framework.decorators import api_viewfrom rest_framework.response import Responsefrom .models import Coursefrom .serializers import CourseSerializer@api_view(["GET", "POST"])def course_list(request):"""获取所有课程信息或新增一个课程:param request::return:"""if request.method == "GET":s = CourseSerializer(instance=Course.objects.all(), many=True)return Response(data=s.data, status=status.HTTP_200_OK)elif request.method == "POST":s = CourseSerializer(data=request.data) # 部分更新用partial=True属性if s.is_valid():s.save(teacher=request.user) # 这里填充teacher属性哦!return Response(data=s.data, status=status.HTTP_201_CREATED)return Response(s.errors, status=status.HTTP_400_BAD_REQUEST)@api_view(["GET", "PUT", "DELETE"])def course_detail(request, pk):"""获取、更新、删除一个课程:param request::param pk::return:"""try: # 常规操作,先看看有没有响应数据course = Course.objects.get(pk=pk)except Course.DoesNotExist: # 如果没有,返回404和错误信息return Response(data={"msg": "没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)else: # 如果有,再进行具体操作if request.method == "GET":s = CourseSerializer(instance=course)return Response(data=s.data, status=status.HTTP_200_OK)elif request.method == "PUT":s = CourseSerializer(instance=course, data=request.data)if s.is_valid():s.save()return Response(data=s.data, status=status.HTTP_200_OK)return Response(data=s.errors, status=status.HTTP_400_BAD_REQUEST)elif request.method == 'PATCH':s = CourseSerializer(instance=course, data=request.data, partial=True)if s.is_valid():s.save()return Response(data=s.data, status=status.HTTP_200_OK)return Response(data=s.errors, status=status.HTTP_400_BAD_REQUEST)elif request.method == "DELETE":course.delete()return Response(status=status.HTTP_204_NO_CONTENT)
from django.urls import pathfrom course import viewsurlpatterns = [# Function Based Viewpath("fbv/list/", views.course_list, name="fbv-list"),path("fbv/detail/<int:pk>/", views.course_detail, name="fbv-detail"),]
(2)类视图
from rest_framework import statusfrom rest_framework.response import Responsefrom rest_framework.views import APIViewfrom .models import Coursefrom .serializers import CourseSerializer"""二、 类视图 Class Based View"""class CourseList(APIView):def get(self, request):queryset = Course.objects.all()s = CourseSerializer(instance=queryset, many=True)# s = CourseSerializer(instance=queryset.first())return Response(s.data, status=status.HTTP_200_OK)def post(self, request):s = CourseSerializer(data=request.data) # 这里的data是前端返回的数据,在return前要调用.is_valid()方法校验if s.is_valid():s.save(teacher=self.request.user) # 注意:这里依然要用代码确定teacher哦!!!# 分别是<class 'django.http.request.QueryDict'> <class 'rest_framework.utils.serializer_helpers.ReturnDict'>print(type(request.data), type(s.data))return Response(data=s.data, status=status.HTTP_201_CREATED)return Response(data=s.errors, status=status.HTTP_400_BAD_REQUEST)class CourseDetail(APIView):@staticmethoddef get_object(pk):try:return Course.objects.get(pk=pk)except Course.DoesNotExist:returndef get(self, request, pk):obj = self.get_object(pk=pk)if not obj:return Response(data={"msg": "没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)s = CourseSerializer(instance=obj)return Response(s.data, status=status.HTTP_200_OK)def put(self, request, pk):obj = self.get_object(pk=pk)if not obj:return Response(data={"msg": "没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)s = CourseSerializer(instance=obj, data=request.data)if s.is_valid():s.save()return Response(data=s.data, status=status.HTTP_200_OK)return Response(data=s.errors, status=status.HTTP_400_BAD_REQUEST)def delete(self, request, pk):obj = self.get_object(pk=pk)if not obj:return Response(data={"msg": "没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)obj.delete()return Response(status=status.HTTP_204_NO_CONTENT)
from django.urls import pathfrom course import viewsurlpatterns = [# Class Based Viewpath("cbv/list/", views.CourseList.as_view(), name="cbv-list"),path("cbv/detail/<int:pk>/", views.CourseDetail.as_view(), name="cbv-detail"),]
(3)通用类视图
from rest_framework import genericsfrom .models import Coursefrom .serializers import CourseSerializer"""三、 通用类视图 Generic Class Based View"""class GCourseList(generics.ListCreateAPIView):queryset = Course.objects.all()serializer_class = CourseSerializerdef perform_create(self, serializer):"""重写该方法的来龙去脉1、通过查看ListCreateAPIView源码可知,该类的创建功能来自于对父类mixins.CreateModelMixin的继承2、继续查看该父类源码,发现它实现创建功能的方法为create()方法3、阅读create()方法,发现其执行保存的逻辑在perform_create()方法中故:如果想自定义保存操作,只需要重写perform_create()方法即可。:param serializer::return:"""serializer.save(teacher=self.request.user)class GCourseDetail(generics.RetrieveUpdateDestroyAPIView):queryset = Course.objects.all()serializer_class = CourseSerializer
from django.urls import pathfrom course import viewsurlpatterns = [# Generic Class Based Viewpath("gcbv/list/", views.GCourseList.as_view(), name="gcbv-list"),path("gcbv/detail/<int:pk>/", views.GCourseDetail.as_view(), name="gcbv-detail"),]
使用Apifox请求一下http://127.0.0.1:8000/course/gcbv/list:
{"count": 4,"next": null,"previous": null,"results": [{"id": 4,"teacher": "admin","name": "及于需重两务","introduction": "nostrud adipisicing nulla","price": "36.00","created_at": "2022-05-09 15:40:56","updated_at": "2022-05-09 15:40:56"},{"id": 2,"teacher": "kkk","name": "给前端同学的设计模式精讲课","introduction": "从“写代码”到“写好代码”到“设计代码”,不仅是技术的提升,更是编程思维的提升,其中最关键的就是设计模式。但很多人想学习设计模式时,往往是查到的资料一堆,有用的知识少见,学得云里雾里,难以实践。本课从23种设计模式中精选前端常用的7种设计模式,利用场景化实例教学,让想学的人真正学明白、会应用、能实践。","price": "56.87","created_at": "2022-05-09 11:57:14","updated_at": "2022-05-09 11:57:14"},{"id": 3,"teacher": "admin","name": "色体中华","introduction": "ullamco anim labore exercitation laboris","price": "90.00","created_at": "2022-05-09 15:40:07","updated_at": "2022-05-09 15:40:07"},{"id": 1,"teacher": "yyy","name": "基于 Vue3 ,打造前台+中台通用提效解决方案","introduction": "写了那么多低效的前台项目,如何高效开发,避免无谓加班?本课程通过带你学习42 种前台常见业务模型的构建原理、15 种中台通用组件与一身的通用型项目,为你之后遇到每一个前台功能提供可操作的实现方案与物料库。掌握这些,前端开发事半功倍真的不是梦!","price": "99.87","created_at": "2022-05-09 10:57:27","updated_at": "2022-05-09 12:07:26"}]}
可以发现,通用类视图对返回结果也做了一定的封装,count表示数据条数,previous和next是用于分页的,真正的数据放在了result里面。
如果我修改每页大小为两条数据,
REST_FRAMEWORK = {"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination","PAGE_SIZE": 2,}
返回的结果是下面这样的:
{"count": 4,"next": "http://127.0.0.1:8000/course/gcbv/list/?page=2","previous": null,"results": [{"id": 4,"teacher": "admin","name": "及于需重两务","introduction": "nostrud adipisicing nulla","price": "36.00","created_at": "2022-05-09 15:40:56","updated_at": "2022-05-09 15:40:56"},{"id": 2,"teacher": "kkk","name": "给前端同学的设计模式精讲课","introduction": "从“写代码”到“写好代码”到“设计代码”,不仅是技术的提升,更是编程思维的提升,其中最关键的就是设计模式。但很多人想学习设计模式时,往往是查到的资料一堆,有用的知识少见,学得云里雾里,难以实践。本课从23种设计模式中精选前端常用的7种设计模式,利用场景化实例教学,让想学的人真正学明白、会应用、能实践。","price": "56.87","created_at": "2022-05-09 11:57:14","updated_at": "2022-05-09 11:57:14"}]}
牛逼吧,超链接字段!
(4)视图集
from rest_framework import viewsetsfrom .models import Coursefrom .serializers import CourseSerializer"""四、 DRF的视图集viewsets"""class CourseViewSet(viewsets.ModelViewSet):queryset = Course.objects.all()serializer_class = CourseSerializerdef perform_create(self, serializer):serializer.save(teacher=self.request.user)
from django.urls import pathfrom course import viewsurlpatterns = [# DRF viewsets manual urlpath("viewsets/", views.CourseViewSet.as_view({"get": "list", "post": "create"}), name="viewsets-list"),path("viewsets/<int:pk>/", views.CourseViewSet.as_view({"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy"}), name="viewsets-detail"),]
上面写url的方法着实恶心,只是为了增加对视图集的理解,下面才是正规操作:
from django.urls import path, includefrom rest_framework.routers import DefaultRouterfrom course import viewsrouter = DefaultRouter()router.register(prefix="viewsets", viewset=views.CourseViewSet)urlpatterns = [# DRF viewsets router urlpath("", include(router.urls))]
4.认证和权限
基本概念
先捋捋概念:
- 认证:对用户登录的身份进行校验
- 权限:对于一个认证通过的用户,他能访问哪些接口,或者对于一个接口,他能拿到什么级别的数据
再捋捋顺序:
- 由源码分析可得:认证发生在权限检查和限流检查之前
REST_FRAMEWORK = { “DEFAULT_PERMISSION_CLASSES”: [ “rest_framework.permissions.IsAuthenticatedOrReadOnly”, ], “DEFAULT_AUTHENTICATION_CLASSES”: [ “rest_framework.authentication.BasicAuthentication”, # 基本认证:用户名+密码 “rest_framework.authentication.SessionAuthentication”, “rest_framework.authentication.TokenAuthentication”, # 如果加了这个,app中就必须要有’rest_framework.authtoken’
# 可以在此添加自定义的认证方案...]
}
DRF的认证和权限**均**依赖的**两个核心数据**:- request.user- request.auth:DRF的认证机制其实是依赖了`django.contrib.auth`。对上面`DEFAULT_AUTHENTICATION_CLASSES`列表中的多个认证类,DRF会**按照顺序从上往下**进行验证,并使用**第一个成功通过的认证类**的返回值来**设置**`request.user`和`request.auth`。如果认证成功,DRF会把`request.user`设置为`django.contrib.auth.models.User`的实例,把`request.auth`设置为`None`。<br />举例:如果`BasicAuthentication`认证通过,就不用往下认证了,直接使用该认证结果设置`request.user`和`request.auth`。<a name="WTpGm"></a>### 认证-常用认证类<a name="Kl26o"></a>#### BasicAuthentication用户名密码认证,默认会使用base64编码,安全性极低<a name="NDi4h"></a>#### SessionAuthentication需要后端提供csrf令牌<a name="x6CXw"></a>#### TokenAuthentication为每个用户生成一个token(令牌)。<br />需要在`settings.py`文件的`INSTALLED_APPS`中添加 `'rest_framework.authtoken'` 这个app,然后在命令行执行`python mange.py migrate`命令,该命令会生成一张token表。<br />现在就只剩生成token了。<br />我们想在每个用户生成的时候创建一个token,这就需要用到Django的信号机制了。```pythonfrom django.db.models.signals import post_savefrom django.dispatch import receiverfrom django.conf import settingsfrom rest_framework.authtoken.models import Token@receiver(post_save, sender=settings.AUTH_USER_MODEL) # Django的信号机制def generate_token(sender, instance=None, created=False, **kwargs):"""创建用户时自动生成Token:param sender::param instance::param created: 默认不是新建:param kwargs::return:"""if created: # 如果是新建,则生成一个TokenToken.objects.create(user=instance)
其实上面的代码已经完成了token的生成,此后再创建用户,就会在token表中插入一条记录。
那么用户如何获取自己的token呢?很简单,添加一个视图函数即可,DRF已经帮我们完成,只需在根urls.py文件中添加一条记录:
from django.urls import path, includefrom rest_framework.authtoken import views # 引入相关视图urlpatterns = [...path("api-token-auth/", views.obtain_auth_token), # 获取Token的接口]
注意,用户获取token时,还是需要BasicAuth模式的,即需要提供用户名和密码来获取token。
认证-设置认证策略(在视图中添加认证)
下面举例了两种方式:
- FBV添加认证:通过装饰器
- CBV视图添加认证:通过类属性
以上两种方式都是对视图的个性化设置,优先级要比settings.py文件中DEFAULT_AUTHENTICATION_CLASSES里设置的权限类要高,只有一个视图没有单独指定认证策略时,才会使用全局配置。
from django.conf import settingsfrom django.db.models.signals import post_savefrom django.dispatch import receiverfrom rest_framework import genericsfrom rest_framework import statusfrom rest_framework.authentication import BasicAuthentication, TokenAuthenticationfrom rest_framework.authtoken.models import Tokenfrom rest_framework.decorators import api_view, authentication_classesfrom rest_framework.permissions import IsAuthenticatedfrom rest_framework.response import Responsefrom .models import Coursefrom .serializers import CourseSerializer@receiver(post_save, sender=settings.AUTH_USER_MODEL) # Django的信号机制def generate_token(sender, instance=None, created=False, **kwargs):if created:Token.objects.create(user=instance)"""一、 函数式编程 Function Based View"""@api_view(["GET", "POST"])@authentication_classes((BasicAuthentication,))def course_list(request):if request.method == "GET":s = CourseSerializer(instance=Course.objects.all(), many=True)return Response(data=s.data, status=status.HTTP_200_OK)elif request.method == "POST":s = CourseSerializer(data=request.data) # 部分更新用partial=True属性if s.is_valid():s.save(teacher=request.user)return Response(data=s.data, status=status.HTTP_201_CREATED)return Response(s.errors, status=status.HTTP_400_BAD_REQUEST)@api_view(["GET", "PUT", "DELETE"])@authentication_classes((TokenAuthentication, ))def course_detail(request, pk):try:course = Course.objects.get(pk=pk)except Course.DoesNotExist:return Response(data={"msg": "没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)else:if request.method == "GET":s = CourseSerializer(instance=course)return Response(data=s.data, status=status.HTTP_200_OK)elif request.method == "PUT":s = CourseSerializer(instance=course, data=request.data)if s.is_valid():s.save()return Response(data=s.data, status=status.HTTP_200_OK)return Response(data=s.errors, status=status.HTTP_400_BAD_REQUEST)elif request.method == "DELETE":course.delete()return Response(status=status.HTTP_204_NO_CONTENT)"""三、 通用类视图 Generic Class Based View"""class GCourseList(generics.ListCreateAPIView):queryset = Course.objects.all()serializer_class = CourseSerializerauthentication_classes = (BasicAuthentication,)permission_classes = (IsAuthenticated,)def perform_create(self, serializer):serializer.save(teacher=self.request.user)class GCourseDetail(generics.RetrieveUpdateDestroyAPIView):queryset = Course.objects.all()serializer_class = CourseSerializerauthentication_classes = (TokenAuthentication,)
权限-常用的权限类
drf自带的权限类:
DRF默认使用AllowAny,即不设置任何权限。IsAdminUser看的是User表中的is_staff字段。
权限-设置权限策略(在视图中添加权限)
权限的设置和认证一样,对于FBV使用装饰器,对于CBV则使用类属性
#!/usr/bin/python3# -*- coding:utf-8 -*-# __author__ = "__Jack__"from django.conf import settingsfrom django.db.models.signals import post_savefrom django.dispatch import receiverfrom rest_framework import genericsfrom rest_framework import statusfrom rest_framework.authentication import BasicAuthentication, SessionAuthentication, TokenAuthenticationfrom rest_framework.authtoken.models import Tokenfrom rest_framework.decorators import api_view, authentication_classes, permission_classesfrom rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnlyfrom rest_framework.response import Responsefrom .models import Coursefrom .serializers import CourseSerializer@receiver(post_save, sender=settings.AUTH_USER_MODEL) # Django的信号机制def generate_token(sender, instance=None, created=False, **kwargs):if created:Token.objects.create(user=instance)"""一、 函数式编程 Function Based View"""@api_view(["GET", "POST"])@authentication_classes((BasicAuthentication, SessionAuthentication, TokenAuthentication))@permission_classes((IsAuthenticatedOrReadOnly,))def course_list(request):"""获取所有课程信息或新增一个课程:param request::return:"""if request.method == "GET":s = CourseSerializer(instance=Course.objects.all(), many=True)return Response(data=s.data, status=status.HTTP_200_OK)elif request.method == "POST":s = CourseSerializer(data=request.data) # 部分更新用partial=True属性if s.is_valid():s.save(teacher=request.user)return Response(data=s.data, status=status.HTTP_201_CREATED)return Response(s.errors, status=status.HTTP_400_BAD_REQUEST)@api_view(["GET", "PUT", "DELETE"])@authentication_classes((BasicAuthentication, SessionAuthentication, TokenAuthentication))@permission_classes((IsAuthenticated,))def course_detail(request, pk):"""获取、更新、删除一个课程:param request::param pk::return:"""try:course = Course.objects.get(pk=pk)except Course.DoesNotExist:return Response(data={"msg": "没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)else:if request.method == "GET":s = CourseSerializer(instance=course)return Response(data=s.data, status=status.HTTP_200_OK)elif request.method == "PUT":s = CourseSerializer(instance=course, data=request.data)if s.is_valid():s.save()return Response(data=s.data, status=status.HTTP_200_OK)return Response(data=s.errors, status=status.HTTP_400_BAD_REQUEST)elif request.method == "DELETE":course.delete()return Response(status=status.HTTP_204_NO_CONTENT)"""三、 通用类视图 Generic Class Based View"""class GCourseList(generics.ListCreateAPIView):queryset = Course.objects.all()serializer_class = CourseSerializerauthentication_classes = (IsAuthenticatedOrReadOnly,)permission_classes = (IsAuthenticated,)def perform_create(self, serializer):serializer.save(teacher=self.request.user)class GCourseDetail(generics.RetrieveUpdateDestroyAPIView):queryset = Course.objects.all()serializer_class = CourseSerializerpermission_classes = (IsAuthenticated,)
权限-自定义对象级别权限
什么是对象级权限?
现在的程序有一个问题,只要是经过认证的用户,就都可以对课程进行增删改查,理想的情况是:只有课程的发布者才有权修改或者删除自己的课程,对于别人,只能查看课程。所以每一个课程对象应该对不同的用户有不同的权限,这就是对象级权限。
如何自定义对象级权限呢?
在app文件夹下新建一个permissions.py文件:
from rest_framework import permissionsclass IsOwnerOrReadOnly(permissions.BasePermission):"""自定义权限:只允许对象的所有者能够编辑"""def has_object_permission(self, request, view, obj):"""所有的request请求都有读权限,因此一律允许GET/HEAD/OPTIONS方法:param request::param view::param obj::return: bool"""if request.method in permissions.SAFE_METHODS: # SAFE_METHODS = ("GET", "HEAD", "OPTIONS")return True# 对象的所有者才有写权限return obj.teacher == request.user
然后向之前一样设置一下权限类就OK了!
from rest_framework import genericsfrom rest_framework.permissions import IsAuthenticatedfrom .models import Coursefrom .permissions import IsOwnerOrReadOnly # 1、导入自定义的权限类from .serializers import CourseSerializerclass GCourseDetail(generics.RetrieveUpdateDestroyAPIView):queryset = Course.objects.all()serializer_class = CourseSerializerpermission_classes = (IsAuthenticated, IsOwnerOrReadOnly) # 2、将其注册到需要的视图中
