Django-REST-framework使用技巧(一)

  • 1.Quickstart
    • 1.1 项目搭建
    • 1.2 序列化
    • 1.3 视图
    • 1.4 URLs
    • 1.5 设置
  • 2.Serialization
    • 2.1 创建一个模型
    • 2.2 创建一个序列化类
    • 2.3 使用Serializers
    • 2.4 使用ModelSerializers
    • 2.5 使用Serializer编写常规的Django视图
  • 3.测试我们在Web API上的第一次访问

1、Quickstart

环境

Python 3.7.2

MacOS High Sierra

django-2.2.1

djangorestframework-3.9.4

1.1 项目

我们将创建一个简单的API来允许管理员用户查看和编辑和系统中的用户和组。

项目创建

创建目录

mkdir tutorial
cd tutorial

创建virtualenv,环境隔离我们本地的依赖包关系

virtualenv env # 会得到下面结果回应
    Using base prefix '/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7'
New python executable in /Users/administrator/Desktop/Django-REST-Framework/tutorial/env/bin/python3.7
Also creating executable in /Users/administrator/Desktop/Django-REST-Framework/tutorial/env/bin/python
Installing setuptools, pip, wheel...
done.
source env/bin/activate #激活虚拟环境,可以看到以下结果显示
(env) AdministratordeiMac:tutorial administrator$ 

在虚拟环境安装Django 和 Django REST Framework

pip install --upgrade pip #先升级pip
pip install django # 安装django
pip install djangorestframework # 安装djangorestframework
pip install pygments #代码高亮

创建项目,并建立一个app

django-admin startproject tutorial . #建立Django项目。后面"."表示在当前文件夹建立项目
python manage.py startapp quickstart 或则django-admin.py startapp quickstart #新建一个APP

项目布局应该如下:

startarchitecture.png
quickstart.png

第一次同步数据库

python manage.py migrate #第一次同步数据库,如下:
migrate.png

创建超级用户,用户名admin,密码123456(超级用户随便起,但是要记住名字和密码,后面要用到)

python manage.py createsuperuser
superuser.png

1.2 序列化

首先定义一些序列化器,创建一个名为tutorial/quickstart/serializers.py的文件,我们将用它来表示数据。

#文件路径tutorial/quickstart/serializers.py

from django.contrib.auth.models import User, Group
from rest_framework import serializers


class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ('url', 'username', 'email', 'group')
        

class GroupSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Group
        field = ('url', 'name')

注意这里,我们使用了超链接关系(HyperlinkedModelSerializer)。你还可以使用主键和其他关系,但是这种超媒体引
擎驱动是不是很棒的RESTful设计呢。

1.3 视图

在tutorial/quickstart/views.py中,输入:

#文件路径tutorial/quickstart/views.py

from rest_framework import viewsets
from .serializers import UserSerializer, GroupSerializer
from django.contrib.auth.models import User, Group


class UserViewSet(viewsets.ModelViewSet):

    # 允许用户查看或者编辑API端点

    queryset = User.objects.all().order_by('-date_joined')
    serializer_class = UserSerializer


class GroupViewSet(viewsets.ModelViewSet):
    
    # 允许组查看或者编辑API端点

    queryset = Group.objects.all()
    serializer_class = GroupSerializer

我们将所有通用行为分组到ViewSets类中,而不是编写多个视图。
如果需要,我们可以轻松第将这些视图分解为单个视图(这个后面会细说的),但是使用视图集可以使视图逻辑组织的非常好,
并且非常简洁。

1.4 URLs

配置路由

# tutorial/urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include
from rest_framework import routers
from quickstart import views


router = routers.DefaultRouter()

router.register('users', views.UserViewSet)
router.register('group', views.GroupViewSet)

# 使用自动URL路由连接API
# 另外,我们还包括可以浏览API的登录URL
urlpatterns = [
    path('admin/', admin.site.urls),
    url('^', include(router.urls)),
    url('^api-auth/', include('rest_framework', namespace='rest_framework')),
]

