初心者開発者が4DaysでDjangoアプリを作成した話【Day2】

## はじめに
この記事は続編です。
Day1を見てない方はこちらから読んでいただけるとわかりやすいと思います。

https://qiita.com/Jinn_0220/items/21667581457e0380bc7a

## Day2でやったこと

- アカウント周り
    - 管理アカウントが組織内のユーザーを作成できるようにする
    - ログインするとアカウントが持つ権限によって飛ぶページを変える

## 開発
### ここまで実装したものと、Day２の実装
ここまでは、簡単にログイン機能を実装しました。
今回のmodelで特徴的なのは、組織の管理者と一般ユーザーで権限を分けることです。
それを、<b>is_admin</b>でフラグ立てています。
Day2での開発では、<b>is_admin</b>がTrueのアカウントが、is_adminがFlaseの一般ユーザーを作成できるようにします。
create_superuserをすでにmodelで作っているので、それを利用します。

```attendance_app/models.py
from django.db import models
from django.contrib.auth.models import (BaseUserManager,
                                        AbstractBaseUser,
                                        PermissionsMixin)
from django.utils.translation import gettext_lazy as _

class UserManager(BaseUserManager):
    def create_user(self, username, email, password=None, is_admin=False, date_joined=None):
        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(
            email=self.normalize_email(email),
            username=username,
            is_admin=is_admin,
            date_joined=date_joined
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, username, email, password=None):
        user = self.create_user(
            email=email,
            username=username,
            password=password,
            date_joined=None  # date_joined は必要なので None を渡す
        )
        user.is_admin = True
        user.is_staff = True  # スーパーユーザーはスタッフ権限を持つ必要がある
        user.is_superuser = True
        user.save(using=self._db)
        return user

class User(AbstractBaseUser, PermissionsMixin):
    user_id = models.AutoField(primary_key=True)
    username = models.CharField(max_length=50, unique=True)
    email = models.CharField(max_length=50, unique=True)
    is_admin = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False) 
    date_joined = models.DateField(auto_now_add=True)

    objects = UserManager()

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    def __str__(self):
        return self.username
```

### 一般ユーザー作成機能
以下を追加して実装します。

- 一般ユーザーを作成するページを作成
- 管理アカウントの画面に、ユーザー作成ページに飛ぶボタン設置


```attendance_app/templates/attendance_app/create_user.html
{% extends 'attendance_app/base.html' %}

{% block title %}
<div class="h1">Create New User</div>
{% endblock %}

{% block content %}
<form method="post">
    {% csrf_token %}
    {{ form.non_field_errors }}
    {% for field in form %}
    {{ field.label }}
    {{ field }}
    {{ field.errors }}
    <br>
    {% endfor %}
    <div class="mt-3">
        <button type="submit" class="btn btn-primary">Create User</button>
secondary">Cancel</a>
    </div>
</form>
{% endblock %}
```

```attendance_app/forms.py
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from .models import User
from django import forms

class CreateUserForm(forms.ModelForm):
    password = forms.CharField(widget=forms.PasswordInput)

    class Meta:
        model = User
        fields = ('username', 'password','email')
```
まず新しくテンプレートcreate_user.htmlを作成する
ここで、新しいユーザーを登録する画面を表示する
一般ユーザーの作成に必要な変更をform.pyに加える
一般ユーザーはusername、password,emailの情報で作成できるようにCreateUserFormを作成した
上記の変更を動かすために、viewsとurlsに変更を加える

```attendance_app/views.py
class CreateUserView(FormView):
    template_name = 'attendance_app/create_user.html'
    form_class = CreateUserForm

    def form_valid(self, form):
        username = form.cleaned_data['username']
        password = form.cleaned_data['password']
        email = form.cleaned_data['email']
        organization_name = self.request.user.organization_name
        # create_staffメソッドを使用して新しいユーザーを作成
        User.objects.create_staff(username=username, email=email, password=password, organization_name=organization_name)
        return redirect('attendance_app:admin_dashboard')
```

```attendance_app/views.py
path("create/user/", views.CreateUserView.as_view(), name="create_user"), 
```

### 権限ごとにアクセスできるページを変える
以下のようにして、実装する

- Loginしたときにis_adminの値によってリダイレクト先を設定
- urls.pyもviews.pyに合わせて設定する

```attendance_app/views.py
class LoginView(BaseLoginView):
    form_class = LoginFrom
    template_name = "attendance_app/login.html"

    def form_valid(self, form):
        # 親クラスのform_validを呼び出してログインを処理
        response = super().form_valid(form)
        # ログインしたユーザーを取得
        user = form.get_user()
        # is_adminの値によってリダイレクト先を設定
        if user.is_authenticated and user.is_admin:
            return HttpResponseRedirect(reverse('attendance_app:admin_dashboard'))
        else:
            return HttpResponseRedirect(reverse('attendance_app:user_dashboard'))
```
上記のようにviews.pyでuser.is_authenticated and user.is_adminで条件分岐させてリダイレクト先を変えている。

```attendance_app/urls.py
path("ad/dashboard/", views.AdminDashboardView.as_view(), name="admin_dashboard"), 
path("user/dashboard/", views.UserDashboardView.as_view(), name="user_dashboard"),
```
適当にテンプレートを作成して、権限ごとにリダイレクト先を変更する。
テンプレートとurls.pyに合わせて、views.pyも修正していく。

```attendance_app/views.py
class AdminDashboardView(LoginRequiredMixin, TemplateView):
    template_name = 'attendance_app/admindash.html'

class UserDashboardView(LoginRequiredMixin, TemplateView):
    template_name = 'attendance_app/userdash.html'
```

## Day2の振り返り
Day2では、アカウント周りとそれに付随するリダイレクトとを調整しました。
この日は開発に当てられたリソースが少なく、微妙な進捗になりましたが、確実に前進しました。
本来はアカウントに権限を分け与え適切にリダイレクトするRBACを完璧に実装したかったのですが、今回は時間的に実装が難しいと考えて、簡易的な実装としました。
RBACの実装も今後完璧にしていきたいので、勉強して別途記事にしてみたいと思います。
Day1と同様コードを端折ってる部分があるので、後々足していきます。
今しばらくお待ちくださいmm