19.Djangoでログインユーザーを参照しよう

Djangoアプリで投稿作成時にログインユーザー名を自動的に投稿データに格納し、ログイン中のユーザーがその投稿を作成したユーザーと一致する場合のみ投稿を編集できるようにしてみましょう。これもDjangoの機能を使って簡単に実装できます。

目次

モデルの変更とデータベースへの反映

現在作成しているbbsアプリケーションには任意の文字列を入力するためのcontentフィールドとuser_nameフィールドしか存在しません。

投稿者名として、Djangoの管理サイトで利用しているAuthorモデルのフィールドをそのままbbsアプリケーションのArticleモデルに関連付けて使用するよう、モデルを変更し、データベースに反映しましょう。

まずはモデルの変更です。models.py を次のように修正します。

from django.db import models
from django.urls import reverse # reverse関数をインポート

class Article(models.Model):
    content = models.CharField(max_length=140)
    author = models.ForeignKey(
        'auth.User',
        on_delete=models.CASCADE,
    )

    def __str__(self):
        return self.content

    # その投稿の詳細へのリンク
    def get_absolute_url(self):
        return reverse('bbs:detail', kwargs={'pk': self.pk})

authorカラムを定義し、その内容として外部キーで’auth.User’を関連付けています。

この変更をデータベースに反映しましょう。

まずはマイグレーションファイルを作成します。

Windows PowerShell で django-tutorial ディレクトリに入り、次のコマンドを実行します。

py manage.py makemigrations bbs

マイグレーションファイルを作成

マイグレーションファイルが作成されました。

次に、この変更差分をデータベースに反映しましょう。

py manage.py migrate

変更をデータベースに反映

これでモデルの変更とデータベースへの反映は完了です。

ビューの変更

投稿作成時にログインユーザー名を投稿に格納するようにしましょう。

views.py を次のように修正します。

from django.urls import reverse_lazy
from django.views import generic
from .models import Article
from django.contrib.auth.mixins import LoginRequiredMixin

class IndexView(generic.ListView):
    model = Article

class DetailView(generic.DetailView):
    model = Article

class CreateView(LoginRequiredMixin, generic.edit.CreateView):
    model = Article
    fields = ['content'] # 項目をcontentに変更

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super(CreateView, self).form_valid(form)

class UpdateView(LoginRequiredMixin, generic.edit.UpdateView):
    model = Article
    fields = '__all__'

class DeleteView(LoginRequiredMixin, generic.edit.DeleteView):
    model = Article
    success_url = reverse_lazy('bbs:index')

表示するフィールドを content だけにして、author(form.instance.author)に現在ログインしているユーザー情報(self.request.user)を代入します。

これで投稿作成時に、ログインユーザーの情報がその投稿の情報としてデータベースに格納されるようになりました。

投稿作成についてはこれでOKです。

テンプレートの変更

変更に合わせてテンプレートも修正しておきましょう。

article_detail_html を次のように修正します。

{% extends "./base.html" %}

{% block content %}
  <h1>{{ article.id }}の個別投稿ページ</h1>
  <p>{{ article.content }},{{ article.author }}</p>
  <div>
    <button onclick='location.href="{% url "bbs:index" %}"'>一覧</button>
    <!-- ログインユーザーのidがその投稿のものと一致する場合 -->
    {% if request.user.id == object.author_id %}
      <button onclick='location.href="{% url "bbs:update" article.pk %}"'>編集</button>
      <button onclick='location.href="{% url "bbs:delete" article.pk %}"'>削除</button>
    {% endif %}
  </div>
{% endblock %}

表示する項目名と、「編集」「削除」ボタンを表示する条件を変更しました。

編集フォームのアクセス制御

次に、ログイン中のユーザーがその投稿を作成したユーザーと一致する場合のみ投稿を編集できるように変更しましょう。

views.py を次のように修正します。

from django.urls import reverse_lazy
from django.views import generic
from .models import Article
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied # インポート

class IndexView(generic.ListView):
    model = Article

class DetailView(generic.DetailView):
    model = Article

class CreateView(LoginRequiredMixin, generic.edit.CreateView):
    model = Article
    fields = ['content']

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super(CreateView, self).form_valid(form)

