I am trying to create a form in Django that can create one Student object with two Contact objects in the same form. The second Contact object must be optional to fill in (not required).
Schematic view of the objects created in the single form:
Contact 1
Student <
Contact 2 (not required)
I have the following models in 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}"
In forms.py, I have created two forms to register students and contacts. A student is also connected to a User object for login and authentication, but this is not relevant. Hence, when a user is created, the user is defined as the 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__'
In views.py, I have created a view that saves the data (so far only student data, not contact data).
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')
And in registration/signup_form.html, the template is as follows:
{% 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'),
]
How can I create one view that has one form that creates 1 Student object and 2 Contact objects (of which the 2nd Contact is not required)?
Things I have tried:
Using formsets to create multiple contacts at once, but I only managed to create multiple Contacts and could not manage to add Students to that formset.
I added this to 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'),
]
But I only managed to create multiple Contacts and was not able to add a Student object through that form. I tried creating a ModelFormSet to add fields for the Student object, but that did not work either.
######## EDIT ##########
Following #nigel222's answer, I have now created the following form:
class StudentSignUpForm(forms.ModelForm):
class Meta:
model = Student
fields = (
'username',
'email',
'first_name',
'last_name',
'year',
'school',
'student_email',
'account_status',
'phone_number',
'homework_coach',
'password1',
'password2',
'contact1_first_name',
'contact1_last_name',
'contact1_phone_number',
'contact1_email',
'contact1_street',
'contact1_street_number',
'contact1_zipcode',
'contact1_city',
'contact2_first_name',
'contact2_last_name',
'contact2_phone_number',
'contact2_email',
'contact2_street',
'contact2_street_number',
'contact2_zipcode',
'contact2_city',
)
# user
username = forms.CharField(label='Username', min_length=5, max_length=150)
email = forms.EmailField(label='Email')
password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Confirm Password', widget=forms.PasswordInput)
# contact 1
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)
contact1_email = forms.EmailField(required=False) # not required because some students might not know contact information
contact1_street = forms.CharField(max_length=100, required=False)
contact1_street_number = forms.CharField(max_length=10, required=False)
contact1_zipcode = forms.CharField(max_length=10, required=False)
contact1_city = forms.CharField(max_length=100, required=False)
# contact 2
contact2_first_name = forms.CharField(max_length=50, required=True)
contact2_last_name = forms.CharField(max_length=50, required=True)
contact2_phone_number = forms.CharField(max_length=50, required=False)
contact2_email = forms.EmailField(required=False) # not required because some students might not know contact information
contact2_street = forms.CharField(max_length=100, required=False)
contact2_street_number = forms.CharField(max_length=10, required=False)
contact2_zipcode = forms.CharField(max_length=10, required=False)
contact2_city = forms.CharField(max_length=100, required=False)
def username_clean(self):
username = self.cleaned_data['username'].lower()
new = User.objects.filter(username = username)
if new.count():
raise ValidationError("User Already Exist")
return username
def email_clean(self):
email = self.cleaned_data['email'].lower()
new = User.objects.filter(email=email)
if new.count():
raise ValidationError(" Email Already Exist")
return email
def clean_password2(self):
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 and password2 and password1 != password2:
raise ValidationError("Password don't match")
return password2
def save(self, commit = True):
user = User.objects.create_user(
self.cleaned_data['username'],
self.cleaned_data['email'],
self.cleaned_data['password1']
)
return user
#transaction.atomic
def form_valid(self, form):
student = form.save()
user = super().save(commit=False)
user.is_student = True
user.save()
contact1 = Contact(
student = student,
contact_first_name = form.cleaned_data['contact1_first_name'],
contact_last_name = form.cleaned_data['contact1_last_name'],
contact_phone_number = form.cleaned_data['contact1_phone_number'],
contact_email = form.cleaned_data['contact1_email'],
contact_street = form.cleaned_data['contact1_street'],
contact_street_number = form.cleaned_data['contact1_street_number'],
contact_zipcode = form.cleaned_data['contact1_zipcode'],
contact_city = form.cleaned_data['contact1_city'],
)
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'],
contact_last_name = form.cleaned_data['contact2_last_name'],
contact_phone_number = form.cleaned_data['contact2_phone_number'],
contact_email = form.cleaned_data['contact2_email'],
contact_street = form.cleaned_data['contact2_street'],
contact_street_number = form.cleaned_data['contact2_street_number'],
contact_zipcode = form.cleaned_data['contact2_zipcode'],
contact_city = form.cleaned_data['contact2_city'],
)
contact2.save()
return redirect('home')
And because the form is now a ModelForm, instead of a UserCreationForm, I had to add various functions to validate the user's credentials, such as save(), clean_password2() and email_clean().
And created this view in views.py:
def student_signup(request):
if request.method == 'POST':
student_signup_form = StudentSignUpForm()
if student_signup_form.is_valid():
student_signup_form.form_valid(student_signup_form)
return redirect('home')
else:
student_signup_form = StudentSignUpForm()
return render(request, 'registration/signup_form.html', {'student_signup_form': student_signup_form})
The forms render perfectly now: there are fields for the User's password and username, the Student's name etc, and finally the two contacts, all in one form. So we're getting there!
The only problem is that when I fill the form in, nothing is saved in the database after I hit submit. Does anyone know why this happens?
What I'd try:
I don't understand your StudentSignUpForm magic. However, if it's effectively the same as a ModelForm:
class StudentSignUpForm(forms.Modelform):
class Meta:
model = Student
fields = ('first_name', 'last_name', ...)
then just add non-model fields
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)
And then put everything together in 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( ...)
If you want to do further validation beyond what's easy in a form definition you can. (You may well want to check that if conatct2_first_name is specified, contact2_last_name must also be specified).
def form_valid( self, form):
# extra validations, add errors on fail
n=0
if form.cleaned_data['contact2_first_name']:
n+=1
if form.cleaned_data['contact2_last_name']:
n+=1
if n==1:
form.add_error('contact2_first_name',
'Must provide first and last names for contact2, or omit both for no second contact')
form.add_error('contact2_last_name',
'Must provide first and last names for contact2, or omit both for no second contact')
contact2_provided = (n != 0)
...
if not form.is_valid():
return self.form_invalid( self, form)
with transaction.atomic():
student = form.save()
contact1 = ( ... # as before
if contact2_provided:
contact2 = ( ...
Thanks to #nigel222 I have come a long way in creating the form. However, the form did not save the data to the database properly, but I managed to change the view and the form a little bit and it works now.
This is a blueprint on how to create two objects in one form in Django. It creates one Student model (linked to a User model) and two Contact models (of which one is optional).
Forms.py:
class StudentSignUpForm(forms.ModelForm):
class Meta:
model = Student
fields = (
'username',
'first_name',
'last_name',
'year',
'school',
'student_email',
'account_status',
'phone_number',
'homework_coach',
'password1',
'password2',
'contact1_first_name',
'contact1_last_name',
'contact1_phone_number',
'contact1_email',
'contact1_street',
'contact1_street_number',
'contact1_zipcode',
'contact1_city',
'contact2_first_name',
'contact2_last_name',
'contact2_phone_number',
'contact2_email',
'contact2_street',
'contact2_street_number',
'contact2_zipcode',
'contact2_city',
)
# user
username = forms.CharField(label='Username', min_length=5, max_length=150)
password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Confirm Password', widget=forms.PasswordInput)
# contact 1
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)
contact1_email = forms.EmailField(required=False) # not required because some students might not know contact information
contact1_street = forms.CharField(max_length=100, required=False)
contact1_street_number = forms.CharField(max_length=10, required=False)
contact1_zipcode = forms.CharField(max_length=10, required=False)
contact1_city = forms.CharField(max_length=100, required=False)
# contact 2
contact2_first_name = forms.CharField(max_length=50, required=False) # not required because second contact is optional
contact2_last_name = forms.CharField(max_length=50, required=False)
contact2_phone_number = forms.CharField(max_length=50, required=False)
contact2_email = forms.EmailField(required=False) # not required because some students might not know contact information
contact2_street = forms.CharField(max_length=100, required=False)
contact2_street_number = forms.CharField(max_length=10, required=False)
contact2_zipcode = forms.CharField(max_length=10, required=False)
contact2_city = forms.CharField(max_length=100, required=False)
def username_clean(self):
username = self.cleaned_data['username'].lower()
new = User.objects.filter(username = username)
if new.count():
raise ValidationError("User Already Exist")
return username
def clean_password2(self):
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 and password2 and password1 != password2:
raise ValidationError("Password don't match")
return password2
#transaction.atomic
def form_valid(self, form):
user = User.objects.create_user(
self.cleaned_data['username'],
self.cleaned_data['password1']
)
user.is_student = True
user.save()
student = form.save(commit=False)
student.user = user
student = form.save()
contact1 = Contact(
student = student,
contact_first_name = form.cleaned_data['contact1_first_name'],
contact_last_name = form.cleaned_data['contact1_last_name'],
contact_phone_number = form.cleaned_data['contact1_phone_number'],
contact_email = form.cleaned_data['contact1_email'],
contact_street = form.cleaned_data['contact1_street'],
contact_street_number = form.cleaned_data['contact1_street_number'],
contact_zipcode = form.cleaned_data['contact1_zipcode'],
contact_city = form.cleaned_data['contact1_city'],
)
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'],
contact_last_name = form.cleaned_data['contact2_last_name'],
contact_phone_number = form.cleaned_data['contact2_phone_number'],
contact_email = form.cleaned_data['contact2_email'],
contact_street = form.cleaned_data['contact2_street'],
contact_street_number = form.cleaned_data['contact2_street_number'],
contact_zipcode = form.cleaned_data['contact2_zipcode'],
contact_city = form.cleaned_data['contact2_city'],
)
contact2.save()
return redirect('home')
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})
Related
I have a form class (incorrect) :
class TeamGoalForm(ModelForm):
class Meta:
employees = forms.ModelMultipleChoiceField(queryset=Employee.objects.filter(departament=Department.objects.get(manager=Manager.objects.get(end_user_id = request.user.username.upper())),widget=forms.CheckboxSelectMultiple()))
department = forms.ModelChoiceField(queryset=Department.objects.all())
model = TeamGoal
fields = '__all__'
widgets = {
'employees' : forms.Select(attrs={'class': 'form-control', 'placeholder':'Select employees'}),
}
'department':forms.Select(attrs={'class': 'form-control', 'placeholder':'Select department'}),
I want to pass parameter request.user.username.upper() which I have in my view.py. How to implement this in my TeamGoalForm?
my view.py
#login_required(login_url='login')
def add_team_goal(request):
form = TeamGoalForm(is_manager(request))
if request.method == 'POST':
form = TeamGoalForm(request.POST)
if form.is_valid():
form.save()
return redirect('team_goal')
team = get_team(request)
if team.exists():
return render(request, 'smth.html', {'form':form,'team':team})
My Employee model:
# Employee section
class Employee(models.Model):
name = models.CharField(max_length=30, verbose_name='Name')
lastname = models.CharField(max_length=30, verbose_name='Lastname')
.............
history = HistoricalRecords()
def __str__(self):
return self.name + ' ' + self.lastname
My Department:
class Department(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=30)
.........
manager = models.ForeignKey(Manager, related_name='manager_name', null=True, on_delete = models.SET_NULL)
history = HistoricalRecords()
My Managers:
class Manager(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=30)
lastname = models.CharField(max_length=30)
history = HistoricalRecords()
def __str__(self):
return self.name + ' ' + self.lastname
My TeamGoal:
class TeamGoal(models.Model):
team_goal_title = models.CharField(max_length=30, verbose_name='Title')
team_goal_description = models.CharField(max_length=100, blank=True, verbose_name='Description')
department = models.ForeignKey(Department, verbose_name='Department', on_delete = models.CASCADE, related_name='+', blank=True, null=True, help_text=u'If you assign the team goal for the whole department, please fill only Department field and skip Employee field.')
employees = models.ManyToManyField(Employee, null=True, blank=True, symmetrical=False, related_name='employee_name')
......
history = HistoricalRecords()
In my app I can create Team goal for whole department or for specific group of employees.
I would really advise not to give Manager the same name as a user and then match on that: it makes keeping records in sync quite hard. You can link to the user model with:
from django.conf import settings
class Manager(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
history = HistoricalRecords()
def __str__(self):
return f'{self.user.first_name} {self.user.lastname}'
you can pass the user as parameter to the ModelForm and then filter the queryset:
class TeamGoalForm(ModelForm):
def __init__(self, *args, user=None, **kwargs):
super().__init_(*args, **kwargs)
if user is not None:
self.field['employees'] = Employee.objects.filter(
department__manager__user=user
)
class Meta:
model = TeamGoal
fields = '__all__'
widgets = {
'employees' : forms.SelectMultiple(attrs={'class': 'form-control', 'placeholder':'Select employees'}),
'department':forms.SelectMultiple(attrs={'class': 'form-control', 'placeholder':'Select department'})
}
and in the view pass the logged in user to the TeamGoalForm:
#login_required(login_url='login')
def add_team_goal(request):
if request.method == 'POST':
form = TeamGoalForm(request.POST, user=request.user)
if form.is_valid():
form.save()
return redirect('team_goal')
else:
form = TeamGoalForm(user=request.user)
team = get_team(request)
return render(request, 'smth.html', {'form':form,'team':team})
I have a Custom User model with a team field. I also have a Team model with a ManyToManyField for the Custom Users.
I have a template that allows a user to create a team using whatever Team Name they want. Upon creation, I want that user's Team to be assigned but the team primary key isn't created until the Team is created so how do I get that primary key id into the Custom User model?
My direction of thought is that I would perform a user save after the "create-team" form save in the views, but I'm not sure how to do that.
models.py
class Team(models.Model):
team_id = models.BigAutoField(auto_created=True, primary_key=True)
team_name = models.CharField(max_length=35, null=False, default='YourTeam')
team_type = models.CharField(choices=MEMBERSHIP_CHOICES, default='Free', max_length=30)
num_users = models.IntegerField(default=1)
emails = models.ManyToManyField('CustomUser', related_name='teamemails')
class CustomUser(AbstractUser):
username = None
first_name = models.CharField(max_length=255, unique=False, verbose_name='first name')
last_name = models.CharField(max_length=255, unique=False, verbose_name='last name')
email = models.EmailField(max_length=255, unique=True)
team = models.ForeignKey(Team, on_delete=models.SET_NULL, null=True, blank=True, related_name='userteam')
team_leader = models.BooleanField(default=False)
team_member = models.BooleanField(default=False)
forms.py
class CreateTeamForm(forms.ModelForm):
team_name = forms.CharField(label='Team Name', required=True)
class Meta:
model = Team
fields = ['team_name']
views.py
#verified_email_required
def create_team(request):
template = 'users/create_team.html'
if request.method == "POST":
form = CreateTeamForm(request.POST)
if form.is_valid():
form = form.save(commit=False)
form.team_name = request.team_name
form.save()
messages.success(request, 'Team Created!')
return redirect('home')
else:
messages.error(request, 'Oops! Something went wrong')
else:
form = CreateTeamForm()
context = {
'form':form,
}
return render(request, template, context)
I'm using Django and Allauth. I need the user to be able to signup with additional field, so I created a custom user call Profile, a custom SignupForm and an Adapter, but when I submit I get the error "save() prohibited to prevent data loss due to unsaved related object 'user'".
custom form
class CustomSignupForm(SignupForm):
first_name = forms.CharField(
max_length=200,
label='Nome*',
widget=forms.TextInput(attrs={'placeholder': 'Il tuo nome'}),
required=True
)
last_name = forms.CharField(
max_length=200,
label='Cognome*',
widget=forms.TextInput(attrs={'placeholder': 'Il tuo cognome'}),
required=True
)
etc...
def signup(self, request, user):
user.email = self.cleaned_data['email']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.address = self.cleaned_data['address']
user.cap = self.cleaned_data['cap']
user.city = self.cleaned_data['city']
user.province = self.cleaned_data['province']
user.state = self.cleaned_data['state']
user.pi = self.cleaned_data['pi']
user.cf = self.cleaned_data['cf']
user.save()
adapter
class AccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=False):
data = form.cleaned_data
user.email = data['email']
user.first_name = data['first_name']
user.last_name = data['last_name']
user.address = data.get('address')
user.cap = data['cap']
user.city = data['city']
user.province = data['province']
user.state = data['state']
user.pi = data['pi']
user.cf = data['cf']
if 'password1' in data:
user.set_password(data['password1'])
else:
user.set_unusable_password()
self.populate_username(request, user)
if commit:
user.save()
return user
custom user model (Profile)
class Profile(AbstractUser):
artist_name = models.CharField(max_length=200, blank=True, null=True)
address = models.CharField(max_length=200, null=True)
cap = models.PositiveIntegerField(null=True)
city = models.CharField(max_length=200, null=True)
province = models.CharField(max_length=200, null=True)
state = models.CharField(max_length=200, null=True)
pi = models.CharField(max_length=200, blank=True, null=True)
cf = models.CharField(max_length=200, blank=True, null=True)
bio = models.TextField(blank=True, null=True)
image = models.ImageField(blank=True, null=True)
instagram_url = models.URLField(blank=True, null=True)
facebook_url = models.URLField(blank=True, null=True)
twitter_url = models.URLField(blank=True, null=True)
soundcloud_url = models.URLField(blank=True, null=True)
credits = models.PositiveIntegerField(default=2)
def __str__(self):
return self.email
settings.py
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_AUTHENTICATION_METHOD = 'email'
AUTH_USER_MODEL = 'profiles.Profile'
ACCOUNT_ADAPTER = 'main.adapter.AccountAdapter'
ACCOUNT_FORMS = {
'signup': 'main.forms.CustomSignupForm',
}
What's wrong in my code?
Try adding a save method on your form class:
class CustomSignupForm(SignupForm):
# ...
def save(self, user):
user.save()
I'm really having some trouble with this. I've got some custom user's setup and those users can be attached to companies via foreign key. I'm just having trouble saving them. I've tried a ton of different variations of getting the user attached to a company and I just can't crack it. The forms do work and it does both create a "customer" and a "customer company".
I know this needs to be a variation of:
if customer_form.is_valid() and customer_company_form.is_valid():
customer_company = customer_company_form.save()
customer = customer_form.save(commit=False)
customer.user = customer_company
customer_company.save()
models.py
class CustomerCompany(models.Model):
COUNTRIES = (
('USA', 'United States'),
('CAN', 'Canada')
)
name = models.CharField(max_length=100, blank=True, unique=True)
website = models.CharField(max_length=100, blank=True)
phone = models.CharField(max_length=10, blank=True)
address = models.CharField(max_length=100, blank=True)
city = models.CharField(max_length=255, blank=True)
state = USStateField(blank=True, null=True)
us_zipcode = USZipCodeField(blank=True, null=True)
ca_province = models.CharField(max_length=50, blank=True, null=True)
ca_postal_code = models.CharField(max_length=7, blank=True, null=True)
country =models.CharField(max_length=3, choices=COUNTRIES,
blank=True)
def get_absolute_url(self):
return reverse('accounts:customer_company_detail',kwargs={'pk':self.pk})
def __str__(self):
return self.name
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,
primary_key=True, related_name='customer_profile')
company = models.ForeignKey(CustomerCompany, on_delete=models.CASCADE, null=True)
phone = models.CharField(max_length=10)
def __str__(self):
return self.user.first_name + ' ' + self.user.last_name
forms.py
class CustomerSignupForm(UserCreationForm):
first_name = forms.CharField(max_length=50, required=True)
last_name = forms.CharField(max_length=50, required=True)
phone = forms.CharField(max_length=10, required=True)
email = forms.EmailField(required=True)
class Meta(UserCreationForm.Meta):
model = User
#transaction.atomic
def save(self, commit=True):
user = super(CustomerSignupForm, self).save(commit=False)
user.is_customer = True
user.first_name = self.cleaned_data.get('first_name')
user.last_name = self.cleaned_data.get('last_name')
user.email = self.cleaned_data.get('email')
user.save()
customer = Customer.objects.create(user=user)
customer.phone = self.cleaned_data.get('phone')
customer.save()
return user
class CustomerCompanyCreateForm(forms.ModelForm):
ca_province = CAProvinceField(required=False, label="Province")
ca_postal_code = CAPostalCodeField(required=False, label="Postal Code")
class Meta:
model = CustomerCompany
fields = ['name', 'website', 'phone', 'address', 'city', 'state',
'us_zipcode', 'country', 'ca_province', 'ca_postal_code']
labels = {
"us_zipcode": "Zipcode",
}
views.py Updated with working code
def customer_signup(request):
if request.method == 'POST':
customer_form = CustomerSignupForm(request.POST)
customer_company_form = CustomerCompanyCreateForm(request.POST)
if customer_form.is_valid() and customer_company_form.is_valid():
# first save the user object
user_obj = customer_form.save(commit=False)
# Then use this object to get to my Customer model via the related name
customer = user_obj.customer_profile
# now save the CustomerCompany
company = customer_company_form.save()
# attach the customer to the Company
customer.company = company
# now fully save the customer after he's attached to his company
customer.save()
return redirect('customer_dashboard:customer_dashboard')
else:
messages.error(request, 'Please correct the errors below.')
else:
customer_form = CustomerSignupForm()
customer_company_form = CustomerCompanyCreateForm()
return render(request, 'accounts/registration/customer_signup_combined.html', {
'customer_form' : customer_form,
'customer_company_form' : customer_company_form,
})
You're saving both forms in your view but you're not connecting the two objects.
Calling save on the customer_form will return a User object since its a User ModelForm. You can use this object to get to the Customer object via the customer_profile related_name and set the company field to the Company instance returned when you save the customer_company_form.
It should look like this:
if customer_form.is_valid() and customer_company_form.is_valid():
user_obj = customer_form.save(commit=True)
customer = user_obj.customer_profile
company = customer_company_form.save(commit=True)
customer.company = company
customer.save()
return redirect('customer_dashboard:customer_dashboard')
I'm trying to overwrite save method for custom creation form in Django. I have model UserProfile which has many attributes. I've created a UserProfileCreationForm which should create and save both User and UserProfile. Now, it saves User but it can't save UserProfile (I've overwritten attribute date_of_birth to be able to choose instead of write date into charfield).
So what I want is to make this form save both User and UserProfile or raise error when I do UserProfileCreationForm_object.save()
Here is my form:
class UserProfileCreationForm(UserCreationForm):
username = forms.CharField(label="Username",max_length=40)
email = forms.EmailField(label="Email address", max_length=40)
first_name = forms.CharField(label='First name', max_length=40)
last_name = forms.CharField(label='Last name', max_length=40)
date_of_birth = forms.DateField(label='Date of birth',
widget=SelectDateWidget(years=[y for y in range(1930,2050)]),
required=False)
password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
password2 = forms.CharField(label="Password confirmation", widget=forms.PasswordInput)
class Meta():
model = UserProfile
fields = ('username','email','first_name','last_name','password1','password2','date_of_birth','telephone','IBAN',)
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
msg = "Passwords don't match"
raise forms.ValidationError("Password mismatch")
return password2
def save(self, commit=True):
user = User(username=self.cleaned_data['username'],
first_name=self.cleaned_data['first_name'],
last_name=self.cleaned_data['last_name'],
email=self.cleaned_data['email'])
user.save()
# user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
user_profile = super(self).save()
if commit:
user.save()
user_profile.save()
return user
In case it is necessary, here is the model:
class UserProfile(models.Model):
# ATRIBUTY KTORE BUDE MAT KAZDY
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_profile')
iban = models.CharField(max_length=40, blank=True,verbose_name='IBAN')
telephone = models.CharField(max_length=40, null=True, blank=True)
HOW_DO_YOU_KNOW_ABOUT_US_CHOICES = (
('coincidence', u'It was coincidence'),
('relative_or_friends', 'From my relatives or friends'),
)
how_do_you_know_about_us = models.CharField(max_length=40, choices=HOW_DO_YOU_KNOW_ABOUT_US_CHOICES, null=True,
blank=True)
MARITAL_STATUS_CHOICES = (
('single', 'Single'),
('married', 'Married'),
('separated', 'Separated'),
('divorced', 'Divorced'),
('widowed', 'Widowed'),
)
marital_status = models.CharField(max_length=40, choices=MARITAL_STATUS_CHOICES, null=True, blank=True)
# TRANSLATORs ATTRIBUTES
# jobs = models.Model(Job)
language_tuples = models.ManyToManyField(LanguageTuple)
rating = models.IntegerField(default=0)
number_of_ratings = models.BigIntegerField(default=0)
def __unicode__(self):
return '{} {}'.format(self.user.first_name, self.user.last_name)
def __str__(self):
return '{} {}'.format(self.user.first_name, self.user.last_name)
First, you don't need to redifine some fields like username, email, etc...
Here is a complete example with a save ovride:
class RegisterForm(UserCreationForm):
email = forms.EmailField()
def __init__(self, *args, **kwargs):
super(RegisterForm, self).__init__(*args, **kwargs)
#self.error_class = BsErrorList
def clean_email(self):
email = self.cleaned_data["email"]
try:
User._default_manager.get(email__iexact=email)
except User.DoesNotExist:
return email
raise forms.ValidationError(
'A user with that email already exists',
code='duplicate_email',
)
def save(self, commit=True):
user = super(RegisterForm, self).save(commit=False)
user.email = self.cleaned_data["email"]
# Create user_profile here
if commit:
user.save()
return user
Edit: This solution reuse django's User form to register a new User then you have to create your custom profile.