[PYTHON] L'histoire d'avoir un regard doux et douloureux sur les utilisateurs personnalisés sur Django

introduction

J'aimerais améliorer les fonctions d'enregistrement / d'authentification / de connexion des utilisateurs avec Django, et je suis bloqué lorsque je continue, donc je vais garder une trace.

Conclusion

** Lors du développement d'une application avec Django, définissez les exigences et la conception avant de lancer un projet **

** Sauf si cela a déjà été tissé par conception, etc., il est fondamentalement préférable de protéger 1 projet et 1 application jusqu'à ce que Django puisse être développé. ** **

** Même en développant en étudiant, recommencez le projet sans vous fatiguer et ne le réutilisez pas. ** **

Qu'est-il arrivé

Le didacticiel Django est terminé, donc [cet article](https://qiita.com/okoppe8/items/54eb105c9c94c0960f14#%E6%89%8B%E9%A0%86%EF%BC%96%E3%83% 95% E3% 82% A9% E3% 83% BC% E3% 83% A0% E4% BD% 9C% E6% 88% 90) et [cet article](https://narito.ninja/blog/detail Basé sur / 38 /) J'essayais diverses choses pour créer un modèle d'application TODO avec des fonctions d'enregistrement / d'authentification / de connexion des utilisateurs dans Django, mais je détournais le projet du tutoriel car c'était le modèle ʻUser` gênant et Je suis resté coincé dans les spécifications de «SuperUser».

Fonction d'enregistrement des membres dans Django

Lorsque vous utilisez la fonction d'enregistrement d'appartenance avec Django, utilisez essentiellement le modèle ʻUser` fourni par Django. Au contraire, tant que vous introduisez un framework, vous utiliserez probablement le mécanisme d'authentification fourni par le framework, à moins qu'il y ait quelque chose qui ne va pas avec un framework, et qu'il est définitivement intégré. Certains ont des modèles et des méthodes.

Donc, le problème à partir de là est que Django vous permet de gérer plusieurs applications dans un dossier Projet, et inversement, vous pouvez installer la même application dans plusieurs dossiers de projet. Cependant, en fait, un seul modèle ʻUser peut être utilisé par projet. À proprement parler, Django recommande fortement de créer un modèle utilisateur personnalisé basé sur le modèle User pour le déploiement, et il doit être créé lors de la première migration. À ce moment-là, afin de gérer le modèle utilisateur avec la fonction d'authentification, la partie de ʻAUTH_USER_MODEL définie dans settings.py est définie, mais il est plus rapide de redémarrer le projet pour changer cela plus tard. Cela demande du temps et des efforts. Référence

En d'autres termes, si vous souhaitez gérer plusieurs applications avec un seul projet, vous devez décider quelle application aura les fonctions d'authentification lorsque vous lancerez projet.

Je ne suis pas ça ... En d'autres termes, même si j'ai défini la classe User dans le didacticiel, je voulais créer une autre application et y ajouter une fonction d'authentification, alors j'ai lancé project et j'ai juste démarré la classe User. Cela signifie que ce sera difficile à faire.

Cependant, je ne peux pas m'empêcher de faire ce que j'ai fait, donc cette fois j'ai essayé de découvrir ce qui pouvait être fait avec le projet existant, y compris la signification des études ultérieures.

Ce que j'ai fait

--Personnaliser le modèle ʻUser existant ――Créez un nouveau modèle qui étend le modèle ʻUser Créez-le dans le dossier de l'application et associez-le en utilisant ʻOneToOneField. --Modifiez ʻadmin.py sur le côté avec le modèle User pour refléter le modèle User personnalisé et le modèle qui étend le modèle User sur l'écran de gestion.

En outre, à ce stade, le modèle qui utilise le modèle du côté de l'application TODO en tant que fonction et le modèle d'extension ʻUser` ci-dessus (qui remplace également l'enregistrement pour l'indicateur d'authentification de messagerie) sont dans des fichiers séparés.

Et, bien sûr, jouer avec models.py devra refaire faire des migrations, alors faisons-le soigneusement.

Personnalisation du modèle utilisateur existant

2020-05-03_00h05_45.png

Excusez-moi pour l'image car dessiner un diagramme de répertoires est un problème. L'environnement de développement sous src est ʻapp, polls et todo sous le dossier du projet définissent le modèle utilisateur dans chaque application, et l'application que vous voulez créer cette fois est todo`.

Tout d'abord, nous allons personnaliser le corps du modèle ʻUser`. Référence: Personnaliser le modèle utilisateur avec Django

app/models.py

from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.models import PermissionsMixin, UserManager
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.core.validators  import EmailValidator

class CustomUserManager(UserManager):

	use_in_migrations = True

	def _create_user(self,email,password, **extra_fields):
		if not email:
			raise ValueError('The given email must be set')

		email = self.normalize_email(email)
		user = self.model(email=email, **extra_fields)
		user.set_password(password)
		user.save(using=self._db)
		return user

	def create_user(self,email,password=None, **extra_fields):

		extra_fields.setdefault('is_staff', False)
		extra_fields.setdefault('is_superuser', False)
		return self._create_user(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(emai, password, **extra_fields)

class User(AbstractBaseUser, PermissionsMixin):

	username = models.CharField(max_length=30, unique=True)
	email = models.EmailField(_('email address'), unique=True, validators=[EmailValidator('Invalid email address.')])
	first_name = models.CharField(_('first name'), max_length=30, blank=True)
	last_name = models.CharField(_('last name'), max_length=150, blank=True)


	is_staff = models.BooleanField(
		_('staff status'),
		default=False,
		help_text=_(
			'Designates whether this 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 = CustomUserManager()

	EMAIL_FIELD = 'email'
	USERNAME_FIELD = 'email'
	REQUIRED_FIELD = []

	class Meta:
		verbose_name = _('user')
		verbose_name_plural = _('users')

	def email_user(self, subject, message, from_email=None, **kwargs):
		send_mail(subject, message, from_email, [self.email], **kwargs)


class SampleDB(models.Model):
	class Meta:
		db_table = 'sample_table' # tablename
		verbose_name_plural = 'sample_table' # Admintablename
	sample1 = models.IntegerField('sample1', null=True, blank=True) #Numéros de magasin
	sample2 = models.CharField('sample2', max_length=255, null=True, blank=True)


class CustomUserManager(UserManager):

	use_in_migrations = True

	def _create_user(self,email,password, **extra_fields):
		if not email:
			raise ValueError('The given email must be set')

		email = self.normalize_email(email)
		user = self.model(email=email, **extra_fields)
		user.set_password(password)
		user.save(using=self._db)
		return user

	def create_user(self,email,password=None, **extra_fields):

		extra_fields.setdefault('is_staff', False)
		extra_fields.setdefault('is_superuser', False)
		return self._create_user(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(emai, password, **extra_fields)

Cette partie remplace les méthodes de création d'utilisateurs et de super-utilisateurs. En d'autres termes, puisqu'il s'agit de la partie liée à la connexion à l'écran de gestion de Django, j'ai pris la forme de copier la partie de django.contrib.auth.models```UserManager et d'ajouter et de corriger les parties nécessaires. .. La source est ici.


class User(AbstractBaseUser, PermissionsMixin):

	#Élément de base du modèle utilisateur.
	username = models.CharField(max_length=30, unique=True)
	email = models.EmailField(_('email address'), unique=True, validators=[EmailValidator('Invalid email address.')])
	first_name = models.CharField(_('first name'), max_length=30, blank=True)
	last_name = models.CharField(_('last name'), max_length=150, blank=True)

	#admin Une méthode pour déterminer si un utilisateur a accès à un site
	is_staff = models.BooleanField(
		_('staff status'),
		default=False,
		help_text=_(
			'Designates whether this user can log into this admin site.'),
	)

	#Une méthode pour déterminer si un utilisateur est actif
	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 = CustomUserManager()

	#Pour le dire clairement, le champ sans maillages, le champ utilisé comme nom d'utilisateur et le champ à saisir lors de la création d'un super utilisateur sont spécifiés par le haut.
	EMAIL_FIELD = 'email'
	USERNAME_FIELD = 'email'
	REQUIRED_FIELD = []

	class Meta:
		verbose_name = _('user')
		verbose_name_plural = _('users')

	#Méthodes d'envoi d'e-mails
	def email_user(self, subject, message, from_email=None, **kwargs):
		send_mail(subject, message, from_email, [self.email], **kwargs)

Cela hérite également de ʻAbstractBaseUser de django.contrib.auth.models . Puisque PermissionsMixina l'héritage dans la classe d'origine, il est également hérité ici. En tant que rôle, il semble s'agir d'un ensemble de méthodes liées à l'autorité. models.BooleanField définit la valeur par défaut par défaut pour BooleanField dans le champ de modèle et renvoie True, False. Puisque je veux me connecter avec une adresse e-mail au lieu d'un nom d'utilisateur dans la source de référence, je supprime le champ ʻusername et laisse le champ ʻemail jouer ce rôle. Le paramètre est ʻUSERNAME_FIELD = 'email'. Cette fois, j'ai laissé «nom d'utilisateur» car cette application TODO est à usage général et je veux la refactoriser en fonction de cela pour créer quelque chose avec des fonctions supplémentaires.

Au fait


def get_full_name(self):
        """Return the first_name plus the last_name, with a space in
        between."""
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

def get_short_name(self):
    """Return the short name for the user."""
    return self.first_name

#Défini au cas où une autre application accède à l'attribut de nom d'utilisateur, renvoie l'adresse e-mail lors de l'accès
@property
def username(self):
    return self.email



Des éléments tels que sont ajoutés au besoin. Cette fois, je n'utilise pas les champs first_name et last_name, donc je ne les ai pas implémentés, mais je pense que ce serait utile si vous souhaitez créer un formulaire qui vous permet d'enregistrer votre prénom et votre nom.

Créez un nouveau modèle qui étend le modèle utilisateur Créez-le dans le dossier de l'application et utilisez ʻOneToOneField`

todo/models/account.py



from django.conf import settings
from django.db import models
from django.core import validators

class Activate(models.Model):
	user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
	key = models.CharField(max_length=255, blank=True, unique=True)
	expiration_date = models.DateTimeField(blank=True, null=True)

	def __str__(self):
		return self.key


	class Meta:
		verbose_name = 'Indicateur d'authentification de messagerie'
		verbose_name_plural = 'Indicateur d'authentification de messagerie'

Cette fois, créez un modèle qui étend le modèle ʻUser du côté de l'application TODO. Cette fois, en plus des informations du côté du modèle ʻUser, définissez les champs pour l'authentification du courrier. ʻOneToOneField` est un paramètre pour définir une relation un-à-un avec un modèle. Puisqu'il n'y a qu'un seul indicateur d'authentification pour l'utilisateur, il existe une relation un-à-un. Il semble que plusieurs-à-un etc. seront définis par «Clé étrangère».

Après cela, supprimez models.py pour empaqueter le modèle divisé, créez un dossiermodels, créez-y __init __. Py et mettez le modèle divisé dans le même dossier. __init __. py définit comme suit d'importer le modèle à emballer:

todo/models/__init__.py



from .todo import Todo
from .account import Activate

Ceci termine la division du modèle.

Modifiez ʻadmin.py` du côté où se trouve le modèle utilisateur afin de refléter le modèle utilisateur personnalisé et le modèle qui étend le modèle utilisateur sur l'écran de gestion.

Après cela, si vous modifiez ʻadmin.py`, ce sera un paragraphe.

app/admin.py



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
#compte d'application todo.Importer un objet Activate depuis py
from todo.models.account import Activate
from.models import SampleDB

class MyUserChangeForm(UserChangeForm):
    class Meta:
        model = User
        fields = '__all__'


class MyUserCreationForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('email',)

#L'indicateur d'authentification de courrier associé peut être géré sur l'écran de gestion du modèle utilisateur.
class ActivateInline(admin.StackedInline):
    model = Activate
    max_num = 1
    can_delete = False


class MyUserAdmin(UserAdmin):
    fieldsets = (
        (None, {'fields': ('username', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
        (_('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 = ('username','email', 'first_name', 'last_name', 'is_staff')
    list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
    search_fields = ('username','email', 'first_name', 'last_name')
    ordering = ('email',)
    #Spécifiez la classe ActivateInline
    inlines = [ActivateInline]


admin.site.register(User, MyUserAdmin)
admin.site.register(SampleDB)


Empruntez maintenant à django.contrib.auth.admin.py et remplacez-le pour que vous puissiez voir votre modèle d'utilisateur personnalisé dans l'écran d'administration. Le cœur est la partie from todo.models.account import Activate.

todo/admin.py



from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.utils.translation import ugettext_lazy as _
from .models import Todo, Activate
from app.models import User

# Register your models here.

class TodoAdmin(admin.ModelAdmin):

	fields = ['todo_title', 'memo', 'datetime', 'tags']

	list_display = ('todo_title', 'datetime', 'created_at', 'tag_list')
	list_display_links = ('todo_title', 'datetime','tag_list')

	def get_queryset(self, request):
		return super().get_queryset(request).prefetch_related('tags')

	def tag_list(self, obj):
		return u", ".join(o.name for o in obj.tags.all())

	pass

admin.site.register(Todo,TodoAdmin)
admin.site.register(Activate)


De cette façon, le modèle ʻActivate peut également être géré du côté de l'application todo, mais il est plus pratique de le gérer avec le modèle ʻUser car il est lié à la relation, donc importez le modèle avec de todo.models.account import Activate. main, Cela signifie créer une classe ʻInline du côté de la classe User. Pour le moment, au fur et à mesure que nous poursuivons le travail, nous traiterons chaque fois qu'une erreur se produira dans ʻUSERNAME_FIELD = 'email'.

référence

Personnaliser le modèle utilisateur avec Django Django, Personnalisation du modèle utilisateur (OneToOne) Autoriser l'inscription avec le modèle utilisateur personnalisé de Django Créer un utilisateur personnalisé avec Django AbstractBaseUser Personnaliser la méthode d'authentification à partir du document officiel D'après le document officiel django.contrib.auth

Recommended Posts

L'histoire d'avoir un regard doux et douloureux sur les utilisateurs personnalisés sur Django
Une histoire sur Python pop and append
Une histoire sur l'adoption de Django au lieu de Rails dans une jeune startup
Une histoire accro aux variables globales et à la portée de Go
Une histoire sur l'implémentation d'un écran de connexion avec django
Une histoire sur la modification de Python et l'ajout de fonctions
Histoire autour de la maternelle, de l'école maternelle, du jardin d'enfants
Jetez un œil au profilage et au vidage avec Dataflow
Un aperçu rapide de votre profil dans l'appli django
À propos des arguments de filtre personnalisés de Django
Une histoire d'une personne qui voulait importer django depuis le shell interactif de python et enregistrer des choses dans la base de données