Как составлять username из других полей?

Я пытаюсь зарегистрировать расширенного пользователя:

class Teacher(AbstractUser):
    name = models.CharField(max_length=20, unique=False)
    surname = models.CharField(max_length=20)
    email = models.EmailField(max_length=255, unique=True, blank=True)
    USERNAME_FIELD = 'email'
    user = models.OneToOneField(settings.AUTH_USER_MODEL, null=True,
        on_delete=models.CASCADE)
    subject = models.ForeignKey(Subject, related_name='teacher_subject',
        null=True, on_delete=models.CASCADE)
    password = models.CharField(max_length=50)
    REQUIRED_FIELDS = ['name', 'surname', 'password']
    username = None

Но получаю ошибку

UNIQUE constraint failed: me_teacher.username

Как видно из модели, я попытался использовать email вместо username, но это не помогло. Поэтому я хочу создавать его из имени и фамилии пользователя, но как мне это сделать?


Ответы (1 шт):

Автор решения: Za Ars

Думаю, для создания уникального username от ФИ можно, используя slugify (from django.utils.text import slugify) и кол-во записей или номер последней записи.

Вот примерное решение через сигналы

@receiver(pre_save, dispatch_uid="teachers.username_gen")
def username_gen(sender, instance, **kwargs):
    if sender != Teachers:
        return
    if not instance.username:
        return
    counter = 0
    username = None
    while True: 
        new_username = f'{slugify(instance.surname)}_{slugify(instance.name)}'
        if username:
            username = f'{new_username}_{rand.randint(0,1000)}'
        if not Teachers.objects.filter(username=username).exists():
            break
    instance.username = username


UPDATE:

А вообще я только ща заметил у вас строку user = models.OneToOneField(settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)

Ее не должно быть, раз вы используете Teacher как основную модель. Приложите settings.py , часть которая отвечает за настройки модели пользователей


UPDATE:

Нашел проблему благодаря коду с гитхаба. Вам нужно переопределить менеджер объектов, примерно так (это рабочий вариант, но можно улучшить)

from django.apps import apps
from django.contrib import auth
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.hashers import make_password

class TeachersManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        """
        Create and save a user with the given username, email, and password.
        """
        if not email:
            raise ValueError('The given username must be set')
        email = self.normalize_email(email)
        # Lookup the real model class from the global app registry so this
        # manager method can be used in migrations. This is fine because
        # managers are by definition working on the real model.
        GlobalUserModel = apps.get_model(self.model._meta.app_label, self.model._meta.object_name)
        user = self.model(email=email, **extra_fields)
        user.password = make_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email=None, 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=None, password=None, **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(email, password, **extra_fields)

    def with_perm(self, perm, is_active=True, include_superusers=True, backend=None, obj=None):
        if backend is None:
            backends = auth._get_backends(return_tuples=True)
            if len(backends) == 1:
                backend, _ = backends[0]
            else:
                raise ValueError(
                    'You have multiple authentication backends configured and '
                    'therefore must provide the `backend` argument.'
                )
        elif not isinstance(backend, str):
            raise TypeError(
                'backend must be a dotted import path string (got %r).'
                % backend
            )
        else:
            backend = auth.load_backend(backend)
        if hasattr(backend, 'with_perm'):
            return backend.with_perm(
                perm,
                is_active=is_active,
                include_superusers=include_superusers,
                obj=obj,
            )
        return self.none()


class Teacher(AbstractUser):

    objects = TeachersManager() # <==== Вот это не забываем добавить

    name = models.CharField(max_length=20, unique=False)
    surname = models.CharField(max_length=20)
    email = models.EmailField(max_length=255, unique=True, blank=True)
    USERNAME_FIELD = 'email'
    #user = models.OneToOneField(settings.AUTH_USER_MODEL, null=True,
    #    on_delete=models.CASCADE)
    subject = models.ForeignKey(Subject, related_name='teacher_subject',
        null=True, on_delete=models.CASCADE)
    password = models.CharField(max_length=50)
    REQUIRED_FIELDS = ['name', 'surname', 'password']

    @receiver(pre_save, dispatch_uid="teacher.username_gen")
    def username_gen(sender, instance, **kwargs):
        if sender != Teacher:
            return
        if not instance.username:
            return
        counter = 0
        username = None
        while True:
            new_username = f'{slugify(instance.surname)}_{slugify(instance.name)}'
            if username:
                username = f'{new_username}_{rand.randint(0,1000)}'
            if not Teacher.objects.filter(username=username).exists():
                break
        instance.username = username
→ Ссылка