Django Custom User registration

I got an issue with registration of Users.
I use a custom user linked to the default one.
i’ll show the code

in models.py of my accounts:

class ProfiloUtente(models.Model):
	user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)

	utente = models.ForeignKey(Utente, on_delete=models.CASCADE)
	studio = models.ManyToManyField(Studio)
	telefono_fisso = models.CharField(max_length=20, default=None, blank=True)
	telefono_mobile = models.CharField(max_length=20, default=None, blank=True)
	indirizzo = models.CharField(max_length=40, default=None, blank=True)
	citta = models.CharField(max_length=50, default=None, blank=True)
	cap = models.CharField(max_length=5, default=None, blank=True)
	provincia = models.CharField(max_length=30, default=None, blank=True)
	cod_fiscale = models.CharField(max_length=16, default=None, blank=True)
	p_iva = models.CharField(max_length=27, default=None, blank=True)
	def __str__(self):
		return self.user.username

and in views.py my register view as:

def register(request):
	if request.method =='POST':
		form = RegistrationForm(request.POST)
		profilo_utente_form = ProfiloUtenteForm(request.POST)
		if form.is_valid() and profilo_utente_form.is_valid():

			user = form.save()
			profile = profilo_utente_form.save(commit=False)
			profile.user = user

			profile.save()
			profilo_utente_form.save_m2m()

			return redirect('/incarico_slice')
		else:
			args = {'form': form, 'profilo_utente_form': profilo_utente_form}
			return render(request, 'accounts/register.html', args)
	else:
		form = RegistrationForm()

		profilo_utente_form = ProfiloUtenteForm()


	args = {'form': form, 'profilo_utente_form': profilo_utente_form}
	return render(request, 'accounts/register.html', args)

lastly in forms.py using these default user fields:

class RegistrationForm(UserCreationForm):
	"""docstring for RegistrationForm"""
	email = forms.EmailField(required=True)
	first_name = forms.CharField(max_length=30, required=True)
	last_name = forms.CharField(max_length=100, required=True)


	class Meta: # define a metadata related to this class
		model = User
		fields = (
			'username',
			'email',
			'first_name',
			'last_name',
			'password1',
			'password2',

		)
	def save(self, commit=True):
		user = super(RegistrationForm, self).save(commit=False)
		user.email = self.cleaned_data['email']
		user.first_name = self.cleaned_data['first_name']
		user.last_name = self.cleaned_data['last_name']


		if commit:
			user.save() # running sql in database to store data
		return user


just to watch register.html

	<h1>Register!</h1>
	<form method="post">
		{% csrf_token %}
		{{ profilo_utente_form.as_p }}
		{{ form.as_p }}

		<button type="submit">Submit</button>


	</form>

The strange thing is that if i do something wrong in my registration form, in the database appears that it creates The Default user model without creating his related userProfile ( ProfiloUtente), and this is something that shouldn’t happen, still trying to fix it.

Any help is appreciated!!!
Thanks in advance

In your view you’re saving your user before you process the profile. The statement user = form.save() is creating the user object at that point.

You might try user = form.save(commit=False) if you need the reference for the profile, process the profile data, and once that has been successful, finish up with the user.save()

Ken

Thanks Ken!! That worked.
how could i set the email to be unique with this structure?

this is strange.

