Adding ForeignKey to UserProfile giving foreign key constraint fails - django

I've been trying to figure this out for hours, and believe me, I really looked everywhere on Stack Overflow.
In my UserProfile, I have a ForeignKey reference to another model (called "Company"), and upon registration, I create a new Company and point my UserProfile ForeignKey to that Company.
models.py is as follows:
class UserProfile(models.Model):
company = models.ForeignKey(Company)
title = models.CharField(max_length = 50, default = '')
user = models.OneToOneField(User, default = 0, null = True)
class Company(models.Model):
"""A company profile."""
name = models.CharField(max_length = 50)
I use a Form to do the signing up. Here's the form:
class SignupForm(ModelForm):
name = forms.CharField(label = "Name")
company = forms.CharField(max_length = 50)
email = forms.EmailField(label = "Email")
password = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = User
fields = ("name", "company", "email", "password")
def save(self, commit=True):
user = super(SignupForm, self).save(commit=False)
name = self.cleaned_data["name"].split()
if len(name) == 1:
# User did not enter a last name.
user.first_name = name
else:
user.first_name, user.last_name = name
user.email = self.cleaned_data["email"]
user.set_password(self.cleaned_data["password"])
user.username = user.email
if commit:
user.save()
return user
and here's the signup view:
def signup(request):
if request.method == 'POST':
form = SignupForm(request.POST)
if form.is_valid():
# Check if email has already been used for an account.
email = request.POST['email']
existing_account = User.objects.filter(email = email)
if existing_account:
form = SignupForm()
return render_to_response('registration/signup.html',
{ 'form': form,
'error': 'duplicate_email',
})
# Otherwise, save the form and create a new user.
new_user = form.save()
company = Company(name=request.POST['company'])
company.save()
user_profile = new_user.get_profile()
user_profile.company = company
user_profile.save()
new_user = authenticate(
username = email,
password = request.POST['password']
)
# Log the user in automatically.
login(request, new_user)
# Redirect user to Thank You page.
return HttpResponseRedirect('/thanks')
else:
form = SignupForm()
return render_to_response('registration/signup.html', {
'form': form,
})
The error I am getting is telling me that company_id cannot be null. I clearly add a new Company. Please let me know what you think might be wrong.
Thanks

I've had this exact error today, with no reason, except that it was caused by SQLite. With SQLite, the id field of one table went from INTEGER PRIMARY KEY to INTEGER. If you're using SQLite, try deleting the offending table and recreate it with a syncdb.
What is the value of
user_profile = new_user.get_profile()
?

Not sure if this feels too hackish for your tastes but perhaps you could create/save the Company object and pass it in to your SignupForm.save() method as a positional/keyword argument.
The issue you'd get there is that you'd be expecting a CharField and you'd be passing in a company object. So you'd probably want to give company.pk to the company field in your form.

Related

Can't get Django forms to save to User and Profile model

