adamk
adamk

Reputation: 75

How to create User and User Profile in a single Django Admin form

I am struggling to figure out how to save User Profile for a new user created within Django Admin.

I have a custom User model and a simple user Profile with OneToOneField:

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_('email address'), unique=True)
    is_staff = models.BooleanField(_('staff'), default=False)
    is_active = models.BooleanField(_('active'), default=True)
    ...
class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    first_name = models.CharField(_('first name'), max_length=80, blank=False)
    last_name = models.CharField(_('last name'), max_length=80, blank=False)
    ...

I have the following user creation form:

class UserAdminCreationForm(forms.ModelForm):
    password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
    password2 = forms.CharField(
        label="Password confirmation", widget=forms.PasswordInput
    )
    first_name = forms.CharField(label="First name")
    last_name = forms.CharField(label="Last name")

    class Meta:
        model = User
        fields = ("email",)

    def save(self, commit=True):
        user = super(UserAdminCreationForm, self).save(commit=False) 
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user

My admin Add user form is rendered correctly and includes fields from both User and Profile models. After saving the form, a new user and a new profile are created in the database. However, the first_name and last_name fields in profile table are empty.

What I need to do to ensure that profile is saved together with the new user?

UPDATE

Overwriting the save method and ignoring the commit parameter worked for me:

    def save(self, commit=True):
        user = super(UserAdminCreationForm, self).save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        user.save()
        profile, created = Profile.objects.update_or_create(user=user)
        profile.first_name = self.cleaned_data["first_name"]
        profile.last_name = self.cleaned_data["last_name"]
        profile.save()
        return user

Upvotes: 2

Views: 1560

Answers (1)

bdoubleu
bdoubleu

Reputation: 6127

Rather than making a custom form you can edit both models in one admin change form by using an inline.

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User

from .models import Profile

class ProfileInline(admin.StackedInline):
    model = Profile
    can_delete = False
    verbose_name_plural = 'Profile'
    fk_name = 'user'

class CustomUserAdmin(UserAdmin):
    inlines = (ProfileInline, )

    def get_inline_instances(self, request, obj=None):
        if not obj:
            return list()
        return super().get_inline_instances(request, obj)


admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)

Since you're already defining your own custom user model, I would recommend doing away with the profile model entirely. It's just going to cause excess queries retrieving profile fields from the user instances.

UDPATE if you want to continue using your form:

def save(self, commit=True):
    user = super(UserAdminCreationForm, self).save(commit=False) 
    user.set_password(self.cleaned_data["password1"])
    if commit:
        user.save()
    Profile.objects.update_or_create(
        user=user,
        defaults={
            'first_name': self.cleaned_data['first_name'],
            'last_name': self.cleaned_data['last_name'],
        }
    )
    profile.first_name = self.cleaned_data["first_name"]
    profile.save()
    return user

Upvotes: 2

Related Questions