python3.doc_60
R e i - D r e a m
for Laravel
TOP > Python3 > Django(サンプル集)
Guest
login

最終投稿日:2022年6月18日

目次
○ 認証・ユーザーモデルのカスタマイズ
出来合いのモデルをいじくろう!!
プロフィールフォーム
プロフィール画面を作ってみるよ!!
画像アップロード
画像やファイルをアップロードするよ!!
CSV UpDownLoad
CSVファイルをアップロードしたり、ダウンロードしたり!!
AJAX
AJAXで動的画面を作ろう!!
グラフ表示
[matplotlib]ライブラリでグラフを作成!!
色々DownLoad
色々な形式をダウンロードしよう!!
認証・ユーザーモデルのカスタマイズ
ユーザーモデルのカスタマイズ
この章のゴールは、ユーザーモデルをカスタマイズし、username ではなく、mailaddress でログインする事とします。
【注意】
    ユーザーモデルをカスタマイズする場合は、データベースをゼロの状態で始める事!
    要するにまだ、makemigrationsもmigrateしちゃ駄目!!
    ※その間サーバーを立ち上げる事はできない(エラーとなる)
● ユーザーモデルのコピー
まずは任意のアプリディレクトリ( users って名前が他モジュールとの親和性の観点から良いらしい)の models.py に Django の User モデルを丸っとコピーします。
    ~\django\contrib\auth\models.py
上記のファイルから以下の2クラスをコピーします。
    class UserManager(BaseUserManager)
    class AbstractUser(AbstractBaseUser, PermissionsMixin)
コピーしたら、以下の様に修正します。
大まかに説明すると、username を削除し、mail に差し替えます。
[users/models.py]
from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.contrib.auth.base_user import BaseUserManager
class UserManager(BaseUserManager):
    use_in_migrations = True
#    def _create_user(self, username, email, password, **extra_fields):
    def _create_user(self, email, password, **extra_fields):
#        if not username:
         if not email:
#            raise ValueError('The given username must be set')
            raise ValueError('The given mail must be set')
        email = self.normalize_email(email)
#        username = self.model.normalize_username(username)
#        user = self.model(username=username, email=email, **extra_fields)
    user = self.model(email=email, **extra_fields)
    user.set_password(password)
    user.save(using=self._db)
    return user
# def create_user(self, username, email=None, password=None, **extra_fields):
    def create_user(self, email=None, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
#        return self._create_user(username, email, password, **extra_fields)
        return self._create_user(email, password, **extra_fields)
#    def create_superuser(self, username, email, password, **extra_fields):
    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')
#        return self._create_user(username, email, password, **extra_fields)
        return self._create_user(email, password, **extra_fields)
#class AbstractUser(AbstractBaseUser, PermissionsMixin):
class User(AbstractBaseUser, PermissionsMixin):
#    username_validator = UnicodeUsernameValidator()
#    username = models.CharField(
#        _('username'),
#        max_length=150,
#        unique=True,
#        help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
#        validators=[username_validator],
#        error_messages={
#            'unique': _("A user with that username already exists."),
#        },
#    )
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)
#    email = models.EmailField(_('email address'), blank=True)
    email = models.EmailField(_('email address'), unique=True)
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user can log into this admin site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
    objects = UserManager()
    EMAIL_FIELD = 'email'
#    USERNAME_FIELD = 'username'
    USERNAME_FIELD = 'email'
#    REQUIRED_FIELDS = ['email']
    REQUIRED_FIELDS = []
    class Meta:
        # User クラス(単数形)
        verbose_name = _('user')
        # User クラス(複数形)
        verbose_name_plural = _('users')
#        abstract = True
    def clean(self):
        super().clean()
        self.email = self.__class__.objects.normalize_email(self.email)
    def get_full_name(self):
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()
    def get_short_name(self):
        return self.first_name
    def email_user(self, subject, message, from_email=None, **kwargs):
        send_mail(subject, message, from_email, [self.email], **kwargs)
    # 他アプリから username が呼ばれる事を考慮して
    @property
    def username(self):
        return self.email
● settings.pyに以下を追記する
AUTH_USER_MODEL = 'users.User'
    ※usersアプリにあるUserモデルの意味
● settings.pyに以下を追記する
admin.pyを以下の様に追記する
    adminを使わない場合は不要
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.utils.translation import ugettext_lazy as _
from .models import User
class MyUserChangeForm(UserChangeForm):
    class Meta:
        model = User
        fields = '__all__'
class MyUserCreationForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('email',)
class MyUserAdmin(UserAdmin):
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name')}),
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                                                      'groups', 'user_permissions')}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2'),
        }),
    )
    form = MyUserChangeForm
    add_form = MyUserCreationForm
    list_display = ('email', 'first_name', 'last_name', 'is_staff')
    list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
    search_fields = ('email', 'first_name', 'last_name')
    ordering = ('email',)
