Django 学习之Rest Framework 视图相关

drf除了在数据序列化部分简写代码以外,还在视图中提供了简写操作。所以在django原有的django.views.View类基础上,drf封装了多个子类出来提供给我们使用。

Django REST framwork 提供的视图的主要作用:

控制序列化器的执行(检验、保存、转换数据)

控制数据库查询的执行

调用请求类和响应类[这两个类也是由drf帮我们再次扩展了一些功能类。

 

在接下来的例子中我们将是在上一篇:Django Rest Framework_序列化器_Serializer

继续引用其数据库数据已经模型,为了方便我们学习,所以先创建一个子应用req,并注册配置:

python manage.py startapp req

.视图请求与响应

1.Request

REST framework 传入视图的request对象不再是Django默认的HttpRequest对象,而是REST framework提供的扩展了HttpRequest类的Request类的对象。

REST framework 提供了Parser解析器,在接收到请求后会自动根据Content-Type指明的请求数据类型(如JSON、表单等)将请求数据进行parse解析,解析为类字典[QueryDict]对象保存到Request对象中。

Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。

无论前端发送的哪种格式的数据,我们都可以以统一的方式读取数据。

(1) 常用属性

# 1.data

request.data` 返回解析之后的请求体数据。类似于Django中标准的`request.POST`和 request.FILES`属性,但提供如下特性:

包含了解析之后的文件和非文件数据

包含了对POSTPUTPATCH请求方式解析后的数据

利用了REST frameworkparsers解析器,不仅支持表单类型数据,也支持JSON数据

 

#2.query_params

request.query_params与Django标准的request.GET相同,只是更换了更正确的名称而已。

2.Response

rest_framework.response.Response

REST framework提供了一个响应类Response,使用该类构造响应对象时,响应的具体数据内容会被转换(render渲染)成符合前端需求的类型。

REST framework提供了Renderer 渲染器,用来根据请求头中的Accept(接收数据类型声明)来自动转换响应数据到对应格式。如果前端请求中未进行Accept声明,则会采用默认方式处理响应数据,我们可以通过配置来修改默认响应格式。

可以在rest_framework.settings查找所有的drf默认配置项。

REST_FRAMEWORK = {

    'DEFAULT_RENDERER_CLASSES': (  # 默认响应渲染类

        'rest_framework.renderers.JSONRenderer',  # json渲染器

        'rest_framework.renderers.BrowsableAPIRenderer',  # 浏览API渲染器

    )

}

(1)构造方式

Response(data, status=None, template_name=None, headers=None, content_type=None)

data数据不要是render处理之后的数据,只需传递python的内建类型数据即可,REST framework会使用renderer渲染器处理data。

data不能是复杂结构的数据,如Django的模型类对象,对于这样的数据我们可以使用Serializer序列化器序列化处理后(转为了Python字典类型)再传递给data参数。

参数说明:

data: 为响应准备的序列化处理后的数据;

status: 状态码,默认200

template_name: 模板名称,如果使用`HTMLRenderer` 时需指明;

headers: 用于存放响应头信息的字典;

content_type: 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据来设置该参数。

(2)常用属性

#1.data

传给response对象的序列化后,但尚未render处理的数据

status_code

状态码的数字

content

经过render处理后的响应数据

(3)状态码

为了方便设置状态码,REST framewrok`rest_framework.status`模块中提供了常用状态码常量。

#1.信息告知 - 1xx

#2.成功 - 2xx

#3.重定向 - 3xx

#4.客户端错误 - 4xx

#5.服务器错误 - 5xx

详见:常见Http状态码大全详解

.视图

Django REST framwork 提供的视图的主要作用:

控制序列化器的执行(检验、保存、转换数据)

控制数据库查询的执行

REST framework 提供了众多的通用视图基类与扩展类,以简化视图的编写。

1.APIView

rest_framework.views.APIView

APIView是REST framework提供的所有视图的基类,继承自Django的View父类。

APIView与View的不同之处在于:

传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象;

 视图方法可以返回REST framework的Response对象,视图会为响应数据设置(render)符合前端要求的格式;

任何APIException异常都会被捕获到,并且处理成合适的响应信息;

在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。

支持定义的类属性

authentication_classes列表或元祖,身份认证类

permissoin_classes列表或元祖,权限检查类

throttle_classes列表或元祖,流量控制类

在APIView中仍以常规的类视图定义方法来实现get() 、post() 或者其他请求方式的方法。

例:

(1)View与APIView比较

现在首先我们将刚建好的名为reqappserializer.py序列器,然后使用如下内容:

 

from rest_framework import serializers
from students.models import Student

def check_user(data):
    if data == "teacher":
        raise serializers.ValidationError("用户名不能为teacher")
    return data
class StudentModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        # fields = "__all__"  # 表示引用所有字段
        fields = ["id", "name", "age", "description", "is_18"]  # is_18 为自定制字段,需要在models里自定义方法。
        # exclude = ["age"]  # 使用exclude可以明确排除掉哪些字段, 注意不能和fields同时使用。
        # exclude与fields互斥的,不能同事同时使用
        # 传递额外的参数,为ModelSerializer添加或修改原有的选项参数
        extra_kwargs = {
            "name": {"max_length": 10, "min_length": 4, "validators": [check_user]},
            "age": {"max_value": 150, "min_value": 0},
        }
    def validate_name(self, data):
        if data == "root":
            raise serializers.ValidationError("用户名不能为root!")
        return data
    def validate(self, attrs):
        name = attrs.get('name')
        age = attrs.get('age')

        if name == "hszTH" and age == 24:
            raise serializers.ValidationError("hszTH的故事。。。")
        return attrs
serializers.py

 

并在req应用的urls.py添加内容为:

 

from django.urls import path, re_path
from . import views

urlpatterns = [
    # 区分View 与 APIView
    path('student1/', views.Student1View.as_view()),
    path('student2/', views.Student2APIView.as_view()),

]
urls.py

 

最后是views.py的内容:

 

# View
from django.views import View
from django.http import JsonResponse


class Student1View(View):
    def get(self, request):
        print(request)  # <WSGIRequest: GET '/req/student1/'>
        data_dict = {"name": "hsz", "age": 24}
        return JsonResponse(data_dict)

# APIView
from rest_framework.views import APIView
from rest_framework.views import Response
from rest_framework import status


class Student2APIView(APIView):
    def get(self, request):
        print(request)  # <rest_framework.request.Request object at 0x7f6f8d1cd7f0>
        print(request.query_params)  # <QueryDict: {}>
        data_dict = {"name": "hsz", "age": 24}
        return Response(data_dict, status=status.HTTP_204_NO_CONTENT, headers={"name": "zero"})
View Code

 

通过运行测试:

首先是请求request最后输出的类型不一样,具体见代码中的解释。

然后是运行测试截图:

View得到:

浏览器得:

View post截图:

APIView截图:

APIView post截图:

APIView不仅可以自定义状态码,还可以添加响应头内容。

(2)APIView实现接口的使用

req应用下的urls.py文件添加

 

# 使用APIView
    path("student3/", views.Student3APIView.as_view()),
re_path(r"^student3/(?P<pk>\d+)/$", views.Student4APIView.as_view()),
View Code

 

views.py文件添加如下内容:

 

"""
使用APIView提供学生信息的5个API接口
GET    /req/student3/               # 获取全部数据
POST   /req/student3/               # 添加数据

GET    /req/student3/(?P<pk>\d+)/    # 获取一条数据
PUT    /req/student3/(?P<pk>\d+)/    # 更新一条数据
DELETE /req/student3/(?P<pk>\d+)/    # 删除一条数据
"""

from students.models import Student
from req.serializers import StudentModelSerializer


def Student31APIView(APIView):
    def get(self, request):
        # get 对应的是数据查询,查询所有
        student_list = Student.objects.all()

        serializer = StudentModelSerializer(instance=student_list, many=True)

        return Response(serializer.data)

    def post(self, request):
        # 获取用户提交的数据 post 对应的是数据增加
        data_dict = request.data

        serializer = StudentModelSerializer(data=data_dict)
        # 数据校验
        serializer.is_valid(raise_exception=True)

        # 数据保存
        return Response(serializer.data)


#    
class Student32APIView(APIView):
    def get(self, request, pk):
        # 获取pk值对用的模型对象,需要PK的查询就是查询某一条数据
        student_obj = Student.objects.get(pk)
        serializer = StudentModelSerializer(instance=student_obj)

        return Response(serializer.data)
        
    def put(self, request, pk):
        # 通过pk更新一条数据
        student_obj = Student.objects.get(pk)
        serializer = StudentModelSerializer(instance=student_obj, data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

    def delete(self, request, pk):
        # 删除一条数据
        Student.objects.filter(pk).delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
views.py

 

最后可以实现数据的增删改查。

测试新增数据:

2.GenericAPIView[通用视图类]

rest_framework.generics.GenericAPIView

继承自APIVIew主要增加了操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类

提供的关于序列化器使用的属性与方法

属性:

serializer_class 指明视图使用的序列化器

方法:

get_serializer_class(self)

当出现一个视图类中调用多个序列化器时,那么可以通过条件判断在get_serializer_class方法中通过返回不同的序列化器类名就可以让视图方法执行不同的序列化器对象了。

返回序列化器类,默认返回serializer_class,可以重写,例如:

def get_serializer_class(self):

    if self.request.user.is_staff:

        return FullAccountSerializer

return BasicAccountSerializer

#########################

get_serializer(self, args, *kwargs)

返回序列化器对象,主要用来提供给Mixin扩展类使用,如果我们在视图中想要获取序列化器对象,也可以直接调用此方法。

 

提供的关于数据库查询的属性与方法:

属性:queryset 指明使用的数据查询集

方法:

get_queryset(self)

返回视图使用的查询集,主要用来提供给Mixin扩展类使用,是列表视图与详情视图获取数据的基础,默认返回queryset属性,可以重写,例如:

def get_queryset(self):

    user = self.request.user

    return user.accounts.all()

get_object(self)

返回详情视图所需的模型类数据对象,主要用来提供给Mixin扩展类使用。

在试图中可以调用该方法获取详情信息的模型类对象。

其他可以设置的属性

pagination_class   指明分页控制类

filter_backends      指明过滤控制后端

  简单介绍了一下,下面我们就通过代码来感受一下。

首先在urls.py内容添加:

 

    # 使用GenericAPIView
    path("student4/", views.Student41GenericAPIView.as_view()),
re_path(r"^student4/(?P<pk>\d+)/$", views.Student42GenericAPIView.as_view()),
urls.py

 

然后在views.py新增内容为:

 

"""
使用GenericAPIView提供学生信息的5个API接口
GET    /req/student4/               # 获取全部数据
POST   /req/student4/               # 添加数据

GET    /req/student4/(?P<pk>\d+)/    # 获取一条数据
PUT    /req/student4/(?P<pk>\d+)/    # 更新一条数据
DELETE /req/student4/(?P<pk>\d+)/    # 删除一条数据
"""
from rest_framework.generics import GenericAPIView


class Student41GenericAPIView(GenericAPIView):
    queryset = Student.objects.all()

    serializer_class = StudentModelSerializer

    def get(self, request):
        # 获取多条数据
        student_list = self.get_queryset()

        serializer = self.get_serializer(instance=student_list, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)


class Student42GenericAPIView(GenericAPIView):
    # 获取所有的模型对象集合
    queryset = Student.objects.all()
    # 指定序列化器
    serializer_class = StudentModelSerializer

    def get(self, request, pk):
        # 参数pk名,必须要叫pk,否则会报错
        student_obj = self.get_object()
        serializer = self.get_serializer(instance=student_obj)
        return Response(serializer.data)

    def put(self, request, pk):
        student_obj = self.get_object()
        serializer = self.get_serializer(instance=student_obj, data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

    def delete(self, request, pk):
        student_obj = self.get_object()
        student_obj.delete()

        return Response(status=status.HTTP_204_NO_CONTENT)
views.py

 

GenericAPIView这种方式进程测试:

更新数据测试:

数据被成功修改:

3.五个视图扩展类

使用GenericAPIView结合视图Mixin扩展类,快速实现数据接口的APIView

ListModelMixin      实现查询所有数据功能

CreateModelMixin    实现添加数据的功能

RetrieveModelMixin  实现查询一条数据功能

UpdateModelMixin    更新一条数据的功能

DestroyModelMixin   删除一条数据的功能

 

应用urls.py内容新加为:

 

 # 使用GenericAPIView 结合mixin的扩展类 实现接口
    path("student5/", views.Student51MixinGenericAPIView.as_view()),
re_path(r"^student5/(?P<pk>\d+)/$", views.Student52MixinGenericAPIView.as_view()),
urls.py

 

views.py新增的内容为:

 

"""
使用GenericAPIView结合视图Mixin扩展类,快速实现数据接口的APIView
ListModelMixin      实现查询所有数据功能
CreateModelMixin    实现添加数据的功能
RetrieveModelMixin  实现查询一条数据功能
UpdateModelMixin    更新一条数据的功能
DestroyModelMixin   删除一条数据的功能
"""

from rest_framework.mixins import ListModelMixin
from rest_framework.mixins import CreateModelMixin


class Student51MixinGenericAPIView(GenericAPIView, ListModelMixin, CreateModelMixin):
    # 获取所有的模型对象集合
    queryset = Student.objects.all()
    # 指定序列化器
    serializer_class = StudentModelSerializer

    # 获取数据
    def get(self, request):
        return self.list(request)

    # 新增
    def post(self, request):
        return self.create()

"""
RetrieveModelMixin  实现查询一条数据功能
UpdateModelMixin    更新一条数据的功能
DestroyModelMixin   删除一条数据的功能
"""
from rest_framework.mixins import RetrieveModelMixin
from rest_framework.mixins import UpdateModelMixin
from rest_framework.mixins import DestroyModelMixin

class Student52MixinGenericAPIView(GenericAPIView,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin):
    # 获取所有的模型对象集合
    queryset = Student.objects.all()
    # 指定序列化器
    serializer_class = StudentModelSerializer
    def get(self,request,pk):
        return self.retrieve(request)

    def put(self,reuqest,pk):
        return self.update(reuqest)

    def delete(self,request,pk):
        return self.destroy(request)  # 不要使用delete方法
views.py

 

删除id=7的数据:

从数据库查看id7的数据被删除:

4.内置的扩展子类

 应用urls.py内容新加为:

 

# 使用内置的扩展子类,生成API接口
    path("student6/", views.Student61ListGenericAPIView.as_view()),
    re_path("^student6/(?P<pk>\d+)/$", views.Student62RUDGenericAPIView.as_view()),
urls.py

 

应用views.py添加的内容为:

 

"""
ListAPIView    获取所有数据
CreateAPIView 添加数据
"""
from rest_framework.generics import ListAPIView, CreateAPIView

class Student61ListGenericAPIView(ListAPIView, CreateAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
"""
RetrieveAPIView                 获取一条数据
UpdateAPIView                   更新一条数据
DestorAPIView                   删除一条数据
RetrieveUpdateDestoryAPIView    上面三个的缩写
"""
# from rest_framework.generics import RetrieveAPIView, UpdateAPIView, DestroyAPIView
from rest_framework.generics import RetrieveUpdateDestroyAPIView

# class Student62RUDGenericAPIView(RetrieveAPIView, UpdateAPIView, DestroyAPIView):
class Student62RUDGenericAPIView(RetrieveUpdateDestroyAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
views.py

 

测试id=3查找数据:

测试结果可以查询到数据:

5.视图集

上面5个接口使用了8行代码生成,但是我们可以发现有一半的代码重复了

所以,我们要把这些重复的代码进行整合,但是依靠原来的类视图,其实有2方面产生冲突的

1. 查询所有数据、添加数据是不需要声明pk的,而其他的接口需要    [路由冲突]

2. 查询所有数据和查询一条数据,都是属于get请求             [请求方法冲突]

为了解决上面的2个问题,所以DRF提供了视图集来解决这个问题

需要再urls.py添加下面内容:

 

# 视图集的使用
    path("student7/", views.Student7ModelViewSet.as_view({"get": "list", "post": "create"})),
    re_path("^student7/(?P<pk>\d+)/$", views.Student7ModelViewSet.as_view({"get": "retrieve","put": "update","delete": "destroy"})),
urls.py

 

视图文件views.py添加内容为:

 

# 将注释的取消,将注释的下一行注释效果一样
# from rest_framework.viewsets import GenericViewSet
# from rest_framework.mixins import ListModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, \
#     CreateModelMixin
from rest_framework.viewsets import ModelViewSet

# 将注释的取消,将注释的下一行注释效果一样
# class Student7ModelViewSet(GenericViewSet,ListModelMixin,UpdateModelMixin,RetrieveModelMixin,DestroyModelMixin,CreateModelMixin):
class Student7ModelViewSet(ModelViewSet):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
views.py

 

测试一个查询所有数据,结果成功:

 

综上所有的代码为:

app注册配置:

'req.apps.ReqConfig'

总路由urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('stu/', include("students.urls")),
    path('sers/', include("sers.urls")),
    path('req/', include("req.urls")),

]
urls.py

使用之前的模型类models.py

from django.db import models


# Create your models here.
class Student(models.Model):
    # 模型字段
    name = models.CharField(max_length=100, verbose_name="姓名")
    sex = models.BooleanField(default=1, verbose_name="性别")
    age = models.IntegerField(verbose_name="年龄")
    class_null = models.CharField(max_length=5, verbose_name="班级编号")
    description = models.TextField(max_length=1000, verbose_name="个性签名")

    class Meta:
        db_table = "tb_student"
        verbose_name = "学生"
        verbose_name_plural = verbose_name

    def is_18(self):
        return "big!" if self.age >=18 else "small!"
models.py

序列器serializer.py

from rest_framework import serializers
from students.models import Student


def check_user(data):
    if data == "teacher":
        raise serializers.ValidationError("用户名不能为teacher")
    return data


class StudentModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        # fields = "__all__"  # 表示引用所有字段
        fields = ["id", "name", "age", "description", "is_18"]  # is_18 为自定制字段,需要在models里自定义方法。
        # exclude = ["age"]  # 使用exclude可以明确排除掉哪些字段, 注意不能和fields同时使用。
        # exclude与fields互斥的,不能同事同时使用
        # 传递额外的参数,为ModelSerializer添加或修改原有的选项参数
        extra_kwargs = {
            "name": {"max_length": 10, "min_length": 4, "validators": [check_user]},
            "age": {"max_value": 150, "min_value": 0},
        }

    def validate_name(self, data):
        if data == "root":
            raise serializers.ValidationError("用户名不能为root!")
        return data

    def validate(self, attrs):
        name = attrs.get('name')
        age = attrs.get('age')

        if name == "hszTH" and age == 24:
            raise serializers.ValidationError("hszTH的故事。。。")

        return attrs
serializers.py

子路由urls.py

from django.urls import path, re_path
from . import views

urlpatterns = [

    path("students/", views.Student2View.as_view()),
    re_path(r"^students/(?P<pk>\d+)/$", views.Student1View.as_view()),

    path("student3/", views.Student3View.as_view()),
    # 反序列化阶段(update, 执行的请求方式为put)
    re_path(r'^student4/(?P<pk>\d+)/$', views.Student4View.as_view()),
    # 一个序列化器同时实现序列化和反序列化
    path('student5/', views.Student5View.as_view()),
    # 使用模型类序列化器
    path('student6/', views.Student6View.as_view()),

]
urls.py

以上所有点的视图代码:

from django.shortcuts import render

# Create your views here.
from django.http import JsonResponse
from django.views import View
from students.models import Student
from django.shortcuts import HttpResponse
from sers.serializers import StudentSerializer
import json


class Student1View(View):
    """使用序列化器进行数据的序列化操作"""
    """序列化器转换一条数据[模型转换成字典]"""

    def get(self, request, pk):
        # 接收客户端传过来的参数,进行过滤查询,先查出学生对象
        student = Student.objects.get(pk=pk)
        # 转换数据类型
        # 1.实例化序列化器类
        """
            StudentSerializer(instance=模型对象或者模型列表,客户端提交的数据,额外要传递到序列化器中使用的数据)
        """
        serializer = StudentSerializer(instance=student)

        # 2.查看序列化器的转换结果
        print(serializer.data)
        return JsonResponse(serializer.data)


class Student2View(View):
    """序列化器转换多条数据[模型转换成字典]"""

    def get(self, request):
        student_list = Student.objects.all()
        print(student_list)
        # 序列化器转换多个数据
        # many=True 表示本次序列化器转换如果有多个模型对象列参数,则必须声明 Many=True
        serializer = StudentSerializer(instance=student_list, many=True)

        print(serializer.data)
        return JsonResponse(serializer.data, safe=False)


from sers.serializers import Student3Serializer


class Student3View(View):
    #  反序列化之数据校验
    def post(self, request):
        # 对数据进行解码
        data = request.body.decode()
        # 对数据(用户提交的数据)进行反序列化
        data_dict = json.loads(data)
        # 调用序列化器进行实例化
        serializer = Student3Serializer(data=data_dict)
        # 进行校验
        # is_valid在执行的时候,会自动先后调用 字段的内置选项,自定义验证方法,自定义验证函数
        # 调用序列化器中写好的验证代码
        # 验证结果
        # raise_exception=True 抛出验证错误信息,并阻止代码继续往后运行
        serializer.is_valid(raise_exception=True)

        # 获取错误信息
        print(serializer.errors)

        # 获取合法的数据信息
        print(serializer.validated_data)

        # save 表示让序列化器开始执行反序列化代码。create和update的代码
        serializer.save()

        # return HttpResponse("OK")
        return JsonResponse(serializer.validated_data)


class Student4View(View):
    def put(self, request, pk):
        data = request.body.decode()
        import json
        data_dict = json.loads(data)
        # 通过pk从数据库中得到相应的原数据
        student_obj = Student.objects.get(pk=pk)
        print(pk)
        # 有instance参数,调用save方法,就会调用update方法。
        serializer = Student3Serializer(instance=student_obj, data=data_dict)
        # print(serializer)
        serializer.is_valid(raise_exception=True)

        serializer.save()  # 触发序列器中的update方法

        return JsonResponse(serializer.validated_data)


class Student5View(View):
    def get(self, request):
        # 获取所有数据
        student_list = Student.objects.all()
        serializer = Student3Serializer(instance=student_list, many=True)

        return JsonResponse(serializer.data, safe=False)

    def post(self, request):
        data = request.body.decode()
        data_dict = json.loads(data)
        # 调用序列化器进行实例化
        serializer = Student3Serializer(data=data_dict)
        # 进行校验
        serializer.is_valid(raise_exception=True)
        # 这个为什么要等于instance
        serializer.save()

        return JsonResponse(serializer.data)


from sers.serializers import StudentModelSerializer


class Student6View(View):
    # 如果是get方法请求数据
    def get(self, request):
        # 获取所有数据
        student_list = Student.objects.all()

        serializer = StudentModelSerializer(instance=student_list, many=True)

        return JsonResponse(serializer.data, safe=False)

    #  如果是post方法新增数据
    def post(self, request):
        data = request.body.decode()
        data_dict = json.loads(data)

        serializer = StudentModelSerializer(data=data_dict)

        serializer.is_valid(raise_exception=True)

        serializer.save()

        return JsonResponse(serializer.data)
views.py

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2019-01-02 11:42  pycoder_hsz  阅读(374)  评论(0编辑  收藏  举报