【Django】組み込みタグを使って一般ユーザーから任意のテキストや機能を隠す
今回は、Djangoで使える組み込みタグを使用して、一般ユーザーからは特定のテキストや機能を見えないようにし、権限を持つユーザー、つまりアクティブユーザーからは表示されるような処理を実装していきたいと思います。
アクティブユーザーと言っても、「スーパーユーザー」や「スタッフ」、そしてもっとも権限の低い「アクティブ」などがあります。
サイトによっても、管理者のみが運用しているパターンとアカウントや会員登録を備えているパターンの2種類があるかと思います。
管理者のみの場合は、一般ユーザーからだけ非表示にできれば良いので単純ですが、アカウントや会員が存在し、さらに「スタッフ」の権限を持っている場合は複数のパーミッション(許可)である「スーパーユーザー」「スタッフ」「アクティブ」の処理をそれぞれ実装する必要がでてくると思います。
ここでは前者のログイン可能な管理者のみに限ったスーパーユーザー主体で話を進めて行きます。
それでは始めます。
| 実行環境 | 
|---|
| Windows Subsystem for Linux Ubuntu | 
| Python 3.6.9 | 
| pip 9.0.1 | 
| 使用ライブラリ | ライセンス | 
|---|---|
| Django==3.1 | BSD | 
Djangoにはデフォルトでユーザー認証システムが備わっており、設定ファイル「settings.py」の INSTALLED_APPS内に「'django.contrib.auth'」というモジュールから認証システムに必要な機能を引き出すことができます。
# config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',  # 認証システム
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
管理者として登録したスーパーユーザーやその他の権限のあるユーザー情報は「django.contrib.auth」モジュール内にあるUserモデルに保存されます。
簡単にですが、Userオブジェクトを使用して、各属性を取得してみます。
まずはスーパーユーザーを作成します。
$ python3 manage.py createsuperuser --username=admin --email=admin@sample.com
Password:
Password (again):
このパスワードは短すぎます。最低 8 文字以上必要です。
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
ではDjangoシェルにて、Userオブジェクトを実装していきます。
$ python3 manage.py shell
先ほど「createsuperuser」コマンドを使用してユーザー登録を行いましたが、Userオブジェクトを使用してユーザーを作成することもできます。
>>>
>>> # インポート
>>> from django.contrib.auth.models import User
>>>
>>> # スーパーユーザーの作成
>>> user = User.objects.create_superuser(
...             username='django',
...             email='django@sample.com',
...             password='djangopass',
...        )
>>>
>>> user.save()
>>>
これにより「django」というユーザー名のスーパーユーザーが保存され、現在スーパーユーザーの権限を持っているのは、最初に作成した「admin」というユーザー名のスーパーユーザーと「django」のスーパーユーザーです。
各ユーザー情報を確認してみます。
>>>
>>> user = User.objects.all()
>>>
>>> # 各属性を取得
>>> for u in user:
...     print('ユーザー:'+ u.username)
...     print('メールアドレス:'+ u.email)
...     print('パスワード:'+ u.password)
...     print('スーパーユーザー:', u.is_superuser)
...     print('スタッフ:', u.is_staff)
...     print('アクティブ:', u.is_active)
...     print('-------------------------')
...
ユーザー:admin
メールアドレス:admin@sample.com
パスワード:pbkdf2_sha256$216000$8rfOKCEYCWSG$5MjY2PolN+EzCtfi1FEkzwb28buGj3cHXBfueHUKdrA=
スーパーユーザー: True
スタッフ: True
アクティブ: True
-------------------------
ユーザー:django
メールアドレス:django@sample.com
パスワード:pbkdf2_sha256$216000$KyjMLlnILsGX$kayAvK3QbIXBafLhLLe/WkyfAepi1fbIKkqa39kunVM=
スーパーユーザー: True
スタッフ: True
アクティブ: True
-------------------------
>>>
ユーザーが認証されているかどうかを確かめるには、「is_authenticated」属性を使用します。
>>>
>>> user = user.get(username='django')
>>>
>>> # 認証されているかはブール値で返す
>>> user.is_authenticated
True
>>>
>>> if user.is_authenticated:
...     print('{}さんはアクティブです'.format(user.username))
... else:
...     print('一般ユーザーさん')
...
djangoさんはアクティブです
>>>
権限を確認するための属性ではありませんが、ユーザーがログインされているかされていないかをチェックすることができます。
このような処理を、Djangoの組み込みタグを使用して一般ユーザーには任意のテキストや機能を隠すことができます。
では先に進む前に、作成した「django」ユーザーを削除しておきます。
>>> user.delete()
(1, {'auth.User': 1})
>>>
>>> User.objects.all()
<QuerySet [<User: admin>]>
>>>
>>> quit()
以下のような「非公開リスト」や「投稿」といったアクセスポイントのあるイメージを元に、一般ユーザーからの表示を隠して行きます。

