Django之数据接口开发

你说的曾经没有我的故事 提交于 2020-01-12 18:21:38

1.REST

REST介绍

REST与技术无关,代表的仅仅是一种架构,一种风格。他的全称为REpresentational State Transfer,意为"表述性信息转移"。
 网络API:主要是强调是通过HTTP(s)请求来请求一个URL获得(json)数据。
 普遍认为,在设计网络接口的时候设计成RESTful API被认为是最好的选择
REST的作者认为:REST是最适合互联网应用的架构。凭什么REST的作者就有勇气认为REST是最适合互联网应用的架构呢?首先HTTP是无状态无连接的,REST架构强调两个词无状态幂等性。它提出,URL的设计必须使用名词而不能使用动词,我们要用HTTP的动词来表示我们需要做什么操作,因为HTTP的动词已经足够我们进行各种各样的操作。

 幂等性强调的是水平扩展
  - 互联网应用都会面临高并发,所以我们一般都会对系统进行水平扩展:单机结构变多机结构(分布式集群)

HTTP的动词

GET 	从服务器取出资源
POST	向服务器添加资源(不需要幂等性)
DELETE	从服务器删除资源
PUT		更新服务器资源(客户端提供改变后的完整资源)
PATCH	更新服务器资源(客户端提供改变的属性)

状态码

200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。

400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。

500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

2.接口设计开发

  • 我们在做数据接口的时候要围绕实体设计,不要围绕业务设计。

做数据接口我们需要安装三方(djangorestframework)

pip install djangorestframework

我们需要在settings.py注册我们的

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
 	# 注册djangorestframework
    'rest_framework',
]

基于函数的视图(FBV)

1)Views视图

# 只接受get请求
@api_view(('GET', ))    
def get_peovinces(request):
    """获取省级行政区域"""

    # 获取数据库字段为空的数据(指定获取 distid, name) 字段
    queryset = District.objects.filter(pid__isnull=True).only('distid', 'name')
    # 对数据进行序列化(加many=True序列化出来是列表,不加就是字典)
    serializer = DistrictSimpleSerializer(queryset, many=True)
    return Response(serializer.data)

2)定义序列化器

  • 在当前的apps下新建一个serializer.py,专门用来定义我们的序列化器类
  • 创建一个DistrictSimpleSerializer类
class DistrictSimpleSerializer(serializers.ModelSerializer):
    """创建序列化器"""
    class Meta:
    	# 指定序列化哪个model(表名)
        model = District
        # fields:需要保留的字段
        fields = ('distid', 'name')

3).映射URL

urlpatterns = [
    path('districts/', get_peovinces),
]

基于类的视图(CBV)

我们在写接口类的时候需要继承父类,下面列几个父类

ListAPIView			查询多个
RetrieveAPIView		查询单个
CreateAPIView		新增数据
DestroyAPIView		删除数据
RetrieveUpdateDestroyAPIView	拿单个/删除/更新

查询接口:定制需要查询字段
1)Views

class EstateView(ListAPIView):
	"""获取楼盘信息"""

    queryset = District.objects.all().only('district', 'agents')
    # 用哪个类来序列化楼盘
    serializer_class = EstateSerializer

2)定义序列化器

  • 在当前的apps下新建一个serializer.py文件,专门用来定义我们的序列化器类
  • 定义一个EstateSerializer类
class EstateSerializer(serializers.ModelSerializer):

    class Meta:
        model = Estate
        exclude = ('district', 'agents')

3).映射URL

urlpatterns = [
    path('estates/', EstateView.as_view()),  # 查省
]

全套接口1:方法重写定制数据

1)定义序列化器

  • 在当前的apps下新建一个serializer.py文件,专门用来定义我们的序列化器类
  • 定义一个EstateSerializer类,用于查看(因为我们在查看的时候把地区给排除了)
  • 定义一个EstatePostSerializer类,用于添加与修改
class EstateSerializer(serializers.ModelSerializer):

    class Meta:
        model = Estate
        exclude = ('district', 'agents')


class EstatePostSerializer(serializers.ModelSerializer):

    class Meta:
        model = Estate
        exclude = ('agents', )

2).映射URL

urlpatterns = [
    path('estates/', EstateView.as_view()),
    # 配置当请求单个的时候传入一个值(pk: pk是一种约定,传入的名字必须叫做pk)
    path('estates/<int:pk>', EstateView.as_view()),
]

3)Views

