【Django3 入門】ログイン処理や管理画面のカスタマイズなど

Django

今回は、Viewの応用的な使い方や、ログイン処理などをみていきます。

前提

・「my_app」というアプリを作成・「settings.py」に登録
・「manage.py」と同じ階層に「templates」「static」ディレクトリを作成し、読込先のディレクトリのパスをこちらに変更

.
.
.
from pathlib import Path
import os

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates')
STATIC_DIR = os.path.join(BASE_DIR, 'static').
.
.
.
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [TEMPLATE_DIR,],
.
.
.
STATIC_URL = '/static/'
STATICFILES_DIRS = (
    STATIC_DIR,
)


・プロジェクトディレクトリの「urls.py」に以下のようにパスを登録

path('my_app/', include('my_app.urls')),

リダイレクトの実行

モデルの作成

from django.db import models

# Create your models here.
class Item(models.Model):
    name = models.CharField(max_length=50)
    price = models.IntegerField()

    class Meta:
        db_table = 'items'


「my_app」に「urls.py」を作成

from django.urls import path
from . import views

app_name = 'my_app'

urlpatterns = [
    path('item_list', views.item_list, name='item_list'),
]


「admin.py」の修正

from django.contrib import admin
from .models import Item

# Register your models here.
admin.site.register(Item)


マイグレーションの実行

python manage.py makemigrations my_app
python manage.py migrate


管理画面にアクセスするためのスーパーユーザーの作成

python manage.py createsuperuser


ユーザー名、メールアドレス、パスワードを入力してアカウントを作成

「/admin」にアクセス後、ログイン

そして、データを適当にいくつか追加します。

一覧ページ

「views.py」の修正

from django.shortcuts import render
from .models import Item

# Create your views here.

def index(request):
    return render(request, 'index.html')

def item_list(request):
    items = Item.objects.all()
    return render(request, 'store/item_list.html', context={
        'items': items
    })


「templates」ディレクトリに「store」ディレクトリと、その中に「item_list.html」を作成

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Item List</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
    <div class="container py-4">
        <table class="table">
            <tr>
                <th>名前</th>
                <th>値段</th>
            </tr>
            {% for item in items %}
                <tr>
                    <td>{{ item.name }}</td>
                    <td>{{ item.price }}円</td>
                </tr>
            {% endfor %}
        </table>
    </div>
</body>
</html>


「/my_app/item_list」にアクセスすると、以下のように表示されます。

詳細ページ

「views.py」の修正

.
.
.
def item_detail(request, id):
    item = Item.objects.filter(pk=id).first()
    return render(
        request,
        'store/item_detail.html',
        context={
            'item': item
        }
    )


パスの追加

path('item_detail/<int:id>', views.item_detail, name='item_detail'),


HTMLファイルの追加

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Item Detail</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
    <div class="container py-4">
        <h1>{{ item.name }}: {{ item.price }}円</h1>
    </div>
</body>
</html>


一覧ページにリンクを追加

.
.
.
{% for item in items %}
    <tr>
        <td>
            <a href="{% url 'my_app:item_detail' id=item.id %}">{{ item.name }}</a>
        </td>
        <td>{{ item.price }}円</td>
    </tr>
{% endfor %}
.
.
.


ブラウザで項目名をクリックすると詳細ページにうつります。

google にリダイレクト

「views.py」の修正

from django.shortcuts import redirect, render
.
.
.
def to_google(request, word):
    return redirect(f'https://www.google.com/search?q={word}')


パスの追加

path('to_google<word>', views.to_google, name='to_google'),


リンクの追加

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Item Detail</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
    <div class="container py-4">
        <h1>{{ item.name }}: {{ item.price }}円</h1>
        <p>
            <a href="{% url 'my_app:to_google' word=item.name %}">Googleで検索</a>
        </p>
    </div>
</body>
</html>

データが存在しない場合のリダイレクト

たとえば「/my_app/item_detail/100」にアクセスされ、
id=100 のデータが存在しないとき、一覧ページにリダイレクトするには以下のようにします。

.
.
.
def item_detail(request, id):
    item = Item.objects.filter(pk=id).first()
    if item is None:
        return redirect('my_app:item_list')