因为我们使用视图集而不是视图,所以我们可以通过简单地向路由器类注册视图集,来自动为我们的API生成URL conf。
同样,如果我们需要更多地控制API URL,我们可以简单地使用常规的基于类的视图,并明确地编写URL conf。
最后,我们将包括默认的和注销视图,以用于可以浏览的API,这是可选的。但是如果你的API需要身份验证,并且想使用可浏览的API,则会很有用。

1.5 设置(Setting)

将rest_framework 添加到INSTALLED_APPS.

设置路径:tutorial/setting.py中:

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

第一阶段已经完成,下面测一下我们的API:开启服务

python manage.py runserver

我们可以使用从命令行curl等工具访问我们api:

(env) AdministratordeiMac:tutorial administrator$ curl -H 'Accept: application/son; indent=4' -u admin:123456 http://127.0.0.1:8000/users/

[
    {
        "url": "http://127.0.0.1:8000/users/1/",
        "username": "admin",
        "email": "1232@qq.com",
        "groups": []
    }
]

或者命令行工具 httpie ...

(env) AdministratordeiMac:tutorial administrator$ http -a admin:123456http://127.0.0.1:8000/users/

HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

[
    {
        "url": "http://127.0.0.1:8000/users/1/",
        "username": "admin",
        "email": "1232@qq.com",
        "groups": []
    }
]

或者直接通过浏览器访问 URL http://127.0.0.1:8000/users/ ,如下如所示:

apiresult.png

2、Serialization(序列化)

添加上文创建APP quickstart

在tutorial/setting.py中:

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

2.1 创建一个模型

在quickstart/models.py文件中添加模型

from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles


# 提取出pyment 支持的所有语言的语法分析程序
LEXERS = [item for item in get_all_lexers() if item[1]]

# 提取除了'pyments' 支持的所有语言列表
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])

# 提取出'pyment' 支持的所有格式化风格列表
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())


class Snippet(models.Model):

    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()
    linenos = models.BooleanField(default=False)  # 是否显示行号
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=120)
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=120)

    class Meta:
        ordering = ('-created',)

然后执行数据迁移和同步

python manage.py makemigrations

Migrations for 'quickstart':
quickstart/migrations/0001_initial.py
- Create model Snippet


python manage.py migrate

Operations to perform:
Apply all migrations: admin, auth, contenttypes, quickstart, sessions
Running migrations:
Applying quickstart.0001_initial... OK

2.2 创建一个序列化类

我们开始使用Web API 的第一件事就是提供一种,将代码片段的实例序列化或者反序列化为表示形式(json等)的方法。我们可以通过声明与Django forms非常相似的序列化器(serializers)实现。在上面我们已经创建了serializers.py的文件,现在修改如下:

    # 路径quickstart/serializers.py 

from rest_framework import serializers
from .models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=120)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default="python")
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):

        # 给定验证过的数据创建并返回一个新的Snippet实例。
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):

        # 根据已验证的数据更新并返回已经存在的Snippet实例

        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.linenos = validated_data.get('linenos', instance.linenos)
        instance.language = validated_data.get('language', instance.language)
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance

序列化器列的第一部分定义了获得序列化/反序列化的字段。create()update()方法定义了在调用serializer.save()时如何创建和修改完整的实例。

序列化器列与Django form类非常类似,在各种字段中,包含类似验证标志,例如require,max_lengthdefault

字段标识还可以控制serializer在某些情况下如何显示,比如渲染HTML时候,上面{'base_template':'textarea.html'},标志等同于在Django Form类中使用widget=widget.Textarea.这对于控制如何显示可以浏览的API 特别有用,我们将在后面讲解看到。

我们实际上也可以通过使用ModelSerializer类来节省一些时间,我们稍后会看到,但是现在我们将使用我们明确定义的serializer。

2.3 使用Serializers

再进一步了解之间,我们先熟悉使用我们的新类Serializer类。让我们进入Django sehll