class EstateView(ListAPIView, RetrieveAPIView, RetrieveUpdateDestroyAPIView):
	"""获取楼盘信息"""

    queryset = District.objects.all().only('district', 'agents')
    # 用哪个类来序列化楼盘
    serializer_class = EstateSerializer
	
	# 重写方法用来判断走的是什么请求(因为我们在这儿走get的时候设置不看所在区域,但是添加的时候我们需要区域;所以我们另外写了一个序列化器)
    def get_serializer_class(self):
        if self.request.method in ('POST', 'PUT', 'PATCH'):
            return EstatePostSerializer
        else:
        	# get 和 delete 走下面这个
            return EstateSerializer    

    # 重写get方法区分继承哪个父类的get(多重继承;传入的pk值在request里面)
    def get(self, request, *args, **kwargs):
    	# 如果request里面有pk返回单个,否则返回多个
        if 'pk' in kwargs:
            cls = RetrieveAPIView
        else:
            cls = ListAPIView

        return cls.get(self, request, *args, **kwargs)

全套接口2:快速开发全套接口

1)定义序列化器

  • 在当前的apps下新建一个serializer.py文件,专门用来定义我们的序列化器类
  • fields = ‘__all__’ 表示所有的数据都要
class HouseTypeSerializer(serializers.ModelSerializer):

    class Meta:
        model = HouseType
        fields = '__all__'

2)Views

class HouseTypeViewSet(ModelViewSet):
    # ReadOnlyModelViewSet  只读接口
    # ModelViewSet	只读的全套接口
    queryset = HouseType.objects.all()
    serializer_class = HouseTypeSerializer

3).映射URL

urlpatterns = [
    ...
]

# 配置路由器
router = SimpleRouter()
# 斜杠会自己补,自己不要写斜杠(注册视图集)
router.register('housetypers', HouseTypeViewSet)
# 把urls合并到urlpatterns里面
urlpatterns += router.urls

3.下面所提到的功能我们只针对CBV

数据接口分页

 我们在请求到数据之后我们会对这些数据进行分页处理,这是基本的功能。那么分页又分为游标分页和分页器分页两种。分页器可以配置全局分页,游标分页就得自己在单个页面添加。

单个页面分页

 — 以游标分页为例

在views视图下自定义的分页类

class EstatePagination(CursorPagination):
    # 页面大小的查询参数(最大不能大于max_page_size)
    page_size_query_description = 'size'
    # 默认页面显示的数据为30
    max_page_size = 30
    # 根据estated分页
    ordering = 'estateid'

我们就可以在需要自定义分页页面的views视图类里面加上下面这句即可:

# 指定用自定义的分页类规则进行分页
pagination_class = EstatePagination

配置全局分页功能

 — 以分页器分页为例

我们现在settings.py给rest_framework写一个全局配置

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'debug_toolbar',
    'rest_framework',
]

"""
PageNumberPagination    根据页码分页
LimitOffsetPagination   跳过多少条查多少条分页
"""

REST_FRAMEWORK = {
    # 默认的分页类
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    # 每页显示多少条数据
    'PAGE_SIZE': 5,
}

我们添加了全局分页功能后,每隔页面都会使用我们配置的默认的分页器,但是有些页面我们不需要分页,我们就可以在当前页面的views视图类里面加上:

# 指定当前页不分页
pagination_class = None

数据接口数据缓存

在settings.py配置我们的缓存服务

# 配置redis缓存
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache', # 用 xxx 来做缓存
        # 连接Redis数据库(服务器地址)
        # 一主带多从(可以配置个Redis,写走第一台,读走其他的机器)
        'LOCATION': [
            'redis://47.98.174.108:6379/0',
        ],
        'KEY_PREFIX': 'apidemo',   # 把自己的项目名当做文件前缀
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient', # 连接选项(默认,不改)
            'CONNECTION_POOL_KWARGS': {
                'max_connections': 512,    # 连接池的连接(最大连接)
            },
            'PASSWORD': 'password', # 你的Redis密码
        }
    },
}

方法1:使用混入类实现缓存

  • 混入类不能对游标分页进行缓存,所以我们混入类进行缓存的时候我们的分页功能必须是使用的分页器分页

1)drf框架为我们提供了缓存服务,但是我们需要安装三方库**drf-extensions **

pip install drf-extensions 

2)添加混入类做我我们的缓存,这么做我们需要在settings.py加入一段配置:

# 做缓存框架的配置
REST_FRAMEWORK_EXTENSIONS = {
	# 缓存的时间
    'DEFAULT_CACHE_RESPONSE_TIMEOUT': 120,
    # 用哪一组进行缓存
    'DEFAULT_USE_CACHE': 'default',
    # 单个对象缓存走这一组
    'DEFAULT_OBJECT_CACHE_KEY_FUNC': 'rest_framework_extensions.utils.default_object_cache_key_func',
    # 多个对象缓存走这一组
    'DEFAULT_LIST_CACHE_KEY_FUNC': 'rest_framework_extensions.utils.default_list_cache_key_func',
}