I am trying to extend the Django User model by creating a user Profile model. When users register for the site, I want them to be able to select what class period they are in. To do this, I've tried to create a form that alters the User model, and a form that alters the Profile model. The problem is that when I try to put both forms into 'users/register.html' I am getting an error that says 'Anonymous User has to data _meta'. Below is my original code that only has the form for altering the User model in 'users/register.html'. How can I configure the registration so that users are able to save to the User and Profile model when they are first signing up for the site?
models.py
class Profile(models.Model):
'''
periods = [
('-','-'),
('1','1'),
('2','2'),
('3','3'),
('4','4'),
('6','6'),
('7','7'),
]
'''
user = models.OneToOneField(User,on_delete=models.CASCADE)
period = models.IntegerField(default=1)
first_name = models.CharField(max_length=100,default='Home')
last_name = models.CharField(max_length=100,default='Simpson')
def __str__(self):
return f'{self.user.username}'
forms.py
class UserRegisterForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username','email','password1','password2']
class UserProfileForm(forms.ModelForm):
periods = [
(1,1),
(2,2),
(3,3),
(4,4),
(6,6),
(7,7),
]
period = forms.ChoiceField(choices=periods)
first_name = forms.CharField(max_length=100)
last_name = forms.CharField(max_length=100)
class Meta:
model = Profile
fields = ['first_name','last_name','period']
signals.py
#receiver(post_save,sender=User)
def create_profile(sender,instance,created,**kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save,sender=User)
def save_profile(sender,instance,**kwargs):
instance.profile.save()
views.py
def login(request):
context = {
'title':'Login',
}
return render(request,'users/login.html',context)
def register(request):
if request.method == "POST":
form = UserRegisterForm(request.POST)
if form.is_valid():
email = form.cleaned_data.get('email')
email_domain = re.search("#[\w.]+", email)
if email_domain.group() == EMAIL_DOMAIN:
form.save()
username = form.cleaned_data.get('username')
messages.success(request,f'Account created for {username}! You are now able to sign in.')
return redirect('users-login')
else:
messages.error(request,f'Sorry. You are not authorized to register.')
else:
form = UserRegisterForm()
context = {
'title':'Register',
'form':form
}
return render(request,'users/register.html',context)
This is happening because you are putting both the forms in the register page . When you have not created any user so how you can create a profile and how would you be able to add or retrieve data from it?
Now the solution for that is ,
1 . You create a page for registering the user say "users/register.html" , when they successfully register there, create a signal for creating the Profile for him , then successfully log the user in . Then redirect the just registered user to the profile change page.
Take both the forms in the user register page but do not validate the profile_change form .Create the user in the view and then reference it to the profile_change_form .
A simple code bit for that .
def registerUser(request) :
if request.method == "POST" :
user_form = UserRegisterForm(request.POST)
if user_form.is_valid():
username = request.POST.get("username")
# fields you require
user = User(username = username , password = password)
user.save()
profile_field_objects = request.POST.get("profile_field_data")
profile = Profile(user = user , profile_field_objects = profile_field_objects)
profile.save()
# rest you can code

How to add a user to group programatically in Django

I have a Django project where each user belongs to just one group, Doctor and Nurse but not both.
I showed a form in my front-end to let Admin users add the user profiles, where each user profile is made with First name, Last name, Email, Groups etc.
My challenge is when an admin user assigns a user profile to a group from the front-end that user is not added to the group when I check the Django admin i.e when I click on the on user on the Django admin I can't see the user being assigned to the group.
The important part of my forms.py is this part
from django.contrib.auth.models import Group
groups = forms.ModelChoiceField(label='Role', queryset=Group.objects.all(), widget=forms.Select(attrs={'class':'form-control'}))
The full form can be seen here
from django.contrib.auth.forms import UserCreationForm
from django_starter_app.models import User, Post
from django.contrib.auth.models import Group
class RegisterForm(UserCreationForm):
username = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'Enter Username'}))
email = forms.CharField(widget=forms.EmailInput(attrs={'class':'form-control', 'placeholder':'Email'}))
first_name = forms.CharField(label='Firstname', widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'Firstname'}))
last_name = forms.CharField(label='Lastname', widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'Lastname'}))
groups = forms.ModelChoiceField(label='Role', queryset=Group.objects.all(), widget=forms.Select(attrs={'class':'form-control'}))
password1 = forms.CharField(label='Password', widget=forms.PasswordInput(attrs={'class':'form-control', 'placeholder':'Password'}))
password2 = forms.CharField(label='Confirm Password', widget=forms.PasswordInput(attrs={'class':'form-control', 'placeholder':'Confirm Password'}))
class Meta():
model = User
fields = ('username', 'email', 'first_name', 'last_name', 'groups', 'password1', 'password2')
def save(self, commit=True):
user = super().save(commit=False)
user.username = self.cleaned_data['username']
user.email = self.cleaned_data['email']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.groups = self.cleaned_data['groups']
user.password1 = self.cleaned_data['password1']
user.password2 = self.cleaned_data['password2']
if commit:
user.save()
return user
on my views
def register_user(request):
if request.method == 'POST':
reg = RegisterForm(request.POST)
if reg.is_valid():
reg.save()
role= reg.cleaned_data.get('groups')
if role== 1:
doctor_group = Group.objects.get(name='Doctor')
reg.groups.add(doctor_group)
return redirect('register_user')
elif role == 2:
nurse_group = Group.objects.get(name='Nurse')
reg.groups.add(nurse_group)
return redirect('register_user')
else:
reg = RegisterForm()
return render(request, 'django_starter_app/register.html', {'register':reg})
Please I will like to know where I am getting it wrong I suspect where I am making the checks
if role == 1:
doctor_group = Group.objects.get(name='Doctor')
reg.groups.add(doctor_group)
return redirect('register_user')
elif role == 2:
nurse_group = Group.objects.get(name='Nurse')
reg.groups.add(nurse_group)
return redirect('register_user')
You can try like below. In my opinion and based on your code there is no need to do elif.
def register_user(request):
if request.method == 'POST':
reg = RegisterForm(request.POST)
if reg.is_valid():
user = reg.save(commit=False)
role = reg.cleaned_data.get('groups')
group = user.set_add(user)
reg.save()
return redirect('register_user')
else:
reg = RegisterForm()
return render(request, 'django_starter_app/register.html', {'register':reg})
reg = RegisterForm(request.POST)
if reg.is_valid():
**user = reg.save()**
role= reg.cleaned_data.get('groups')
if role== 1:
doctor_group = Group.objects.get(name='Doctor')
**user.groups.add(doctor_group)**
ModelName.objects.create( user = user,)
return redirect('register_user')
elif role == 2:
nurse_group = Group.objects.get(name='Nurse')
**user.groups.add(nurse_group)**
ModelName.objects.create( user = user,)
return redirect('register_user')
Once you have validated the form you need a variable to hold the save form.
After getting the groups you need to use that variable.
I have highlighted the required changes in the code.

