User модель, login/logout, password hashing, custom user model
Система аутентификации Django — один из самых мощных встроенных компонентов. Она включает модели пользователей, управление сессиями, хеширование паролей и готовые view для входа/выхода.
💡 Правило: Никогда не храните пароли в открытом тексте. Всегда используйте
create_user()илиset_password()для хеширования.
from django.contrib.auth.models import User
# Создание пользователя
user = User.objects.create_user(
username='john',
email='john@example.com',
password='secret123'
)
# Создание суперпользователя
admin = User.objects.create_superuser(
username='admin',
email='admin@example.com',
password='admin123'
)
# Получение пользователя
user = User.objects.get(username='john')
# Проверка пароля
if user.check_password('secret123'):
print('Password is correct')
# Смена пароля
user.set_password('newpassword123')
user.save()| Поле | Описание |
|---|---|
username | Уникальное имя пользователя (max_length=150) |
email | Email адрес |
password | Хешированный пароль |
first_name | Имя |
last_name | Фамилия |
is_active | Активен ли пользователь |
is_staff | Может ли войти в админку |
is_superuser | Полные права (автоматически is_staff=True) |
last_login | Дата последнего входа |
date_joined | Дата регистрации |
from django.contrib.auth import authenticate
# Проверка учетных данных
user = authenticate(username='john', password='secret123')
if user is not None:
# Учетные данные верны
print(f'Welcome, {user.username}!')
else:
# Неверные учетные данные
print('Invalid username or password')from django.contrib.auth import login
from django.shortcuts import redirect, render
def login_view(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('dashboard')
else:
return render(request, 'login.html', {'error': 'Invalid credentials'})
return render(request, 'login.html')from django.contrib.auth import logout
from django.shortcuts import redirect
def logout_view(request):
logout(request)
return redirect('login')from django.contrib.auth.decorators import login_required
@login_required
def dashboard_view(request):
return render(request, 'dashboard.html')
# С кастомными параметрами
@login_required(login_url='/custom-login/', redirect_field_name='next')
def protected_view(request):
return render(request, 'protected.html')from django.contrib.auth.decorators import permission_required
@permission_required('blog.add_post')
def create_post(request):
return render(request, 'post_create.html')
# С обработкой отсутствия прав
@permission_required('blog.delete_post', raise_exception=True)
def delete_post(request, pk):
post = get_object_or_404(Post, pk=pk)
post.delete()
return redirect('post_list')from django.contrib.auth.decorators import user_passes_test
def is_moderator(user):
return user.groups.filter(name='Moderators').exists()
@user_passes_test(is_moderator)
def moderator_dashboard(request):
return render(request, 'moderator.html')
# С кастомным login_url
@user_passes_test(
lambda u: u.is_staff,
login_url='/admin/login/',
redirect_field_name=None # Не сохранять next
)
def staff_view(request):
return render(request, 'staff.html')from django.contrib.auth.views import LoginView
from django.urls import reverse_lazy
class CustomLoginView(LoginView):
template_name = 'accounts/login.html'
redirect_authenticated_user = True # Редирект если уже авторизован
next_page = reverse_lazy('dashboard') # Куда после входа
def get_success_url(self):
# Динамический URL после входа
return self.request.GET.get('next', reverse_lazy('dashboard'))from django.contrib.auth.views import LogoutView
class CustomLogoutView(LogoutView):
template_name = 'accounts/logged_out.html'
next_page = reverse_lazy('login') # Куда после выхода
# Или для редиректа без шаблона
# next_page = '/login/'from django.contrib.auth.views import (
PasswordResetView,
PasswordResetDoneView,
PasswordResetConfirmView,
PasswordResetCompleteView,
)
class CustomPasswordResetView(PasswordResetView):
template_name = 'accounts/password_reset.html'
email_template_name = 'accounts/password_reset_email.html'
subject_template_name = 'accounts/password_reset_subject.txt'
success_url = reverse_lazy('password_reset_done')
class CustomPasswordResetDoneView(PasswordResetDoneView):
template_name = 'accounts/password_reset_done.html'
class CustomPasswordResetConfirmView(PasswordResetConfirmView):
template_name = 'accounts/password_reset_confirm.html'
success_url = reverse_lazy('password_reset_complete')
class CustomPasswordResetCompleteView(PasswordResetCompleteView):
template_name = 'accounts/password_reset_complete.html'# urls.py
from django.urls import path
from django.contrib.auth import views as auth_views
from . import views
urlpatterns = [
# Встроенные views
path('login/', auth_views.LoginView.as_view(template_name='accounts/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(next_page='login'), name='logout'),
path('password-reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
path('password-reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
path('password-reset-confirm/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('password-reset-complete/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
# Кастомные views
path('register/', views.register, name='register'),
path('profile/', views.profile, name='profile'),
]# accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
"""Кастомная модель с дополнительными полями."""
phone = models.CharField(max_length=20, blank=True)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
bio = models.TextField(blank=True)
date_of_birth = models.DateField(null=True, blank=True)
email_verified = models.BooleanField(default=False)
class Meta:
db_table = 'auth_user'
verbose_name = 'user'
verbose_name_plural = 'users'
def __str__(self):
return self.username
def get_full_name(self):
return f'{self.first_name} {self.last_name}'.strip() or self.usernamefrom django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models
class CustomUserManager(BaseUserManager):
"""Менеджер для кастомной модели пользователя."""
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('Email is required')
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_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', 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(email, password, **extra_fields)
class CustomUser(AbstractBaseUser, PermissionsMixin):
"""Полностью кастомная модель пользователя (email вместо username)."""
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
phone = models.CharField(max_length=20, blank=True)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = CustomUserManager()
USERNAME_FIELD = 'email' # Поле для аутентификации
REQUIRED_FIELDS = ['first_name', 'last_name'] # Обязательные поля при создании суперпользователя
class Meta:
db_table = 'users'
verbose_name = 'user'
verbose_name_plural = 'users'
def __str__(self):
return self.email
def get_full_name(self):
return f'{self.first_name} {self.last_name}'.strip() or self.email
def get_short_name(self):
return self.first_name or self.email# settings.py
AUTH_USER_MODEL = 'accounts.CustomUser'
# Настройки аутентификации
LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'dashboard'
LOGOUT_REDIRECT_URL = 'login'
# Настройки паролей
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
]
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {'min_length': 8}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]# accounts/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.contrib.auth import get_user_model
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Layout, Row, Column
User = get_user_model()
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.form_action = 'register'
self.helper.layout = Layout(
Row(
Column('username', css_class='col-md-6'),
Column('email', css_class='col-md-6'),
),
Row(
Column('password1', css_class='col-md-6'),
Column('password2', css_class='col-md-6'),
),
Submit('submit', 'Register', css_class='btn btn-primary btn-block')
)
def clean_email(self):
email = self.cleaned_data.get('email')
if User.objects.filter(email=email).exists():
raise forms.ValidationError('Email already registered')
return email
class ProfileForm(forms.ModelForm):
class Meta:
model = User
fields = ('first_name', 'last_name', 'email', 'phone', 'bio')
widgets = {
'bio': forms.Textarea(attrs={'rows': 4}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
Row(
Column('first_name', css_class='col-md-6'),
Column('last_name', css_class='col-md-6'),
),
'email',
'phone',
'bio',
Submit('submit', 'Update Profile', css_class='btn btn-primary')
)
class PasswordChangeForm(forms.Form):
old_password = forms.CharField(widget=forms.PasswordInput)
new_password1 = forms.CharField(widget=forms.PasswordInput, label='New password')
new_password2 = forms.CharField(widget=forms.PasswordInput, label='Confirm password')
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
'old_password',
'new_password1',
'new_password2',
Submit('submit', 'Change Password', css_class='btn btn-primary')
)
def clean_old_password(self):
old_password = self.cleaned_data.get('old_password')
if not self.user.check_password(old_password):
raise forms.ValidationError('Current password is incorrect')
return old_password
def clean_new_password2(self):
new_password1 = self.cleaned_data.get('new_password1')
new_password2 = self.cleaned_data.get('new_password2')
if new_password1 and new_password2 and new_password1 != new_password2:
raise forms.ValidationError('Passwords do not match')
if len(new_password1) < 8:
raise forms.ValidationError('Password must be at least 8 characters')
return new_password2# accounts/views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import login, logout, update_session_auth_hash
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .forms import RegistrationForm, ProfileForm, PasswordChangeForm
def register(request):
"""Регистрация нового пользователя."""
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
messages.success(request, 'Registration successful!')
return redirect('profile')
else:
form = RegistrationForm()
return render(request, 'accounts/register.html', {'form': form})
@login_required
def profile(request):
"""Просмотр и редактирование профиля."""
if request.method == 'POST':
form = ProfileForm(request.POST, request.FILES, instance=request.user)
if form.is_valid():
form.save()
messages.success(request, 'Profile updated successfully!')
return redirect('profile')
else:
form = ProfileForm(instance=request.user)
return render(request, 'accounts/profile.html', {'form': form})
@login_required
def change_password(request):
"""Смена пароля."""
if request.method == 'POST':
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
request.user.set_password(form.cleaned_data['new_password2'])
request.user.save()
update_session_auth_hash(request, request.user) # Не разлогинивать
messages.success(request, 'Password changed successfully!')
return redirect('profile')
else:
form = PasswordChangeForm(request.user)
return render(request, 'accounts/change_password.html', {'form': form})
def logout_view(request):
"""Выход из системы."""
logout(request)
messages.info(request, 'You have been logged out.')
return redirect('login')Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.