- ログイン機能を実装しただけではデータベースから表示するデータが全ユーザーで共有されてしまう。
- 特定のユーザーが登録した情報だけを表示する処理を実装する。
実装手順
- Userモデルと表示したいモデルの連携
- フォームにて、ログイン中のユーザ情報を自動保存(表示したいモデルに)
- ユーザごとに表示するデータを切り替える
- 独自制限の追加(detail, update, delete)
Userモデルと表示したいモデルの連携
- CustomUserモデルを作成する
- トップページに表示したいモデル(Post)と連携する(user_nameフィールドの追加)
models.py
from django.db import models
from model_utils.models import TimeStampedModel
from accounts.models import CustomUser
class Post(TimeStampedModel):
# created = models.DateTimeField(auto_now_add=True) 新規作成
# modified = models.DateTimeField(auto_now=True) 更新
title = models.CharField(max_length=255)
body = models.TextField()
user_name = models.ForeignKey(CustomUser, on_delete=models.CASCADE) # 追加
def __str__(self):
return self.title
カラムの追加によるエラー対応
- マイグレーション後に、モデルにカラムを追加する場合エラーが発生する。
- エラーの内容:「デフォルト値なしではフィールドの登録ができない」
- Djangoのフィールドには、デフォルトで空欄が許されない引数が指定されているため
- エラー対応:
- そのままターミナル上でデフォルト値を入力する場合は「1」
- 一度ターミナルを終了しコード自体に変更を加える場合は「2」
- 1を入力した場合:追加したカラムに値を入力する
- 2を入力した場合:コードに戻り、
user_name = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True) # nullを追加
などを追記する - マイグレート
$python3 manage.py makemigrations
You are trying to add a non-nullable field 'user' to nippomodel without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
1を入力した場合
Select an option: 1
どの値を入力するかを聞かれるのでここでは、スーパーユーザのidを入力
>>> 1
$ python3 manage.py migrate
これでUserモデル(CustomUser)との連携は完了。次は、フォームを修正し、追加したuser_nameフィールドにログイン中のユーザを自動入力させたいと思う。
フォームにて、ログイン中のユーザ情報を自動保存(表示したいモデルに)
- CreateViewのform_valid()をオーバーライドする
※今回は、form_classを用いず、modelのみを使用して作成したフォームをもとに実装していく
views.py
class Create(LoginRequiredMixin, CreateView):
template_name = 'report/create.html'
model = Post # form_classは記述が多くなるのでmodelを使用
fields = ['title', 'body']
def form_valid(self, form):
'''
フォームの保存(post)時にログインユーザをモデルに保存
'''
# ユーザーを投稿者として保存できるようにする
object = form.save(commit=False) # 入力値をモデルに保存せず保留
object.user_name = self.request.user # ログインユーザ取得
object.save() # モデルに保存
return super().form_valid(form)
# idパラメータも渡す
def get_success_url(self):
return reverse('report:detail', kwargs={'pk': self.object.id})
- form_valid()は、フォームからの入力値がpostされるタイミングで実行される
-
object = form.save(commit=False)
:ユーザからの入力値をモデルに保存せずに、一旦所持する。戻り値は、モデルオブジェクト。(CustomUserオブジェクト) -
self.request.user
:ログイン中のユーザ情報を取得する。 - CustomUserモデルのuser_nameフィールドに対してログイン中のユーザ情報を格納する。
-
object.save()
:入力値とログイン中のユーザ情報を含んだデータをモデル(Post)に保存する。
ユーザごとに表示するデータを切り替える
- ListViewのget_queryset()をオーバーライドする
views.py
class Index(LoginRequiredMixin, ListView):
template_name = 'report/index.html'
def get_queryset(self):
current_user = self.request.user.username # ログイン中のユーザ名を取得(CustomUserモデルのusernameレコードの値を取得)
user_data = CustomUser.objects.get(username=current_user) # QuerySet(条件が一致するレコードを全て取得)
if user_data:
queryset = Post.objects.filter(user_name=user_data).all() # QuerySet(一致するレコード全て取得)
queryset = queryset.order_by("created")
return queryset
-
self.request.user.username
:ログイン中のユーザ名を取得 -
CustomUser.objects.get(username=current_user)
:CustomUserモデルのusernameカラムにおいて、取得したユーザ名と同じ値のレコードを1件取得する -
Post.objects.filter(user_name=user_data).all()
:Postモデルのuser_nameカラムにおいて、上記で取得した1件のレコードと一致するレコードを全て取得する -
queryset.order_by("created")
:作成日時順にレコードを並び替える
※今回は、サインアップ、ログイン・ログアウト機能を実装している前提である。そのため、サインアップし、ログインしなければアプリを利用することができない。つまり、user_data, current_userがFalseになることはない。言い換えれば、queryset = Noneの場合の定義は不要。
独自制限の追加(detail, create, update, delete)
- このままではdetail, update, delete時において、自分以外のログインユーザに内容が表示されてしまう。
- 自分しかアクセスできないように、ログイン制限以外の独自制限を作成。
- 注意点:継承する際は、一番左に持ってくること。ただし、LoginRequiredMixinより後ろ。
独自制限の作成(共有化)
- 制限を対象のビュークラスで共有するため、UserPassesTestMixinを継承したクラスを作成し、独自のアクセス制限を設定
- アクセス制限は、
test_func()
に定義する
- アクセス制限は、
- 制限をかけたいビュークラスに継承する
views.py
from django.contrib.auth.mixins import UserPassesTestMixin
from django.shortcuts import redirect
class OwnerOnly(UserPassesTestMixin):
# 参照条件
def test_func(self):
object = self.get_object()
return object.user_name == self.request.user
views.py
'''~省略~'''
class Detail(LoginRequiredMixin, OwnerOnly, DetailView):
# 省略
class Update(LoginRequiredMixin, OwnerOnly, UpdateView):
# 省略
class Delete(LoginRequiredMixin, OwnerOnly, DeleteView):
# 省略