Django 管理サイト管理者以外のユーザー認証を実装する

2020/10/21 (更新:2020/11/19)

Django

概要

Django には管理サイトの管理者ユーザー機能はありますが、管理サイトを利用しない一般ユーザーの機能はありません。
Django のユーザー機能を利用し、一般ユーザーの標準機能を実装します。
今後のシステムへの流用を考慮し、自前処理は必要最低限に抑えシンプル構成で実装します。
リポジトリです。
github.com/tassk-work/django-accounts-sample

一般ユーザーの標準機能

  • ユーザー登録
  • ログイン
  • ログアウト
  • パスワード変更

プロジェクト構成

  • configシステム設定
  • accounts一般ユーザー機能
  • sample主アプリケーション

コード

システム設定

デフォルトとの差異のみ引用します。

config/settings.py

INSTALLED_APPS = [
    ...
    'accounts',
    'sample',
]

TEMPLATES = [
    {
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        ...
    },
]

MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'

システムURL

config/urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('accounts.urls')),
    path('', include('sample.urls')),
]

一般ユーザー機能

一般ユーザー機能URL

ログインloginと、ログアウトlogoutは、Django の機能を利用します。
ユーザー登録signupと、パスワード変更password_changeは、Django の機能をカスタマイズします。

accounts/urls.py

app_name = 'accounts'
urlpatterns = [
    path('login/', auth_views.LoginView.as_view(
        success_url = '/',
        template_name = 'accounts/login.html',
    ), name='login'),
    path('logout/',auth_views.LogoutView.as_view(next_page = '/'), name='logout'),
    path('signup/', views.Signup.as_view(), name='signup'),
    path('password_change/', views.PasswordChange.as_view(), name='password_change'),
]

ユーザー登録フォーム

accounts/forms.py

{% endblock %}
class SignupForm(auth_forms.UserCreationForm):
    class Meta:
        model = auth_models.User
        fields = ('username', 'password1', 'password2')

一般ユーザー機能ビュー ユーザー登録

CreateViewを継承し実装します。
ユーザー登録後、主アプリケーションページに遷移します。

accounts/views.py Signup

class Signup(CreateView):
    form_class = forms.SignupForm
    template_name = 'accounts/signup.html'
    success_url = '/'

    def form_valid(self, form):
        user = form.save()
        login(self.request, user)
        self.object = user 
        return redirect(self.get_success_url())

一般ユーザー機能ビュー パスワード変更

PasswordChangeViewを継承し実装します。
パスワード変更後、主アプリケーションページに遷移し、メッセージを表示します。

accounts/views.py PasswordChange

class PasswordChange(auth_views.PasswordChangeView):
    template_name = 'accounts/password_change.html'
    success_url = '/'

    def form_valid(self, form):
        res = super().form_valid(form)
        messages.success(self.request, 'Password Changed.')
        return res

ログインテンプレート

templates/login.html

{% extends 'base.html' %}
{% block content %}
<div>
    <h1>Login</h1>
    <form method="post">
        {% csrf_token %}
        <table>
            {{ form }}
        </table>
        <input type="submit" value="Submit">
    </form>
    <a href="{% url 'accounts:signup' %}">Signup</a>
</div>
{% endblock %}

パスワード変更テンプレート

templates/password_change.html

{% extends 'base.html' %}
{% block content %}
<div>
    <h1>Password Change</h1>
    <p>Login {{ user.username }}</p>
    <form method="post">
        {% csrf_token %}
        <table>
            {{ form }}
        </table>
        <input type="submit" value="Submit">
    </form>
</div>
{% endblock %}

ユーザー登録テンプレート

templates/signup.html

{% extends 'base.html' %}
{% block content %}
<div>
    <h1>Signup</h1>
    <form method="post">
        {% csrf_token %}
        <table>
            {{ form }}
        </table>
        <input type="submit" value="Submit">
    </form>
</div>
{% endblock %}

主アプリケーション

sample/urls.py

app_name = 'sample'
urlpatterns = [
    path('', views.index, name='index'),
]

sample/views.py

@login_required
def index(request):
    return render(request, 'sample/index.html')

sample/index.py

{% extends 'base.html' %}
{% block content %}
<div>
    <div>
        {% if messages %}
            <ul class="messages">
                {% for message in messages %}
                    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
                {% endfor %}
            </ul>
        {% endif %}
    </div>
    <h1>Index</h1>
    <p>Login {{ user.username }}</p>
    <div>
        <a href="{% url 'accounts:password_change' %}">Password Change</a>
        <a href="{% url 'accounts:logout' %}">Logout</a>
    </div>
</div>
{% endblock %}