Create a new user, manually

I am trying to build an admin page where an admin user can create a new user. Although it seems simple enough to have the user create their own account, or to create such through the Django Admin interface, I am really struggling to work out how to build my custom admin to allow creation of new users.

I am using django_allauth for users creeating their own accounts, but the project also requires that an admin can create new accounts (in fact it will probably and up with admins creating all accounts).

While I can create views and forms to create other new elements, I cannot work out how to do this for users. Are there special CreateUser views or forms?

Any pointers appreciated.

It’s going to be more helpful if you would describe in more detail exactly what isn’t working for you when you’re trying to create a user.
Are you using the standard User class? If so, you can look at the create_user helper method in the docs to see one way of doing it.
If you’re using a custom user object, we’ll probably need to see that Model along with the view you’re using to create the user.

I have a Profile model:

class Profile(models.Model):
    id = models.BigAutoField(primary_key=True)
    #id = models.CharField(primary_key=True, default=shortuuid.uuid, max_length=26)
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    account = models.ForeignKey('Account', verbose_name=_('account'), on_delete=models.CASCADE)
    home_phone = PhoneNumberField(_('Home phone'), max_length=30, null=True, blank=True, help_text=_('If outside the UK include country code (eg: "+39", "+353", "+33"…)'))
    work_phone = PhoneNumberField(_('Work phone'), max_length=30, null=True, blank=True, help_text=_('If outside the UK include country code (eg: "+39", "+353", "+33"…)'))
    mobile_phone = PhoneNumberField(_('Mobile phone'), max_length=30, null=True, blank=True, help_text=_('If outside the UK include country code (eg: "+39", "+353", "+33"…)'))
    receive_news = models.BooleanField(_('Receive news'), default=True, db_index=True)
    
    createdate = models.DateTimeField(auto_now_add=True, db_index=True)
    moddate = models.DateTimeField(auto_now=True, db_index=True)

    @staticmethod
    def get_or_create_for_user(user):
        if hasattr(user, 'profile'):
            return user.profile
        else:
            return Profile.objects.create(user=user)

And in settings.py:

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'allauth.account.auth_backends.AuthenticationBackend',
)

Which is what I think links the Profile to the User.

I put together a simple view:

class ControlProfileCreateView(CreateView):
	model = get_user_model()
	template_name = 'manageit/control/profile_edit.html'
	form_class = ControlProfileForm
	success_url = reverse_lazy('admin-control')

and form:

class ControlProfileForm(forms.ModelForm):
	
	class Meta:
		model = Profile
		fields = (
			'home_phone',
			'work_phone',
			'mobile_phone',
			'receive_news'
		)

I can get an empty form in a browser, but when I save the form I get an error that the Column 'user_id' cannot be null. I know this is a MySQL error, and I understand why (because the user field in Profile needs an id to link it to auth_user table).

No real idea how to get around that.

I tried searching for queires others had posted about this subject, and found some notes on allauth, which used this form:

class ControlProfileForm(forms.Form):
    model = get_user_model()
    first_name = forms.CharField(max_length=30, label='First name')
    last_name = forms.CharField(max_length=30, label='Last name')
    email = forms.CharField(max_length=30, label='E-mail')
    #home_phone = forms.CharField(max_length=20, label='Home phone')
    #work_phone = forms.CharField(max_length=20, label='Work phone')
    #mobile_phone = forms.CharField(max_length=20, label='Mobile phone')


    def signup(self, request, user):
        user.first_name = self.cleaned_data['first_name']
        user.last_name = self.cleaned_data['last_name']
        user.email = self.cleaned_data['email']
        # Replace 'profile' below with the related_name on the OneToOneField linking back to the User model
        #up = user.profile
        #up.home_phone = self.cleaned_data['home_phone']
        user.save()
        #up.save()

But on calling the form in a browser I get an error:

__init__() got an unexpected keyword argument 'instance'

Which is buried in django/views/generic/edit.py

I want the admin user to be able to create the Profile/auth_user rows, on a form, but not in the Django Admin interface.

Sorry, this keeps saving before I finish!

First, I’m a little confused - are you trying to create both the User and the Profile in the same view? Or are you looking to have a form that just collects / creates Profile data to be associated with a User?

(The reason I’m asking is because your ControlProfileCreateView has a model attribute of User and not Profile, so your intent is not clear to me.)

Also, you wrote:

No, that’s not correct - what links your Profile to the User is precisely this:

So in general, what this means is that you need the PrimaryKey of the User object when you’re trying to create your profile.

If you’re create both objects in the same view, the general pattern is:

new_user = YourUserForm.save()
new_profile = YourProfileForm.save(commit=False)
new_profile.user = new_user
new_profile.save()

Otherwise, if this is a second view for just the Profile, you need to pass the PK of the referenced user in as a parameter. Then, when you’re creating the form, use that Id to initialize the user field.

Are you trying to create both the User and the Profile in the same view?

Yes, as they are intrinsically linked, and I don’t want the admin to have to create a user, and then create a profile.

I think with ControlProfileCreateView I tried it with both User and Profile as the model. Same result.

