pythondjangodjango-models

How to save data into two related tables in Django?


I'm new to Django and I'm following a tutorial to create a school system. I have a CustomUser that is related to three different tables by a OneToOneField(), when registering a user, depending on the type (HOD, teacher, student), I create two signals with @receiver to create an instance of the POST request and then save it in its respective table.

Problem:

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()

If I comment out the line user.Teachers.address=request.POST.get('address'), the user is created without problems and the instance in the Teachers table is created with the address field empty.

What I need is to understand why the error occurs and how I can fix it. I had thought about creating a custom model for each table instead of using a CustomUser and referencing the other tables afterwards, but I don't know how optimal that solution is. Any advice to improve the code is also welcome. Thanks

EDIT: Traceback.

Traceback


Solution

  • You're trying to set attributes on a related model (user.Teachers.address) without first creating an instance of that model.

    When you create a CustomUser instance, the Teachers instance is automatically created by the signal defined in your code. However, the Teachers instance is not accessible directly via user.Teachers until it has been saved and is properly linked.

    The line user.Teachers.address=request.POST.get('address') fails because user.Teachers doesn't yet exist in the way you're trying to use it.

    Instead of trying to set the address directly on the Teachers instance via the CustomUser, you should create or retrieve the Teachers instance after the CustomUser has been saved.

    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')