Django View的应用·登录功能·AbstructBaseUser(自用备忘录)

Django的自用备忘录

本次将总结关于视图的应用

软件开发环境

操作系统:Mac
编辑器:VSCode
Python版本:3.10.9
Django版本:4.1.0

将模型注册到管理界面

from django.contrib import admin
from .models import Items

admin.site.register(Items) #Itemsはmodel名

重定向处理

from django.shortcuts import redirect
def one_item(request):
    return redirect('store:item_detail',id=1)
    #リダイレクト時にidを渡すことも可

def item_detail(request,id):
    item = Items.objects.filter(pk=id).first()
    #detail/15のようにidがないURLを指定されたらリストに飛ばすなどの処理もこのように可能
    if item is None:
        return redirect('store:item_list')
    return render(request,'store/item_detail.html',context={
        'item':item
    })

错误处理 (Incorrect handling/error handling)

・如果不将setting.py中的DEBUG设置为False,则无法进行错误处理。
・在ALLOWED_HOSTS中添加自己的主机。
ALLOWED_HOSTS=[‘127.0.0.1’]
・在项目的urls.py中添加想要设置的状态码处理程序。
handler404=views.函数名(在404错误情况下要执行的函数(有两个参数))
handler500=views.函数名(在500错误情况下要执行的函数(有一个参数))

故意引发404错误的方法。

from django.http import Http404
#if id ==0: この条件の時に404エラーを発生させるなど
raise Http404

当输入了无意的URL时,会显示404错误页面。

#views.py
def page_not_found(request,exception):
    #statusに404を設定しないとターミナルのアクセスログに404ではなく200(通常アクセス)と認識される
    return render(request,'store/404.html',status=404)
#   return redirect('store:item_list') またはホームに飛ばすなんかもあり

#プロジェクトのurls.py
from store import views
handler404 = views.page_not_found

#テンプレートに404.htmlを作成で完了
500错误是什么意思

在服务器处理出现问题时发生的错误
在编写Django时无法检查到错误,只能在连接服务器时发现错误
如果在出现此错误的页面上预先填写例如电子邮件地址等信息,用户就能更容易地了解出现了什么问题以及应该如何解决。

from django.shortcuts import get_object_or_404
get_object_or_404
#指定したモデルを呼び出し、getを行う。値を取得できなかった場合raise Http404を送出する
#例)item = get_object_or_404(Items,pk=id)

get_list_or_404
#指定したモデルを呼び出し、filterを行う。同上
#リストで取り出すためリストの中身がない状態で取り出すと404を返す
#例)items = get_list_or_404(Items,id__gt=2) idが2より大きいものを取り出す

登录功能的实现篇

在保存用户信息时,一定要将密码进行哈希处理后再保存。在指定进行哈希处理的函数(哈希算法)时,可以添加到setting.py文件的PASSWORD_HASHERS变量中。
有很多官方提供的哈希处理函数可以选择,而Argon2和BCrypt似乎是强大的选项。

要设置验证选项,请在settings.py中设置AUTH_PASSWORD_VALIDATORS的选项。
点击此处查看详细信息。

#追加
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 = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        #追加
        'OPTIONS':{
            'min_length':8,
        }
    },
#(略)
]

在使用argon2之前,需要在虚拟环境中安装pip。

$pip install django\[argon2\]

通过这样做可以成功安装。
当在zsh中执行命令时,如果包含方括号,会引发错误,所以需要进行转义才能顺利完成。

使用Django默认的User类来实现登录相关功能(实际场景中很少使用,仅供备忘)。

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

class Profile(models.Model):
    #デフォルトのUserモデルとOneToOneで結びつけて追加したい項目を追加
    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 #管理画面でみた時にわかりやすく

将admin.py文件中的内容描述,以便在管理界面中可查看。

from django.contrib import admin
from .models import Profile

admin.site.register(Profile)

在forms.py中设置表格以注册到相应的模型
继承ModelForm并与模型关联

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')
        