admin.site.register(User, MyUserAdmin)
● このタイミングでマイグレーションする
※主キーを uuid に変更したい場合は、先に定義を変更してからマイグレーションする事
    python manage.py makemigrations
    python manage.py migrate
上記コマンドを叩くとテーブルが10個作成され、その内の3がカスタマイズした「users_***」テーブルと変わります。
    users_user
    users_user_groups
    users_user_user_permissions
以上で、ユーザーモデルのカスタマイズは完了です!
python manage.py createsuperuser
上記コマンドから、スーパーユーザーを作成するとメールアドレスを聞かれるハズです。
比較するパスワードのハッシュ値を変更する(平文も可能)
デフォルトのままだと、Django の設定に基づいた「パスワードのハッシュ化」「パスワードのバリデーション」ルールになります。
【Auth 認証】を参考にしてください。
    ※アカウント登録画面を自作する場合で、独自のバリデーションを作成する場合は全削除で良い
● パスワードの独自ハッシュ化
エントリポイントを作成する必要があります。
クラス名「MyPassWordHashers」を新たに作成し、パスは以下とします。
    #「my_common」までのパスは通ってるものとします。
    プロジェクト/my_common/my_login.py
[セッティング.py]
以下の変数名でエントリポイントを設定します。
    PASSWORD_HASHERS = [
        'my_login.MyPassWordHashers'
    ]
    # ハッシュアルゴリズム設定(最初のが設定値となる)
    MY_HASHERS_ALG = [
        'md5',
        'sha256',
        'sha512',
    ]
[my_login.py]
以下が本来のクラス(PBKDF2PasswordHasher)の抜粋となります。
    class PBKDF2PasswordHasher(BasePasswordHasher):
        algorithm = "pbkdf2_sha256"
        iterations = 100000
        digest = hashlib.sha256
        def encode(self, password, salt, iterations=None):
            assert password is not None
            assert salt and '$' not in salt
            if not iterations:
                iterations = self.iterations
            hash = pbkdf2(password, salt, iterations, digest=self.digest)
            hash = base64.b64encode(hash).decode('ascii').strip()
            return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
上記メソッドで平文のパスワードを受け取り、ハッシュ化されたパスワードを返却しています。
これを以下の様に変更します。
from django.contrib.auth.hashers import *
from django.conf import settings
import hashlib
class MyPassWordHashers(PBKDF2PasswordHasher):
    algorithm = settings.MY_HASHERS_ALG[0]
    iterations = 1000
    def encode(self, password, salt, iterations=None):
        if password.strip() == '':
            return ''
        # ハッシュパスワード文字の前に結合する文字列
        prefix = self.algorithm + '$' + str(self.iterations) + '$$'
        fn = settings.MY_HASHERS_ALG[0]
        fn = eval('hashlib.' + fn)
        # ハッシュ化する
        new_password = prefix + fn(password.encode('utf-8')).hexdigest()
        return new_password
独自のログイン画面を作成する
基本的にログイン画面は Django の機能として自動的に作成する事ができますが、
バリデーション等の仕組みがないので自作します。
※account アプリに作成するサンプルとなります。
[セッティング.py]
お約束のアプリの登録。
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'account.apps.AccountConfig',
]
# ログイン画面のパスを登録
LOGIN_URL = '/account/login/'
# 直接ログイン画面へ遷移しログインが成功した場合の遷移先パスを登録
LOGIN_REDIRECT_URL = '/account/index'
[プロジェクト/urls.py]
urlpatterns = [
    url(r'^account/', include('account.urls', namespace='account')),
]
[apps/account/urls.py]
from django.conf.urls import url
from django.views.generic import RedirectView
from django.contrib.auth import views as auth_views
from .views import *
app_name = 'account'
urlpatterns = [
    url(r'^$', RedirectView.as_view(url='login/')),
    # カスタマイズするのでいつもの views.py に設定する
    url(r'^login/$', LoginView.as_view(), name='login'),
    # logout は Django の処理にお任せ
    url(r'^logout/$', auth_views.LogoutView.as_view(template_name='account/logout.html'), name='logout'),
]
[apps/account/forms.py]
from django import forms
class AccountForm(forms.Form):
    username = forms.CharField(
        label = 'ユーザー名',
        required = False,
        widget = forms.TextInput(
            attrs={'size':'20;'}
        )
    )
    password = forms.CharField(
        label = 'パスワード',
        required = False,
        widget = forms.TextInput(
            attrs={'size':'20;'}
        )
    )
    # 好きなバリデーションを登録する
    def clean(self):
        dt = self.cleaned_data
        username = self.cleaned_data['username'].strip()
        if username == '':
            self.add_error('username', 'ユーザー名を記入してください。')
