🔐

DjangoでSNS(1/2):ユーザー認証編

2024/01/21に公開

後編はこちら

長いので前後半に分割した。

  • 前編:ユーザー認証
  • 後編:SNS本体(CRUD操作)

後編はこちら。
https://zenn.dev/pb/articles/5fb3717da42726

はじめに

最近Djangoの勉強に取り組んでいる。CRUD操作を学ぶためにTodoリストをつくったり、ユーザー認証を試すためにログインとログアウトくらいしかできないアプリをつくったりした(まだスクラップだが...)。

https://zenn.dev/pb/scraps/35486afd55ab23
https://zenn.dev/pb/scraps/9a9be748411387

今度はCRUD操作とユーザー認証を組み合わせて何かつくろうと考え、SNSに挑戦することにした。

つくるもの

  • 関数ベースビューでつくる。クラスベースビューの方がシンプルに書けるが、処理を追いづらいというか...
  • CSSとかBootStrapは使わない。デザインを捨てて、機能の実装に焦点を当てる。

トップ画面

  • 最初の画面。
  • ユーザー登録画面とログイン画面に進める。

トップ画面

ユーザー登録画面

  • ユーザー名、メールアドレス、パスワード、パスワード(確認用)を入力して登録する。
  • 登録に成功するとトップ画面に遷移する。

ユーザー登録画面

ログイン画面

  • ログインする。
  • ログインに成功するとタイムラインに遷移する。

ログイン画面

タイムライン

  • 全てのユーザーの投稿が更新日時順に表示される。
  • 表示される情報は更新日時とユーザー名、本文。
  • 投稿の本文をクリックすると、その投稿の詳細画面に遷移する。
  • 投稿のユーザー名をクリックすると、そのユーザーだけの投稿一覧画面に遷移する。

タイムライン

ユーザーごとの投稿一覧画面

  • タイムラインで選択したユーザーの投稿だけの一覧を表示する。

ユーザーごとの投稿一覧画面

投稿詳細画面

  • 詳細情報として作成日時、更新日時、ユーザー名、本文が表示される(詳細といってもタイムラインに表示している情報とほとんど同じだが...)。
  • 自分で作成した投稿に限り、更新ボタンと削除ボタンが表示される。

投稿詳細画面(自分の投稿)

投稿作成画面

  • 投稿を新規作成する。

投稿作成画面

投稿更新画面

  • 投稿を更新する。

投稿更新画面

投稿削除画面

  • 投稿を削除してよいか確認する。

投稿削除画面

ログアウト画面

  • ログアウトしたことを示す。
  • トップ画面へのリンクを表示する。

ログアウト画面

初期設定

Django用のディレクトリ作成

  • エクスプローラーで任意の場所にDjango用のディレクトリをつくる。ここではCドライブ直下にdjangoというディレクトリをつくった。
  • VSCodeを開く>FileOpen Folderdjangoを選択してディレクトリを開く。

仮想環境作成(任意)

仮想環境をつくる。

command-line
C:\django> python -m venv venv

仮想環境を有効化する。有効化されるとコマンドラインの先頭に環境名が表示される。

command-line
C:\django> venv\Scripts\activate
(venv) C:\django>

Djangoインストール

command-line
(venv) C:\django> pip install django

プロジェクト作成

configプロジェクトを作成する。

command-line
(venv) C:\django> django-admin startproject config .

アプリケーション作成

ユーザー認証を行うaccountsアプリケーション、SNS本体となるsnsアプリケーションを作成する。

command-line
(venv) PS C:\django> python manage.py startapp accounts
(venv) PS C:\django> python manage.py startapp sns

設定ファイル編集

config\settings.pyを編集する。

先ほど作成したアプリケーションを登録する。

django\config\settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
+     "accounts",
+     "sns",
]

言語を変更する。

django\config\settings.py
- LANGUAGE_CODE = "en-us"
+ LANGUAGE_CODE = "ja"

タイムゾーンを変更する。

django\config\settings.py
- TIME_ZONE = "UTC"
+ TIME_ZONE = "Asia/Tokyo"

動作確認

command-line
(venv) PS C:\django> python manage.py runserver

http://127.0.0.1:8000/にアクセスすると、いつものロケットが表示される。

ロケット打ち上げ画面

トップ画面

ユーザー認証の前にとりあえずトップ画面を作る。

View

sns\views.pyファイルに以下を入力する。

django\sns\views.py
from django.shortcuts import render


def top_view(request):
    return render(request, "sns/top.html")
  • render()でテンプレートを表示するだけである。

URL

config\urls.pyファイルを以下のように編集する。

django\config\urls.py
from django.contrib import admin
- from django.urls import path
+ from django.urls import include, path

urlpatterns = [
    path("admin/", admin.site.urls),
+     path("", include("sns.urls")),
]

snsディレクトリにurls.pyファイルをつくり、以下を入力する。

django\sns\urls.py
from django.urls import path

from . import views

app_name = "sns"

urlpatterns = [
    path("top/", views.top_view, name="top"),
]

Template