Django permissions outside of the admin and in a custom form

I have an app with 2 user levels; Superuser, and Staff. Superuser will login to this app and create a new user and assign the user either Superuser or Staff permissions, but they are not logging in through the default Django admin, they will be logging into a custom admin. How do I display the permission checkboxes in my form and how do I save them for that new user being created? Is it merely just a normal checkbox or is there something I need to override to be able to accomplish this?
Here is my CreateUserForm as it is now.
class CreateUserForm(forms.Form):
username = forms.EmailField(max_length=50)
email = forms.EmailField()
first_name = forms.CharField(max_length=150)
last_name = forms.CharField(max_length=150)
password1 = forms.CharField(max_length=30, widget=forms.PasswordInput(render_value=False), label='Password')
password2 = forms.CharField(max_length=30, widget=forms.PasswordInput(render_value=False), label='Password Confirmation')
address_1 = forms.CharField(max_length=50)
address_2 = forms.CharField(max_length=50)
city = forms.CharField(max_length=50)
province = forms.CharField(max_length=2)
country = forms.CharField(max_length=50)
postal_code = forms.CharField(max_length=10)
work_phone = forms.CharField(max_length=20)
mobile_phone = forms.CharField(max_length=20)
fax = forms.CharField(max_length=20)
url = forms.CharField()
comments = forms.CharField(widget=forms.Textarea)
def clean_username(self):
try:
User.objects.get(username=self.cleaned_data['username'])
except User.DoesNotExist:
return self.cleaned_data['username']
raise forms.ValidationError("Sorry, this username has already been taken. Please choose another.")
def clean_email(self):
try:
User.objects.get(email=self.cleaned_data['email'])
except User.DoesNotExist:
return self.cleaned_data['email']
raise forms.ValidationError("Sorry, this email has already been taken. Please choose another.")
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError("You must type the same password each time.")
if ' ' in self.cleaned_data['username']:
raise forms.ValidationError("username must not contain spaces")
return self.cleaned_data
def save(self):
new_user = User.objects.create_user(
username = self.cleaned_data['username'],
email = self.cleaned_data['email']
)
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.set_password(self.cleaned_data['password'])
new_user.is_active = True
new_user.save()
new_profile = UserProfile(
# TODO:
)
Thanks
If you mean the permission checkboxes for is_staff and is_superuser, then you're probably best off using a ModelForm w/ the User model. This will automatically take care of everything for you.
class UserForm(forms.ModelForm):
class Meta:
model = User
exclude = ('last_login', 'date_joined')
EDIT per OP update: You can just add 2 forms.BooleanField() fields to your form for is_superadmin and is_staff (or name them differently), and much like you're already doing in the save method you can do new_user.is_staff = self.cleaned_data['is_staff']. You may consider using a choice field instead, w/ a dropdown w/ 3 entries, "Normal User", "Staff User", "Admin User", and then set is_staff and is_superadmin according to the user's selection.
It's also still possible to use a ModelForm on the User model and just add the necessary extra fields in addition. It may or may not be worth it to you, depending how custom you're getting.
Another note about your form - when it comes to editing existing users, this won't be able to be used for that w/ the current clean methods on username/email. But if you tweak those to exclude the current instance (if set) from the lookup, you will be able to use this form for editing existing users (though since it's not a model form you'd need to populate the initial data manually).

