Django開発~ブログ構築編4~サイトマップの続きです。
Djangoで、カスタムユーザーモデルを実装してみましょう。
カスタムユーザーモデルとは
Djangoでは、既存のユーザーモデルをカスタマイズして利用した方がよいとされています。参考
ユーザーモデルはデータベースと依存関係の状態になるケースが多いので、後からユーザーモデルのプロパティを変更したいとなった場合、既存のデータと不整合を生じてしまいます。
ユーザーモデルをカスタマイズしたい場合は、AbstractUserとAbstractBaseUserを継承した新しいユーザー(カスタムユーザー)を設定します。
AbstractUserはデフォルトのユーザーフィールドを保持し、パーミッションの設定を行うことができます。AbstractBaseUserは、より詳細な設定を行うことを可能にします(その代りカスタマイズが大変になります)。
よくわかりませんよね? 実装しながら学んでいきましょう^^
accounts アプリケーション作成
accountsアプリケーションを作成し、カスタムユーザーを実装します。
1 |
make app app=accounts |
settings.pyのアップデート
accounts アプリを作成したら、settings.pyに記述しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
vi app/app/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # myapp 'blog', 'accounts', # new # sitemap 'django.contrib.sites', 'django.contrib.sitemaps', ] # new AUTH_USER_MODEL = 'accounts.CustomUser' |
カスタムユーザーモデルの作成
makeコマンドが実行後、accoutns アプリケーションフォルダーが作成されますので、models.pyにカスタムユーザーモデルを記述しましょう。
1 2 3 4 5 6 7 8 |
vi app/accounts/models.py from django.contrib.auth.models import AbstractUser from django.db import models class CustomUser(AbstractUser): pass |
マイグレーションしたいところですが、ユーザーに紐づけたデータが存在する場合は、マイグレーションできません。
ブログアプリでは、Postモデルの変更を行う必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
vi app/blog/models.py from django.db import models from django.utils import timezone # from django.contrib.auth.models import User from accounts.models import CustomUser # new from django.urls import reverse class PublishedManager(models.Manager): def get_queryset(self): return super(PublishedManager, self).get_queryset().filter(status='published') class Post(models.Model): STATUS_CHOICES = ( ('draft', 'Draft'), ('published', 'Published'), ) title = models.CharField(max_length=250) slug = models.SlugField(max_length=250, unique_for_date='publish') author = models.ForeignKey(CustomUser, # Change on_delete=models.CASCADE, related_name='blog_posts') body = models.TextField() publish = models.DateTimeField(default=timezone.now) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft') objects = models.Manager() # The default manager published = PublishedManager() # Our custom manager class Meta: ordering = ('-publish',) def __str__(self): return self.title def get_absolute_url(self): """Get absolue url with reverse method""" return reverse('blog:post_detail', args=[self.publish.year, self.publish.month, self.publish.day, self.slug]) |
念のため、コンテナを立ち上げなおしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# コンテナ停止 docker-compose down # ボリュームの一覧 docker volume ls DRIVER VOLUME NAME local django-lesson_postgres_data # データを作成していた場合は、マイグレーションに失敗するため、ボリュームを削除する docker volume rm django-lesson_postgres_data # コンテナ立ち上げ docker-compose up -d |
ここまで完了したらマイグレーションを実行します。
1 |
make migrate |
ユーザー作成・変更フォーム作成
フォームの作成を行います。Djangoの慣習では、forms.pyファイルにフォームを記述することになっているので、accountsフォルダ配下に作成しましょう。
1 |
touch app/accounts/forms.py |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
vi app/accounts/forms.py from django.contrib.auth import get_user_model from django.contrib.auth.forms import UserCreationForm, UserChangeForm class CustomUserCreationForm(UserCreationForm): """ https://docs.djangoproject.com/en/2.2/topics/auth/default/#django.contrib.auth.forms.UserCreationForm """ class Meta(UserChangeForm): model = get_user_model() fields = ('email', 'username') class CustomUserChangeForm(UserChangeForm): """ https://docs.djangoproject.com/en/2.2/topics/auth/default/#django.contrib.auth.forms.UserChangeForm """ class Meta(UserChangeForm): model = get_user_model() fields = ('email', 'username') |
admin.pyにユーザーモデルを追加
管理画面からカスタムユーザーが確認できるようにadmin.pyに設定を追加しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
vi app/accounts/admin.py from django.contrib import admin from django.contrib.auth import get_user_model from django.contrib.auth.admin import UserAdmin from .forms import CustomUserCreationForm, CustomUserChangeForm CustomUser = get_user_model() class CustomUserAdmin(UserAdmin): add_form = CustomUserChangeForm form = CustomUserChangeForm model = CustomUser list_display = ['email', 'username',] admin.site.register(CustomUser, CustomUserAdmin) |
管理ユーザーを作成して、localhost:8000/admin にアクセスしてみましょう。
1 2 |
# 管理ユーザーの作成(Emailも忘れずに設定ください) make admin |
Django認証機能
Django認証機能を使えるようにしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from django.contrib import admin from django.urls import path, include from django.contrib.sitemaps.views import sitemap from blog.sitemaps import PostSitemap sitemaps = { 'posts': PostSitemap, } urlpatterns = [ path('admin/', admin.site.urls), path('blog/', include('blog.urls', namespace='blog')), path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'), path('accounts/', include('django.contrib.auth.urls')), # new ] |
ユーザーがログイン済みか否かの判定をlist.htmlに加えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
vi app/blog/templates/blog/post/list.html {% extends "blog/base.html" %} {% block title %}My Blog{% endblock %} {% block content %} {% if user.is_authenticated %} <div class="row"> <h1 class="col-6">My Blog</h1> <a href="{% url 'logout' %}" class="btn btn-danger float-right col-2 mr-2">Log Out</a> <a href="{% url 'blog:create' %}" class="btn btn-primary float-right col-2">POST</a> </div> {% for post in posts %} <h2> <a href="{{ post.get_absolute_url }}"> {{ post.title }} </a> </h2> <p class="date"> Published {{ post.publish }} by {{ post.author }} </p> {{ post.body|truncatewords:30|linebreaks }} <div class="container"> <div class="row"> <a href="{% url 'blog:update' post.id %}" class="btn btn-success col-2 mr-2">Update</a> <a href="{% url 'blog:delete' post.id %}" class="btn btn-danger col-2">Delete</a> </div> </div> {% endfor %} {% else %} <p>You are not logged in</p> <a href="{% url 'login' %}" class="btn btn-primary">Log In</a> {% endif %} {% endblock %} |
login.htmlの作成
registrationフォルダ配下にlogin.htmlを作成する決まりになっています。
accountsフォルダ配下に作成しましょう。
1 2 |
mkdir -p app/accounts/templates/registration touch app/accounts/templates/registration/login.html |
login.htmlを記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
vi app/accounts/templates/registration/login.html {% extends 'blog/base.html' %} {% block title %}Log In{% endblock title%} {% block content %} <h2>Log In</h2> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit" class=”btn btn-primary w-25”>Log In</button> </form> {% endblock content %} |

ログイン・ログアウトリダイレクト先
settings.pyにログイン・ログアウトのリダイレクト先を記述できます。
1 2 3 |
# blogアプリケーションのurls.pyに指定したapp_nameとname LOGIN_REDIRECT_URL = 'blog:post_list' LOGOUT_REDIRECT_URL = 'blog:post_list' |
ここまで完了したら管理画面からログアウトして、「localhost:8000/blog」にアクセスして、ログインの処理が正常にできるか確かめてみましょう。
SignUpの実装
最後にSignUpの実装をして終わりにしましょう。
まず、list.htmlにSignUpのリンクをはりましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
vi app/blog/templates/blog/post/list.html {% extends "blog/base.html" %} {% block title %}My Blog{% endblock %} {% block content %} {% if user.is_authenticated %} <div class="row"> <h1 class="col-6">My Blog</h1> <a href="{% url 'logout' %}" class="btn btn-danger float-right col-2 mr-2">Log Out</a> <a href="{% url 'blog:create' %}" class="btn btn-primary float-right col-2">POST</a> </div> {% for post in posts %} <h2> <a href="{{ post.get_absolute_url }}"> {{ post.title }} </a> </h2> <p class="date"> Published {{ post.publish }} by {{ post.author }} </p> {{ post.body|truncatewords:30|linebreaks }} <div class="container"> <div class="row"> <a href="{% url 'blog:update' post.id %}" class="btn btn-success col-2 mr-2">Update</a> <a href="{% url 'blog:delete' post.id %}" class="btn btn-danger col-2">Delete</a> </div> </div> {% endfor %} {% else %} <p>You are not logged in</p> <div class="row"> <a href="{% url 'login' %}" class="btn btn-primary col-2 mr-2">Log In</a> <a href="{% url 'signup' %}" class="btn btn-success col-2">SignUp</a> <!-- new --> </div> {% endif %} {% endblock %} |
次にsignup.htmlをaccountsのtemplates/registrationフォルダ配下に作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
vi app/accounts/templates/registration/signup.html {% extends 'blog/base.html' %} {% block title %}Sign Up{% endblock title%} {% block content %} <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Sign Up</button> </form> {% endblock content %} |
accounts フォルダに urls.pyを作成します。
1 2 3 4 5 6 7 8 9 10 11 |
vi app/accounts/urls.py from django.urls import path from .views import SignUpPageView urlpatterns = [ path('signup/', SignUpPageView.as_view(), name='signup'), ] |
プロジェクトのurls.pyにもaccountsのパスを記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
vi app/app/urls.py from django.contrib import admin from django.urls import path, include from django.contrib.sitemaps.views import sitemap from blog.sitemaps import PostSitemap sitemaps = { 'posts': PostSitemap, } urlpatterns = [ path('admin/', admin.site.urls), path('blog/', include('blog.urls', namespace='blog')), path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'), path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('accounts.urls')),#new ] |
最後の、SignUpのViewを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
vi app/accounts/views.py from django.urls import reverse_lazy from django.views import generic from .forms import CustomUserCreationForm class SignUpPageView(generic.CreateView): form_class = CustomUserCreationForm success_url = reverse_lazy('login') template_name = 'registration/signup.html' |
localhost:8000/blog にアクセスして、SignUpが実装できたか確かめてみましょう。

おまけ
おまけとして、ログインしたユーザーの記事のみ表示されるようにしてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
vi app/blog/templates/blog/post/list.html {% extends "blog/base.html" %} {% block title %}My Blog{% endblock %} {% block content %} {% if user.is_authenticated %} <div class="row"> <h1 class="col-6">My Blog</h1> <a href="{% url 'logout' %}" class="btn btn-danger float-right col-2 mr-2">Log Out</a> <a href="{% url 'blog:create' %}" class="btn btn-primary float-right col-2">POST</a> </div> {% for post in posts %} <!-- 判定を追加 --> {% if user.id == post.author.id %} <h2> <a href="{{ post.get_absolute_url }}"> {{ post.title }} </a> </h2> <p class="date"> Published {{ post.publish }} by {{ post.author }} </p> {{ post.body|truncatewords:30|linebreaks }} <div class="container"> <div class="row"> <a href="{% url 'blog:update' post.id %}" class="btn btn-success col-2 mr-2">Update</a> <a href="{% url 'blog:delete' post.id %}" class="btn btn-danger col-2">Delete</a> </div> </div> {% endif %} {% endfor %} {% else %} <p>You are not logged in</p> <div class="row"> <a href="{% url 'login' %}" class="btn btn-primary col-2 mr-2">Log In</a> <a href="{% url 'signup' %}" class="btn btn-success col-2">SignUp</a> </div> {% endif %} {% endblock %} |
「{% if user.id == post.author.id %}」にて、ログインユーザーの記事のみ、表示されるように変更しました。
user は、Djangoの返却値でデフォルトで設定されているユーザー情報が格納されている変数名で、user.idとするとログインユーザーの情報が取得できます。post.author.idは、Postモデルに設定したauthorカラムからユーザーのIDを取得しています。
これらの値を突き合わせることで、ログインしたユーザーの投稿記事か否かの判定を行っています。
おわりに
よりblogに近づいてきましたね^^
Djangoの機能は色々あるので、日々新しい発見がありますね。
コメントを残す
コメントを投稿するにはログインしてください。