I'm developing a registration app that use User model with OneToOneField.
models.py
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.db import models
from django.dispatch import receiver
class UserProfile(models.Model):
GENDER_CHOICES = (
('M', 'Male'),
('F', 'Female'),
)
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField('First name', max_length=30)
surname = models.CharField('Last name', max_length=150)
gender = models.CharField('Gender', max_length=1, choices=GENDER_CHOICES)
date_of_birth = models.DateField('Date of birth', null=True)
profile_image = models.ImageField('Profile image', upload_to='users_upload/profiles/%Y/%m/%d')
company_name = models.CharField('Company Name', max_length=75, blank=True, null=True)
fiscal_data = models.CharField('Fiscal data', max_length=200)
city = models.CharField('City', max_length=150)
address = models.CharField('Address', max_length=200)
postcode = models.CharField('Zip Code', max_length=50)
country = models.CharField('Country', max_length=75)
telephone_number = models.CharField('Telephone', max_length=20)
email_address = models.EmailField('Email')
class Meta:
unique_together = ['fiscal_data', 'email_address']
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.userprofile.save()
forms.py
from django import forms
from django.contrib.auth import get_user_model, password_validation
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from .models import UserProfile
class UserForm(UserCreationForm):
class Meta:
model = User
fields = ('username', 'password1', 'password2')
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = [
'name', 'surname', 'gender', 'date_of_birth',
'user_type', 'profile_image', 'company_name',
'address', 'postcode', 'city', 'country',
'telephone_number', 'email_address',
'fiscal_data',
]
views.py
from django.contrib.auth.forms import UserCreationForm
from django.db import transaction
from django.shortcuts import render
from .forms import UserProfileForm
from .models import UserProfile
@transaction.atomic
def createUser(request):
if request.method == 'POST':
user_form = UserCreationForm(request.POST)
profile_form = UserProfileForm(request.POST or None, request.FILES or None)
if user_form.is_valid() and profile_form.is_valid():
user = user_form.save()
user.refresh_from_db()
profile_form = UserProfileForm(request.POST, instance=user.userprofile)
profile_form.full_clean()
profile_form.save()
else:
user_form = UserCreationForm()
profile_form = UserProfileForm()
return render(request, 'usermanager/editing/create_user.html', {
'user_form': user_form,
'profile_form': profile_form
})
create_user.html
<form method="POST" enctype="multipart/form-data" novalidate>{% csrf_token %}
<div class="card-body">
{{ user_form.as_p }}
{{ profile_form.as_p }}
</div>
<div class="card-footer">
<div class="row justify-content-md-center">
<div class="col-md-auto">
<input type="submit" class="btn btn-danger shadow" value="Create Profile">
</div>
</div>
</div>
</form>
When I try to register a new user using the form I see this error message:
UNIQUE constraint failed: usermanager_userprofile.email_address
This happen even if I try to register a new user using the administration panel.
How I can resolve this?
I've used this strategy to create my model, for for createUser view I've used this.
Traceback:
Environment:
Request Method: POST Request URL: http://127.0.0.1:8000/create-user/
Django Version: 2.2.5 Python Version: 3.6.8 Installed Applications: ['django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'kernel', 'usermanager'] Installed Middleware: ['django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware']
Traceback:
File "/home/max/Django/area-test/app_usermanager_dev/devenv/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner 34. response = get_response(request)
File "/home/max/Django/area-test/app_usermanager_dev/devenv/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response 115. response = self.process_exception_by_middleware(e, request)
File "/home/max/Django/area-test/app_usermanager_dev/devenv/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response 113. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/lib/python3.6/contextlib.py" in inner 52. return func(*args, **kwds)
File "/home/max/Django/area-test/app_usermanager_dev/app_usermanager/usermanager/views.py" in createUser 30. profile_form.save() # Gracefully save the form
File "/home/max/Django/area-test/app_usermanager_dev/devenv/lib/python3.6/site-packages/django/forms/models.py" in save 453. 'created' if self.instance._state.adding else 'changed',
Exception Type: ValueError at /create-user/ Exception Value: The UserProfile could not be changed because the data didn't validate.
In your UserProfile
model, there is a field:
email_address = models.EmailField('Email', unique=True)
When you add unique=True
it means that field will be unique for all the entries in that model. You can either remove that keyword argument and run makemigrations and migrate. Or you need to enter unique email address for each UserProfile
entry.
Probably the error is occurring because you are not validating the profile form when you initiated it second time:
profile_form = UserProfileForm(request.POST, instance=user.userprofile)
profile_form.full_clean()
# before calling save, you need to call profile_form.is_valid()
profile_form.save()
TBH, I think you should remove your post save signal create_user_profile
and save_user_profile
, because it is over complicating your implementation of createUser
method. Also, post save signals would be useful if you didn't have a mechanism to create a UserProfile
but you have a form to do that, so why need a signal?
If you remove those signals, then try the following code:
def createUser(request):
if request.method == 'POST':
user_form = UserCreationForm(request.POST)
profile_form = UserProfileForm(request.POST or None, request.FILES or None)
if user_form.is_valid() and profile_form.is_valid():
user = user_form.save()
profile_form = profile_form.save(commit=False)
profile_form.user = user
profile_form.save()
FYI, you should use snake_case
when naming methods as per pep8 standard. So its better if you name createUser
to create_user
.