Jack Sparrow
Jack Sparrow

Reputation: 21

Django: Extending User Model - Inline User fields in UserProfile

Is there a way to display User fields under a form that adds/edits a UserProfile model? I am extending default Django User model like this:

class UserProfile(models.Model):
    user = models.OneToOneField(User, unique=True)
    about = models.TextField(blank=True)

I know that it is possible to make a:

class UserProfileInlineAdmin(admin.TabularInline):

and then inline this in User ModelAdmin but I want to achieve the opposite effect, something like inverse inlining, displaying the fields of the model pointed by the OneToOne Relationship (User) in the page of the model defining the relationship (UserProfile). I don't care if it would be in the admin or in a custom view/template. I just need to know how to achieve this.

I've been struggling with ModelForms and Formsets, I know the answer is somewhere there, but my little experience in Django doesn't allow me to come up with the solution yet. A little example would be really helpful!

Upvotes: 1

Views: 1298

Answers (1)

dokkaebi
dokkaebi

Reputation: 9190

This has been brought up before.

Here's a blog post with what I think is my favorite solution. The gist is to use two ModelForms, and render them into a single <form> tag in the template making use of the prefix kwarg:

http://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/

Here's another method which I like a bit less, but is also valid. They use two separate <form>s on the page, with different actions and two submit buttons:

Proper way to handle multiple forms on one page in Django

This one talks more specifically about Users and UserProfiles:

How to create a UserProfile form in Django with first_name, last_name modifications?

Update

Here is what I ended up with

# models.py
class UserProfile(models.Model):

    favorite_color = models.CharField(max_length=30)
    user = models.OneToOneField(User)


# forms.py
class UserProfileForm(forms.ModelForm):

    class Meta:
        model = UserProfile
        # we fill the 'user' value in UserCreateView.form_valid
        exclude = ('user',)


# views.py
from django.contrib.auth.forms import UserCreationForm
class UserCreateView(FormView):

    # url to redirect to after successful form submission
    success_url = reverse_lazy('user_list')
    template_name = "userform.html"

    def get_context_data(self, *args, **kwargs):
        data = super(UserCreateView, self).get_context_data(*args, **kwargs)
        data['userform'] = self.get_form(UserCreationForm, 'user')
        data['userprofileform'] = self.get_form(UserProfileForm, 'userprofile')
        return data

    def post(self, request, *args, **kwargs):
        forms = dict((
            ('userform', self.get_form(UserCreationForm, 'user')),
            ('userprofileform', self.get_form(UserProfileForm, 'userprofile')),
        ))
        if all([f.is_valid() for f in forms.values()]):
            return self.form_valid(forms)
        else:
            return self.form_invalid(forms)

    def get_form(self, form_class, prefix):
        return form_class(**self.get_form_kwargs(prefix))

    def get_form_kwargs(self, prefix):
        kwargs = super(UserCreateView, self).get_form_kwargs()
        kwargs.update({'prefix': prefix})
        return kwargs

    def form_valid(self, forms):
        user = forms['userform'].save()
        userprofile = forms['userprofileform'].save(commit=False)
        userprofile.user_id = user.id
        userprofile.save()
        return HttpResponseRedirect(self.get_success_url())

    def get(self, request, *args, **kwargs):
        return self.render_to_response(self.get_context_data())


# userform.html
<form action="" method="POST" class="form">
    {% csrf_token %}
    {{ userform.as_p }}
    {{ userprofileform.as_p }}
    <button type="submit">Submit</button>
</form>

# urls.py
...
url(r'^create/$', UserCreateView.as_view(), name='user_create'),
...

Upvotes: 2

Related Questions