Создание нескольких объектов в одной форме Django
Я пытаюсь создать форму в Django, которая может создавать один объект Student с двумя объектами Contact в одной форме. Второй объект Contact должен быть необязательным для заполнения (не обязательным).
Схематическое представление объектов, созданных в единой форме:
Contact 1
Student <
Contact 2 (not required)
У меня есть следующие модели в models.py:
class User(AbstractUser):
is_student = models.BooleanField(default=False)
is_teacher = models.BooleanField(default=False)
class Student(models.Model):
ACCOUNT_STATUS_CHOICES = (
('A', 'Active'),
('S', 'Suspended'),
('D', 'Deactivated'),
)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
year = models.ForeignKey(Year, on_delete=models.SET_NULL, null=True)
school = models.ForeignKey(School, on_delete=models.SET_NULL, null=True)
student_email = models.EmailField() # named student_email because email conflicts with user email
account_status = models.CharField(max_length=1, choices=ACCOUNT_STATUS_CHOICES)
phone_number = models.CharField(max_length=50)
homework_coach = models.ForeignKey(Teacher, on_delete=models.SET_NULL, null=True, blank=True, default='')
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
plannings = models.ForeignKey(Planning, on_delete=models.SET_NULL, null=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class Contact(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
contact_first_name = models.CharField(max_length=50)
contact_last_name = models.CharField(max_length=50)
contact_phone_number = models.CharField(max_length=50)
contact_email = models.EmailField()
contact_street = models.CharField(max_length=100)
contact_street_number = models.CharField(max_length=10)
contact_zipcode = models.CharField(max_length=30)
contact_city = models.CharField(max_length=100)
def __str__(self):
return f"{self.contact_first_name} {self.contact_last_name}"
В файле forms.py я создал две формы для регистрации студентов и контактов. Студент также подключен к объекту User для входа и аутентификации, но это не имеет значения. Следовательно, когда создается пользователь, он определяется как user.
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from django.db import transaction
from .models import Student, Teacher, User, Year, School, Location, Contact
class StudentSignUpForm(UserCreationForm):
ACCOUNT_STATUS_CHOICES = (
('A', 'Active'),
('S', 'Suspended'),
('D', 'Deactivated'),
)
#student
first_name = forms.CharField(max_length=50, required=True)
last_name = forms.CharField(max_length=50, required=True)
year = forms.ModelChoiceField(queryset=Year.objects.all(), required=False)
school = forms.ModelChoiceField(queryset=School.objects.all(), required=False) # not required for new schools / years that are not yet in the database
student_email = forms.EmailField(required=True)
account_status = forms.ChoiceField(choices=ACCOUNT_STATUS_CHOICES)
phone_number = forms.CharField(max_length=50, required=True)
homework_coach = forms.ModelChoiceField(queryset=Teacher.objects.all(), required=False)
class Meta(UserCreationForm.Meta):
model = User
fields = (
'username',
'first_name',
'last_name',
'year',
'school',
'student_email',
'account_status',
'phone_number',
'homework_coach',
'password1',
'password2',
)
@transaction.atomic
def save(
self,
first_name,
last_name,
year,
school,
student_email,
account_status,
phone_number,
homework_coach,
):
user = super().save(commit=False)
user.is_student = True
user.save()
Student.objects.create( # create student object
user=user,
first_name=first_name,
last_name=last_name,
year=year,
school=school,
student_email=student_email,
account_status=account_status,
phone_number=phone_number,
homework_coach=homework_coach
)
return user
class ContactForm(forms.ModelForm):
contact_first_name = forms.CharField(max_length=50, required=True)
contact_last_name = forms.CharField(max_length=50, required=True)
contact_phone_number = forms.CharField(max_length=50, required=False)
contact_email = forms.EmailField(required=False) # not required because some students might not know contact information
contact_street = forms.CharField(max_length=100, required=False)
contact_street_number = forms.CharField(max_length=10, required=False)
contact_zipcode = forms.CharField(max_length=10, required=False)
contact_city = forms.CharField(max_length=100, required=False)
class Meta:
model = Contact
fields = '__all__'
В файле views.py я создал представление, которое сохраняет данные (пока только данные студента, не контактные данные).
class StudentSignUpView(CreateView):
model = User
form_class = StudentSignUpForm
template_name = 'registration/signup_form.html'
def get_context_data(self, **kwargs):
kwargs['user_type'] = 'student'
return super().get_context_data(**kwargs)
def form_valid(self, form):
# student
first_name = form.cleaned_data.get('first_name')
last_name = form.cleaned_data.get('last_name')
year = form.cleaned_data.get('year')
school = form.cleaned_data.get('school')
student_email = form.cleaned_data.get('student_email')
account_status = form.cleaned_data.get('account_status')
phone_number = form.cleaned_data.get('phone_number')
homework_coach = form.cleaned_data.get('email')
user = form.save(
# student
first_name=first_name,
last_name=last_name,
year=year,
school=school,
student_email=student_email,
account_status=account_status,
phone_number=phone_number,
homework_coach=homework_coach,
)
login(self.request, user)
return redirect('home')
А в файле registration/signup_form.html шаблон выглядит следующим образом:
{% block content %} {% load crispy_forms_tags %}
<form method="POST" enctype="multipart/form-data">
{{ formset.management_data }}
{% csrf_token %}
{{formset|crispy}}
<input type="submit" value="Submit">
</form>
{% endblock %}
Urls.py:
from .views import StudentSignUpView
urlpatterns = [
path('', views.home, name='home'),
path('signup/student/', StudentSignupView.as_view(), name='student_signup'),
]
Как я могу создать одно представление, которое имеет одну форму, создающую 1 объект Student и 2 объекта Contact (из которых 2-й контакт не обязателен)?
То, что я пробовал:
Использование наборов форм для создания нескольких контактов одновременно, но мне удалось создать только несколько Контактов и не удалось добавить Студентов в этот набор форм.
Я добавил это в views.py:
def formset_view(request):
context={}
# creating the formset
ContactFormSet = formset_factory(ContactForm, extra = 2)
formset = ContactFormSet()
# print formset data if it is valid
if formset.is_valid():
for form in formset:
print(form.cleaned_data)
context['formset']=formset
return render(request, 'registration/signup_form.html', context)
Urls.py:
urlpatterns = [
path('', views.home, name='home'),
path('signup/student/', views.formset_view, name='student_signup'),
]
Но мне удалось создать только несколько контактов и не удалось добавить объект Student через эту форму. Я попробовал создать ModelFormSet для добавления полей для объекта Student, но и это не сработало.
Что бы я попробовал:
Я не понимаю вашей магии StudentSignUpForm
. Однако, если это фактически то же самое, что и ModelForm:
class StudentSignUpForm(forms.Modelform):
class Meta:
model = Student
fields = ('first_name', 'last_name', ...)
тогда просто добавьте немодельные поля
contact1_first_name = forms.CharField(max_length=50, required=True)
contact1_last_name = forms.CharField(max_length=50, required=True)
contact1_phone_number = forms.CharField(max_length=50, required=False)
...
contact2_first_name = forms.CharField(max_length=50, required=True)
...
contact2_zipcode = forms.CharField(max_length=10, required=False)
contact2_city = forms.CharField(max_length=100, required=False)
А затем соберите все вместе в form_valid:
@transaction.atomic
def form_valid( self, form):
student = form.save()
contact1 = Contact(
student = student,
contact_first_name = form.cleaned_data['contact1_first_name'],
contact_last_name = ...
)
contact1.save()
if (form.cleaned_data['contact2_first_name'] and
form.cleaned_data['contact2_last_name'] # blank if omitted
):
contact2 = Contact(
student=student,
contact_first_name = form.cleaned_data['contact2_first_name'],
...
)
contact2.save()
return HttpResponseRedirect( ...)
Если вы хотите сделать дополнительную проверку, помимо той, что легко выполняется в определении формы, вы можете:
def form_valid( self, form):
# extra validations, add errors on fail
if (something based on form.cleaned_data[ ...]):
form.add_error( 'field_name', 'error_message')
if ( ...)
form.add_error( ...)
...
if not form.is_valid():
return self.form_invalid( self. form)
with transaction.atomic():
student = form.save()
contact1 = ( ... # as before
Благодаря @nigel222 я прошел долгий путь в создании формы. Однако форма не сохраняла данные в базу данных должным образом, но мне удалось немного изменить представление и форму, и теперь она работает.
Это чертеж о том, как создать два объекта в одной форме в Django. Он создает одну модель Student (связанную с моделью User) и две модели Contact (одна из которых является необязательной).
Forms.py:
Models.py:
class Student(models.Model):
ACCOUNT_STATUS_CHOICES = (
('A', 'Active'),
('S', 'Suspended'),
('D', 'Deactivated'),
)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
year = models.ForeignKey(Year, on_delete=models.SET_NULL, null=True, blank=True, default='')
school = models.ForeignKey(School, on_delete=models.SET_NULL, null=True, blank=True, default='')
student_email = models.EmailField() # named student_email because email conflicts with user email
account_status = models.CharField(max_length=1, choices=ACCOUNT_STATUS_CHOICES, default='A')
phone_number = models.CharField(max_length=50)
homework_coach = models.ForeignKey(Teacher, on_delete=models.SET_NULL, null=True, blank=True, default='')
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
plannings = models.ForeignKey(Planning, on_delete=models.SET_NULL, null=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class Contact(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
contact_first_name = models.CharField(max_length=50)
contact_last_name = models.CharField(max_length=50)
contact_phone_number = models.CharField(max_length=50)
contact_email = models.EmailField()
contact_street = models.CharField(max_length=100)
contact_street_number = models.CharField(max_length=10)
contact_zipcode = models.CharField(max_length=30)
contact_city = models.CharField(max_length=100)
def __str__(self):
return f"{self.contact_first_name} {self.contact_last_name}"
Urls.py:
urlpatters = [
path('signup/student/', views.student_signup, name='student_signup'),
]
Views.py:
def student_signup(request):
if request.method == 'POST':
student_signup_form = StudentSignUpForm(request.POST)
if student_signup_form.is_valid():
student_signup_form.form_valid(student_signup_form)
return redirect('home')
else:
return HttpResponse("Form is not valid")
else:
student_signup_form = StudentSignUpForm()
return render(request, 'registration/signup_form.html', {'student_signup_form': student_signup_form})