Permissions, Groups, decorators, mixins, object-level permissions
Система авторизации Django управляет правами доступа пользователей к функциям приложения. Permissions определяют что пользователь может делать, Groups упрощают управление правами.
💡 Правило: Назначайте права через группы, а не напрямую пользователям. Это упрощает управление доступом при масштабировании.
Django автоматически создаёт 4 разрешения для каждой модели:
| Permission | Codename | Описание |
|---|---|---|
| Can view | view_model | Просмотр объектов |
| Can add | add_model | Создание объектов |
| Can change | change_model | Редактирование объектов |
| Can delete | delete_model | Удаление объектов |
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from blog.models import Post
# Получение content type для модели
post_ct = ContentType.objects.get_for_model(Post)
# Получение permissions
add_perm = Permission.objects.get(codename='add_post')
change_perm = Permission.objects.get(codename='change_post')
delete_perm = Permission.objects.get(codename='delete_post')
view_perm = Permission.objects.get(codename='view_post')
# Все permissions для Post
perms = Permission.objects.filter(content_type=post_ct)# models.py
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
is_published = models.BooleanField(default=False)
class Meta:
permissions = [
('can_publish', 'Can publish posts'),
('can_moderate', 'Can moderate comments'),
('can_feature', 'Can feature posts'),
]После makemigrations создадутся новые permissions в БД.
from django.contrib.auth.models import Group, Permission
# Создание группы
editors = Group.objects.create(name='Editors')
moderators = Group.objects.create(name='Moderators')
# Добавление permissions в группу
add_perm = Permission.objects.get(codename='add_post')
change_perm = Permission.objects.get(codename='change_post')
editors.permissions.add(add_perm, change_perm)
moderators.permissions.add(change_perm, delete_perm)
# Назначение группы пользователю
user.groups.add(editors)
user.groups.add(moderators)
# Проверка принадлежности к группе
if user.groups.filter(name='Editors').exists():
print('User is an editor')
# Удаление из группы
user.groups.remove(editors)
# Очистка всех групп
user.groups.clear()# Все permissions пользователя (включая групповые)
user.get_all_permissions()
# {'blog.add_post', 'blog.change_post', ...}
# Проверка конкретного разрешения
user.has_perm('blog.add_post') # True/False
# Проверка нескольких разрешений
user.has_perms(['blog.add_post', 'blog.change_post'])
# Permissions без групповых
user.get_user_permissions()
# Только групповые permissions
user.get_group_permissions()from django.contrib.auth.decorators import permission_required, user_passes_test
# Проверка одного разрешения
@permission_required('blog.add_post')
def create_post(request):
return render(request, 'post_create.html')
# Проверка с кастомной обработкой
@permission_required('blog.delete_post', login_url='/login/', redirect_field_name='next')
def delete_post(request, pk):
post = get_object_or_404(Post, pk=pk)
post.delete()
return redirect('post_list')
# raise_exception=True возвращает 403 вместо редиректа
@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')
# Проверка нескольких разрешений
@permission_required(['blog.add_post', 'blog.change_post'])
def edit_post(request, pk):
post = get_object_or_404(Post, pk=pk)
return render(request, 'post_edit.html', {'post': post})
# Кастомная проверка
def is_editor(user):
return user.groups.filter(name='Editors').exists()
@user_passes_test(is_editor)
def editor_dashboard(request):
return render(request, 'editor_dashboard.html')from django.contrib.auth.mixins import (
LoginRequiredMixin,
PermissionRequiredMixin,
UserPassesTestMixin,
)
from django.views.generic import CreateView, UpdateView, DeleteView
# Проверка одного разрешения
class PostCreateView(PermissionRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
permission_required = 'blog.add_post'
# Кастомная обработка отсутствия прав
def handle_no_permission(self):
if self.raise_exception:
raise PermissionDenied(self.get_permission_denied_message())
return redirect('login')
# Проверка нескольких разрешений
class PostManageView(PermissionRequiredMixin, UpdateView):
model = Post
fields = ['title', 'content']
permission_required = ['blog.change_post', 'blog.publish_post']
# Кастомная проверка
class EditorOnlyView(UserPassesTestMixin, View):
def test_func(self):
return self.request.user.groups.filter(name='Editors').exists()
def get(self, request):
return render(request, 'editor_only.html')
# Комбинирование mixins
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
success_url = reverse_lazy('post_list')
def test_func(self):
# Только автор или модератор может удалить
post = self.get_object()
user = self.request.user
return (
post.author == user or
user.groups.filter(name='Moderators').exists() or
user.is_superuser
)from django.core.exceptions import PermissionDenied
def post_edit(request, pk):
post = get_object_or_404(Post, pk=pk)
# Object-level проверка
if post.author != request.user and not request.user.has_perm('blog.change_post'):
raise PermissionDenied('You cannot edit this post')
# Обработка формы
if request.method == 'POST':
form = PostForm(request.POST, instance=post)
if form.is_valid():
form.save()
return redirect('post_detail', pk=post.pk)
return render(request, 'post_edit.html', {'form': form, 'post': post})class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
fields = ['title', 'content']
def test_func(self):
post = self.get_object()
# Только автор может редактировать
return post.author == self.request.user
def get_permission_denied_message(self):
return 'You can only edit your own posts'pip install django-guardian# settings.py
INSTALLED_APPS = [
# ...
'guardian',
]
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend', # Default
'guardian.backends.ObjectPermissionBackend', # Guardian
]# models.py
from guardian.models import GroupObjectPermissionBase, UserObjectPermissionBase
class Post(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
permissions = [
('can_publish', 'Can publish post'),
]
class PostUserObjectPermission(UserObjectPermissionBase):
content_object = models.ForeignKey(Post, on_delete=models.CASCADE)
class PostGroupObjectPermission(GroupObjectPermissionBase):
content_object = models.ForeignKey(Post, on_delete=models.CASCADE)# Использование guardian
from guardian.shortcuts import assign_perm, remove_perm, get_objects_for_user
# Назначение права на объект
assign_perm('blog.change_post', user, post)
assign_perm('blog.delete_post', group, post)
# Проверка права на объект
user.has_perm('blog.change_post', post) # True/False
# Получение объектов с правами
posts = get_objects_for_user(user, 'blog.change_post')
# Удаление права
remove_perm('blog.change_post', user, post)# admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User, Group
from .models import Post
# Inline для permissions пользователя
class UserPermissionsInline(admin.TabularInline):
model = User.user_permissions.through
extra = 0
verbose_name = 'Permission'
verbose_name_plural = 'Permissions'
# Inline для групп пользователя
class UserGroupsInline(admin.TabularInline):
model = User.groups.through
extra = 0
verbose_name = 'Group'
verbose_name_plural = 'Groups'
@admin.register(User)
class UserAdmin(BaseUserAdmin):
inlines = [UserGroupsInline, UserPermissionsInline]
list_display = ['username', 'email', 'is_staff', 'is_active']
list_filter = ['is_staff', 'is_superuser', 'is_active', 'groups']
@admin.register(Group)
class GroupAdmin(admin.ModelAdmin):
filter_horizontal = ['permissions'] # Горизонтальный фильтр для permissions
list_display = ['name', 'permission_count']
def permission_count(self, obj):
return obj.permissions.count()
permission_count.short_description = 'Permissions'# blog/models.py
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
STATUS_CHOICES = [
('draft', 'Draft'),
('pending', 'Pending Review'),
('published', 'Published'),
]
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
permissions = [
('can_publish', 'Can publish posts'),
('can_moderate', 'Can moderate content'),
('can_feature', 'Can feature posts'),
]
ordering = ['-created_at']
def __str__(self):
return self.title
def can_edit(self, user):
"""Проверка может ли пользователь редактировать пост."""
return (
self.author == user or
user.has_perm('blog.change_post') or
user.is_superuser
)
def can_delete(self, user):
"""Проверка может ли пользователь удалить пост."""
return (
self.author == user or
user.has_perm('blog.delete_post') or
user.is_superuser
)
def can_publish(self, user):
"""Проверка может ли пользователь опубликовать пост."""
return user.has_perm('blog.can_publish') or user.is_superuser# blog/views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.auth.decorators import permission_required
from django.views.generic import CreateView, UpdateView, DeleteView
from django.core.exceptions import PermissionDenied
from .models import Post
from .forms import PostForm
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def test_func(self):
post = self.get_object()
return post.can_edit(self.request.user)
def get_permission_denied_message(self):
return 'You do not have permission to edit this post.'
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
template_name = 'blog/post_confirm_delete.html'
success_url = '/posts/'
def test_func(self):
post = self.get_object()
return post.can_delete(self.request.user)
class PostPublishView(LoginRequiredMixin, UserPassesTestMixin, View):
"""Публикация поста (требует специального разрешения)."""
def test_func(self):
return self.request.user.has_perm('blog.can_publish')
def post(self, request, pk):
post = get_object_or_404(Post, pk=pk)
post.status = 'published'
post.save()
return redirect('post_detail', pk=post.pk)
@permission_required('blog.can_moderate')
def moderation_queue(request):
"""Очередь модерации для постов."""
posts = Post.objects.filter(status='pending').order_by('created_at')
return render(request, 'blog/moderation_queue.html', {'posts': posts})# accounts/management/commands/setup_groups.py
from django.core.management.base import BaseCommand
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from blog.models import Post
class Command(BaseCommand):
help = 'Setup default groups and permissions'
def handle(self, *args, **kwargs):
# Получаем content type для Post
post_ct = ContentType.objects.get_for_model(Post)
# Получаем все permissions для Post
add_perm = Permission.objects.get(codename='add_post', content_type=post_ct)
change_perm = Permission.objects.get(codename='change_post', content_type=post_ct)
delete_perm = Permission.objects.get(codename='delete_post', content_type=post_ct)
view_perm = Permission.objects.get(codename='view_post', content_type=post_ct)
publish_perm = Permission.objects.get(codename='can_publish', content_type=post_ct)
moderate_perm = Permission.objects.get(codename='can_moderate', content_type=post_ct)
# Создаём группы
authors, _ = Group.objects.get_or_create(name='Authors')
editors, _ = Group.objects.get_or_create(name='Editors')
moderators, _ = Group.objects.get_or_create(name='Moderators')
# Назначаем permissions
authors.permissions.set([add_perm, change_perm, view_perm])
editors.permissions.set([add_perm, change_perm, delete_perm, view_perm, publish_perm])
moderators.permissions.set([view_perm, moderate_perm, change_perm])
self.stdout.write(
self.style.SUCCESS('Successfully setup groups and permissions')
)Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.