表示するモデルのデータをユーザごとに切り替える

- ログイン機能を実装しただけではデータベースから表示するデータが全ユーザーで共有されてしまう。
- 特定のユーザーが登録した情報だけを表示する処理を実装する。

# 実装手順
- Userモデルと表示したいモデルの連携
- フォームにて、ログイン中のユーザ情報を自動保存（表示したいモデルに）
- ユーザごとに表示するデータを切り替える
- 独自制限の追加(detail, update, delete)

## Userモデルと表示したいモデルの連携
- [CustomUserモデルを作成する](https://qiita.com/kkk777/items/ae8eb88d504420e9f5cd)
- トップページに表示したいモデル(Post)と連携する(user_nameフィールドの追加)

```python: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のフィールドには、[デフォルトで空欄が許されない引数](https://djangobrothers.com/blogs/django_null_blank/)が指定されているため
- エラー対応：
    - そのままターミナル上でデフォルト値を入力する場合は「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のみを使用して作成したフォームをもとに実装していく
```python: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()をオーバーライドする

```python: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()`に定義する
- 制限をかけたいビュークラスに継承する


```python: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
```
```python:views.py
'''~省略~'''
class Detail(LoginRequiredMixin, OwnerOnly, DetailView):
    # 省略
class Update(LoginRequiredMixin, OwnerOnly, UpdateView):
    # 省略
class Delete(LoginRequiredMixin, OwnerOnly, DeleteView):
    # 省略
```
- 詳細は[Djangoでアクセス制限を行う（AccessMixinクラス）](https://qiita.com/kkk777/items/6f467a14b0f9616a0a79)