先ほどインタラクティブシェルで実装した認証に関する処理を、組み込みタグを使用して認証処理を実装したいHTMLタグの上下に記述します。
イメージ画像のテンプレートファイルを「index.html」とします。
<!-- app/templates/app/index.html -->
{% extends 'app/base.html' %}
{% block content %}
    {% if request.user.is_authenticated %}
    <!-- 公開リストのリンク -->
    <div>
        <a href="{% url 'app:blog_private' %}">
            <button type="submit">非公開リスト</button>
        </a>
    </div>
    <br>
    <!-- 投稿のリンク -->
    <div>
        <a href="{% url 'app:new_blog' %}">
            <button type="submit">投稿</button>
        </a>
    </div>
    <br>
    {% endif %}
    ....
    ....
{% endblock %}
「request」オブジェクトに「user」属性を渡すと、ログインされているのか(認証)をブール値で取得することができます。
要するに一般ユーザーからは「非公開リスト」と「投稿」が見えなくなります。

ユーザーがログイン済みなのかどうかが今一度分からなくなるので、同じようなタグを使用してアクティブユーザーなのか一般ユーザーなのかを表示しておきます。
ここではベースとなるテンプレートファイルにて以下を記述します。
<!-- app/templates/app/base.html -->
<!doctype html>
<html>
    <head>
        <title>ブログ</title>
        ...
    </head>
    <body>
        ...
            <!-- ユーザーのタイプを表示 -->
            {% if request.user.is_authenticated %}
                <h1>
                    ブログ {{ user.username }}さんは
                    {% if request.user.is_superuser %}
                        スーパーユーザー
                    {% endif %}
                    です
                </h1>
            {% else %}
                <h1>ブログ 一般ユーザーさんです</h1>
            {% endif %}
            <!-- ここまで -->
            {% block content %}
            {% endblock %}
    </body>
</html>

ただし、見えなくなっただけです。
テキストは隠すことができましたが、リンクに添えられているURLパターンは常時生きているので、仮に直接URLからアクセスを試みられたら表示されてしまいます。


一般ユーザーからのアクセスを制限させるには、いくつか方法がありますが、ここでは単純な方法を利用して制限させてみます。
アクセス制限に関する機能は、ビューにおいて、関数ビュー用のデコレーターとクラスベースビュー用に使用するサブクラスが用意されています。
代表的なのは
| 関数ビュー | 
|---|
| login_required | 
| permission_required | 
| クラスベースビュー | 
|---|
| LoginRequiredMixin | 
| PermissionRequiredMixin | 
これらは設置するだけで魔法のように処理を行ってくれるので、興味のある方は実装してみてください。
ここではrequestオブジェクトからUser属性を取得して、以下の「非公開リスト」と「投稿」へのアクセスを制限するための単純な方法を実装していきます。

処理前のファイルは以下です。
アプリディレクトリ内の「urls.py」。
# app/urls.py
from django.urls import path
from . import views
app_name = 'app'
urlpatterns = [
        path('', views.index, name='index'),  # トップページ
        ...
        path('new_blog', views.new_blog, name='new_blog'),  # 投稿
        ...
        path('blog_private', views.blog_private, name='blog_private'),  # 非公開リスト
        ...
]
処理前の「views.py」です。
「非公開リスト」と「投稿」以外の関数ビューは割愛しています。
# app/views.py
from django.shortcuts import render, redirect
from .models import Blog
from .forms import BlogForm
...
def blog_private(request):
    """
   非公開リスト
    """
    blog_private = Blog.objects.filter(is_publick=False).order_by('-id')
    return render(request, 'app/blog_private.html', {'blog_private': blog_private})
def new_blog(request):
    """
    投稿
    """
    if request.method =="POST":
        form = BlogForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('app:index')
    else:
        form =BlogForm
    return render(request, 'app/new_blog.html', {'form': form})