Simple form not validating

I have found here on stackoverflow a method to extend django's built-in authentication using signals. My base User is defined by 'email' and passwords (so no username there). So I'm trying to modify it to my needs, but I'm geting a validation error for my form. Strange thing is that error is connected to the User.email field and I'm getting 'already in use' even though I'm just registering at the moment. Is it trying to save it 2 times or what ? I've discovered it when I was sending dictionary with data to form's contstructor in shell: form = MyForm(data={}). After this form was still invalid, but changing email to different value finally gave me True.
The user_created function connected to registration signal :
def user_created(sender, user, request, **kwargs):
form = CustomRegistrationForm(request.POST, request.FILES)
if form.is_valid():
data = UserProfile(user=user)
data.is_active = False
data.first_name = form.cleaned_data['first_name']
data.last_name = form.cleaned_data['last_name']
data.street = form.cleaned_data['street']
data.city = form.cleaned_data['city']
data.save()
else:
return render_to_response('user/data_operations/error.html', {'errors': form._errors}, context_instance=RequestContext(request))
user_registered.connect(user_created)
My form :
class CustomRegistrationForm(RegistrationForm):
first_name = forms.CharField(widget=forms.TextInput(attrs=attrs_dict), max_length=50)
last_name = forms.CharField(widget=forms.TextInput(attrs=attrs_dict), max_length=50)
street = forms.CharField(widget=forms.TextInput(attrs=attrs_dict), max_length=50)
city = forms.CharField(widget=forms.TextInput(attrs=attrs_dict), max_length=50)
My model :
class UserProfile(models.Model):
first_name = models.CharField(_("Name"), max_length=50, blank=False,)
last_name = models.CharField(_("Last name"), max_length=50, blank=False,)
street = models.CharField(_("Street"), max_length=50, blank=False,)
city = models.CharField(_("City"), max_length=50, blank=False,)
user = models.ForeignKey(User, unique=True, related_name='profile',)
Registration form :
class RegistrationForm(forms.Form):
email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
maxlength=75)),
label=_("Adres email"))
password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
label=_("Haslo"))
password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
label=_("Haslo powtorzone"))
def clean_email(self):
email = self.cleaned_data.get("email")
if email and User.objects.filter(email=email).count() > 0:
raise forms.ValidationError(
_(u"Already in use."))
return email
your 'user_registered' signal is sent after the User is saved. So it already has an 'email' field defined.
UPDATE
Using restless thinking :
form = CustomRegistrationForm(request.POST, request.FILES, notvalidateemail=True)
and in form :
def __init__(self, *args, **kwargs):
self.notvalidateemail = kwargs.pop('notvalidateemail',False)
super(CustomRegistrationForm, self).__init__(*args, **kwargs)
def clean_email(self):
if self.notvalidateemail:
return
else:
#your cleaning here
return email
Problem:
Your form is first saved by django-registration. Then you save it again in user_created.
Solutions:
Use a different form in user_created. One that won't have already saved fields (these from User model like email). You just want to save additional data in user_created, right?
Add some parameters to the form like:
in user_created:
form = CustomRegistrationForm(dontvalidateemail=True, request.POST, request.FILES)
and in form's init;
self.dontvalidateemail = dontvalidateemail
then just check it in clean_email.

Django cascade save?

