25

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?

markwalker_
  • 9,401
  • 7
  • 51
  • 86
Natim
  • 15,199
  • 21
  • 80
  • 140

7 Answers7

40

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
  • 11,427
  • 12
  • 47
  • 68
  • 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 Feb 15 '10 at 05:18
  • Natim, you can - but it will need to do it with your hands:) – Nikita Hismatov May 03 '12 at 17:22
  • You can specify the ordering of the fields...as ns-keip mentioned...as long as you do the rendering yourself.. https://docs.djangoproject.com/en/1.8/topics/forms/#rendering-fields-manually – Max.Mirkia Apr 08 '15 at 14:15
  • How do I get password1 & password2 to work for user registration? Only 'password' is available. – Raven Mar 10 '21 at 05:46
35

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(UserProfileForm, 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(UserProfileForm, 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
Matthew Schinckel
  • 32,344
  • 6
  • 71
  • 109
Natim
  • 15,199
  • 21
  • 80
  • 140
  • Where it says "Media" it should say "Meta". – Daishiman Mar 17 '10 at 17:34
  • 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 Jul 10 '10 at 01:10
  • 5
    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 Jul 14 '10 at 23:08
  • More information about the signal settings up here : http://stackoverflow.com/questions/1909617/django-registration-and-user-profile-creation/1909719#1909719 – Natim Apr 19 '11 at 15:32
  • From Django 1.5, you can just use the AUTH_USER_MODEL :) https://docs.djangoproject.com/en/dev/topics/auth/customizing/#substituting-a-custom-user-model – Natim Apr 01 '13 at 08:40
5

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
  • 643
  • 6
  • 13
  • 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 Dec 10 '09 at 02:43
  • 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 Dec 10 '09 at 19:53
1

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

https://github.com/nathanborror/django-basic-apps

Manur
  • 8,030
  • 2
  • 25
  • 28
Adam Nelson
  • 7,250
  • 10
  • 42
  • 61
  • This is unuseful cause you dont create combine formset from UserProfile and django User model/ – Denis Jan 29 '13 at 11:48
1

This is something i used in one my projects long back hopefully it should help out the others googling this problem .

class SignUpForm(forms.ModelForm):
    first_name = forms.CharField(max_length = 30)
    last_name = forms.CharField(max_length = 30)
    username = forms.CharField(max_length = 30)
    password = forms.CharField(widget = forms.PasswordInput)
    password1 = forms.CharField(widget = forms.PasswordInput)
    class Meta:
        model = UserProfile
        exclude = ['user']

    def clean_username(self): # check if username does not exist before
        try:
            User.objects.get(username=self.cleaned_data['username']) #get user from user model
        except User.DoesNotExist :
            return self.cleaned_data['username']

        raise forms.ValidationError("This user exist already choose an0ther username")



    def clean(self, *args , **kwargs):
        super(SignUpForm).clean(*args ,**kwargs) # check if password 1 and password2 match each other
        if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:#check if both pass first validation
            if self.cleaned_data['password1'] != self.cleaned_data['password2']: # check if they match each other
                raise forms.ValidationError("Passwords don't match each other")

        return self.cleaned_data
    def save(self): # create new user
        new_user = User.objects.create_user(username=self.cleaned_data['username'],password=self.cleaned_data['password1'],email=self.cleaned_data['email'])
        new_user.first_name = self.cleaned_data['first_name']
        new_user.last_name = self.cleaned_data['last_name']
        new_user.save()
        UserProf =  super(SignUpForm,self).save(commit = False)
        UserProf.user = new_user
        UserProf.save()
        return UserProf
0

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
  • 8,990
  • 5
  • 55
  • 68
  • 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 Nov 13 '09 at 08:44
  • 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 Nov 13 '09 at 09:24
  • 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 Nov 13 '09 at 17:17
0

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
  • 19,972
  • 3
  • 61
  • 86
  • It is not a bad idea. But it is more convenient to have only one form. – Natim Dec 10 '09 at 02:44
  • 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 Dec 10 '09 at 23:01