使用drf框架编写api

安装djangorestframework

pip install djangorestframework

修改常用配置

DEBUG = True

ALLOWED_HOSTS = ['*']

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'api',
        'USER': 'abel',
        'PASSWORD': 'abel',
        'HOST': '127.0.0.1',
        'POST': 3306,

    }
}

INSTALLED_APPS = [
    ...
    'rest_framework',
    '你的app名称',
]

LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False

项目的同名目录下的 __init__.py


import pymysql

pymysql.install_as_MySQLdb()

url 路由分发

from django.urls import path, include

urlpatterns = [
    path('api/', include("api.urls", namespace='api'))
]
from django.urls import path, re_path

from api import views

app_name = 'api'
urlpatterns = [
    path('login/', views.LoginView.as_view(), name='login'),
    path('code/', views.CodeView.as_view(), name='code'),
    path('tempauth/', views.TempAuthView.as_view(), name='tempauth'),

    # APIView
    path('articles/', views.ArticleView.as_view(), name='articles'),
    re_path(r'article/(?P<id>\d+)/$', views.ArticleDetailView.as_view(), name='article-detail'),
    # 等价于上面的写法 建议使用上面一种 便于理解
    # path('article/<id>/', views.ArticleDetailView.as_view(), name='article-detail'),
]

view 编写

  • 为了方便复用,尽量使用 ListAPIView, CreateAPIView, (此二者可以合并为 ListCreateAPIView)

  • RetrieveAPIView, UpdateAPIView, DestroyAPIView

  • 按照上面编写的路由规则 访问url形式为:

  • http://127.0.0.1:8000/api/articles/
    http://127.0.0.1:8000/api/article/78/ 
    
class ArticleView(CreateAPIView, ListAPIView):
    # 查询结果集
    queryset = Articles.objects.all()
    # 序列化器类
    # serializer_class = ArticleSerializer
    # 自定义分页器
    pagination_class = MyPageNumberPagination
    # 自定义queryset结果集
    filter_backends = [ArticleFilter]

    def get_serializer_class(self):
        # 根据不同请求 加载不同的序列化器
        if self.request.method == 'POST':
            return ArticleSerializer
        elif self.request.method == 'GET':
            return ArticleSerializerForList

    # 钩子 重写该方法可以在 序列化完成 数据入库之前的时机  插入其他字段值
    def perform_create(self, serializer):
        print(serializer.initial_data)
        userinfo_id = serializer.initial_data.get('user')
        topic_id = serializer.initial_data.get('topic')
        # serializer.save() 方法的内部 又调用了 序列化器中的 create()方法
        article_obj = serializer.save(topic_id=topic_id, user_id=userinfo_id)
        return article_obj

class ArticleDetailView(RetrieveAPIView, UpdateAPIView, DestroyAPIView):
    # 查询结果集
    queryset = Articles.objects.all()
    # 序列化器类
    serializer_class = ArticleSerializerForList
    # django 默认的主键形参为pk 如果改用其他 应通过 lookup_field 显试指定出来
    lookup_field = 'id'

分页器

from rest_framework.pagination import LimitOffsetPagination


class MyPageNumberPagination(LimitOffsetPagination):
    # 默认数据量
    default_limit = 10
    # 最大数据量
    max_limit = 50
    # 数据量
    limit_query_param = 'limit'
    # 起始偏移位置
    offset_query_param = 'offset'

    def get_paginated_response(self, data):
        """
        overwrite 不要父类给的数据格式
        :param data:
        :return:
        """
        return Response(data)

    def get_offset(self, request):
        """
        overwrite 让起始偏移量 始终为0
        :param request:
        :return:
        """
        return 0

结果集过滤器

class ArticleFilter(BaseFilterBackend):

    def filter_queryset(self, request, queryset, view):
        min_id = request.query_params.get('min_id')
        max_id = request.query_params.get('max_id')
        if min_id:
            # 处理上滑 翻页
            queryset = queryset.filter(id__lt=min_id).order_by('-id')
        elif max_id:
            # 处理 下拉刷新
            queryset = queryset.filter(id__gt=max_id).order_by('-id')
        else:
            queryset = queryset.order_by('-id')
        return queryset

序列化器

class ArticleImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = ArticleImages  # 设置关联模型     model就是关联模型
        # fields = '__all__'  # fields设置字段   __all__表示所有字段
        exclude = []