snsディレクトリに以下のようにディレクトリとファイルをつくる。

  django
  ├── configとか
  └── sns
+     └── templates
+         └── sns
+             └── top.html

top.htmlファイルに以下を入力する。

django\templates\top.html
<!DOCTYPE html>
<html lang='ja'>
  <head>
    <meta charset='UTF-8' />
    <title>俺のSNS</title>
  </head>
  <body>
      <p>俺のSNS</p>
      <p>日常で思ったことなどをつぶやくSNSです</p>
      <ul>
        <li><a href="#undefined">ユーザー登録</a></li>
        <li><a href="#undefined">ログイン</a></li>
      </ul>
  </body>
</html>
  • ユーザー登録、ログインへのリンクはまだつくっていないので、仮で#undefinedを指定した。以前の私は#だけだったが、仮であることをより明確にするために#undefinedにした(参考)。

動作確認

http://127.0.0.1:8000/top/にアクセスすると、以下のようにトップ画面が表示される。

トップ画面

ユーザー登録画面

Model(カスタムユーザーの定義)

DjangoにはデフォルトのUserモデルが存在するが、一度マイグレーションするとfieldを追加できないらしい(参考:【Django】カスタムユーザーの作成)。そこでカスタムユーザーと呼ばれるUserモデルを独自に定義する。

accounts\models.pyに以下を入力する。

django\accounts\models.py
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass
  • Userモデルと同じfieldを用いるなら、AbstractUserクラスを継承するだけである。

認証に使用するモデルをデフォルトのユーザーからカスタムユーザーに変更する。

settings.pyに以下を追加する("アプリ名.モデル名")。

django\config\settings.py
+ AUTH_USER_MODEL = "accounts.User"

↑あれを追加しないと、エラーが出る。

ERRORS:
accounts.User.groups: (fields.E304) Reverse accessor 'Group.user_set' for 'accounts.User.groups' clashes with reverse accessor for 'auth.User.groups'.
        HINT: Add or change a related_name argument to the definition for 'accounts.User.groups' or 'auth.User.groups'.
accounts.User.user_permissions: (fields.E304) Reverse accessor 'Permission.user_set' for 'accounts.User.user_permissions' clashes with reverse accessor for 'auth.User.user_permissions'.
        HINT: Add or change a related_name argument to the definition for 'accounts.User.user_permissions' or 'auth.User.user_permissions'.
auth.User.groups: (fields.E304) Reverse accessor 'Group.user_set' for 'auth.User.groups' clashes with reverse accessor for 'accounts.User.groups'.
        HINT: Add or change a related_name argument to the definition for 'auth.User.groups' or 'accounts.User.groups'.
auth.User.user_permissions: (fields.E304) Reverse accessor 'Permission.user_set' for 'auth.User.user_permissions' clashes with reverse accessor for 'accounts.User.user_permissions'.
        HINT: Add or change a related_name argument to the definition for 'auth.User.user_permissions' or 'accounts.User.user_permissions'.

モデルを変更したのでマイグレーションを行う。

command-line
(venv) C:\django> python manage.py makemigrations
(venv) C:\django> python manage.py migrate

Form

accountsディレクトリにforms.pyファイルを作成し、以下を入力する。

django\accounts\forms.py
from django.contrib.auth.forms import UserCreationForm

from .models import User


class SignUpForm(UserCreationForm):
    class Meta:
        model = User
        fields = ("username", "email", "password1", "password2")
  • fieldsで指定した項目がユーザー登録画面に入力項目として表示される。

View

accounts\views.pyに以下を入力する。

django\accounts\views.py
from django.shortcuts import render, redirect

from .forms import SignUpForm


def signup_view(request):
    if request.method == "POST":
        form = SignUpForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect("sns:top")
    else:
        form = SignUpForm()
    param = {"form": form}
    return render(request, "accounts/signup.html", param)

URL

config\urls.pyファイルを以下のように編集する。

django\config\urls.py
urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("sns.urls")),
+     path("accounts/", include("accounts.urls")),
]

accountsディレクトリにurls.pyファイルをつくり、以下を入力する。

django\accounts\urls.py
from django.urls import path

from . import views

app_name = "accounts"

urlpatterns = [
    path("signup/", views.signup_view, name="signup"),
]

Template

accountsディレクトリに以下のようにディレクトリとファイルをつくる。

  django
  ├── configとか
  └── accounts
+     └── templates
+         └── accounts
+             └── signup.html

signup.htmlファイルに以下を入力する。

django\accounts\templates\accounts\signup.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>ユーザー登録</title>
  </head>
  <body>
    <h1>ユーザー登録</h1>
    <form action="{% url 'accounts:signup' %}" method="post">
      {% csrf_token %} {{ form.as_p }}
      <button type="button" onclick="location.href=`{% url 'sns:top' %}`">
        戻る
      </button>
      <input type="submit" value="登録" />
    </form>
  </body>
