博文相关接口(博文的增删改查)

1. 功能分析

POST /posts/ 文章发布,视图类PostView
请求体 application/json
{
 "title":"string",
 "content":"string"
}
响应
201 发布成功
400 请求数据错误
GET /posts/(\d+) 查看指定文章,视图函数getpost
响应
200 成功返回文章内容
404 文章不存在
GET /posts/ 文章列表页,视图类PostView
响应
200 成功返回文章列表

2. 创建博文应用

$ python manage.py startapp post

注意:一定要把应用post加入到settings.py中,否则不能迁移

路由:

# blog/urls.py
urlpatterns = [
    # 下面三个有一个即可
    url(r'^admin/', admin.site.urls),  # url在 2.x版本 re_path
    url(r'^$', index),
    url(r'^index$', index),
    url(r'^users/', include('user.urls')),  # 多级路由,查看include的原码
    url(r'^posts/', include('post.urls'))
]
# post/urls.py
from django.conf.urls import url
from .views import PostView, get_post, put, delete
# from user.views import authenticate


urlpatterns = [
    # 路径/posts/
    # View类调用as_view之后类等价一个视图函数,可以被装饰
    # 装饰器函数新的函数

    # url(r'^$', authenticate(PostView.as_view())),  # 如果所有视图函数都需要认证,可以使用此种方法
    url(r'^$', PostView.as_view()),
    url(r'^(\d+)$', get_post),
    url(r'^put$', put),
    url(r'^delete$', delete)
    ]

模型:

from django.db import models
from user.models import User


class Post(models.Model):
    class Meta:
        db_table = 'post'

    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=128, null=False)  # 文章标题不可以为空
    postdate = models.DateTimeField(null=False)
    author = models.ForeignKey(User, on_delete=models.PROTECT)  # 指定外键,migrate会生成author_id字段
    delete = models.BooleanField(null=False)

    # self.content是Content的实例,其内容是self.content.content

    def __repr__(self):
        return "<Post {} {} {} {}>".format(self.id, self.title, self.postdate, self.content)

    __str__ = __repr__


class Content(models.Model):
    class Meta:
        db_table = "content"

    # 没有主键,会自动创建一个自增主键
    post = models.OneToOneField(Post, primary_key=True, on_delete=models.PROTECT)
    # 一对一,这边会有一个外键post_id引用post.id;models.PROTECT是不允许删除
    content = models.TextField(null=False)
    delete = models.BooleanField(null=False)

    def __repr__(self):
        return "<Content {} {}>".format(self.post.id, self.content[:20])

    __str__ = __repr__

on_delete:

  • models.CASCADE
  • models.PROTECT
  • models.SET_NULL
  • models.DO_NOTHING
  • Django 2.0开始,on_delete必须提供,

2.1 视图分类

  •  function-based view 视图函数:视图功能有函数实现
  • class-based view 视图类:视图功能有基于django.views.View类的子类实现

django.views.View类原理:django.views.View类本质就是一个对请求方法分发到与请求方法同名函数的调度器。 django.views.View类,定义了http的方法的小写名称列表,这些小写名称其实就是处理请求的方法名的小写。as_view()方法就是返回一个内建的 view(request, *args, **kwargs) 函数,本质上其实还是url映射到了函数上, 只不过view函数内部会调用 dispatch(request, *args, **kwargs) 分发函数。 dispatch函数中使用request对象的请求方法小写和http_method_names中允许的HTTP方法匹配,匹配说明是正 确的HTTP请求方法,然后尝试在View子类中找该方法,调用后返回结果。找不到该名称方法,就执行 http_method_not_allowed方法返回405状态码 。看到了getattr等反射函数,说明基于反射实现的。

as_view()方法,用在url映射配置中:

本质上,as_view()方法还是把一个类伪装成了一个视图函数。

这个视图函数,内部使用了一个分发函数,使用请求方法名称把请求分发给存在的同名函数处理。

但是这种方式适合把PostView类所有方法都认证,但是实际上就post方法要认证。所以,authenticate还是需要加载到post方法上去。因此,要修改authenticate函数。

2.2 发布接口实现