#ログイン用のフォーム
class LoginForm(forms.Form):
    username=forms.CharField(label='名前',max_length=150)
    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。

新出现的特别事物

当用户提交用户表单后,
通过user = user_form.save()函数将用户信息保存至数据库。
然后,使用user.set_password(user.password)对用户密码进行加密,并将加密后的密码设置给用户。
接着,使用user.save()函数保存用户信息至数据库。

而与用户模型通过OneToOne关联的profile模型需要进行以下操作:
首先,通过profile_form.save(commit=False)将临时保存的profile表单信息暂存起来。
然后,将profile.user=user赋值,将保存的用户信息与profile关联。
最后,通过profile.save()函数将用户和profile的信息一并保存至数据库中。

在登录函数中,通过authenticate函数来验证用户是否存在。

通过添加@login_required装饰器,在登录状态时才能执行指定的函数。

{% 如果用户已经认证 %}:只在用户已经认证的情况下执行或显示

from django.shortcuts import render
from user.forms import UserForm,ProfileForm,LoginForm
from django.contrib.auth import authenticate,login,logout
from django.http import HttpResponse
from django.shortcuts import redirect
from django.contrib.auth.decorators import login_required

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

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
    })
    
#ログイン用ビュー
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: #userが存在する場合
            if user.is_active: #そしてuserがactiveの場合
                login(request,user) #login関数は第一引数にrequest,第二引数にそのユーザにするとログインができる
                return redirect('user:index')
            else:
                return HttpResponse('アカウントがアクティブではないです')
        else: #userが存在しない時
            return HttpResponse('ユーザが存在しません')
    return render(request,'user/login.html',context={
        'login_form':login_form
    })
    
#ログアウト
@login_required #loginしていないとこの関数のURLにいっても起動しない
def user_logout(request):
    logout(request)
    return redirect('user:index')

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

验证用户和密码的有效性 hé de

要判断密码是否有效

from django.contrib.auth.password_validation import validate_password,
from django.contrib.auth.password_validation import password_change

使用

验证密码(password, user)

检查用户的密码是否合适(不太短,不太常见,不容易从用户名推测出来)。第一个参数接收密码,第二个参数接收用户。

在注册阶段对密码进行验证。

编辑views.py文件

from django.core.exceptions import ValidationError
from django.contrib.auth.password_validation import validate_password
#登録用
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():
        #下記から追加した部分。(commit=False)によってまだ保存しないで持っておく
        user = user_form.save(commit=False)
        #パスワードのバリデーションを行う
        try:
            validate_password(user_form.cleaned_data.get('password'),user)
            #forms.pyでvalidate_passwordをしている場合はuser_form.save()でいい
        except ValidationError as e:
            user_form.add_error('password',e) #add_errorでフォームにエラーを追加できる
            return render(request,'user/registration.html',context={
                'user_form':user_form, #再表示させる
                'profile_form':profile_form
            })
        #以下同じ。ValidationError発生後は下記は行われない
        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
    })

自己编写数据验证器

只要在settings.py中自定义并将其添加到AUTH_PASSWORD_VALIDATORS中作为验证器,就可以了。

在项目的根目录下创建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は全てがTrueの場合Trueを返して中の処理を実行
        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'
    },
]

使用 AbstractBaseUser 实现登录功能

如果想要利用现有的字段并删除username字段,可以使用AbstractUser。
当想要从头开始重建User类时,可以使用AbstractBaseUser来方便操作。

步骤
1. 创建自定义管理器和自定义用户类
2. 修改settings.py文件,使用户指向自定义类
3. 进行迁移
4. 创建表单和管理员界面

使用PermissionsMixin可以将一般的Django权限,如superuser等,引入到自定义用户中。

要查看一般用户所拥有的字段和属性,请查阅官方资料。

定制化的內容在這裡。

请实际尝试写一下。

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