class UpdateView(LoginRequiredMixin, generic.edit.UpdateView):
    model = Article
    fields = ['content'] # 項目をcontentのみに変更

    def dispatch(self, request, *args, **kwargs):
        obj = self.get_object()
        if obj.author != self.request.user:
            raise PermissionDenied('編集権限がありません。')
        return super(UpdateView, self).dispatch(request, *args, **kwargs)

class DeleteView(LoginRequiredMixin, generic.edit.DeleteView):
    model = Article
    success_url = reverse_lazy('bbs:index')

投稿編集用の UpdateView でも新規投稿フォームと同様、入力項目を content のみに変更。

また、PermissionDenied をインポートして、despatchメソッドをオーバーライド、投稿者名(author)がログインユーザー(request.user)と同じでなかったらアクセスを拒否して、「編集権限がありません」というエラーメッセージを返すようにしています。

動作確認

それでは動作確認です。

Djangoコマンドを使ってサーバーを起動し、ブラウザで確認してみましょう。

http://127.0.0.1:8000/accounts/login にアクセスしてログインしてみましょう。

ログインページ

投稿一覧ページ

投稿一覧ページが表示されました。

「新規投稿」をクリックして投稿を作成してみましょう。

新規投稿ページ

新規投稿ページが表示されました。

content だけが入力できるようになっています。

実際に投稿を作成してみましょう。

投稿完了

新しい投稿を登録しました。

ユーザー名は入力していませんが、現在ログインしているユーザー名が登録されているのがわかります。

管理サイトでも確認してみましょう。

Authorフィールドにログインユーザー名が格納されています

Authorフィールドに先ほど投稿を作成した時にログインしていたユーザー名が格納されているのがわかります。

次は編集フォームを確認してみましょう。

先ほどの個別投稿ページにアクセスし、「編集」ボタンをクリックします。

投稿編集ページ

編集ページでも入力できる項目が Content だけになっています。

では次に、別のユーザーで作成した投稿の詳細ページへアクセスしてみましょう。

別のユーザーで作成した投稿の詳細ページ

こちらは admin さんが作成した投稿なので「編集」「削除」ボタンが表示されないようになっています。

編集用のURLを直接叩いて無理矢理編集画面にアクセスしてみましょう。

http://127.0.0.1:8000/bbs/27/update のようにURLの末尾に update を追加します。

アクセス制御が実現できています

403エラー、つまり閲覧禁止を示すエラーメッセージが表示されました。

ログインしているユーザーと、投稿を作成したユーザーが一致していないのでURLを直接叩いて編集しようとしてもできないようアクセス制御が実現できています。

ログを確認

Windows PowerShell のログを確認してみると、「編集権限がありません。」と先ほど設定したエラーメッセージが返されているのがわかります。

きちんとしたアクセス制御が実装できました!

このエントリーをはてなブックマークに追加

19.Djangoでログインユーザーを参照しよう」への2件のフィードバック

  1. kikuchi

    OperationalError at /bbs/
    no such column: bbs_article.author_id

    このようなエラーが出てしまいます。コードは同じように記載しております。
    自分個人のwebサイトを作っている際にカスタムユーザーを作成して、記事とつなげるためにforeignkey を設定するとこういったエラーが出ます。author_id がないですよというエラーが出ているのですが、どのように改善したらよいのか分かりません。

    返信
    1. 管理人 投稿作成者

      正確にはbbs_articleテーブルにauthor_idというカラムがないよ、というエラーですね。
      ご自分でカスタムユーザーを作成されているとのことですので、まずは下記を確認してみてください。

      • マイグレーションが正しく適用されているか
      • bbs_article モデルに author というForeignKeyフィールドがちゃんと定義されているか
      • bbs_articleテーブルにauthor_idというカラムが間違いなく存在するか
      • settings.pyのAUTH_USER_MODELが正確に設定されているか

      カスタムユーザーまわりについては下記の記事もご参照ください。

      返信

コメントを残す

頂いたコメントは一読した後表示させて頂いております。
反映まで数日かかる場合もございますがご了承下さい。