用户从浏览器端提交Json数据,包含titlecontent。提交博文需要认证用户,从请求的header中验证jwt。 新建一个工具包,将jsonify函数挪进去。

# utils/__init__.py
def json_ify(instance, allow=None, exclude=()):  # exclude是排除在外的字段
    """此函数的作用是过滤输出字段"""
    # allow优先,如果有,就使用allow指定的字段,这时候exclude无效
    # allow如果为空,就全体,但要将exclude中的排除

    cls = type(instance)
    if allow:
        fn = (lambda x: x.name in allow)
    else:
        fn = (lambda x: x.name not in exclude)

    return {k.name: getattr(instance, k.name) for k in filter(fn, cls._meta.fields)}


d = {
    '400': {'error': "用户或密码错误"},
    '401': {'error': "用户输入错误"},
    '404': {'error': 'Not found'},
    '204': {'Good!': '删除成功'}
}
    @authenticate
    def post(self, request: HttpRequest):  # 提交文章数据走这里
        print('post +++++++++++')

        post = Post()
        content = Content()
        try:
            payload = simplejson.loads(request.body)
            print(payload, '~~~~~~~~~~~++++++++++++')
            post.title = payload['title']
            text = payload['content']
            # post.author = User(id=request.user.id)  # 这是一个对象,包含user的id、name、email、password
            post.author = request.user  # 一个对象,推荐使用这种写法
            post.postdate = datetime.datetime.now(
                datetime.timezone(datetime.timedelta(hours=8)))  # 数据库存放的是UTC(世界统一时间),与北京时间差8小时

            # post.save()  # post已经save后(注意save会自动提交),如果出现异常,那么异常后的代码是执行不了的,(content是没法save的)
            # raise Exception()  # 抛出异常
            # 该怎么解决呢?利用事务的原子性;另外还要注意,post必须要小save了,content才能save,两者顺序不能换

            with transaction.atomic():  # 原子操作
                post.save()  # save完后,post表的id就有了

                content.post = post  # 考虑此处为什么是post,而不是post.id ==》此处的写法是Content定义时,就是一对一引用Post
                content.content = text

                # print(content.post, '^^^^^^^^^^^^')  # 注意这两句是不能打印的,直接报错'str' object is not callable
                # print(content.content, '###########')

                content.save()

            return JsonResponse({
                'post': json_ify(post, allow=['id', 'title'])
            }, status=201)  # 增加成功,状态码用201,不要随便修改
        except Exception as e:
            logging.error(e)
            return JsonResponse({'error': '用户输入错误'}, status=400)

显示事务处理:Django中每一次save()调用就会自动提交,那么上例中第一次事务提交后如果第二次提交前出现异常,则 post.save()不会回滚。解决办法可以使用事务的原子方法,参考 https://docs.djangoproject.com/en/1.11/topics/db/transactions/#django.db.transaction.atomic

2.3 文章接口实现

根据post_id查询博文并返回。这里需要认证吗? 如果博文只能作者看,就需要认证。我们这里的公开给所有人看,所以不需要认证。同样,下面的list接口也是不 需要认证的。

@require_GET
def get_post(request: HttpRequest, _id):  # 详情页,查看一篇文章,与文章相关的所有信息都应该返回
    print(request)
    # print(_id, type(_id))  # 类型是字符串
    try:
        _id = int(_id)
        post = Post.objects.get(pk=_id)  # only one
        print(post, '101010100101010100')
        return JsonResponse({
            'post':
                {
                    'id': post.id,
                    'title': post.title,
                    'postdate': int(post.postdate.timestamp()),
                    'author': post.author.name,
                    'author_id': post.author_id,  # post.author.id
                    'content': post.content.content  # 后面的content.content就是上面的text
                }
        })
    except Exception as e:
        logging.error(e)
        return JsonResponse(d['404'], status=404)

2.4 列表页接口实现

发起GET请求,通过查询字符串 http://url/posts/?page=2 查询第二页数据。

进一步完整分页功能:

GET /posts/?page=3&size=20 文章列表页,视图类PostView
响应
200 成功返回文章列表