class ArticleSerializer(serializers.ModelSerializer):
    # 此处为序列化器的嵌套  应用于关联表
    images = ArticleImageSerializer(many=True)

    class Meta:
        model = Articles  # 设置关联模型     model就是关联模型
        # fields = '__all__'  # fields设置字段   __all__表示所有字段
        # fields = ['content', 'location']
        # 排除的字段  直接丢弃  不验证 不入库
        exclude = ['topic', 'user']
        # exclude = []

    # view中数据保存方法 save()执行之前  会调用该方法 通常应用在 序列化器嵌套情况下 一并将嵌套表的字段 入库
    def create(self, validated_data):
        images = validated_data.pop('images')
        article_obj = Articles.objects.create(**validated_data)
        images_obj = [ArticleImages.objects.create(**image, article=article_obj) for image in images]
        article_obj.images = images_obj
        return article_obj

    # 验证字段 钩子
    # def validate_content(self, value):
    #     """ 验证是否还正在拍卖"""
    #     print('正在验证', value)
    #     # item_id = self.initial_data.get('item')
    #     # exists = models.AuctionItem.objects.filter(id=item_id, status=3).exists()
    #     # if not exists:
    #     #     raise exceptions.ValidationError('拍卖商品不存在或已成交')
    #     return value
class ArticleSerializerForList(serializers.ModelSerializer):
    class Meta:
        model = Articles
        exclude = []
	# 定义模型中没有的数据字段
    topic = serializers.SerializerMethodField(label='所属话题', default='默认话题内容...', read_only=True)
    user = serializers.SerializerMethodField(label='所属用户手机号', default='13333333333', read_only=True)
    images = serializers.SerializerMethodField(label='文章包含的图片', default='', read_only=True)

    # 钩子方法   固定格式  get_xx(self,value)  xx表示上面对应的自定义字段 value表示当前的model对象
    def get_images(self, value):
        images = ArticleImages.objects.filter(article_id=value.id).all()
        datas = []
        for image in images:
            datas.append({
                'cos_path': image.cos_path,
                'key': image.key
            })
        return datas

    def get_topic(self, value):
        topic = value.topic
        return model_to_dict(instance=topic)

    def get_user(self, value):
        user = value.user
        return model_to_dict(instance=user)

model中外键和多对多关系的用法

related_name 用作反向关联取得别名 如果表A中有两个外键字段 指向表B中的id 表B方向关联查找数据的时候就不知道应该反向关联谁了 related_name 显示指定别名可以防止此类情况
关联自己 可以使用self

class Comments(models.Model):
    content = models.CharField(verbose_name='评论内容', max_length=255)
    depth = models.IntegerField(verbose_name='评论级别')

    # 外键
    article = models.ForeignKey(verbose_name='所属文章', to='Articles', on_delete=models.CASCADE, null=True, blank=True)
    user = models.ForeignKey(verbose_name='所属用户', to='Users', on_delete=models.CASCADE, null=True, blank=True)
    root = models.ForeignKey(verbose_name='所属根评论', to='self', related_name='roots', null=True, blank=True,
                             on_delete=models.CASCADE)
    reply = models.ForeignKey(verbose_name='上级评论', to='self', related_name='replys', null=True, blank=True,
                              on_delete=models.CASCADE)
class Articles(models.Model):
    content = models.CharField(verbose_name='文章内容', max_length=255)
    location = models.CharField(verbose_name='地理位置', max_length=255)
    view_count = models.PositiveIntegerField(verbose_name='浏览数', default=0)
    like_count = models.PositiveIntegerField(verbose_name='点赞数', default=0)
    comment_count = models.PositiveIntegerField(verbose_name='评论数', default=0)

    # 外键
    topic = models.ForeignKey(verbose_name='所属话题', to='Topics', on_delete=models.CASCADE, null=True, blank=True)
    user = models.ForeignKey(verbose_name='所属用户', to='Users', on_delete=models.CASCADE, null=True, blank=True)
    # 浏览记录 多对多
    viwers = models.ManyToManyField('Users', related_name='views')

上面的多对多关系 起始就是建立了中间表 中间表使用两个外键关联两张表 构成多对多关系 与手动创建中间表效果一样

class ViewRecords(models.Model):
     # 外键
     user = models.ForeignKey(verbose_name='所属用户', to='Users', on_delete=models.CASCADE, null=True, blank=True)
     article = models.ForeignKey(verbose_name='所属文章', to='Articles', on_delete=models.CASCADE, null=True, blank=True)

跨表取值

  • A表含有主键指向B表 现有A表的模型对象a 可以直接通过 a.主键名获取B表模型对象从而得到B表的数据 (主动查询)
  • 反之,现有B表的模型对象b,想通过b取到A表的值,可以通过 b.a表小写表名_set 来得到A表中的数据 _set 为固定格式写法

数据库填充数据脚本

原理 就是在外部通过加载django环境 让脚本中可以利用django的model快速操作数据库

