views:

934

answers:

6

If think my question is pretty obvious and almost every developer working with UserProfile should be able to answer it.

However, I could not find any help on the django documentation or in the Django Book.

When you want to do a UserProfile form in with Django Forms, you'd like to modify the profile fields as well as some User field.

But there is no forms.UserProfileForm (yet?) !

How do you do that ?

+7  A: 

Here is how I finally did :

class UserProfileForm(forms.ModelForm):
    first_name = forms.CharField(label=_(u'Prénom'), max_length=30)
    last_name = forms.CharField(label=_(u'Nom'), max_length=30)

    def __init__(self, *args, **kw):
        super(forms.ModelForm, self).__init__(*args, **kw)
        self.fields['first_name'].initial = self.instance.user.first_name
        self.fields['last_name'].initial = self.instance.user.last_name

        self.fields.keyOrder = [
            'first_name',
            'last_name',
            ...some_other...
            ]

    def save(self, *args, **kw):
        super(forms.ModelForm, self).save(*args, **kw)
        self.instance.user.first_name = self.cleaned_data.get('first_name')
        self.instance.user.last_name = self.cleaned_data.get('last_name')
        self.instance.user.save()

    class Meta:
        model = UserProfile
Natim
Where it says "Media" it should say "Meta".
Daishiman
This solution does not working when the profile has not yet been created. I don't have a fix for it but when I do I will post it.
Justin Hamade
This doesn't happend if you use `post_save` signal on the `User` model as describe here : http://docs.djangoproject.com/en/dev/topics/auth/#storing-additional-information-about-users
Natim
thanks @natim! had the same question, this solved it.
Keith Fitzgerald
A: 

I take it normal that you don't find any information in the docs as you merge two models into a single form.

Alternatively and maybe very obviously you can: Create two modelforms, one for the user and the other for the userprofile. Set the userprofile modelform to display only firstname and lastname. Put both forms in the same template within a single <form> tag. When it is submitted, call the save methods of each form.

shanyu
Yes, but it is not really straight forward to use two forms for actually one ... With this solution, you cannot order the fields as you want and continuing to use the `{{ form }}`. with the solution I provided, you can do whatever you want continuing to use the form framework.
Natim
I just don't like the idea of merging two models in a modelform. Apart from my subjective opinion, I have no concrete objections against your solution, and it seems that both solutions have their pros and cons. Use at will ;)
shanyu
I agree with you, but it is regarding the point of view that you look for this specific problem. UserProfile and User model are intimately related. For me UserProfile model is just a way of adding some missing fields to the User model. And when you want to update your profile, you want to update all those fields regardless to if they are in the UserProfile or the User model since they are combined together to stock properties about the user.
Natim
+2  A: 

This is how I did it in the current trunk (Revision: 11804). The solution of Natim was not working for me.

In admin.py:

class ProfileAdmin(admin.ModelAdmin):
    form = ProfileForm

    def save_model(self, request, obj, form, change):
        obj.user.first_name = form.cleaned_data['first_name']
        obj.user.last_name = form.cleaned_data['last_name']
        obj.user.save()
        obj.save()

In forms.py:

class ProfileForm(forms.ModelForm):
    first_name = forms.CharField(max_length=256)
    last_name = forms.CharField(max_length=256)

    def __init__(self, *args, **kwargs):
        super(ProfileForm, self).__init__(*args, **kwargs)
        try:
            self.fields['first_name'].initial = self.instance.user.first_name
            self.fields['last_name'].initial = self.instance.user.last_name
        except User.DoesNotExist:
            pass

    class Meta:
         fields = ['first_name', 'last_name', ...etc.]
wunki
But with your ProfileForm, you cannot save the profile.Mine does not modify the admin. But yours does, but cannot be use without the admin.
Natim
It does save the profile with ``obj.save()``. Your correct that it only works in the admin. I had to build it into that. For it to work in your app. views you should move the logic from the admin into the form. Like you did with your ``save`` method.
wunki
A: 

Why not have two model forms on the back-end and just present them as a single form in your template? Drop the name fields from your UserProfileForm and create a second model form for the user object?

Tom
It is not a bad idea. But it is more convenient to have only one form.
Natim
Different approaches I guess. I went with what I described above precisely because it was more convenient to use the multiple Django form objects so they could be re-used independently of each other (and the processing is cleaner than what you have above (IMHO)).To be clear, I'm not talking about multiple HTML forms on a page; it looks like one form to the user.
Tom
A: 

You can also try to use the django-basic-apps project which has a profiles app:

http://github.com/lincolnloop/django-basic-apps

Adam Nelson
A: 

I stumbled across this today and after some googling I found a solution that is a bit cleaner in my opinion:

#in forms.py
class UserForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ["username", "email"]

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile

#in views.py
def add_user(request):
    ...
    if request.method == "POST":
        uform = UserForm(data = request.POST)
        pform = UserProfileForm(data = request.POST)
        if uform.is_valid() and pform.is_valid():
            user = uform.save()
            profile = pform.save(commit = False)
            profile.user = user
            profile.save()
            ....
    ...

#in template
<form method="post">
    {{ uform.as_p }}
    {{ pform.as_p }}
    <input type="submit" ...>
</form>

Source

theycallmemorty
It is another solution, but you cannot order your fields as you want. For example I have a `title` field that should be before the first_name and last_name but still part of pform.
Natim