DjangoのPermissionモデルを利用してViewを制限する


Djangoには`Permission`というモデルがあり、Djangoのmodelを作成すると自動で生成されます。それによって管理画面では対応する権限を持つユーザーのみがテーブルデータを作成・更新・削除できるといった制限が可能です。今回はこの`Permission`を使ってViewの制限をしたいと思います。

## 独自の`Permission`を作る

既存の`Permission`データを使わずに新しく作ってみます。

### `ContentType`の指定

`Permission`モデルは外部キーとして`ContentType`を指定する必要があるので、対応する`ContentType`が無くてはなりません。制限しようとしているViewのあるアプリケーションの`ContentType`を指定すればいいと思いますが、独自のものを作ってもいいかもしれません。

```python
from django.contrib.contenttypes.models import ContentType


ContentType.objects.create(
    app_label='app_label', name='name', model='model')

```

`app_label`として指定しているのはアプリケーション名。つまりsettingsの`INSTALLED_APPS`などに指定するモジュール名の末尾です。

### `Permission`データの作成

```python
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType


content_type = ContentType.objects.get(
    app_label='app_label', name='name', model='model')

Permission.objects.create(
    content_type_id=content_type.id, name='name', codename='codename')

```

`name`は`'Can add permission'`などパーミッションの表示名で、`codename`は`'add_permission'`など、識別名です。

## ユーザーに`Permission`を付与

Djangoの管理画面のユーザー編集画面から権限の付与ができます。

![perm.png](https://qiita-image-store.s3.amazonaws.com/0/14918/77ada5a2-2574-8739-9071-21ec935593bd.png "perm.png")

## ユーザーが特定の`Permission`を持っているか調べる

`User`インスタンスの`has_perm`を呼べば特定の`Permission`を持っているか確認できます。引数に渡すのは`'app_label.codename'`という形になります。たとえばユーザー追加のパーミッションは`'auth.add_user'`です。

```python
from django.contrib.auth import get_user_model


User = get_user_model()
user = User.objects.get(pk=1)

user.has_perm('{app_label}.{codename}'.format(
    app_label='app_label', codename='codename'))

```

## デコレーターでViewを制限

（追記あり）

参考として、Djangoではスタッフユーザーだけが見る事ができるViewを作る場合、デコレータを利用する方法があります。

```python
from django.contrib.admin.views.decorators import staff_member_required
from django.utils.decorators import method_decorator
from django.views.generic.base import View


class SpamView(View):
    @method_decorator(staff_member_required)
    def dispatch(self, *args, **kwargs):
        return super(SpamView, self).dispatch(*args, **kwargs)

```

これをまねて特定のパーミッションを持つユーザーだけが見る事ができるViewを作ります。

```decorators.py
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import user_passes_test


def get_permission_deco(permission_codename,
                        redirect_field_name=REDIRECT_FIELD_NAME,
                        login_url='admin:login'):
    def deco(view_func):
        return user_passes_test(
            lambda u: u.has_perm(permission_codename),
            login_url=login_url,
            redirect_field_name=redirect_field_name
        )(view_func)
    return deco

```

```view.py
from django.utils.decorators import method_decorator
from django.views.generic.base import View

from .decorators import get_permission_deco


class EggView(View):
    @method_decorator(get_permission_deco('app_label.codename'))
    def dispatch(self, *args, **kwargs):
        return super(EggView, self).dispatch(*args, **kwargs)

```

### （追記）

小細工しなくてもDjangoには`permission_required`というのがありました。
https://docs.djangoproject.com/ja/1.10/topics/auth/default/#the-permission-required-decorator

## カスタムタグでテンプレートを制限

カスタムタグの作り方の詳細はこの記事の範囲を超えますので省略しますが、カスタムタグを使ってテンプレートを制限する事もあるかと思うので一例を載せておきます。

```perm_extra.py
from django import template


register = template.Library()

@register.filter(name='has_perm')
def has_perm(user, permission_name):
    return user.has_perm(permission_name)

```

```html
{% load perm_extra %}

{% if user|has_perm:'app_label.codename' %}
  <a href="/path">編集ページへ</a>
{% else %}
  <del>編集ページへ</del>（権限がありません）
{% endif %}
```