[templates/account/login.html]
ろぐいん
    <form method="post">
        {% if form.username.errors.as_text != '' %}
            {{form.username.errors.as_text|cut:'* '}}<br>
        {% endif %}
        {{ form.username.label_tag }}<br>
        {{ form.username }}<br>
        {{ form.password.label_tag }}<br>
        {{ form.password }}<br>
        <input type="hidden" name="next" value="{{ next }}" />
        {% csrf_token %}
        <input type="submit" value="login" />
    </form>
[templates/account/login.html]
ろぐあうと。
[apps/account/views.py]
from django.shortcuts import render
from django.views.generic import View
from .forms import AccountForm
from django.contrib.auth import login, logout, authenticate
from django.http.response import HttpResponseRedirect
from django.urls import reverse
class LoginView(View):
    def get(self, request):
        form = AccountForm()
        d = {'form':form}
        return render(request, 'account/login.html', d)
    def post(self, request):
        form = AccountForm(request.POST)
        if form.is_valid():
            username = request.POST.get('username')
            raw_password = request.POST.get('password')
            # ユーザーが存在するかチェック(存在する場合はユーザーオブジェクトを返却)
            user = authenticate(username=username, password=raw_password)
            if user != None:
                # ログイン処理実施
                login(request, user)
                # 手動でログアウトしたい場合はこちらを使う
            #    logout(request)
                # ログインが成功した場合の遷移先(authenticate でオブジェクトが返却されたのでログインは成功するハズ)
                return HttpResponseRedirect(reverse('hello:index'))
        # ログイン処理をする場合は、メソッド「authenticate」「logout」は必ずセットで使用する事!
        d = {'form':form}
        return render(request, 'account/login.html', d)
独自のユーザー登録画面を作成する
admin から作成する方法もありますが、独自で作成する場合は generic モジュールを活用して簡易に作成する事が可能です。
※登録されるテーブルは DJANGO のユーザーテーブル auth_user となります。
※account アプリに作成するサンプルとなります。
[urls.py]
from django.conf.urls import url
from django.views.generic import RedirectView
from .views import *
app_name = 'account'
urlpatterns = [
    url(r'^$', RedirectView.as_view(url='list/')),
    url(r'^list/$', ListView.as_view(), name='list'),
    url(r'^add/$', AddView.as_view(), name='add'),
    url(r'^edit/(?P\w+)/$', EditView.as_view(), name='edit'),
    url(r'^del/(?P\w+)/$', DelView.as_view(), name='del'),
    url(r'^changepass/$', PassView.as_view(), name='pass'),
]
● 一覧画面の作成
[views.py]
#generic モジュールインポート
from django.views import generic
#Django の User クラスをインポート
from django.contrib.auth.models import User
#generic.ListView を継承する
class ListView(generic.ListView):
    #モデルの定義
    model = User
    #テンプレートの指定
    template_name = "account/user_list.html"
【メモ】
    通常ユーザー登録画面等を作成する場合は、限られた権限を持つ者のみ
    アクセスできる auth 認証の必要なページにする必要があります。
    その様なページを作成する場合は【Auth 認証】を参考に修正する必要があります。
[user_list.html]
ユーザー一覧のオブジェクトは views.py で定義した[モデル] + [_list]で取得できる。
for で回す事が可能なので以下の様に個々の情報を取得できる
また、auth 認証している場合はのパーミッションは perms で取得が可能
例)auth 認証している場合は以下の様に分岐を作れる
{% if perms.auth.add_user %}
    <a href="{% url 'account:add' %}">新規追加</a>
{% endif %}
例)auth 認証していない場合
<a href="{% url 'account:pass' %}">パスワード変更</a>
<a href="{% url 'account:add' %}">新規追加</a>
<table>
    <tr>
        <th>更新</th><th>UserName</th><th>削除</th>
    </tr>
{% for user in user_list %}
    <tr>
        <td><a href="{% url 'account:edit' pk=user.id %}">{{user.id}}</a></td>
        <td>{{user.username}}</td>
        <td><a href="{% url 'account:del' pk=user.id %}">削除</a></td>
    </tr>
{% endfor %}
</table>
● 登録画面の作成
[forms.py]
    from django import forms
    #Django のユーザーフォーム定義をインポート
    from django.contrib.auth.forms import UserCreationForm
    #パーミッションとグループの選択を作成したいのでインポートする
    from django.contrib.auth.models import Permission, Group
    class UserCreateForm(UserCreationForm):
        #グループ モデルを全取得し、 [(id,name),(id,name),...]の型式にする
        group_optiton = [(g.id,g.name) for g in Group.objects.all()]
        #同上
        perm_optiton = [(p.id,p.codename) for p in Permission.objects.all()]
        #グループ項目を定義する
        my_group = forms.MultipleChoiceField(
            required = False,
            widget = forms.CheckboxSelectMultiple(),
            choices = group_optiton,
        )
        #パーミッション項目を定義する
        my_perm = forms.MultipleChoiceField(
            required = False,
            widget = forms.CheckboxSelectMultiple(),
            choices = perm_optiton,
        )