import os
import random
import sys
import uuid

sys.path.append(os.getcwd())
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "miniprogramapi.settings")
import django

django.setup()

from api.models import Topics, Articles, Users, ArticleImages

# 新增用户
for _ in range(20):
    r = random.randint(111111111, 999999999)
    Users.objects.create(
        phone=f'13{r}',
        token=uuid.uuid4(),
        nickname=f'哨兵{r}号',
        avatar='http://himg.bdimg.com/sys/portrait/item/c9eee4b89ce69da5e4b89ce5be80e79a84e78caaa629.jpg'
    )

# 新增话题
for _ in range(20):
    r = random.randint(1, 200)
    Topics.objects.create(
        content=f"我是话题{r}",
        hot=r
    )

# 新增文章
for _ in range(20):
    r = random.randint(1, 200)
    Articles.objects.create(
        content=f"文章内容{r}",
        location=f"明珠路{r}",
        view_count=1,
        like_count=1,
        comment_count=1,
        topic=Topics.objects.get(id=random.choice([topic.id for topic in Topics.objects.all()])),
        user=Users.objects.get(id=random.choice([user.id for user in Users.objects.all()]))

    )

序列化器中数据的校验

  1. 方法一 指定数据行验证类 利用 call()方法进行校验

class LoginSerializer(serializers.Serializer):
    phone = serializers.CharField(label='手机号', validators=[PasswordValidator()])
    


class PasswordValidator(object):
    def __init__(self):
        pass

    def __call__(self, value, *args, **kwargs):
        if len(value) != 11:
            raise serializers.ValidationError('手机号码长度不正确')
        if not re.match(r'^\d+$', value):
            raise serializers.ValidationError('手机号必须为数字')

    def set_context(self, serializer_field):
        # print(serializer_field)
        pass
  1. 方法二 指定自定义验证函数
class LoginSerializer(serializers.Serializer):
    phone = serializers.CharField(label='手机号', validators=[foo])


# 自定义的验证函数 value表示被验证字段
def foo(value):
    pass
  1. 方法三 使用钩子
class LoginSerializer(serializers.Serializer):
    phone = serializers.CharField(label='手机号')
    
    
    # 以 validate_xxx  形式开头的方法是验证钩子  会自动调用进行字段验证
    def validate_phone(self, value):
         print(value + '``````````')
         raise serializers.ValidationError('aaaa')
         return value

登录认证


REST_FRAMEWORK = {
    # 未认证情况下 默认用户和令牌为None
    'UNAUTHENTICATED_TOKEN': None,
    'UNAUTHENTICATED_USER': None,
    # 可以通过全局指定的方式 指定登录认证的中间件位置
    'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.LoginAuthentication']
}


from rest_framework.authentication import BaseAuthentication

from api.models import Users


class MyGeneralAuthentication(BaseAuthentication):
    def authenticate(self, request):
        """
        :param request:
        :return: None -表示本认证器不做任何处理直接交给下一个认证器 认证成功会返回一个元组  (user,token) 表示认证成功 并分贝绑定在
        request.user request.auth 属性上
        """
        # 获取token
        # 注意: 特别值得注意的是  前端 header = {
        #   'authentication': 578fff92-e481-4223-a49a-427cdadfd915
        # }
        # drf框架中接收的时候   需要 加 HTTP_ 前缀 并将key全大写
        token = request.META.get('HTTP_AUTHENTICATION', None)
        # 如果token没有带过来
        if not token:
            return None
        # 如果token是错误的
        user = Users.objects.filter(token=token).first()
        if not user:
            return None
        # 通过认证  会返回一个元组(登录用户对象,登录用户token) 并将其作为request属性绑定上  使用的时候可以通过
        # request.user request.auth 拿到
        # print(user, token)
        return user, token


之后可以在view中指定 认证器类名 处理认证过程的业务逻辑可以通过 重写方法来进行


class ArticleDetailView(RetrieveAPIView, UpdateAPIView, DestroyAPIView):
    # 查询结果集
    queryset = Articles.objects.all()
    # 序列化器类
    serializer_class = ArticleSerializerForList
    # django 默认的主键形参为pk 如果改用其他 应通过 lookup_field 显试指定出来
    lookup_field = 'id'
    # 指定登录认证器
    authentication_classes = [MyGeneralAuthentication]

    def get(self, request, *args, **kwargs):
        response = super().get(request, *args, **kwargs)

        # 此处验证是否登录  如果登录了 就可以更新浏览记录
        if not request.auth:
            return response

        # 检查是否浏览记录已经存在 不用再添加了
        # 拿到该文章所有的浏览记录
        id = kwargs.get('id')
        article = Articles.objects.filter(id=id).first()
        user = request.user

        if user in [u for u in article.viwers.all()]:
            return response

        # 向中间表添加数据  也就是浏览记录
        article.viwers.add(user)

        return response