Sorry, what I meant about the AUTHENTICATION_BACKENDS was that this is what sets-up allauth to manage the auth_user, of course the relationship between User and ``Profile``` is as you write.

Hmm, so maybe a formset would be in order?

While I’m here, you will notice that this line, above, is commented out:

#id = models.CharField(primary_key=True, default=shortuuid.uuid, max_length=31)

I would like to use UUIDs for at least some of the primary keys, but when using this form every time I run makemigrations it reports tha the id field has changed – when I know that it has not. Has anyone else recommendations for UUID/randomised ID generation in django?

Thanks

Ok, fair enough. You then have at least two options.

  1. Create your view for one of the two classes (the Generic Class-Based Views are designed to work with a single form for a single model). Then, add the fields needed for the other model as additional fields in the form. When the form is submitted, manually create that other object from those supplied fields and save them both.

  2. Create your own View class (or view function) with two forms, one for each model. Then save them both using the pattern I described earlier.

A formset isn’t the right answer here, because you’ve got two forms for two different classes. Formsets are designed to work with multiple instances of the same class.

(Sorry, no recommendations for your question about UUIDs.)

Ok, so I started trying to save/create the User, with a form.ModelForm, but found that I needed a username which I think this project will never need - log-in will be with email address), and that the password was null when saved (no password field on the form).

As a test I randomised username and password with initial field values on the form, which saved, but the password is not hashed.

Because the admin never needs to see the username or password there is no point their being on the form, so I thought I could do something like this in the view:

class ControlProfileCreateView(CreateView):
	model = get_user_model()
	template_name = 'bjsc_manage/control/profile_edit.html'
	form_class = ControlUserForm
	success_url = reverse_lazy('admin-control')
	
	def form_valid(self, form):
		user = get_user_model()
		user.username = shortuuid.uuid()
		user.password = shortuuid.uuid()
		user.save

Would that be the right approach, and hash the password?

Yes, you must have a username. In your case, you can copy the email field to the username field.

Also, you’re right, you don’t save the password directly. See the create_user method I referenced above - Create a new user, manually - #2 by KenWhitesell

I have resolved the creation of records, which turns out not to be that complicated, because you can reference Profile through User when committing the .save.

View:

@method_decorator(superuser_required(), name='dispatch')		
class ControlProfileCreateView(CreateView):
	#User = get_user_model()
	model = get_user_model()
	template_name = 'bjsc_manage/control/profile_edit.html'
	form_class = ControlProfileForm
	
	def form_valid(self, form):
		username = shortuuid.uuid()
		password = shortuuid.uuid()
		email = form.data['email']
		user = get_user_model().objects.create_user(username=username, email=email, password=password)

		for the_field in self.form_class.Meta.fields:			
			if type(getattr(user, the_field)) == bool:
				if the_field in form.data:
					setattr(user, the_field, True)
				else:
					setattr(user, the_field, False)
			else:
				setattr(user, the_field, form.data[the_field])
		
		for the_field in self.form_class.Meta.profile_fields:			
			if type(getattr(user.profile, the_field)) == bool:
				if the_field in form.data:
					setattr(user.profile, the_field, True)
				else:
					setattr(user.profile, the_field, False)
			else:
				setattr(user.profile, the_field, form.data[the_field])

		account = Account.objects.get(id=self.kwargs['account_id'])
		user.profile.account = account
		
		user.save()
		
		return HttpResponseRedirect(reverse_lazy('control-account-update', kwargs={'pk': self.kwargs['account_id']}))

Form:

class ControlProfileForm(forms.ModelForm):
	#add extra profile fields
	home_phone = PhoneNumberField(label=_('Home phone'), max_length=30, required=False, help_text=_('If outside the UK include country code (eg: "+39", "+353", "+33"…)'))
	work_phone = PhoneNumberField(label=_('Work phone'), max_length=30, required=False, help_text=_('If outside the UK include country code (eg: "+39", "+353", "+33"…)'))
	mobile_phone = PhoneNumberField(label=_('Mobile phone'), max_length=30, required=True, help_text=_('If outside the UK include country code (eg: "+39", "+353", "+33"…)'))
	receive_news = forms.BooleanField(label=_('Receive news'), required=False, initial=True)
	
	class Meta:
		model = get_user_model()
		
		fields = (
			'first_name',
			'last_name',
			'email',
		)
		
		profile_fields = (
			'home_phone',
			'work_phone',
			'mobile_phone',
			'receive_news'
		)

So, without having to touch the view, I can add fields in the form. Main complication with this is having to deal with checkbox input. It would be nice to be not to have the additional profile fields defined, and then in the Meta too. Any tips?

The other complication is that the User model does not have a unique contraint on the email field. While there are lots of tips on working around this, by far the easiest is to stick this in your models and run makemigrations, migrate.

from django.contrib.auth.models import User
User._meta.get_field('email')._unique = True
User._meta.get_field('email').null = False
User._meta.get_field('email').blank = False

In this case I have also fixed email so it is not null or blank.

The remaining issue is editing the user and profile after creation. If I set up a view to do this I just have the user elements populated on the form, the profile elements are empty, and if I do fill them in they do not save.

@method_decorator(superuser_required(), name='dispatch')		
class ControlProfileUpdateView(UpdateView):
	model = get_user_model()
	template_name = 'bjsc_manage/control/profile_edit.html'
	form_class = ControlProfileForm
	success_url = '/control/'

How to populate the profile elements?

Thanks