3)views视图我们要把我们的混入类(CacheResponseMixin)加入到最前面

class EstateView(CacheResponseMixin, ListCreateAPIView, RetrieveUpdateDestroyAPIView):

    queryset = Estate.objects.all().defer('agents')

    # 数据筛选
    filter_backends = (DjangoFilterBackend, OrderingFilter)
    filterset_class = EstateFilterSet   # 自定义一个类来定义查询条件
    ordering_fields = ('hot', 'estateid')   # 根据什么来排序


    def get_serializer_class(self):
        if self.request.method in ('POST', 'PUT', 'PATCH'):
            return EstatePostSerializer
        else:
            return EstateSerializer

    def get(self, request, *args, **kwargs):
        if 'pk' in kwargs:
            cls = RetrieveAPIView
        else:
            cls = ListAPIView

        return cls.get(self, request, *args, **kwargs)

方法2:使用装饰器实现缓存

@method_decorator可以把原来装饰函数的装饰器打造成装饰类的装饰器

# 把这个装饰器(cache_page)加到list这个方法上(list在ModelViewSet里面,我们并不需要写)
@method_decorator(decorator=cache_page(timeout=600), name='list')
@method_decorator(decorator=cache_page(timeout=600), name='retrieve')
class HouseTypeViewSet(ModelViewSet):
    queryset = HouseType.objects.all()
    serializer_class = HouseTypeSerializer
    pagination_class = None

数据接口筛选排序操作

方法1:在views视图通过重写get_queryset方法进行筛选和排序

class EstateView(CacheResponseMixin, ListCreateAPIView, RetrieveUpdateDestroyAPIView):

    queryset = Estate.objects.all().defer('agents')
    
    def get_queryset(self):
        distid = self.request.GET.get('district')
        # 实现筛选
        if distid:
            self.queryset = self.queryset.filter(district=distid)
        ordering = self.request.GET.get('ordering')
        # 实现排序
        if ordering:
            self.queryset = self.queryset.order_by(ordering)
        return self.queryset

方法2:利用三方库(django-filter)实现筛选排序

1)安装三方库(django-filter)

pip install django-filter

2)在settings.py的apps注册我们的三方库

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 注册django-filter(会在页面上看到一个过滤器)
    'django_filters',
]

3)在views视图

 – 3.1 初级筛选(只支持精确查询)

class EstateView(ListCreateAPIView, RetrieveUpdateDestroyAPIView):

    queryset = Estate.objects.all().defer('agents')

    # 数据筛选(DjangoFilterBackend),排序(OrderingFilter)
    filter_backends = (DjangoFilterBackend, OrderingFilter)
    # 根据哪个列进行筛选(精确筛选)
    filter_fields = ('district', 'hot')
    # 根据哪个列进行排序
    ordering_fields = ('hot', 'estateid')   

 – 3.2 高级筛选(需要自定义我们的查询类)

class EstateView(ListCreateAPIView, RetrieveUpdateDestroyAPIView):

    queryset = Estate.objects.all().defer('agents')

    # 数据筛选(DjangoFilterBackend),排序(OrderingFilter)
    filter_backends = (DjangoFilterBackend, OrderingFilter)
    # 自定义一个过滤类来定义我们的筛选规则
	filterset_class = EstateFilterSet
    # 根据哪个列进行排序
    ordering_fields = ('hot', 'estateid')   

 我们在utils.py定义我们的查询类

class EstateFilterSet(django_filters.FilterSet):
    """自定义Filterset过滤规则"""
    # lookup_expr 	查找规则
    # field_name	与哪个字段对应(默认找它的名字[eg: intro])
    minhot = django_filters.NumberFilter(field_name = 'hot', lookup_expr='gte')
    maxhot = django_filters.NumberFilter(field_name = 'hot',lookup_expr='lte')

	# 在单个字段进行搜索
	# intro = django_filters.CharFilter(lookup_expr='contains')
	
    # 在多个字段里面搜索(自定义方法)
    # method 与下面的方法进行绑定
    keyword = django_filters.CharFilter(method='filter_by_keyword')

    @staticmethod
    def filter_by_keyword(queryset, key, value):
        queryset = queryset.filter(Q(name__contains=value) | Q(intro__startswith=value))
        return queryset

数据接口限流

限流可以对接口访问的频次进行限制,以减轻服务器压力。
它是依赖Redis来进行限流(所以我们要把我们的redis配置好):Redis会记住你的IP地址

在settings.py进行全局配置

REST_FRAMEWORK = {
    # 默认的限流类(使用速率来限流)
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
    ),
    # 限流的速率
    'DEFAULT_THROTTLE_RATES': {
        'anon': '5/min', # 每分钟5次
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!