...
上記の各関数ビューにアクセスされたら、先ほどテンプレートファイルにて組み込みタグで実装した条件分岐を同じように当てはめます。
アクセス制限をかけた処理
# app/views.py
from django.shortcuts import render, redirect
from .models import Blog
from .forms import BlogForm
from django.http import HttpResponse  # インポート
...
def blog_private(request):
    """
    非公開リスト
    """
    if request.user.is_authenticated:
        blog_private = Blog.objects.filter(is_publick=False).order_by('-id')
        return render(request, 'app/blog_private.html', {'blog_private': blog_private})
    else:
        return HttpResponse('<h1>一般の方は表示できません</h1>')
def new_blog(request):
    """
    投稿
    """
    if request.user.is_authenticated:
        if request.method =="POST":
            form = BlogForm(request.POST)
            if form.is_valid():
                form.save()
                return redirect('app:index')
        else:
            form =BlogForm
        return render(request, 'app/new_blog.html', {'form': form})
    else:
        return HttpResponse('<h1>一般ユーザーは表示できません</h1>')
...
単純な処理ですが、requestオブジェクトからUser属性を取得し、認証を行います。
一般ユーザーからURLに直接アクセスしようとすると以下のように認証されていない条件のページが返されます。

ログイン状態だとアクセスができます。

今回はアカウントや会員のいない管理者のみに絞った設定を行ってきましたが、アクセス制限をかけることに至っては、組み込みフィルタタグで表示されない分忘れがちな面があるかと思います。
大規模になればなるほど、このような処理は必須条件となるので色々な制限のやり方を試してみましょう。
処理前のファイルは以下です。
アプリディレクトリ内の「urls.py」。
# app/urls.py
from django.urls import path
from . import views
app_name = 'app'
urlpatterns = [
        path('', views.index, name='index'),  # トップページ
        ...
        path('new_blog', views.New_Blog.as_view(), name='new_blog'),  # 投稿
        ...
        path('blog_private', views.Blog_Private.as_view, name='blog_private'),  # 非公開リスト
        ...
]
処理前の「views.py」です。
「非公開リスト」と「投稿」以外のクラスベースビューは割愛しています。
# app/views.py
from django.views.generic import ListView, FormView
from .models import Blog
from .forms import BlogForm
...
class Blog_Private(ListView):
    """
    非公開リスト
    """
    queryset = Blog.objects.filter(is_publick=False).order_by('-id')
    template_name = 'app/blog_private.html'
    context_object_name = 'blog_private'
class New_Blog(FormView):
    """
    投稿
    """
    form_class = BlogForm
    template_name = 'app/new_blog.html'
    success_url = '/'
    def form_valid(self, form):
        form.save()
        return super().form_valid(form)
...
上記のクラスベースビューにアクセスされたら、先ほどテンプレートファイルにて組み込みタグで実装した条件分岐をget()関数によるオーバーライドで同じように当てはめます。
アクセス制限をかけた処理
# app/views.py
from django.views.generic import ListView, FormView
from .models import Blog
from .forms import BlogForm
from django.http import HttpResponse  # インポート
...
class Blog_Private(ListView):
    """
    非公開リスト
    """
    queryset = Blog.objects.filter(is_publick=False).order_by('-id')
    template_name = 'app/blog_private.html'
    context_object_name = 'blog_private'
    def get(self, request):
        """
        アクセスされたら認証を行う
        """
        if request.user.is_authenticated:
            return super().get(request)
        else:
            return HttpResponse('<h1>一般ユーザーでは表示されません</h1>')
class New_Blog(FormView):
    """
    投稿
    """
    form_class = BlogForm
    template_name = 'app/new_blog.html'
    success_url = '/'
    def get(self, request):
        """
        アクセスされたら認証を行う
        """
        if self.request.user.is_authenticated:
            return super().get(request)
        else:
            return HttpResponse('<h1>一般ユーザーは表示できません</h1>')
    def form_valid(self, form):
        form.save()
        return super().form_valid(form)
...
単純な処理ですが、requestオブジェクトからUser属性を取得し、認証を行います。
一般ユーザーからURLに直接アクセスしようとすると以下のように認証されていない条件のページが返されます。

ログイン状態だとアクセスができます。

今回はアカウントや会員のいない管理者のみに絞った設定を行ってきましたが、アクセス制限をかけることに至っては、組み込みフィルタタグで表示されない分忘れがちな面があるかと思います。
大規模になればなるほど、このような処理は必須条件となるので色々な制限のやり方を試してみましょう。
それでは以上です。
最後までご覧いただきありがとうございました。