python manage.py shell

我们已经导入了几个模块,然后开始创建一些片段代码来处理

AdministratordeiMac:tutorial administrator$ python manage.py shell
Python 3.7.2 (default, Feb 12 2019, 08:16:38) 
[Clang 10.0.0 (clang-1000.11.45.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)

>>> from quickstart.models import Snippet
>>> from quickstart.serializers import SnippetSerializer
>>> from rest_framework.renderers import JSONRenderer
>>> from rest_framework.parsers import JSONParser
>>> 
>>> snippet = Snippet(code='foo="bar"\n')
>>> snippet.save()
>>> 
>>> snippet = Snippet(code='print "Hello , World"\n')
>>> snippet.save()

我们已经有几个可以使用的片段实例,让我们看看序列化中的一个实例:

>>> serializer = SnippetSerializer(snippet)
>>> serializer.data
{'id': 2, 'title': '', 'code': 'print "Hello , World"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
>>> 

此时,我们将模型实例转换成为python原生数据类型。要完成序列化过程,我们将数据渲染成json

>>> content = JSONRenderer().render(serializer.data)
>>> content
b'{"id":2,"title":"","code":"print \\"Hello , World\\"\\n","linenos":false,"language":"python","style":"friendly"}'
>>> 

反序列化是类似的。首先我们将一个流解析为python原生的数据类型:

>>> from django.utils.six import BytesIO
>>> 
>>> stream = BytesIO(content)
>>> data = JSONParser().parse(stream)
>>> data
{'id': 2, 'title': '', 'code': 'print "Hello , World"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
>>> 

然后我们将这些原生的数据类型恢复正常的对象实例。

>>> serializer = SnippetSerializer(data=data)
>>> serializer.is_valid()
True
>>> serializer.validated_data
OrderedDict([('title', ''), ('code', 'print "Hello , World"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
>>> serializer.save()
<Snippet: Snippet object (3)>

注意API工作形式和表单(form)是多么相似。但我们开始使用我门的序列化类编写视图的时候,相似性将会更加明显。
我们也可以序列化查询代替模型实例。为此,我们只需要在序列化参数中添加一个mangy = True

>>> serializer = SnippetSerializer(Snippet.objects.all(),many = True)
>>> serializer.data
[OrderedDict([('id', 3), ('title', ''), ('code', 'print "Hello , World"'), ('linenos', False), 
('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), 
('code', 'print "Hello , World"\n'), ('linenos', False), ('language', 'python'), ('style', 
'friendly')]), OrderedDict([('id', 1), ('title', ''), ('code', 'foo="bar"\n'), ('linenos', 
False), ('language', 'python'), ('style', 'friendly')])]
>>> 

2.4 使用ModelSerializers

我们在SnippetSerializer类中重复包含Snippet模型中的信息。如果能保持代码简洁,就像大家经常说的don't repeat yourself。就像Django 提供了Form类和MoldelForm类一样,RESTFramework包括Serializer类和ModelSerializers类一样。我们试着看看使用ModelSerializer类重构我们的序列化类。再次打开quickstrat/是、serializers类。并将SnippetSerializer类的内容替换为下面内容:
以前的SnippetSerializer类的内容:

from rest_framework import serializers
from .models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=120)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default="python")
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):

        # 给定验证过的数据创建并返回一个新的Snippet实例。
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):

        # 根据已验证的数据更新并返回已经存在的Snippet实例

        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.linenos = validated_data.get('linenos', instance.linenos)
        instance.language = validated_data.get('language', instance.language)
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance

替换如下:

from rest_framework import serializers
from .models import Snippet

class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style')

序列化程序有一个非常棒的属性,就是可以通过打印其结构(representation)来检查序列化实例中的所有字段。

>>> from quickstart.serializers import SnippetSerializer
>>> serialzier = SnippetSerializer()
>>> print(repr(serializer))
SnippetSerializer(<QuerySet [<Snippet: Snippet object (3)>, <Snippet: Snippet object (2)>, <Snippet: Snippet object (1)>]>, many=True):
    id = IntegerField(read_only=True)
    title = CharField(allow_blank=True, max_length=120, required=False)
    code = CharField(style={'base_template': 'textarea.html'})
    linenos = BooleanField(required=False)
    language = ChoiceField(choices=[('abap', 'ABAP'), ('abnf', 'ABNF'), ('ada', 'Ada'), ('adl', 'ADL'), ('agda', 'Agda'), .... ('zephir', 'Zephir')], default='python')
    style = ChoiceField(choices=[('abap', 'abap'),.... ('trac', 'trac'), ('vim', 'vim'), ('vs', 'vs'), ('xcode', 'xcode')], default='friendly')
>>> 

注意ModelSerializer类并不会做任何特别神奇的事情,他们只是创建序列化器类的快捷方式:

  • 自动确定一组字段(不用重复去定义类属性)。
  • 默认简单的实现create()update()方法。

2.5 使用我们的Serializer编写常规的Django视图

我们尝试使用我们的Serializer类编写一些API视图。目前我们不用任何REST Framework 的其他功能,我么只编写常规的Django视图。

路径 : quickstart/view.py,下面是我们开始写的内容
from rest_framework import viewsets
from .serializers import UserSerializer, GroupSerializer
from django.contrib.auth.models import User, Group


class UserViewSet(viewsets.ModelViewSet):

    # 允许用户查看或者编辑API端点

    queryset = User.objects.all().order_by('-date_joined')
    serializer_class = UserSerializer


class GroupViewSet(viewsets.ModelViewSet):

    # 允许组查看或者编辑API端点

    queryset = Group.objects.all()
    serializer_class = GroupSerializer

然后替换如下:


quickstart/view.py #

from django.http import HttpResponse,JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from quickstart.models import Snippet
from quickstart.serializers import SnippetSerializer


@csrf_exempt
def snippet_list(request):
    # 列出所有代码 snippet, 或者创建一个新的snippet

    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JsonResponse(serializer.data, safe=False)
    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)

    return JsonResponse(serializer.errors, status=400)

注意,因为我们希望能够从不具有CSRF令牌的客户端POST到此视图,所以我们需要将该视图标记为@csrf_exempt.这不是你通常想要做的事情,并且RESTframework视图实际上比这更具实用行为,但是他现在足够达到我们的目的。
我们还需要一个与单个相对应的视图,并可用于检索、更新或者删除snippet.

quickstart/view.py #

@csrf_exempt
def snippet_detail(request, pk):
    # 获取、更新或者删除一个代码 snippet

    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)
    
    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JsonResponse(serializer.data)
    
    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return JsonResponse(serializer.errors, status=400)
    
    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

最后,我们需要把这些驶入链接起来,创建quickstart/urls.py 文件


from django.conf.urls import url
from .views import snippet_list, snippet_detail

urlpatterns = [
    url('^quickstart/$', snippet_list),
    url('^quickstart/(?P<pk>[0-9]+)/$', snippet_detail),
]

另外需要在tutorial/urls.py中吧root urlconf来包含我们的quickstart应用的urls。修改为下面内容:

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

urlpatterns = [
    path('admin/', admin.site.urls),
    url('^', include('quickstart.urls')),
]

3. 测试我们在Web API上的第一次访问

启动quickstart服务,我们先退出shell

quit()

启动Django开发服务

AdministratordeiMac:tutorial administrator$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
May 27, 2019 - 07:13:55
Django version 2.2.1, using settings 'tutorial.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

在浏览器中输入http://127.0.0.1:8000/quickstart/

quickjson.png

或者安装httpie

pip install httpie

然后在终端输入:http http://127.0.0.1:8000/quickstart/,如下

httpiejson.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,814评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,124评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,814评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,924评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,815评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,562评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,944评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,582评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,859评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,881评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,700评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,493评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,943评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,115评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,413评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,978评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,182评论 2 339

推荐阅读更多精彩内容