Django-project

这一篇和之前的不太一样,主要讲的也不是django本身的技术问题,而是开发中的注意事项


Django 与开发

git的使用–关于分支

由Git pro中了解到,每一个分支应该代表了当前要完成的任务,也就是说,每一次开发的时候,应该是以开发特性作为分支的名字。当然,也可以使用next等表示开发版本

  • 每次合并结束后,记得要将原先的分支删除掉。
  • 分支的名字不要用_, 而是用-
  • 每次提交都只做一件事情,比如修改了文档1并且增加了文档2。
  • 每个功能都新建一个分支,比如一个分支用于写文档另一个用于开发。
  • 某些功能如果正在开发中,我们可以在标题中加上WiP(Work in process)表示当前的工作正在开发中,不能直接进行合并。
  • 如果要master和当前分支同步的话,不要用个git pull origin master,而是要git rebase master,从而将之前的

API的相关作用

API是将不同模块连接的重要纽带,所以当我们开始项目后,就要及时的给出API,而不是先写代码。

Field的相关

django对数据库的支持很好,但是一次性记下来始终还是太难了,这里记下文件路径:[django/forms/field.py]

http相关知识

毕竟是网页,http的相关指示也是要求有的

http状态码

200——交易成功
201——提示知道新文件的URL
202——接受和处理、但处理未完成
203——返回信息不确定或不完整
204——请求收到,但返回信息为空
205——服务器完成了请求,用户代理必须复位当前已经浏览过的文件
206——服务器已经完成了部分用户的GET请求

http请求类型

GET:向特定的资源发出请求。
POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。
PUT:向指定资源位置上传其最新内容。
DELETE:请求服务器删除Request-URI所标识的资源。

API的内容要求

  • URI后面都要有/

rest framework — serialize(序列化)

当我们需要将我们的model中的数据传输的时候,总不能现场封装一个json吧(以前java里面居然干过这样的蠢事。。。)所以这里使用serialize将我们指定的类数据序列化,
例如:

1
2
3
4
5
6
7
8
9
from datetime import datetime

class Comment(object):
def __init__(self, email, content, created=None):
self.email = email
self.content = content
self.created = created or datetime.now()

comment = Comment(email='leila@example.com', content='foo bar')

首先定义一个类,有一些基本的属性。

1
2
3
4
5
6
7
from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
class Meta:
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()

然后用序列化定义,定义相应的内容以及对应的数据库内容。如果使用了django框架的话,此时可以将自己的model类直接赋值给对应的model属性.
之后,如果我们将comment序列化处理后,将会得到:

1
2
3
serializer = CommentSerializer(comment)
serializer.data
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}

一个序列化处理后的结果。此处可以看见,此时的data已经变成python内建的dict,于是如果此时我们想要传输json的话,我们只需要使用自带的json封装即可:

1
2
3
4
5
from rest_framework.renderers import JSONRenderer

json = JSONRenderer().render(serializer.data)
json
# b'{"email":"leila@example.com","content":"foo bar","created":"2016-01-27T15:17:10.375877"}'

同样的是,这个类还能够进行反序列化:

1
2
3
4
5
6
7
8
9
10
11
from django.utils.six import BytesIO
from rest_framework.parsers import JSONParser

stream = BytesIO(json)
data = JSONParser().parse(stream)

serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}

rest framework — ViewSet & Router

这个viewset能够帮助开发者更加专注与models的开发和交互,并且能够让url能够自动化构建。
我们这里介绍项目中会遇到的几个方法:
viewsets.ModelViewSet:
这个类会提供list, create, retrieve,updatedestroy方法。这些方法能够帮助我们更快的构建页面。
通过继承其对应的类可以实现渲染效果:

1
2
3
class ExampleViewSet(ModelViewSet):
serializer_class = CommentSerializer
queryset = Example.obejcts.all()

然后我们可以在注册一个我们的url

1
2
3
4
5
router = DefaultRouter()
router.register('comments', CommentViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]

这样的话,通过访问指定来的url(xxx/coments/)就能够访问到一个用于调试用的界面。由于我们继承的是ModelViewSet,所以我们函数中带有list,delete,update,create,retrive的功能。具体体现:

  • list:queryset中的内容全部展示在当前页面
  • create:提供一个post方法
  • retrive:可访问(/comments/123/)
  • update:提供了一个put更改
  • delete:(/comments/123/)中有一个delete按钮

提高效率

查看源代码可知,在retrive或者list方法中,会调用一个叫做get_object的方法,那么如果我们不想让这个ViewSet获得默认的数据,或者想让其通过别的方式获得数据的话,可以通过重构这个函数来实现这个功能。

自动生成的url

上面讲了viewset,这里讲一下router的用途。Router就是一个封装好的路由类,通过注册–>加入url的方式完全自动生成动态的url:

1
2
3
4
5
router.register('comments', CommentViewSet)

urlpatterns = [
url(r'^', include(router.urls)),
]

如上,此时匹配的url规则就会包括[/comments/id],当然id是否可以匹配取决于注册的CommentViewSet是否支持update这类方法。

资源的嵌套

当然RESTFUL中提倡资源嵌套,如[/author/1/comment],为了实现资源嵌套,一个router肯定是不够的,而中间的1又是一个变量,这样的话我们可以用提供的装饰器解决:

1
2
3
4
class A(xxx):
@detail_route(methods=['post'],
permission_classes=(IsAuthenticated,),url_path='my-url')
def func()

这里通过使用了装饰器,指定了当我们使用post方法的时候,我们就能够像访问到[/A/id/my-url]的位置,如果不指定名字的话,那么这个my-url就会替换成def后的名字。

资源的嵌套–ViewSet结合

然而,有时候我们就是这么懒,想要用现有的方法,但是对于另一些方法又想要让其访问到ViewSet以外的url,并且此时还要是另一个ViewSet中的数据库的内容。这就要用到Router的Bind技术:

1
2
3
4
5
class MoneyViewSet(ModelViewSet):
....

class BankViewSet(ReadOnlyViewSet):
...

假如如上,MoneyViewSet中,我们定义了一个方法getMoney,所以要从Money的数据库中查找数据,但是此时我们的url设计为[/bank/{id}/money/](因为要找到对应的bank),所以此时我们同时需要取得bank的id并且访问对应点money的数据库。显然,我们要是直接注册MoneyViewSet,就会形成[/money/{id}],显然就不是我们要的url。为了保持我们的设计原则,我们这里可以这样处理:
首先在urls.py中绑定我们的ViewSet:

方便的测试APIClient

很多时候,我们测试的过程中可能包含了【管理员】与【用户】的差异。当我们为了测试时模拟这个用户的过程,我们可以用APIClient:
假设我们此时项目有一个用户对象类为Uesr,继承了来自django自带的AbstractUser:

1
2
3
4
self.user = User.objects.create(username="user")
self.admin = User.objects.create(username="admin")

self.client.force_login(self.admin)

这个过程中,就能够模拟出一个登陆的会话,该会话中我们就是以用户名为"admin"登陆的。