[views.py]
~ 重複インポートは省略 ~
from .forms import UserCreateForm
from django.http import HttpResponseRedirect
from django.db import connection, transaction
from django.contrib.auth.models import Permission, Group
from django.urls import reverse_lazy
#generic.CreateView を継承する
class AddView(generic.CreateView):
    #フォームクラスを定義
    form_class = UserCreateForm
    template_name = 'account/user_form.html'
    success_url = reverse_lazy("account:list")
    #エラーがなかった場合の処理
    def form_valid(self, form):
        #一旦コミットしないで save メソッドを処理する事でインスタンスが取得できる
        user = form.save(commit=False)
        #デフォルトでは False だが、変更したい場合はインスタンスを修正できる
        #user.is_staff = True
        transaction.set_autocommit(False)
        #取得したインスタンスを save 処理する
        user.save()
        #フォームから受け取った my_group 属性を取得(複数チェックボックスなのでリストで取得できる)
        my_group = form.cleaned_data["my_group"]
        #一旦 groups でクエリセットを取得する
        groups = Group.objects.all().filter(id__in = my_group)
        for group in groups:
            #for で回してグループを保存処理する
            user.groups.add(group)
        #以下パーミッションも同じ
        my_perm = form.cleaned_data["my_perm"]
        perms = Permission.objects.all().filter(id__in = my_perm)
        for perm in perms:
            user.user_permissions.add(perm)
        transaction.commit()
        #バリデーション通過後の遷移 URL
        return HttpResponseRedirect(self.success_url)
[user_form.html]
とうろく<br>
<form method='post'>
    {{form.errors.username.as_text}}<br>
    <label>お名前 {{form.username}}</label><br>
    {{form.errors.password1.as_text}}<br>
    <label>パスワード {{form.password1}}</label><br>
    {{form.errors.password2.as_text}}<br>
    <label>パス確認 {{form.password2}}</label><br><br>
    <div>グループ選択</div>
    {{form.my_group}}
    <br><br><br>
    <div>パーミッション選択</div>
    {{form.my_perm}}
    <br>
    <input type='submit' value='送信'>
    {% csrf_token %}
</form>
● 更新画面の作成
[views.py]
    #generic.UpdateView を継承する
    class EditView(generic.UpdateView):
        model = User
        #form_class を指定しない場合は fields を指定する。
        fields = ('username','groups','user_permissions')
        template_name = 'account/user_edit.html'
        success_url = reverse_lazy("account:list")
[user_edit.html]
    更新<br>
    <form method='post'>
        {{form.errors.username.as_text}}<br>
        <label>お名前 {{form.username}}</label><br>
    <br>
        <div>グループ選択</div>
        {{form.groups}}
        <br>
        <div>パーミッション選択</div>
        {{form.user_permissions}}
        <input type='submit' value='送信'>
        {% csrf_token %}
    </form>
● パスワード変更画面の作成
※現在ログイン中のユーザーのパスワード変更をする
※ログイン情報がない場合はエラーになる
[forms.py]
    ~ 重複インポートは省略 ~
    from django.contrib.auth.forms import PasswordChangeForm
    class UserPasswordChangeForm(PasswordChangeForm):
        pass
[views.py]
    ~ 重複インポートは省略 ~
    from django.shortcuts import render, redirect
    from .forms import UserPasswordChangeForm
    from django.views.generic import View
    class PassView(View):
        def get(self,request):
            #ユーザーオブジェクトを渡す
            form = UserPasswordChangeForm(request.user, None)
            context = {'form':form}
            return render(request, 'account/pass.html', context)
        def post(self,request):
            #ユーザーオブジェクトと受け取ったパスワード情報
            form = UserPasswordChangeForm(request.user, request.POST)
            #バリデーション
            if form.is_valid():
                #保存
                form.save()
                return redirect("account:list")
            else:
                context = {'form':form}
                return render(request, 'account/pass.html', context)
[pass.html]
    ぱすわーど<br>
    <form action="" method="POST">
    {{ form.as_p }}
    {% csrf_token %}
    <input type="submit" value="送信">
    </form>
● 削除画面の作成
[views.py]
    class DelView(generic.DeleteView):
        model = User
        fields = '__all__'
        template_name = "account/user_confirm_delete.html"
        success_url = reverse_lazy("account:list")
[user_confirm_delete.html]
    削除確認<br>
    <form action="" method="POST">
    名前:{{ object }}
    <br>
    {% csrf_token %}
    <input type="submit" value="送信">
    </form>
レンズモード 【新規購入限定用】
ログインしてコメントを残そう!!


きっぷる