celery的使用

pip install celery
pip install redis
pip install eventlet # eventlet 只有windows下才需要安装 linux不需要安装

from celery import Celery
"""
broker 指定任务队列
backend 指定结果队列
"""
app = Celery('tasks',
             backend='redis://localhost:6379/0',
             broker='redis://localhost:6379/0')

"""
使用 @app.task 装饰后就成为了一个 可以被celery调度的任务
"""
@app.task
def add(x, y):
    return x + y
from test import add
import time
#不要直接 add(4, 4),这里需要用 celery 提供的接口 delay 进行调用
result = add.delay(4, 4)  
print(result.id)

# 可以通过 reslut.ready() 判断add这个task是否处理结束
while not result.ready():
    time.sleep(1)
# 可以通过 result.get() 来获取task的结果
print(f'task done: {result.get()}')

注意:worker 的启动命令
celery worker -A taskproj -l info -P eventlet
只有windows平台需要指定 -P eventlet 在linux下不需要

celery 与 django 嵌入使用

django 项目名为: miniprogramapi 项目同名目录 miniprogramapi 创建的app名称为 testcelery

  1. 在项目的同名目录下创建celery.py文件 miniprogramapi/miniprogramapi/celery.py

    文件名必须为 celery.py 固定不能变

import os
from celery import Celery

# 设置django环境
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'miniprogramapi.settings')

app = Celery('testcelery')
# 使用CELERY_ 作为前缀,在settings中写配置
app.config_from_object('django.conf:settings', namespace='CELERY')
# 发现任务文件每个app下的task.py
app.autodiscover_tasks()

  1. 在项目的同名目录下 __init__.py 文件中添加内容

    from .celery import app as celery_app
    
    __all__ = ['celery_app']
    
  2. 在项目同名文件夹 settings.py 中添加关于 celery的配置

    # ******************************* celery的配置 ***************************
    # Broker配置,使用Redis作为消息中间件
    CELERY_BROKER_URL = 'redis://127.0.0.1:6379/0'
    # BACKEND配置,这里使用redis
    CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'
    # 结果序列化方案
    CELERY_RESULT_SERIALIZER = 'json'
    
  3. 在创建的app中定义 tasks.py 文件

    文件名必须叫 tasks.py

    from celery import shared_task
    
    
    @shared_task
    def add(x, y):
        return x + y
    
    
    @shared_task
    def mul(x, y):
        return x * y
    
  4. 启动 worker的命令

    celery worker -A miniprogramapi -l info -P eventlet
    
  5. 编写urls

    from django.urls import path
    
    from testcelery.views import TestCelery, ResultCelery
    
    app_name = 'testcelery'
    urlpatterns = [
        # 执行触发celery task
        path('celery/', TestCelery.as_view(), name='celery'),
        # 查询celery task的执行结果
        path('result/', ResultCelery.as_view(), name='result'),
    
    ]
    
    
  6. 编写视图

    # Create your views here.
    from celery.result import AsyncResult
    from rest_framework.response import Response
    from rest_framework.views import APIView
    
    from miniprogramapi import celery_app
    from testcelery import tasks
    
    
    class TestCelery(APIView):
        def get(self, request, *args, **kwargs):
            res = tasks.add.delay(1, 3)
            # 任务逻辑
            data = {'status': 'successful', 'task_id': res.task_id}
            return Response(data)
    
    
    class ResultCelery(APIView):
        def get(self, request, *args, **kwargs):
            result_id = request.query_params.dict().get('result_id')
            # result_id = '1f9b12da-f4da-4562-a6d1-ad2d7e18d86b'
            result_obj = AsyncResult(id=result_id, app=celery_app)
            data = {
                'status': 1,
                'state': result_obj.state,
                'result': result_obj.get()
            }
            return Response(data)
    
    

扩展

除了redis、rabbitmq能做结果存储外,还可以使用Django的orm作为结果存储,当然需要安装依赖插件,这样的好处在于我们可以直接通过django的数据查看到任务状态,同时为可以制定更多的操作,下面介绍如何使用orm作为结果存储。

  1. 安装
pip install django-celery-results
  1. 配置settings.py,注册app
INSTALLED_APPS = (
    ...,
    'django_celery_results',
)
  1. 修改backend配置,将redis改为django-db
# CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0' # BACKEND配置,这里使用redis

CELERY_RESULT_BACKEND = 'django-db'  #使用django orm 作为结果存储
  1. 修改数据库
python3 manage.py migrate django_celery_results
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值