Как сохранить данные в двух связанных таблицах в 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.
В своем ответе я переименовываю модель 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.