#マネージャーの設定(ユーザを作成する際のメゾットを定義する場所)
class UserManager(BaseUserManager):
    def create_user(self,username,email,password=None):
        if not email:
            raise ValueError('Enter Email!')
        user = self.model(
            username=username,
            email = email
            #それ以外のフィールドはデフォルトをいれる
        )
        user.set_password(password) #passwordをセットして
        user.save(using=self._db) #DBに保存
        return user
    
    def create_superuser(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,upload_to='picture/') #upload_toでファイルの保存フォルダを指定
    #passwordフィールドはAbstractBaseUserに定義されてるのでここで定義する必要はなし
    #is_superuserフィールドもPermissionsMixinに定義されているので定義しないでOK
    
    USERNAME_FIELD='email' #USERNAME_FIELDはユーザをユニークと識別するフィールドを指定する
    REQUIRED_FIELDS = ['username'] #superuser作成時に入力する項目。USERNAME_FIELDとパスワードは必須になるのでそれ以外にあれば設定
    
    objects=UserManager() #UserManagerとの紐付け。objectsに対して作ったマネージャーを設定
    
    def __str__(self): #管理画面でみやすくなる
        return self.email

在 settings.py 文件中添加

#作成したUserクラスを指定
AUTH_USER_MODEL='accounts.User' #accountsはアプリ名

通过使用AbstructBaseUser进行用户注册的复习

要归结成中文,在forms.py中定义的clean和save方法的程度决定了在views.py中所需编写的内容。定义验证和保存方法在forms.py中可能更容易。以下是演习中的forms.py和views.py。

from django import forms
from django.contrib.auth import get_user_model
from .models import Users
from django.core.exceptions import ValidationError
from django.contrib.auth.password_validation import validate_password


User=get_user_model()

class RegistForm(forms.ModelForm):
    username = forms.CharField(label='名前')
    age =forms.IntegerField(label='年齢',min_value=0)
    email = forms.EmailField(label='メールアドレス')
    password=forms.CharField(label='パスワード',widget=forms.PasswordInput)
    confirm_password = forms.CharField(label='パスワード再入力',widget=forms.PasswordInput)
    # picture = forms.FileField(label='写真')
    
    class Meta:
        model=Users
        fields=('username','age','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)
        validate_password(self.cleaned_data.get('password'),user)
        user.set_password(self.cleaned_data.get('password'))
        user.save()
        return user

下一个是views.py

from django.shortcuts import render,redirect
from .forms import RegistForm
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError

def home(request):
    return render(request,'accounts/home.html')


def regist(request):
    form = RegistForm(request.POST or None)
    if form.is_valid():
        # user = form.save(commit=False) 不要
        #パスワードのバリデーションを行う
        try:
            # validate_password(form.cleaned_data.get('password'),user) 
            # forms.pyでvalidate_passwordをすでにしているので今回はform.save()をtry文で実行する
            form.save()
            return redirect('accounts:home')
        except ValidationError as e:
            form.add_error('password',e) #add_errorでフォームにエラーを追加できる
        # user.set_password(user.password) 不要
        # user.save() 不要
    
    return render(request,'accounts/regist.html',context={
        'form':form
    })

要实现可以从管理界面创建和编辑。

总的流程是在forms.py文件中定义用于管理画面的表单(包括设置验证规则等),然后在admin.py中继承UserAdmin类的类中加载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

User = get_user_model() #今回作成した利用するUserクラスを指定する

#管理画面でつかうフォーム
class UserCreationForm(forms.ModelForm):
    password=forms.CharField(label='password',widget=forms.PasswordInput)
    confirm_password = forms.CharField(label='password再入力',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)
        #下記のようにforms.pyでvalidate_passwordした場合はviews.pyにてtryで実行するのはform.save()
        validate_password(self.cleaned_data.get('password'),user)
        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):
        #initial すでに登録されているパスワードを返す
        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 UserCreationForm,UserChangeForm

User=get_user_model()

class CustomizeUserAdmin(UserAdmin):
    #管理画面の表示の仕方を変えたい時にUserAdminを継承して変えていく
    form = UserChangeForm #ユーザ編集画面で使うFormを定義
    add_form = UserCreationForm #ユーザ作成画面でつかうFormを定義
    
    #ユーザ一覧画面で表示する要素の定義
    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')
        }),
    )
