Why groups are not populated upon `auth_permission`?
Upon Django I made this migration:
# Generated by Django 5.2.17 on 2026-09-19 15:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('blog', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='article',
options={'permissions': [('create_article', 'Permission for a User to create an Article'), ('update_article', 'Permission for a user to edit an article')]},
),
]
But upon auth_persmission the permission create_article and update_article are not populated:
| id | content_type_id | codename | name |
|---|---|---|---|
| 1 | 1 | add_logentry | Can add log entry |
| 2 | 1 | change_logentry | Can change log entry |
| 3 | 1 | delete_logentry | Can delete log entry |
| 4 | 1 | view_logentry | Can view log entry |
| 5 | 2 | add_permission | Can add permission |
| 6 | 2 | change_permission | Can change permission |
| 7 | 2 | delete_permission | Can delete permission |
| 8 | 2 | view_permission | Can view permission |
| 9 | 3 | add_group | Can add group |
| 10 | 3 | change_group | Can change group |
| 11 | 3 | delete_group | Can delete group |
| 12 | 3 | view_group | Can view group |
| 13 | 4 | add_contenttype | Can add content type |
| 14 | 4 | change_contenttype | Can change content type |
| 15 | 4 | delete_contenttype | Can delete content type |
| 16 | 4 | view_contenttype | Can view content type |
| 17 | 5 | add_session | Can add session |
| 18 | 5 | change_session | Can change session |
| 19 | 5 | delete_session | Can delete session |
| 20 | 5 | view_session | Can view session |
| 21 | 6 | add_category | Can add category |
| 22 | 6 | change_category | Can change category |
| 23 | 6 | delete_category | Can delete category |
| 24 | 6 | view_category | Can view category |
| 25 | 7 | add_article | Can add article |
| 26 | 7 | change_article | Can change article |
| 27 | 7 | delete_article | Can delete article |
| 28 | 7 | view_article | Can view article |
| 29 | 8 | add_user | Can add user |
| 30 | 8 | change_user | Can change user |
| 31 | 8 | delete_user | Can delete user |
| 32 | 8 | view_user | Can view user |
Why though??? As far as I understand upon auth_permission the permissions are stored then once I do upon models.py:
class Article(models.Model):
id=models.AutoField(primary_key=True)
title=models.CharField(max_length=255)
content=models.TextField()
slug=models.SlugField()
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
category = models.ManyToManyField(Category)
class Meta:
permissions = [
("create_article","Permission for a User to create an Article"),
("update_article","Permission for a user to edit an article")
]
But the:
python manage.py makemigrations
python manage.py migrate
Does not create the persmission upon db. Do you know why???
I think that the 3 main issues with the current approach:
- No Database Transactions: In case super().create() succeeded, but the profile creation failed (for example, because of constraint violation in professional_title field), you would have a User object in your database without a profile.
- Signal vs Serializer Anti-Pattern: Using an implicit logic through signal (post_save creates the profile) and explicit logic through serializer (serializer updates the profile right away) is an anti-pattern. You end up having extra database queries (INSERT + right away UPDATE), which makes debugging harder.
- WET Code (not DRY): The very same .create() is copied in both CustomUserCreateSerializer and CustomUserCreatePasswordRetypeSerializer.
Thus, in order to solve this problem, you shall do the following two things:
Drop the post_save signal : Since you manage the profile creation manually, drop the post_save signal altogether. Explicit is always better than implicit. Handle it in the serializer (or model manager).
Use a Mixin and transaction.atomic(): Use a mixin in order to not repeat yourself and use an atomic transaction for this creation.
from django.db import transaction
from rest_framework import serializers
from djoser.serializers import UserCreateSerializer, UserCreatePasswordRetypeSerializer
from .models import User, TeacherProfile, StudentProfile
from .serializers import TeacherProfileSerializer, StudentProfileSerializer
class ProfileCreationMixin:
"""Mixin to extract profile data and create profiles atomically."""
def validate(self, attrs):
role = attrs.get('role')
if role == User.TEACHER and not attrs.get('teacher_profile'):
raise serializers.ValidationError({"teacher_profile": "This field is required for teachers."})
if role == User.STUDENT and not attrs.get('student_profile'):
raise serializers.ValidationError({"student_profile": "This field is required for students."})
return super().validate(attrs)
@transaction.atomic
def create(self, validated_data):
teacher_profile_data = validated_data.pop("teacher_profile", None)
student_profile_data = validated_data.pop("student_profile", None)
user = super().create(validated_data)
if user.role == User.TEACHER and teacher_profile_data:
TeacherProfile.objects.create(user=user, **teacher_profile_data)
elif user.role == User.STUDENT and student_profile_data:
StudentProfile.objects.create(user=user, **student_profile_data)
return user
# Apply the mixin to your serializers
class CustomUserCreateSerializer(ProfileCreationMixin, UserCreateSerializer):
teacher_profile = TeacherProfileSerializer(required=False)
student_profile = StudentProfileSerializer(required=False)
class Meta(UserCreateSerializer.Meta):
model = User
fields = (
"id", "email", "password", "role", "first_name", "last_name",
"teacher_profile", "student_profile",
)
class CustomUserCreatePasswordRetypeSerializer(ProfileCreationMixin, UserCreatePasswordRetypeSerializer):
teacher_profile = TeacherProfileSerializer(required=False)
student_profile = StudentProfileSerializer(required=False)
class Meta(UserCreatePasswordRetypeSerializer.Meta):
model = User
fields = (
"id", "email", "password", "re_password", "role", "first_name", "last_name",
"teacher_profile", "student_profile",
)