.
.
.

エラーハンドリングの実行

400エラーサーバーがクライアントによって送信されたリクエストを処理できなかったことを示す
403エラーユーザーにアクセス権がなく閲覧禁止となっていることを示す
404エラーURLに対応するページが存在しないことを示す
500エラーインターナルサーバーエラー。サーバ側の処理に問題があることを示す

「settings.py」の修正

DEBUG = False

ALLOWED_HOSTS = ['127.0.0.1'] #ローカルの場合

404エラー


存在しないURLにアクセスすると、Not Found が表示されます。



「views.py」の修正

.
.
.
def page_not_found(request, exception):
    return render(request, 'store/404.html', status=404)


HTMLファイルの追加

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>404 Error</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
    <div class="container py-4">
        <h1>ページが存在しません</h1>
        <a href="{% url 'my_app:item_list' %}">一覧ページへ</a>
    </div>
</body>
</html>


「urls.py」の追加

.
.
.
from my_app import views
.
.
.
handler404 = views.page_not_found


存在しないURLにアクセスすると、以下のように表示されます。



意図的に404エラーを発生させるには以下のようにします。

.
.
.
from django.http import Http404
.
.
.
def item_detail(request, id):
    if id == 0:
        raise Http404
.
.
.


「/my_app/item_detail/0」にアクセスすると、404ページに遷移します。

500エラー

まずは意図的に500エラーを起こすため、存在しないHTMLファイルを指定します。

.
.
.
def item_list(request):
    items = Item.objects.all()
    return render(request, 'store/xxx.html', context={
        'items': items
    })
.
.
.


「/my_app/item_list」にアクセスすると、以下のような表示になります。




「views.py」の修正

.
.
.
def server_error(request):
    return render(request, 'store/500.html', status=500)


HTMLファイルの追加

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>500 Error</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
    <div class="container py-4">
        <h1>システムエラーです</h1>
        <p>info@sample.comへお問い合わせください</p>
        <a href="{% url 'my_app:item_list' %}">一覧ページへ</a>
    </div>
</body>
</html>


「urls.py」の修正

.
.
.
handler500 = views.server_error


「/my_app/item_list」にアクセスすると、以下のような表示になります。

get_○○_or_404 メソッド

get_list_or_404メソッド

指定したモデルに対し filter を行い、取得できなければ「raise Http404」を出力

「views.py」の修正

.
.
.
def item_list(request):
    # items = Item.objects.all()
    items = get_list_or_404(Item, price__lt=100)
    return render(request, 'store/item_list.html', context={
        'items': items
    })
.
.
.


「/my_app/item_list」にアクセスしたときの表示

get_object_or_404メソッド

指定したモデルに対し get を行い、取得できなければ「raise Http404」を出力

「views.py」の修正

.
.
.
def item_detail(request, id):
    # if id == 0:
    #     raise Http404
    # item = Item.objects.filter(pk=id).first()
    item = get_object_or_404(Item, pk=id)
    if item is None:
        return redirect('my_app:item_list')

    return render(
        request,
        'store/item_detail.html',
        context={
            'item': item
        }
    )
.
.
.


「/my_app/item_detail/3」にアクセスしたときの表示


「/my_app/item_detail/0」にアクセスしたときの表示

複数のアプリケーションの作成

新しくもう一つアプリを作成

python manage.py startapp user


「settings.py」に作ったアプリを設定

INSTALLED_APPS = [
.
.
.
    'user',
]


「/user」に「urls.py」を作成

from django.urls import path
from django.urls.resolvers import URLPattern
from . import views

app_name = 'user'

urlpatterns = [
    path('user_list', views.user_list, name='user_list')
]


「views.py」を修正

from django.shortcuts import render

# Create your views here.
def user_list(request):
    return render(request, 'user/user_list.html')


「templates」ディレクトリに「user」ディレクトリと、その中に「user_list.html」を作成

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>User List</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
    <div class="container py-4">
        <h1>ユーザーリスト</h1>
        <a href="{% url 'my_app:item_list' %}">アイテムリストへ</a>
    </div>
</body>
</html>


プロジェクトディレクトリの「urls.py」にパスを追加

urlpatterns = [
.
.
.
    path('user/', include('user.urls')),
]