I have a method on my user registration form that looks like this:
def save(self):
user = User(
username = self.cleaned_data['username'],
email = self.cleaned_data['email1'],
first_name = self.cleaned_data['first_name'],
last_name = self.cleaned_data['last_name'],
)
user.set_password(self.cleaned_data['password1'])
user.profile = Profile(
primary_phone = self.cleaned_data['phone'],
)
user.profile.address = Address(
country = self.cleaned_data['country'],
province = self.cleaned_data['province'],
city = self.cleaned_data['city'],
postal_code = self.cleaned_data['postal_code'],
street1 = self.cleaned_data['street1'],
street2 = self.cleaned_data['street2'],
street3 = self.cleaned_data['street3'],
)
user.save()
return user
Problem is when I call form.save() it creates the user object as expected, but doesn't save his profile or address. Why doesn't it cascade and save all the sub-models? I suspect I could call user.profile.save() and user.profile.address.save() manually, but I want the whole thing to succeed or fail together. What's the best way to do this?
Current solution:
def save(self):
address = Address(
country = self.cleaned_data['country'],
province = self.cleaned_data['province'],
city = self.cleaned_data['city'],
postal_code = self.cleaned_data['postal_code'],
street1 = self.cleaned_data['street1'],
street2 = self.cleaned_data['street2'],
street3 = self.cleaned_data['street3'],
)
address.save()
user = User(
username = self.cleaned_data['username'],
email = self.cleaned_data['email1'],
first_name = self.cleaned_data['first_name'],
last_name = self.cleaned_data['last_name'],
)
user.set_password(self.cleaned_data['password1'])
user.save()
profile = Profile(
primary_phone = self.cleaned_data['phone'],
)
profile.address = address
profile.user = user
profile.save()
I had to make profile the "central" object. Needed to set profile.user = user rather than user.profile = profile to make it work (I guess because the key is on the profile model, not on the user model).
Newer solution:
I took a hint from this article suggested in this answer.
Now I have separated my model forms and moved the logic into the view:
def register(request):
if request.POST:
account_type_form = forms.AccountTypeForm(request.POST)
user_form = forms.UserForm(request.POST)
profile_form = forms.ProfileForm(request.POST)
address_form = forms.AddressForm(request.POST)
if user_form.is_valid() and profile_form.is_valid() and address_form.is_valid():
user = user_form.save()
address = address_form.save()
profile = profile_form.save(commit=False)
profile.user = user
profile.address = address
profile.save()
return HttpResponseRedirect('/thanks/')
else:
account_type_form = forms.AccountTypeForm()
user_form = forms.UserForm()
profile_form = forms.ProfileForm()
address_form = forms.AddressForm()
return render_to_response(
'register.html',
{'account_type_form': account_type_form, 'user_form': user_form, 'address_form': address_form, 'profile_form': profile_form},
context_instance=RequestContext(request)
)
I'm not too fond of shifting the burden to the view, but I guess I get a bit more flexibility this way?
It doesn't cascade-save because it doesn't actually know whether or not the other objects need to be saved.
To do it in one go, first start a transaction:
#transaction.commit_on_success
def save(self):
....
Then save the subobjects in order:
user.profile.address.save()
user.profile.save()
user.save()
The problem is that you're trying to create or update fields in a User object that doesn't even exist yet. So the other fields aren't really updated because they aren't associated to any primary keys of the child fields.
Every time you're instantiating a new model field, you have to make sure you're saving so that a child model field has an id (primary key) to associate with.
You need something more like this:
def save(self):
user = User(
username = self.cleaned_data['username'],
email = self.cleaned_data['email1'],
first_name = self.cleaned_data['first_name'],
last_name = self.cleaned_data['last_name'],
)
## save user so we get an id
user.save()
## make sure we have a user.id
if user.id:
## this doesn't save the password, just updates the working instance
user.set_password(self.cleaned_data['password1'])
user.profile = Profile(
primary_phone = self.cleaned_data['phone'],
)
## save the profile so we get an id
user.profile.save()
## make sure we have a profile.id
if user.profile.id:
user.profile.address = Address(
country = self.cleaned_data['country'],
province = self.cleaned_data['province'],
city = self.cleaned_data['city'],
postal_code = self.cleaned_data['postal_code'],
street1 = self.cleaned_data['street1'],
street2 = self.cleaned_data['street2'],
street3 = self.cleaned_data['street3'],
)
## save the profile address
user.profile.address.save()
## final save to commit password and profile changes
user.save()
return user
This cascading save() thing you have going on here just doesn't feel right. You're prone to way too many errors there, if any of the fields doesn't save you'll end up with a partially complete user instance and posisbly end up with duplicates if the user has to go back and try again. Not fun!
Edit: Removed the 2nd half of this because it was not accurate.