File “C:\Users\crist\OneDrive\Desktop\SincroDjango\sincro1.2\accounts\views.py”, line 31, in register
profile.save()
File “C:\Users\crist\AppData\Local\Programs\Python\Python38\lib\site-packages\django\db\models\base.py”, line 692, in save
raise ValueError(
ValueError: save() prohibited to prevent data loss due to unsaved related object ‘user’.

def register(request):
	args = {}
	if request.method =='POST':
		form = RegistrationForm(request.POST)
		profilo_utente_form = ProfiloUtenteForm(request.POST)
		if form.is_valid() and profilo_utente_form.is_valid():

			user = form.save(commit=False)
			profile = profilo_utente_form.save(commit=False)
			profile.user = user

			profile.save()
			profilo_utente_form.save_m2m()
			user.save()

			return redirect('/accounts/login')
		else:
			args = {'form': form, 'profilo_utente_form': profilo_utente_form}
			return render(request, 'accounts/register.html', args)
	else:
		form = RegistrationForm()

		profilo_utente_form = ProfiloUtenteForm()


	args = {'form': form, 'profilo_utente_form': profilo_utente_form}
	return render(request, 'accounts/register.html', args)

how should i flag it?

Move the user.save() to immediately before the profile.save() - You should have good data all the way around at that point.

Don’t flag it - I started to write something I realized was wrong, so I yanked that response.

1 Like

This seems works fine finally.
I still got people who can register using the same email as another user, should i write some logic to escape this issue or should i use something else?

class RegistrationForm(UserCreationForm):
	"""docstring for RegistrationForm"""
	email = forms.EmailField(required=True)
	first_name = forms.CharField(max_length=30, required=True)
	last_name = forms.CharField(max_length=100, required=True)


	class Meta:
		model = User
		fields = (
			'username',
			'email',
			'first_name',
			'last_name',
			'password1',
			'password2',

		)
	def save(self, commit=True):
		user = super(RegistrationForm, self).save(commit=False)
		user.email = self.cleaned_data['email']
		user.first_name = self.cleaned_data['first_name']
		user.last_name = self.cleaned_data['last_name']


		if commit:
			user.save()
		return user

(sorry, im botheering you so much @KenWhitesell)

So I started answering that question, realized my answer was wrong, and needed to back off and think about it a little bit.

The problem with handling it within the form is that it doesn’t prevent someone using the admin facility from modifying the email address to be a duplicate, and, it does expose you to a potential race condition where, even with the check in the form, you end up with duplicate email addresses.

So far I’ve come up with three different answers for how to do this in your situation.

  1. Change from using the system User model to a custom User model where you define the email address as being a unique field.

  2. Add an email address field to your profile model that is unique.

    • Copy your User email address field to it in your form processing
      or
    • Only use it in your form and use that for your authentication process
  3. “Monkey patch” the User object to set the email field as unique and write a custom migration script to have uniqueness enforced at the database layer.

or, for completeness…

  1. Find a third-party-package that you’re comfortable using that provides this facility.

Ken

Got it, i’ll try with one of your proposals right now.
I made this in the meantime

def register(request):
	args = {}
	if request.method =='POST':
		form = RegistrationForm(request.POST)
		profilo_utente_form = ProfiloUtenteForm(request.POST)
		email_valid = (len(User.objects.filter(email=request.POST['email'])) >= 1)
		if (form.is_valid() and profilo_utente_form.is_valid()) and not email_valid:

			user = form.save(commit=False)
			profile = profilo_utente_form.save(commit=False)
			profile.user = user

			user.save()
			profile.save()
			profilo_utente_form.save_m2m()

			return redirect('/accounts/login')
		else:
			args = {'form': form, 'profilo_utente_form': profilo_utente_form}
			return render(request, 'accounts/register.html', args)
	else:
		form = RegistrationForm()

		profilo_utente_form = ProfiloUtenteForm()


	args = {'form': form, 'profilo_utente_form': profilo_utente_form}
	return render(request, 'accounts/register.html', args)

Does this make sense?

It makes sense, but it does have the two holes I identified above -

  • There’s a timing issue here. Two people could be submitting the form at effectively the same time, where the query returns no matching emails up on line 6, but then both save that same email further down.

  • If there’s any other facility (e.g. admin) that can edit the email field, you would need to make sure you provide the same guards there.

  • If it should happen that you end up with two people having the same email address, neither is going to be able to log on, and are not going to get any indication as to why they can’t log on. (Based on your other topic regarding the authentication process.)

It’s perfectly valid for you to accept these risks - I’m not saying you need to mitigate them. But you should be aware of them.

Ken

1 Like

Ok i got it.
Not too many people have to register at the same time, at least 100 people inside all database, i’ll try to avoid problems checking when they edit the email field, it should be enough for now.
Thanks a lot