I create an userprofile as following:
class UserProfile(models.Model):
# This field is required:
user = models.OneToOneField(User, related_name="User can view study permission")
# Other fields:
phone = models.CharField(max_length=20)
disease = models.ManyToManyField(Disease)
date_assigned = models.DateTimeField("Date Assigned")
query = models.ManyToManyField(Query, blank=True)
def __unicode__(self):
studies = ', '.join(study.name for study in self.disease.all())
return "%s can view %s" % (self.user, studies)
It's extended information for users.
Now I am creating a admin page by myself to allow the admin to update the users account information which included the above user profile.
The form and code for this is as following:
def edit(request, user_id):
"""Edit a user's details"""
try:
user = User.objects.get(id=user_id)
except User.DoesNotExist:
user = None
# user exists:
if user:
# Initial form field data:
initial={'user_id': user_id,
'username': user.username,
'fname': user.first_name,
'lname': user.last_name,
'email': user.email,
'phone': user.get_profile().phone,
'groups': user.groups.all(),
'studies': user.get_profile().disease.all(),
'is_admin': user.is_staff,
'is_active': user.is_active
}
request.breadcrumbs(
(_("Home"), reverse('home')),
(_("All Users"), reverse('all users')),
(_("User Details"), reverse('user details', args=[user_id])),
)
if request.method == "GET":
form = UserProfileEditForm(initial=initial,extra=request.user.id)
response = {'heading': 'Edit', 'form': form}
return render_to_response('accounts/edit.html',
response,
context_instance=RequestContext(request)
)
elif request.method == "POST":
form = UserProfileEditForm(request.POST,extra=request.user.id)
if form.is_valid():
Log().add(request, "Edit", "W", "userprofile", user_id)
if form.cleaned_data['password1'] and form.cleaned_data['password2']:
user.set_password(form.cleaned_data['password1'])
user.username = form.cleaned_data['username']
user.first_name = form.cleaned_data['fname']
user.last_name = form.cleaned_data['lname']
user.email = form.cleaned_data['email']
user.groups = form.cleaned_data['groups']
user.is_staff = form.cleaned_data['is_admin']
user.is_active = form.cleaned_data['is_active']
user.save()
# Oddly to make the extra fields found in UserProfile are saved, you
# have to call get_profile().ATTRIBUTE, assign a value, then call
# get_profile().save(). Calling user.save() as the last step won't
# save any changes made to UserProfile:
disease_pks = form.cleaned_data['studies']
user.get_profile().disease = Disease.objects.filter(pk__in=disease_pks)
user.get_profile().phone = form.cleaned_data['phone']
user.get_profile().save()
return HttpResponseRedirect("/accounts/view/%s" % user_id)
else:
# form is not valid:
return render_to_response("accounts/edit.html",
{'form': form, 'heading': 'Edit'},
context_instance=RequestContext(request)
)
# user does not exist:
else:
error = "User #%s cannot be found. Press the 'BACK' button on your browser." % user_id
return HttpResponseRedirect(reverse('DigitalRecords.views.error', args=(error,)))
class UserProfileEditForm(forms.Form):
user_id = forms.IntegerField(widget=forms.HiddenInput())
username = forms.CharField(label=_("Username"))
password1 = forms.CharField(label=_("Password"),
widget=forms.PasswordInput(),
required=False
)
password2 = forms.CharField(label=_("Password (again)"),
widget=forms.PasswordInput(),
required=False
)
fname = forms.CharField(label=_("First name"))
lname = forms.CharField(label=_("Last name"))
email = forms.EmailField()
phone = forms.CharField(label=_("Phone"))
is_admin = forms.BooleanField(label=_("Is an administrator?"), required=False)
is_active = forms.BooleanField(label=_("Is an active user?"), required=False)
groups = forms.ModelMultipleChoiceField(queryset=Group.objects.all(),
widget=forms.CheckboxSelectMultiple()
)
#Attach a form helper to this class
helper = FormHelper()
helper.form_id = "edituserprofile"
helper.form_class = "userprofile"
#Add in a submit and reset button
submit = Submit("Save", "Save Changes")
helper.add_input(submit)
reset = Reset("Reset", "Reset")
helper.add_input(reset)
def __init__(self, *args,**kwargs):
extra = kwargs.pop('extra')
super(UserProfileEditForm, self).__init__(*args, **kwargs)
self.fields["studies"] = forms.ModelMultipleChoiceField(queryset=User.objects.get(id=extra).get_profile().disease.all(),
widget=forms.CheckboxSelectMultiple()
)
def clean_username(self):
"""Check if the username does not already exist"""
username = self.cleaned_data['username']
user_id = self.cleaned_data['user_id']
# handle:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
return self.cleaned_data['username']
else:
if user.id == user_id:
return self.cleaned_data['username']
else:
raise forms.ValidationError('User "%s" already exists' % username)
def clean_fname(self):
fname = self.cleaned_data['fname']
# Remove any diacritics/accented characters:
fname = strip_diacritic(fname).strip()
# Match names that may have hyphens, apostrophes, or periods in them.
# example: John Doe, O'Brien, Leroy-Brown, Cpt. James T. Kirk
pattern = '^([a-zA-Z]+(?:\.)?(?:[\-\' ][a-zA-Z]+(?:\.)?)*)$'
# match the regex to the input string.
results = re.match(pattern, str(fname))
if results == None:
raise ValidationError(u'%s is not a valid name' % fname)
return fname
def clean_lname(self):
"""
Determine if the patient's name is valid. It removed any accented/diacritic
characters and replaces them with the base character for simplicity. Names with
hyphens and/ore apostrophes like Hanna-Barbara and O'Brien are allowed. If the
check fails a validation error is raised.
"""
lname = self.cleaned_data['lname']
# Remove any diacritics/accented characters:
lname = strip_diacritic(lname).strip()
# Match names that may have hyphens, apostrophes, or periods in them.
# example: John Doe, O'Brien, Leroy-Brown, Cpt. James T. Kirk
pattern = '^([a-zA-Z]+(?:\.)?(?:[\-\' ][a-zA-Z]+(?:\.)?)*)$'
# match the regex to the input string.
results = re.match(pattern, str(lname))
if results == None:
raise ValidationError(u'%s is not a valid name' % lname)
return lname
def clean(self):
"""Check if password1 and password2 match"""
cleaned_data = self.cleaned_data
password1 = cleaned_data.get('password1')
password2 = cleaned_data.get('password2')
# Notice it's an 'or' condition because a password change is optional:
if password1 or password2:
if password1 != password2:
msg = "Passwords do not match"
self._errors['password1'] = self.error_class([msg])
self._errors['password2'] = self.error_class([msg])
del cleaned_data['password1']
del cleaned_data['password2']
return cleaned_data
return self.cleaned_data
It works fine if an administrator trying to edit other user's account information.
However, when an administrator tried to update his own account. The disease field will always be blank whatever this field was changed or not.
Does anyone the reason and how should I change my code?
Thank you very much.
I'm not sure about other code, but these lines in your view does not seem appropriate.
user.get_profile().disease = Disease.objects.filter(pk__in=disease_pks)
user.get_profile().phone = form.cleaned_data['phone']
user.get_profile().save()
You are getting new profile object each time by calling user.get_profile() and updating it. Not saving it, instead a new object. So change it to
profile = user.get_profile()
profile.disease = Disease.objects.filter(pk__in=disease_pks)
profile.phone = form.cleaned_data['phone']
profile.save()
Related
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.
I am trying to implement registration with email and phone on a website. A user can register with either phone or email or both. If a user keeps both phone and email field empty, a ValidationError is raised, "You cannot leave both phone and email fields blank. You must fill at least one of the fields."
We have separate clean methods for username, email, phone, password. I do not want to implement the above-mentioned validation on save(). I don't want to define a clean method in the User model, either.
I have written tests for this form, and they pass. But what errors could possibly arise if I use both clean and clean_fieldname together? Could it become a problem when working with views?
I have 3 questions:
Can I use both clean_fieldname and clean methods in a
form?
In what other way can I make sure that user registers with at least
phone or email?
How do clean() and validate() works? I have read django documentation, but I don't understand it completely.
Here's the code I implemented.
class RegisterForm(SanitizeFieldsForm, forms.ModelForm):
email = forms.EmailField(required=False)
message = _("Phone must have format: +9999999999. Upto 15 digits allowed."
" Do not include hyphen or blank spaces in between, at the"
" beginning or at the end.")
phone = forms.RegexField(regex=r'^\+(?:[0-9]?){6,14}[0-9]$',
error_messages={'invalid': message},
required=False)
password = forms.CharField(widget=forms.PasswordInput())
MIN_LENGTH = 10
class Meta:
model = User
fields = ['username', 'email', 'phone', 'password',
'full_name']
class Media:
js = ('js/sanitize.js', )
def clean(self):
super(RegisterForm, self).clean()
email = self.data.get('email')
phone = self.data.get('phone')
if (not phone) and (not email):
raise forms.ValidationError(
_("You cannot leave both phone and email empty."
" Signup with either phone or email or both."))
def clean_username(self):
username = self.data.get('username')
check_username_case_insensitive(username)
if username.lower() in settings.CADASTA_INVALID_ENTITY_NAMES:
raise forms.ValidationError(
_("Username cannot be “add” or “new”."))
return username
def clean_password(self):
password = self.data.get('password')
validate_password(password)
errors = []
email = self.data.get('email')
if email:
email = email.split('#')
if email[0].casefold() in password.casefold():
errors.append(_("Passwords cannot contain your email."))
username = self.data.get('username')
if len(username) and username.casefold() in password.casefold():
errors.append(
_("The password is too similar to the username."))
phone = self.data.get('phone')
if phone:
if phone_validator(phone):
phone = str(parse_phone(phone).national_number)
if phone in password:
errors.append(_("Passwords cannot contain your phone."))
if errors:
raise forms.ValidationError(errors)
return password
def clean_email(self):
email = self.data.get('email')
if email:
if User.objects.filter(email=email).exists():
raise forms.ValidationError(
_("Another user with this email already exists"))
return email
def clean_phone(self):
phone = self.data.get('phone')
if phone:
if User.objects.filter(phone=phone).exists():
raise forms.ValidationError(
_("Another user with this phone already exists"))
return phone
def save(self, *args, **kwargs):
user = super().save(*args, **kwargs)
user.set_password(self.cleaned_data['password'])
user.save()
return user
You can get a lot out of reading the Django code; it is a well-commented codebase! The relevant section is in django/forms/forms.py. When a form is cleaned/validated, it will call full_clean. This will first call _clean_fields, which calls the field clean and looks for a clean_{fieldname} method on the form to call. Then, the form clean is called.
def full_clean(self):
"""
Clean all of self.data and populate self._errors and self.cleaned_data.
"""
self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
self._clean_fields()
self._clean_form()
self._post_clean()
def _clean_fields(self):
for name, field in self.fields.items():
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
def _clean_form(self):
try:
cleaned_data = self.clean()
except ValidationError as e:
self.add_error(None, e)
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
I'm trying to implement a user registration page in Django. Seems like a simple task, but I'm getting an error I just don't understand. Here's my view:
def registration_page(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username = form.cleaned_data['username'],
password = form.cleaned_data['password1'],
email = form.cleaned_data['email1']
)
return HttpResponseRedirect('/register/success/')
else:
form = RegistrationForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('registration/registration_page.html', variables)
And here's my registration form:
class RegistrationForm(forms.Form):
username = forms.CharField(label = u'Username', max_length = 30, error_messages={'required': 'A username is required.'})
email1 = forms.EmailField(label = u'Email Address', error_messages={'required': 'An email address is required.'})
email2 = forms.EmailField(label = u'Email Address confirmation', error_messages={'required': 'A confirmed email address is required.'})
password1 = forms.CharField(label = u'Password', widget = forms.PasswordInput(), error_messages={'required': 'A password is required.'})
password2 = forms.CharField(label = u'Password confirmation', widget = forms.PasswordInput(), error_messages={'required': 'A confirmed password is required.'})
def clean_password2(self):
if 'password1' in self.cleaned_data:
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 == password2:
return password2
raise forms.ValidationError('Passwords must be identical. Remember, passwords are case-sensitive.')
def clean_email2(self):
if 'email1' in self.cleaned_data:
email1 = self.cleaned_data['email1']
email2 = self.cleaned_data['email2']
if email1 == email2:
if User.objects.get(email = email1):
raise forms.ValidationError("The email address '%s' is already associated with an account. This typically means you created an account in the past. Please use it." % email1)
else:
return email2
raise forms.ValidationError('Email addresses must be identical.')
def clean_username(self):
if 'username' in self.cleaned_data:
username = self.cleaned_data['username']
if not re.search(r'^\w+$', username):
raise forms.ValidationError('Username can only contain letters, numbers, and the underscore characters.')
try:
User.objects.get(username = username)
except User.DoesNotExist:
return username
raise forms.ValidationError("Username '%s' is already taken." % username)
The problem pops up in the form.is_valid() and clean_username(). If I add a user that does or does not exist (it doesn't matter if the user exists or not, behavior is the same) and populate the form with data I know is valid, the code should simply return username from clean_username() and form.is_valid() should be True. Instead, I get an error page that looks like this:
DoesNotExist at /register/
User matching query does not exist.
Request Method: POST
Request URL: http://127.0.0.1:8000/register/
Django Version: 1.4.1
Exception Type: DoesNotExist
Exception Value:
User matching query does not exist.
Exception Location: /opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/models/query.py in get, line 366
Python Executable: /opt/local/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
Python Version: 2.7.2
Python Path:
['/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/distribute-0.6.28-py2.7.egg',
'/Library/Python/2.7/site-packages/bpython-0.10.1-py2.7.egg',
'/Library/Python/2.7/site-packages/Pygments-1.5-py2.7.egg',
'/Library/Python/2.7/site-packages/django_registration-0.8-py2.7.egg',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-old',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/PIL',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/setuptools-0.6c11-py2.7.egg-info',
'/Library/Python/2.7/site-packages']
Server time: Sun, 28 Oct 2012 00:51:58 -0400
It's got to be something small I'm missing, but I can't see it.
In your code
def clean_email2(self):
...
# here
if User.objects.get(email = email1):
raise forms.ValidationError("The email address '%s' is already associated with an account. This typically means you created an account in the past. Please use it." % email1)
...
The get() could cause DoesNotExist but is not captured. Is that the issue?
Also, please provide full traceback of the line making the issue.
Furthermore, according to the doc, its better to put the logic of validating passwords & emails to the clean() method.
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.
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.