「/user/user_list」にアクセスしたときの表示

ログインユーザーのパスワード設定

ハッシュ化する関数の指定
https://docs.djangoproject.com/ja/3.1/topics/auth/passwords/#included-hashers

パスワードのバリデーション
https://docs.djangoproject.com/ja/3.1/topics/auth/passwords/#enabling-password-validation


パスワードの長さを最低8文字に設定する場合

.
.
.
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 8
        }
    },
.
.
.


ハッシュ化する関数を指定

.
.
.
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    'django.contrib.auth.hashers.BCryptPasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
]

AUTH_PASSWORD_VALIDATORS = [
.
.
.


Argon2をインストール

pip install django[argon2]

ログイン処理

ユーザー登録

モデルの作成

from os import truncate
from django.db import models
from django.contrib.auth.models import User

# Create your models here.
class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    website = models.URLField(blank=True)
    picture = models.FileField(upload_to='user/', blank=True)

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


「forms.py」の作成

from django import forms
from django.contrib.auth.models import User
from user.models import Profile

class UserForm(forms.ModelForm):
    username = forms.CharField(label='名前')
    email = forms.EmailField(label='メールアドレス')
    password = forms.CharField(label='パスワード', widget=forms.PasswordInput())

    class Meta():
        model = User
        fields = ('username', 'email', 'password')


class ProfileForm(forms.ModelForm):
    website = forms.URLField(label='ホームページ')
    picture = forms.FileField(label='写真')

    class Meta():
        model = Profile
        fields = ('website', 'picture')


「views.py」の修正

from django.shortcuts import render
from user.forms import UserForm, ProfileForm

.
.
.

def index(request):
    return render(request, 'user/index.html')

def register(request):
    user_form = UserForm(request.POST or None)
    profile_form = ProfileForm(request.POST or None, request.FILES or None)

    if user_form.is_valid() and profile_form.is_valid():
        user = user_form.save()
        user.set_password(user.password)
        user.save()
        profile = profile_form.save(commit=False)
        profile.user = user
        profile.save()

    return render(request, 'user/registration.html', context={
        'user_form': user_form,
        'profile_form': profile_form
    })


パスの追加

path('index', views.index, name='index'),
path('register', views.register, name='register'),


「manage.py」と同じ階層に「media」ディレクトリを作成


media ディレクトリのパスの設定

.
.
.
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'



HTMLファイルを3つ作成

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>User List</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
    <div class="container py-4">
        <nav>
            <a href="{% url 'user:index' %}">ホーム</a>
            <a href="{% url 'admin:index' %}">管理画面</a>
            <a href="{% url 'user:register' %}">登録画面</a>
        </nav>
        <div>
            {% block content %}
            {% endblock %}
        </div>
    </div>
</body>
</html>
{% extends 'user/base.html' %}
{% block content %}
<h1>ホーム画面</h1>
{% endblock %}
{% extends 'user/base.html' %}
{% block content %}
<h1>登録画面</h1>
<form method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    {{ user_form.as_p }}
    {{ profile_form.as_p }}
    <input type="submit" value="登録">
</form>
{% endblock %}



DEBUG を True に戻しておきます。

DEBUG = True


マイグレーションの実行

python manage.py makemigrations user
python manage.py migrate user 


「/user/index」にアクセスしたときの表示


登録画面で適当な値を入力して登録します。



次に管理画面へ移り、すでに作っているスーパーユーザーでログインします。

「ユーザー」→「先ほど作ったユーザ名」と進むと、ユーザーの詳細を確認できます。



「admin.py」の修正

from django.contrib import admin
from user.models import Profile

# Register your models here.

admin.site.register(Profile)


改めて管理画面に戻ると、Profiles が追加されています。

ログイン

「forms.py」の修正

.
.
.
class LoginForm(forms.Form):
    username = forms.CharField(label='名前', max_length=15)
    password = forms.CharField(label='パスワード', widget=forms.PasswordInput())
    confirm_password = forms.CharField(label='パスワード再入力', widget=forms.PasswordInput())

    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data['password']
        confirm_password = cleaned_data['confirm_password']
        if password != confirm_password:
            raise forms.ValidationError('パスワードが一致しません')


「views.py」の修正

from django.shortcuts import redirect, render
from user.forms import UserForm, ProfileForm, LoginForm
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
.
.
.
def user_login(request):
    login_form = LoginForm(request.POST or None)
    if login_form.is_valid():
        username = login_form.cleaned_data.get('username')
        password = login_form.cleaned_data.get('password')
        user = authenticate(username=username, password=password)
        if user:
            if user.is_active:
                login(request, user)
                return redirect('user:index')
            else:
                return HttpResponse('アカウントがアクティブではありません')
        else:
            return HttpResponse('ユーザーが存在しません')
    return render(
        request, 'user/login.html', context={
            'login_form': login_form
        }
    )

@login_required
def user_logout(request):
    logout(request)
    return redirect('user:index')

@login_required
def info(request):
    return HttpResponse('ログインしています')


パスの追加

path('user_login', views.user_login, name='user_login'),
path('user_logout', views.user_logout, name='user_logout'),
path('info', views.info, name='info'),


HTMLファイルの作成

{% extends 'user/base.html' %}
{% block content %}
<h1>ログイン画面</h1>
<form method="POST">
    {% csrf_token %}
    {{ login_form.as_p }}
    <input type="submit" value="ログイン">
</form>
{% endblock %}


「base.html」の修正

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>User List</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
    <div class="container py-4">
        <nav>
            <a href="{% url 'user:index' %}">ホーム</a>
            <a href="{% url 'admin:index' %}">管理画面</a>
        {% if user.is_authenticated %}
            <a href="{% url 'user:user_logout' %}">ログアウト</a>
        {% else %}
            <a href="{% url 'user:register' %}">登録画面</a>
            <a href="{% url 'user:user_login' %}">ログイン</a>
        {% endif %}
            <a href="{% url 'user:info' %}">インフォメーション</a>
        </nav>
        <div>
            {% block content %}
            {% endblock %}
        </div>
    </div>
</body>
</html>

パスワードのバリデーション

「views.py」の修正

.
.
.
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
.
.
.
def register(request):
    user_form = UserForm(request.POST or None)
    profile_form = ProfileForm(request.POST or None, request.FILES or None)

    if user_form.is_valid() and profile_form.is_valid():
        user = user_form.save(commit=False)
        try:
            validate_password(user_form.cleaned_data.get('password'))
        except ValidationError as e:
            user_form.add_error('password', e)
            return render(request, 'user/registration.html', context={
                'user_form': user_form,
                'profile_form': profile_form
            })
.
.
.


登録画面でパスワードを簡単なもの(1234 など)に設定して登録すると、
以下のようなエラーが表示されます。

バリデーションの自作

「manage.py」と同じ階層に「utils」ディレクトリと、その中に「validators.py」を作成

from django.core.exceptions import ValidationError
import re

class CustomPasswordValidator():
    def __init__(self):
        pass

    def validate(self, password, user=None):
        if all((re.search('[0-9]', password), re.search('[a-z]', password), re.search('[A-Z]', password))):
            return 
        raise ValidationError('パスワードには、0-9, a-z, A-Z を含んでください')

    def get_help_text(self):
        return 'パスワードには、0-9, a-z, A-Z を含んでください'


「settings.py」につくったクラスを設定

AUTH_PASSWORD_VALIDATORS = [
.
.
.
    {
        'NAME': 'utils.validators.CustomPasswordValidator'
    }
]


登録画面でパスワードを簡単なもの(1234 など)に設定して登録すると、
自作したエラーが表示されます。

ログイン処理(ユーザークラスのカスタマイズ)

参考
https://docs.djangoproject.com/ja/3.1/topics/auth/customizing/#a-full-example


ここからは、好みの場所に新しくディレクトリを作り、そこにプロジェクトディレクトリとアプリディレクトリを作って進めていきます。

最初の項目の「前提」と同じ状況を想定しています。



「models.py」の修正

from django.db import models
from django.contrib.auth.models import (
    BaseUserManager, AbstractBaseUser, PermissionsMixin
)

# Create your models here.

class UserManager(BaseUserManager):
    def create_user(self, username, email, password=None):
        if not email:
            raise ValueError('emailを入力してください')
        user = self.model(
            username=username,
            email=email
        )
        user.set_password(password)
        user.save(using=self._db)
        return user
    
    def create_super_user(self, username, email, password=None):
        user = self.model(
            username=username,
            email=email
        )
        user.set_password(password)
        user.is_staff = True
        user.is_active = True
        user.is_superuser = True
        user.save(using=self._db)
        return user


class User(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(max_length=150)
    email = models.EmailField(max_length=255, unique=True)
    is_active = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)
    website = models.URLField(null=True)
    picture = models.FileField(null=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username'] #スーパーユーザー作成時に入力

    objects = UserManager()

    def __str__(self):
        return self.email


「settings.py」の修正

.
.
.
WSGI_APPLICATION = 'django_practice.wsgi.application'
AUTH_USER_MODEL = 'my_app.User' #追加
.
.
.


マイグレーションの実行

python manage.py makemigrations my_app
python manage.py migrate


スーパーユーザーの作成

python manage.py createsuperuser


「/admin」にアクセスすると、
デフォルトではユーザー名だった部分がメールアドレスに変わっています。



「forms.py」の作成

from django import forms
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.contrib.auth import get_user_model
from django. core.exceptions import ValidationError
from django.forms import fields

User = get_user_model()

class UserCreationForm(forms.ModelForm):
    password = forms.CharField(label='パスワード', widget=forms.PasswordInput)
    confirm_password = forms.CharField(label='パスワード再入力', widget=forms.PasswordInput)

    class Meta:
        model= User
        fields = ('username', 'email', 'password')

    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data.get('password')
        confirm_password = cleaned_data.get('confirm_password')
        if password != confirm_password:
            raise ValidationError('パスワードが一致しません')

    def save(self, commit=False):
        user = super().save(commit=False)
        user.set_password(self.cleaned_data.get('password'))
        user.save()
        return user


class UserChangeForm(forms.ModelForm):
    password = ReadOnlyPasswordHashField()
    website = forms.URLField(required=False)
    picture = forms.FileField(required=False)

    class Meta:
        model = User
        fields = ('username', 'email', 'password', 'is_staff', 'is_active', 'is_superuser', 'website', 'picture')

    def clean_password(self):
        return self.initial['password']


「admin.py」の修正

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth import get_user_model
from .forms import UserChangeForm, UserCreationForm

# Register your models here.

User = get_user_model()

class CustomizeUserAdmin(UserAdmin):
    form = UserChangeForm #ユーザー編集画面のフォーム
    add_form = UserCreationForm #ユーザー作成画面のフォーム

    #一覧画面で表示する項目
    list_display = ('username', 'email', 'is_staff')

    # ユーザー編集画面で表示する項目
    fieldsets = (
        ('ユーザー情報', {'fields': ('username', 'email', 'password', 'website', 'picture')}),
        ('パーミッション', {'fields': ('is_staff', 'is_active', 'is_superuser')}),
    )

    add_fieldsets = (
        ('ユーザー情報', {'fields': ('username', 'email', 'password', 'confirm_password')}),
    )

admin.site.register(User, CustomizeUserAdmin)


「/admin」にアクセスしてログイン後、
「Users」→「userを追加」と進むと以下の表示になります。


入力して保存すると、以下の表示になります。

管理画面のカスタマイズ

モデルの作成

.
.
.
class Student(models.Model):
    name = models.CharField(max_length=20)
    age = models.IntegerField()
    score = models.IntegerField()
    school = models.ForeignKey(
        'School', on_delete=models.CASCADE
    )

    class Meta:
        db_table = 'students'

class School(models.Model):
    name = models.CharField(max_length=20)

    class Meta:
        db_table = 'schools'


マイグレーションの実行

python manage.py makemigrations my_app
python manage.py migrate my_app    


「admin.py」の修正

.
.
.
from .models import Student, School
.
.
.
admin.site.register(Student)
admin.site.register(School)


管理画面を更新すると、「Students」「Schools」が追加されています。

テーブル名の変更

「models.py」の修正

.
.
.
    class Meta:
        db_table = 'students'
        verbose_name_plural = '生徒'
.
.
.
    class Meta:
        db_table = 'schools'
        verbose_name_plural = '学校'


指定した文字に変わります。

データの表示名の変更

「学校」をいくつか追加すると、以下のような表示になっています。


「models.py」の修正

class Student(models.Model):
.
.
.
    def __str__(self):
        return self.name

class School(models.Model):
.
.
.
    def __str__(self):
        return self.name


すると、登録した名前に変わります。

フィールドの順番の指定


生徒に関しても適当にいくつかデータをつくります。


生徒の詳細ページを見ると、項目が「name, age, score, school」の順で並んでいます。


この並び順を変更します。

「admin.py」の修正

.
.
.
admin.site.register(User, CustomizeUserAdmin)
# admin.site.register(Student)
admin.site.register(School)

@admin.register(Student)
class StudentAdmin(admin.ModelAdmin):
    fields = ('name', 'score', 'age', 'school')

表示するフィールドの指定

現在の生徒一覧ページの表示は以下の通り



「admin.py」の修正

.
.
.
@admin.register(Student)
class StudentAdmin(admin.ModelAdmin):
    fields = ('name', 'score', 'age', 'school')
    
    #表示するフィールドの指定
    list_display = ('id', 'name', 'age', 'score', 'school')

    # リンクにするフィールドの指定
    list_display_links = ('name',)

データのソート

「models.py」の修正

.
.
.
    class Meta:
        db_table = 'students'
        verbose_name_plural = '生徒'
        ordering = ('age',)
.
.
.

検索欄の追加

「admin.py」の修正

.
.
.
@admin.register(Student)
class StudentAdmin(admin.ModelAdmin):
.
.
.
    search_fields = ('name', 'age')

フィルターの追加

「admin.py」の修正

.
.
.
@admin.register(Student)
class StudentAdmin(admin.ModelAdmin):
.
.
.
    list_filter = ('name', 'age', 'score', 'school')

直接編集可能に

「admin.py」の修正

.
.
.
@admin.register(Student)
class StudentAdmin(admin.ModelAdmin):
.
.
.
    list_editable = ('age', 'score')

紐づいているデータの数を取得

「admin.py」の修正

.
.
.
admin.site.register(User, CustomizeUserAdmin)
# admin.site.register(Student)
# admin.site.register(School)
.
.
.
@admin.register(School)
class SchoolAdmin(admin.ModelAdmin):
    list_display = ('name', 'student_count')

    def student_count(self, obj):
        count = obj.student_set.count()
        return count


学校の一覧ページ

フィールド名の変更

「admin.py」の修正

.
.
.
@admin.register(School)
class SchoolAdmin(admin.ModelAdmin):
.
.
.
    student_count.short_description = '生徒数'


「models.py」の修正

.
.
.
class School(models.Model):
    name = models.CharField(max_length=20, verbose_name='学校名')
.
.
.

テンプレートの上書き

githubから該当のファイルを取得して上書きするという流れになります。
https://github.com/django/django/tree/main/django/contrib/admin/templates



「templates」ディレクトリに「admin」ディレクトリと、その中に「base_site.html」を作成

htmlの中身は、以下のファイルのコードをコピペ
https://github.com/django/django/blob/main/django/contrib/admin/templates/admin/base_site.html

「base_site.html」を修正

{% extends "admin/base.html" %}

{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}

{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">マイ管理画面</a></h1>
{% endblock %}

{% block nav-global %}{% endblock %}


管理画面を開くと、上書きされているのが確認できます。



同様にCSSも修正します。

下記の「base.html」をみると、「admin/css/base.css」が全体のCSSファイルであることがわかります。
https://github.com/django/django/blob/main/django/contrib/admin/templates/admin/base.html

.
.
.
<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}">
.
.
.


同じように、「/static/admin/css/base.css」となるようディレクトリとファイルを作成します。

下記コードを取得して「base.css」にコピペ
https://github.com/django/django/blob/main/django/contrib/admin/static/admin/css/base.css


好みの個所を修正

:root {
  --primary: #79aec8;
  --secondary: red;
.
.
.





今回は以上になります。
ご覧いただきありがとうございました!

続きはこちら↓

コメント

コンタクトフォーム

    タイトルとURLをコピーしました