Как сохранить данные в двух связанных таблицах в Django?

Я новичок в Django и следую учебнику по созданию школьной системы. У меня есть CustomUser, который связан с тремя различными таблицами с помощью OneToOneField(), при регистрации пользователя, в зависимости от типа (HOD, учитель, ученик), я создаю два сигнала с @receiver, чтобы создать экземпляр запроса POST и затем сохранить его в соответствующей таблице.

Проблема:

  • Когда я пытаюсь сохранить данные, я использую объект CustomUser и вызываю класс create_user(), который я сохраняю в переменной пользователя, но когда я использую этот объект для сохранения других данных из нужной мне таблицы (в данном случае Teachers), пользователь не создается:
def create_teachers(request):
    if auth_user(request) == True:
        # Do something for authenticated users.
        if request.method == 'GET':
            return render(request, 'create_teachers.html')
        else:
            try:
                user = CustomUser.objects.create_user(
                    password='defaultPass',
                    first_name=request.POST.get('first_name'),
                    last_name=request.POST.get('last_name'),
                    email=request.POST.get('email'),
                    user_type=2,
                )
                user.Teachers.address=request.POST.get('address')
                user.save()
                print('Professor created successfully')
                return redirect('/auth/create_teachers')
            except:
                print('Error creating teacher')
                return redirect('/auth/create_teachers')
    else:
        return redirect('../login')
Error creating teacher
[27/Sep/2024 13:46:14] "POST /auth/create_teachers HTTP/1.1" 302 0
[27/Sep/2024 13:46:14] "GET /auth/create_teachers HTTP/1.1" 200 34724
[27/Sep/2024 13:46:14] "GET /static/css/dist/styles.css?v=1727459174 HTTP/1.1" 200 54543
# admin.py
class UserAdmin(BaseUserAdmin):
    ordering = ('email',)

admin.site.register(CustomUser, UserAdmin)
# managers.py
class CustomUserManager(BaseUserManager):
    """
    Custom user model manager where email is the unique identifiers
    for authentication instead of usernames.
    """
    def create_user(self, email, password, **extra_fields):
        """
        Create and save a user with the given email and password.
        """
        if not email:
            raise ValueError(_("The Email must be set"))
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, **extra_fields):
        """
        Create and save a SuperUser with the given email and password.
        """
        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)
# models.py
class CustomUser(AbstractUser):
    username = None
    email = models.EmailField(_("email address"), unique=True)
    user_type_data=((1,'HOD'),(2,'staff'),(3,'users'))
    user_type=models.CharField(default=1,choices=user_type_data,max_length=10)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

    def __str__(self):
        return self.email

class AdminHOD(models.Model):
    id=models.AutoField(primary_key=True)
    admin=models.OneToOneField(CustomUser,on_delete=models.CASCADE)
    created_at=models.DateTimeField(auto_now_add=True)
    updated_at=models.DateTimeField(auto_now_add=True)
    objects=models.Manager()

class Teachers(models.Model):
    id=models.AutoField(primary_key=True)
    admin=models.OneToOneField(CustomUser,on_delete=models.CASCADE)
    phone=PhoneNumberField()
    address=models.TextField()
    course_id=models.ManyToManyField(Courses)
    subject_id=models.ManyToManyField(Subjects)
    section_id=models.ManyToManyField(Sections)
    created_at=models.DateTimeField(auto_now_add=True)
    updated_at=models.DateTimeField(auto_now_add=True)

class Students(models.Model):
    id=models.AutoField(primary_key=True)
    admin=models.OneToOneField(CustomUser,on_delete=models.CASCADE)
    idCard=models.PositiveIntegerField(unique=True)
    age=models.PositiveSmallIntegerField()
    gender=models.CharField(max_length=255,)
    phone=PhoneNumberField()
    address=models.TextField()
    profile_pic=models.FileField()
    session_start=models.DateField()
    session_end=models.DateField()
    course_id=models.ForeignKey(Courses,on_delete=models.SET_DEFAULT, default=1)
    sections_id=models.ForeignKey(Sections,on_delete=models.SET_DEFAULT, default=1)
    created_at=models.DateTimeField(auto_now_add=True)
    updated_at=models.DateTimeField(auto_now_add=True)

