Django REST framework 学习纪要 Tutorial 4 Authentication & Permissions

目前为止,我们的代码没有限制谁可以编辑和删除代码片段,此节我们需要实现以下功能

  • 代码片段需要与创建者关联
  • 只有通过验证的用户才能创建代码片段
  • 只有创建者才能修改或删除代码片段
  • 没有通过验证的用户拥有只读权限

给model添加字段

我们需要添加两个字段,一个用于存储代码片段的创建者信息,一个用于存储代码的高亮信息

    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
    owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
    highlighted = models.TextField()

同时,我们需要在该模型类执行保存操作时,自动填充highlighted字段,使用pygments库。
首先,导入一些包

from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight

然后为Snippet重写父类的save方法

    def save(self, *args, **kwargs):
        """
        use the 'pygments' library to create a highlighted HTML
        representation of code snippet
        """
        lexer = get_lexer_by_name(self.language)
        linenos = self.linenos and 'table' or False
        options = self.title and {'title': self.title} or {}
        formatter = HtmlFormatter(style=self.style, linenos=linenos,
                                  full=True, **options)
        self.highlighted = highlight(self.code, lexer, formatter)
        super(Snippet, self).save(*args, **kwargs)

接下来需要迁移数据库,方便起见,删库,然后重新迁移

(django_rest_framework) [root@localhost tutorial]# rm -f tmp.db db.sqlite3 && \
> rm -rf snippets/migrations/ && \
> python manage.py makemigrations snippets && \
> python manage.py migrate
Migrations for 'snippets':
  snippets/migrations/0001_initial.py
    - Create model Snippet
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, snippets
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK
  Applying snippets.0001_initial... OK

为了测试API,我们需要创建一些用户,最快的方式就是通过createsuperuser命令

(django_rest_framework) [root@localhost tutorial]# python manage.py createsuperuser
Username (leave blank to use 'root'): song
Email address: shelmingsong@gmail.com
Password: 
Password (again): 
Superuser created successfully.
(django_rest_framework) [root@localhost tutorial]# python manage.py createsuperuser
Username (leave blank to use 'root'): user_1
Email address: user_1@gmail.com
Password: 
Password (again): 
Superuser created successfully.
(django_rest_framework) [root@localhost tutorial]# python manage.py createsuperuser
Username (leave blank to use 'root'): user_2
Email address: user_2@gmail.com
Password: 
Password (again): 
Superuser created successfully.

为用户模型添加接口

我们已经创建了三个用户,现在我们需要添加用户相关的接口,修改serializers.py

from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

    class Meta:
        model = User
        fields = ('id', 'username', 'snippets')

因为snippetsuser是一种反向的关联,默认不会包含入ModelSerializer类中,所以需要我们手动添加

我们也需要对views.py进行修改,由于用户页面为只读,所以继承于ListAPIViewRetrieveAPIView

from django.contrib.auth.models import User
from snippets.serializers import UserSerializer


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

配置url.py

    url(r'^users/$', views.UserList.as_view()),
    url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),

关联User和Snippet

此时我们创建一个代码片段,是无法与用户关联的,因为用户信息是通过request获取的。
因此我们需要重写snippet的view中perform_create()方法,这个方法允许我们在对象保存前进行相关操作,处理任何有requestrequested URL传递进来的数据

修改views.py中的SnippetList类,添加perform_create()方法

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

如此,新建代码片段时,会添加owner字段,该字段存储了request中的用户信息

更新serializer

之前我们在views中的SnippetList类中添加了perform_create方法,保存了owner信息,因而也需要在serializer中的SnippetSerializer类中添加owner信息,同时将owner添加进Meta子类的fields字段中

class SnippetSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style', 'owner')

这里我们使用了ReadOnlyField类型,这个类型是只读的,不能被更新,和Charfield(read_only=True)是一样的效果

添加权限认证

我们希望只有登录的用户能够去增加代码片段,未登录则只有查看的权限,此时我们需要用到IsAuthenticatedOrReadOnly

修改views.py,为snippet的两个类views添加permission_classes字段

from rest_framework import permissions


class SnippetList(generics.ListCreateAPIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, )


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, )

添加登陆接口

修改项目的urls.py

urlpatterns = [
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

r'^api-auth/'可以自定,namespace在Django 1.9 + 的版本中可以省略

运行Django服务器,访问your.domain/snippets/,点击右上角的登陆按钮,登陆我们之前创建的用户后,就可以创建代码片段了

创建完几个代码片段后,再访问your.domain/users/时,就可以看到每个用户创建了哪几个代码片段了

对象级别的权限

现在用户都可以对所有的snippets进行增删改查,我们要确保只有创建者可以对snippets进行改动或删除。

snippetsapp中,创建permissions.py

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    custom permission to only allow owners of an object to edit it
    """
    def has_object_permission(self, request, view, obj):
        # allow all user to read
        if request.method in permissions.SAFE_METHODS:
            return True

        # only allow owner to edit
        return obj.owner == request.user

views.py中添加权限

from snippets.permissions import IsOwnerOrReadOnly

class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly)

此时我们访问your.domain/snippets/1/,若用户未登录或登录用户不是该snippets的创建者,则只有读的权限,页面上表现为没有DELETE(上方中间)和PUT(右下角)按钮

通过接口进行权限认证

之前我们是通过浏览器页面进行登录的,而当我们直接使用接口去请求时,如果没有进行登录,而对某个snippet进行修改或是创建一个新的snippet,则会报错

(django_rest_framework) [root@localhost django_rest_framework]# http POST http://127.0.0.1:80/snippets/ code="hahah"
HTTP/1.0 403 Forbidden
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Tue, 28 Nov 2017 14:56:18 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "detail": "Authentication credentials were not provided."
}

(django_rest_framework) [root@localhost django_rest_framework]# http POST http://127.0.0.1:80/snippets/1/ code="hahah"
HTTP/1.0 403 Forbidden
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Tue, 28 Nov 2017 14:56:26 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "detail": "Authentication credentials were not provided."
}

我们在发送请求时,提供用户名和密码,就可以进行操作了

(django_rest_framework) [root@localhost django_rest_framework]# http -a your_username:your_password POST http://127.0.0.1:80/snippets/ code="hahah"
HTTP/1.0 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 104
Content-Type: application/json
Date: Tue, 28 Nov 2017 14:58:10 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "code": "hahah",
    "id": 4,
    "language": "python",
    "linenos": false,
    "owner": "song",
    "style": "friendly",
    "title": ""
}

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 4: Authentication & Permissions

博客更新地址

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

推荐阅读更多精彩内容