完善分页:分页信息,一般有:当前页/总页数、每页条数、记录总数。 当前页:page ,每页条数:size,每页最多多少行 ,总页数:pages = math.ceil(count/size) ,记录总数:total,从select * from table来。

def validate(dic: dict, name: str, default, validate_func):

    try:
        r = int(dic.get(name, default))
        ret = validate_func(r, default)
    except Exception as e:
        logging.error(e)
        ret = default
    return ret


    def get(self, request: HttpRequest):  # 获取全体文章走这里
        print('get ~~~~~~~~~~~')

        page = validate(request.GET, 'page', 1, lambda x, y: x if x > 0 else y)
        print(page)

        # try:  # 页码
        #     page = int(request.GET.get('page', 2))
        #     page = page if page > 0 else 1
        # except Exception as e:
        #     logging.error(e)
        #     page = 1
        #
        # try:  # 每页条数(每页最多能写多少行)
        #     # 注意,这个数据不要轻易让浏览器端改变,如果允许改变,一定要控制范围
        #     size = int(request.GET.get('size', 2))
        #     size = size if 0 < size < 101 else 20
        # except Exception as e:
        #     logging.error(e)
        #     size = 20

        size = validate(request.GET, 'size', 20, lambda x, y: x if 0 < x < 101 else y)

        try:  # 每页条目数
            start = (page - 1) * size

            posts = Post.objects.order_by('-pk')  # 根据主键降序排列
            total = posts.count()

            posts = posts[start: start + size]
            print(posts.query, '77777777777777777')
            # SELECT `post`.`id`, `post`.`title`, `post`.`postdate`, `post`.`author_id`
            # FROM `post` ORDER BY `post`.`id` DESC LIMIT 2 OFFSET 2 7777777777

            return JsonResponse({
                'posts': [
                    json_ify(post, allow=['id', 'title'])
                    for post in posts
                ],  # 列表解析式
                'pagination': {
                    'page': page,
                    'size': size,
                    'total': total,
                    'pages': math.ceil(total / size)
                }
            }, status=200)

        except Exception as e:
            logging.error(e)
            return JsonResponse(d['400'], status=400)

博文的更新、修改、删除代码:

# 更新、修改
@authenticate
def put(request: HttpRequest):
    print('put ~~~~~~~~~~~~~~~~~~~')

    # post = Post()
    # content = Content()

    try:
        payload = simplejson.loads(request.body)
        _id = payload["id"]
        post = Post.objects.get(pk=_id, author_id=request.user.id)
        content = Content.objects.get(pk=_id)

        print(payload, '6666666666')

        title = payload['title']

        post.title = title
        content.content = payload['content']
        post.postdate = datetime.datetime.now(
            datetime.timezone(datetime.timedelta(hours=8)))

        with transaction.atomic():
            post.save()
            content.save()

        return JsonResponse({
            'post': {
                'author': post.author.name,
                'author_id': post.author_id,
                'title': post.title,
                'postdate': post.postdate,
                'content': post.content.content
            }
        }, status=201)

    except Exception as e:
        logging.error(e)
        return JsonResponse(d['400'], status=400)


@authenticate
def delete(request: HttpRequest):
    print('delete ~~~~~~~~~~~~~~')
    print(request)

    try:
        payload = simplejson.loads(request.body)

        _id = payload['id']

        post = Post.objects.get(pk=_id)
        content = Content.objects.get(pk=_id)

        # content.content = payload['content']
        post.postdate = datetime.datetime.now(
            datetime.timezone(datetime.timedelta(hours=8)))

        author_id = post.author_id

        if author_id != request.user.id:
            return JsonResponse(d['400'], status=400)

        post.delete = True
        content.delete = True

        with transaction.atomic():
            post.save()
            content.save()

        return HttpResponse(status=204)
        # return JsonResponse({
        #     'post': {
        #         'id': post.id,
        #         'author': post.author.name,
        #         'author_id': post.author_id,
        #         'postdate': post.postdate,
        #         'content': post.content.content
        #     }
        #
        # }, status=204)

    except Exception as e:
        logging.error(e)
        return JsonResponse(d['400'], status=400)

博文项目源码:https://github.com/sqsltr520/python

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值