I'd like to improve the user registration / authentication / login functions in Django, and I'm stuck when I'm proceeding, so I'll keep a record.
** When developing an app with Django, define and design the requirements before launching the project **
** Unless it's already woven into the design, it's basically better to protect 1 project and 1 app until you can develop Django. ** **
** Even when developing by studying, start the project again without getting tired and do not reuse it. ** **
The Django tutorial is over, so [this 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) and [this article](https://narito.ninja/blog/detail Based on / 38 /)
I was trying to make something like a template of a TODO application with user registration, authentication, and login functions in Django, but I was diverting the tutorial project because it was troublesome. I'm stuck around the specifications of SuperUser
.
If you want to use the membership registration feature in Django, basically use the ʻUser` model provided by Django. Rather, as long as you introduce a framework, you will probably use the authentication mechanism provided by the framework, unless there is something wrong with any framework, and it is definitely built-in. Some have models and methods.
So, the problem is from here, but with Django, you can manage multiple applications in one Project folder, and conversely, you can install the same application in multiple project folders.
However, in fact, only one ʻUser model can be used per project. Strictly speaking, Django strongly recommends creating a custom user model based on the User model for deployment, and it must be created by the first migration. At that time, in order to handle the User model with the authentication function, the part of ʻAUTH_USER_MODEL
set in settings.py
is set, but it is faster to restart the project to change this later. It takes time and effort.
Reference
In other words, if you want to manage multiple apps with one project
, you have to decide which application should have the function around authentication when you launch project
.
I don't follow this ... In other words, even though I defined the User class in the tutorial, I wanted to create another application and add an authentication function there. It means that it will be difficult to make.
However, there is nothing I can do about it, so this time I tried to find out if the existing project
could be used with the meaning of being for later study.
--Customize the existing ʻUser model ――Create a new model that extends the ʻUser
model Create it in the application folder and relate it using ʻOneToOneField. --Edit ʻadmin.py
on the side where the User model is located to reflect the custom User model and the model that extends the User model on the management screen.
Also, at this time, the model that uses the model on the TODO application side as a function and the above ʻUser` extension model (which also overrides the record for the mail authentication flag) are in separate files.
And, of course, messing around with models.py
will have to redo make migrations
, so let's do it carefully.
Excuse me for the image because drawing a directory diagram is a hassle.
Under src is the development environment, and ʻapp,
polls, and
todounder the project folder define the User model in each application, and the application you want to create this time is
todo`.
First of all, we will customize the ʻUser` model body. Reference: Customize User model with Django
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) #Store numbers
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)
This part overrides the methods used to create users and superusers.
In other words, this is the part related to logging in to the Django management screen, so I copied and pasted the ʻUserManager part of
django.contrib.auth.models `and corrected the necessary parts. ..
The source is here.
class User(AbstractBaseUser, PermissionsMixin):
#Basic items of the User model.
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 A method to determine if a user has access to a site
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_(
'Designates whether this user can log into this admin site.'),
)
#A method to determine if a user is active
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()
#To put it plainly, the mail dress field, the field used as the user name, and the field that must be entered when creating a superuser are specified from the top.
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
REQUIRED_FIELD = []
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
#Methods for sending emails
def email_user(self, subject, message, from_email=None, **kwargs):
send_mail(subject, message, from_email, [self.email], **kwargs)
This also inherits ʻAbstractBaseUser from
django.contrib.auth.models . Since
PermissionsMixinhas inheritance in the original class, it is also inherited here. As a role, it seems to be a collection of methods related to authority.
models.BooleanField sets the
defaultdefault value for
BooleanField in the model field and returns True, False. In the reference source, I want to log in with an email address instead of a user name, so I delete the ʻusername
field and let the ʻemail field play that role. The setting is ʻUSERNAME_FIELD ='email'
.
This time, I left ʻusername` because this TODO app is general purpose and I want to refactor it based on this and add functions.
By the way
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
#Defined in case another application accesses the username attribute, returns the email address when accessed
@property
def username(self):
return self.email
Items such as are added as needed.
This time, the first_name
and last_name
fields are not used, so they are not implemented, but I think it would be convenient if you create a form that allows you to register your first and last name.
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 = 'Email authentication flag'
verbose_name_plural = 'Email authentication flag'
This time, create a model that extends the ʻUser model on the TODO application side. This time, in addition to the information on the ʻUser
model side, define the fields for email authentication.
ʻOneToOneFieldis a setting for defining a one-to-one relationship with a model. Since there is only one authentication flag for the user, there is a one-to-one relationship. It seems that many-to-one etc. will be defined by
Foreign Key`.
After that, delete models.py
to package the split model, create amodels
folder, create __init__.py
in it, and put the split model in the same folder.
__init__.py
defines to import the model to be packaged as follows:
todo/models/__init__.py
from .todo import Todo
from .account import Activate
This completes the model split.
After that, if you modify ʻadmin.py`, it will be a paragraph.
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
#todo application account.Import Activate object from 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',)
#The related mail authentication flag can be handled on the management screen of User model.
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',)
#Specify the ActivateInline class
inlines = [ActivateInline]
admin.site.register(User, MyUserAdmin)
admin.site.register(SampleDB)
Now borrow from django.contrib.auth.admin.py
and override this so you can see your custom user model in the admin screen.
The liver is the part of 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)
In this way, the ʻActivate model can be managed on the todo application side as well, but it is more convenient to manage it with the ʻUser
model because it is related to the relationship, so import the model with from todo.models.account import Activate
. hand,
It means creating a ʻInline class on the User class side. For the time being, as we proceed with the work, we will deal with each time an error occurs in ʻUSERNAME_FIELD ='email'
.
Customize User Model with Django Django, User Model Customization (OneToOne) Allow Django to sign up with custom user model (https://hodalog.com/how-to-create-user-sign-up-view/) Create a custom user with Django AbstractBaseUser (https://noumenon-th.net/programming/2019/12/13/abstractbaseuser/) Customize authentication method from official document From the official documentation django.contrib.auth
Recommended Posts