#第一引数をUser,第二引数をCustomizeUserAdminにすることによって
#Userを管理画面から見れるようにして表示方法は自作したCustomizeUserAdminにすることができる
admin.site.register(User,CustomizeUserAdmin)

以下是管理画面的图像。

スクリーンショット 2023-08-06 19.25.27.png

管理面板的自定义- 第一部分

首先,创建基本模型并使表名和数据对象名更易于理解,还要在admin.py中进行注册。

#管理画面のカスタマイズ
class Students(models.Model):
    name=models.CharField(max_length=20)
    age = models.IntegerField()
    score = models.IntegerField()
    school = models.ForeignKey(
        'Schools',on_delete=models.CASCADE
    )
    
    class Meta:
        db_table='students'
        verbose_name_plural='生徒' #管理画面でのテーブルの名前を変更
        
    def __str__(self):
        return self.name + ': '+str(self.age) #管理画面でのオブジェクトの名前を変更
        
class Schools(models.Model):
    name= models.CharField(max_length=20)
    class Meta:
        db_table = 'schools'
        verbose_name_plural='学校' #管理画面でのテーブルの名前を変更
        
    def __str__(self):
        return self.name #管理画面でのオブジェクトの名前を変更

将 admin.py进行注册

admin.site.register(Students)
admin.site.register(Schools)

管理面板的自定义 第2部分 ( de dì èr )

可以在调用admin.site.register函数时将继承于admin.ModelAdmin的类作为第二个参数传入,从而对每个模型的页面进行自定义。或者可以在继承于admin.ModelAdmin的类之前使用@admin.register(模型名)装饰器来实现。

#admin.site.register(Students)
#admin.site.register(Schools) 下記を設定するならコメントアウトしておく
#管理画面での表示の並び替えと表示項目を設定
@admin.register(Students)
class StudentAdmin(admin.ModelAdmin):
    fields = ('name','score','age','school') #並び替えたい順に書く
    list_display = ('id','name','age','score','school') #表示項目の指定
    list_display_links = ('name') #nameをクリックすると詳細に飛ぶリンクを設定
    #scoreを高い順に並べたいときはmodels.pyでMetaクラスにorderingを設定
    search_fields=('name','age',) #検索窓を追加。絞り込みたい項目を設定
    list_filter=('name','age','score','school',) #フィルターを追加
    list_editable = ('age','score','school') #一覧から直接数値を変えることができる

#学校一覧に生徒数も表示したい
@admin.register(Schools)
class SchoolsAdmin(admin.ModelAdmin):
    list_display = ('name','student_count',) #student_countは下記で定義する関数
    
    def student_count(self,obj): #objとはSchoolクラスのインスタンスのこと
        # print(type(obj))
        # print(dir(obj)) #メゾットをみるとstudents_setがある
        count = obj.students_set.count()
        return count
    
    student_count.short_description = '生徒数' #カラム名を変える
    #高校名のカラムを変える場合はmodels.pyにverbose_nameを設定

#models.py 並びかえる時はモデルのクラスMetaに設定
class Meta:
        db_table='students'
        verbose_name_plural='生徒' #管理画面でのテーブルの名前を変更
        ordering=('score',) #管理画面での並び順の設定.降順は-scoreとする
#表示カラム名を変更
class Schools(models.Model):
    name= models.CharField(max_length=20,verbose_name='学校名')

管理界面的定制化,第三部分。

スクリーンショット 2023-08-07 11.55.37.png

点击复制粘贴并进行修改
因为django/django/contrib/admin/templates/admin/base.html文件加载了所有的模板,所以当想要修改某些内容时,可以查看这个文件,从而了解应该编辑哪个文件(在查看这个文件后可以知道,css是通过加载admin/css/base.css来实现的,所以只需要编辑它即可)。

bannerAds