# Receive a signal to create a new user with certain type
@receiver(post_save,sender=CustomUser)
def create_user_profile(sender,instance,created,**kwargs):
    if created:
        if instance.user_type == 1:
            AdminHOD.objects.create(admin=instance)

        if instance.user_type == 2:
            Teachers.objects.create(admin=instance)
            
        if instance.user_type == 3:
            Students.objects.create(admin=instance)

# Save the user
@receiver(post_save,sender=CustomUser)
def save_user_profile(sender,instance,**kwargs):
    if instance.user_type == 1:
        instance.adminhod.save()

    if instance.user_type == 2:
        instance.teachers.save()

    if instance.user_type == 3:
        instance.students.save()

Если я закомментирую строку user.Teachers.address=request.POST.get('address'), пользователь будет создан без проблем, а экземпляр в таблице Teachers будет создан с пустым полем адреса.

Мне нужно понять, почему возникает ошибка и как я могу ее исправить. Я думал о создании пользовательской модели для каждой таблицы вместо использования CustomUser и последующего обращения к другим таблицам, но я не знаю, насколько оптимально такое решение. Любые советы по улучшению кода также приветствуются. Спасибо

EDIT: Traceback.

Traceback

В своем ответе я переименовываю модель Teachers в Teacher, поскольку использование множественных имен таблиц в данном случае приводит к путанице.

Чтобы получить доступ к связанному полю модели, необходимо использовать поле related_name:

# the default related name for a `OneToOneField` is the lowercase name of the model

class Teacher(models.Model):
    id=models.AutoField(primary_key=True)
    admin=models.OneToOneField(
        CustomUser,
        on_delete=models.CASCADE,
        related_name='teacher'
    )
    ...

Теперь в представлении можно создать связанный объект Teacher с помощью related_name:

user.teacher.address=request.POST.get('address')
user.teacher.save()

Вы пытаетесь установить атрибуты для связанной модели (user.Teachers.address) без предварительного создания экземпляра этой модели.

Когда вы создаете экземпляр CustomUser, экземпляр Teachers автоматически создается по сигналу, определенному в вашем коде. Однако экземпляр Teachers недоступен напрямую через user.Teachers до тех пор, пока он не будет сохранен и правильно связан.

Строка user.Teachers.address=request.POST.get('address') не работает, потому что user.Teachers еще не существует в том виде, в котором вы пытаетесь ее использовать.

Вместо того чтобы пытаться установить адрес непосредственно на экземпляре Teachers через CustomUser, следует создать или получить экземпляр Teachers после сохранения CustomUser.

def create_teachers(request):
    if auth_user(request) == True:
        if request.method == 'GET':
            return render(request, 'create_teachers.html')
        else:
            try:
                user = CustomUser.objects.create_user(
                    password='defaultPass',
                    first_name=request.POST.get('first_name'),
                    last_name=request.POST.get('last_name'),
                    email=request.POST.get('email'),
                    user_type=2,
                )

                # Now retrieve the Teachers instance created by the signal
                teacher_instance = Teachers.objects.get(admin=user)
                teacher_instance.address = request.POST.get('address')
                teacher_instance.save()

                print('Professor created successfully')
                return redirect('/auth/create_teachers')
            except Exception as e:
                print(f'Error creating teacher: {e}')
                return redirect('/auth/create_teachers')
    else:
        return redirect('../login')

Во-первых, как я вижу, в вашем классе CustomUser нет ни одного атрибута или поля Teachers. У вас есть CustomUser со следующими атрибутами:

class CustomUser(AbstractUser):
    username = None
    email = models.EmailField(_("email address"), unique=True)
    user_type_data=((1,'HOD'),(2,'staff'),(3,'users'))
    user_type=models.CharField(default=1,choices=user_type_data,max_length=10)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

Где находится атрибут Teachers в объекте экземпляра пользователя, который вы вызываете в следующем коде:

user.Teachers.address=request.POST.get('address')

Как мы видим в коде, у вас есть класс Teachers (чуть ниже класса AdminHOD и выше класса Students), который в вашем коде независим от класса пользователя CustomUser, но нет атрибута Teachers.

То есть вы вызываете что-то от пользователя (я имею в виду экземпляр), который не существует.

Он был объявлен в опубликованном вами образе трассировки: 'CustomUser' object has no attribute 'Teachers'.

Возможно, лучше сначала сохранить пользователя, а затем отредактировать связанного учителя или сделать атрибут Teacher в классе CustomUser.

Вернуться на верх