</html>
  • <form>action属性はフォームデータの送信先である。ここに{% url 'accounts:signup' %}を指定することで、Viewに入力値を送信できる。
  • forms.pyで指定したfieldsの入力フォームが{{ form }}に表示される。.as_pを付けることで各入力項目が<p>タグで囲まれる(無くてもいいが、見やすくなる)。
  • 戻るボタンにはtype="button"を指定した。デフォルトではtype="submit"なので、省略するとフォームを送信してしまう(参考)。
  • ボタン要素にDjangoのURLを指定するには、「onclick="location.href=`{% url 'app_name:hoge' %}`"」と長々しく記述しなければならない。

sns\templates\sns\top.htmlファイルを以下のように変更する。

django\sns\templates\sns\top.html
- <li><a href="#undefined">ユーザー登録</a></li>
+ <li><a href="{% url 'accounts:signup' %}">ユーザー登録</a></li>
  • トップ画面にあるユーザー登録画面へのリンクを有効にした。

動作確認

http://127.0.0.1:8000/accounts/signup/にアクセスすると、以下のようにユーザー登録画面が表示される。

ユーザー登録画面

後でSNSの動作確認に使うので、ユーザーを2つ登録する。パスワードは忘れないように。

項目
ユーザー名 tanaka
メールアドレス tanaka@example.com
パスワード **********(お好みで)
項目
ユーザー名 yamada
メールアドレス yamada@example.com
パスワード **********(お好みで)

登録ボタンを押してエラーがなければトップ画面に遷移する。

ログイン画面

Form

accounts\forms.pyファイルを以下のように編集する(は省略を示す)。

django\accounts\forms.py
- from django.contrib.auth.forms import UserCreationForm
+ from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
+ class LoginForm(AuthenticationForm):
+     pass
  • AuthenticationFormクラスを継承するだけ。

View

accounts\views.pyを以下のように編集する。

django\accounts\views.py
from django.contrib.auth import login
from django.shortcuts import redirect, render

from .forms import LoginForm, SignUpForm

⋮

def login_view(request):
    if request.method == "POST":
        form = LoginForm(request, data=request.POST)
        if form.is_valid():
            user = form.get_user()
            if user:
                login(request, user)
                return redirect(to="/accounts/login/#undefined/")
    else:
        form = LoginForm()
    param = {"form": form}
    return render(request, "accounts/login.html", param)
  • ログイン成功後のリダイレクト先は仮である。

URL

urls.pyファイルを以下のように編集する。

django\accounts\urls.py
urlpatterns = [
    path("signup/", views.signup_view, name="signup"),
+     path("login/", views.login_view, name="login"),
]

Template

accounts\templates\accountsディレクトリにlogin.htmlファイルをつくり、以下を入力する。

django\accounts\templates\accounts\login.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>ログイン</title>
  </head>
  <body>
    <h1>ログイン</h1>
    <form action="{% url 'accounts:login' %}" method="post">
      {% csrf_token %} {{ form.as_p }}
      <button type="button" onclick="location.href=`{% url 'sns:top' %}`">
        戻る
      </button>
      <input type="submit" value="ログイン" />
    </form>
  </body>
</html>

sns\templates\sns\top.htmlファイルを以下のように変更する。

django\sns\templates\sns\top.html
- <li><a href="#undefined">ログイン</a></li>
+ <li><a href="{% url 'accounts:login' %}">ログイン</a></li>

トップ画面にあるログイン画面へのリンクを有効にした。

動作確認

http://127.0.0.1:8000/accounts/login/にアクセスすると、以下のようにログイン画面が表示される。

ログイン画面

先ほど登録したユーザー名とパスワードを入力してログインボタンを押す。ログイン先の画面をまだつくっていないのでボタンを押しても変化はないが、URLがhttp://127.0.0.1:8000/accounts/login/#undefined/になっていれば成功。

ログアウト画面

View

views.pyファイルを以下のように編集する。

django\accounts\views.py
from django.contrib.auth import login, logout
from django.shortcuts import redirect, render

from .forms import LoginForm, SignUpForm

⋮

def logout_view(request):
    logout(request)
    return render(request, "accounts/logout.html")
  • logout()だけでログアウトできる。

URL

urls.pyファイルを以下のように編集する。

django\accounts\urls.py
urlpatterns = [
    path("signup/", views.signup_view, name="signup"),
    path("login/", views.login_view, name="login"),
+     path("logout/", views.logout_view, name="logout"),
]

Template

accounts\templates\accountsディレクトリにlogout.htmlファイルをつくり、以下を入力する。

django\accounts\templates\accounts\logout.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>ログアウト</title>
  </head>
  <body>
    <p>ログアウトしました</p>
    <a href="{% url 'sns:top' %}">トップへ</a>
  </body>
</html>
  • ログアウトしたことを示すとともにトップ画面へのリンクを表示するだけ。

動作確認

http://127.0.0.1:8000/accounts/logout/にアクセスすると、以下のようにログアウト画面が表示される。

ログアウト画面

認証の作成は一旦完了である。次の記事でSNS機能を完成させた後、SNSに認証を紐づける。

https://zenn.dev/pb/articles/5fb3717da42726

Github

TODO:コードアップロードする

参考

Discussion