16

I have the regular Django User model and a UserDetails model (OneToOneField with User), which serves as an extension to the User model. (I tried Django 1.5's feature and it was a headache with strangely horrible documentation, so I stuck with the OneToOneField option)

So, in my quest to build a custom registration page that will have a registration form comprised of the User fields and the UserDetails fields, I wondered if there was a way to generate the form automatically (with all of its validations) out of these two related models. I know this works for a form made of one model:

class Meta:
    model = MyModel

But is there anyway to get a similar functionality for a form comprised of two related models?

ZAD-Man
  • 1,328
  • 23
  • 42
Orca
  • 2,035
  • 4
  • 24
  • 39
  • Yes there is, just make your own model form that you can extend in the init function with a custom save fn. – Naftali Apr 08 '13 at 22:02
  • 2
    @Neal Can you please elaborate on that, with an example if possible, and post it as an answer so I can accept it? – Orca Apr 08 '13 at 22:04
  • I do not know how I can elaborate... The only thing I would put in an answer is exactly what is in my comment above... – Naftali Apr 09 '13 at 01:22
  • Possible duplicate of [Multiple Models in a single django ModelForm?](http://stackoverflow.com/questions/2770810/multiple-models-in-a-single-django-modelform) – Ciro Santilli OurBigBook.com May 12 '16 at 20:59

2 Answers2

17
from django.forms.models import model_to_dict, fields_for_model


class UserDetailsForm(ModelForm):
    def __init__(self, instance=None, *args, **kwargs):
        _fields = ('first_name', 'last_name', 'email',)
        _initial = model_to_dict(instance.user, _fields) if instance is not None else {}
        super(UserDetailsForm, self).__init__(initial=_initial, instance=instance, *args, **kwargs)
        self.fields.update(fields_for_model(User, _fields))

    class Meta:
        model = UserDetails
        exclude = ('user',)

    def save(self, *args, **kwargs):
        u = self.instance.user
        u.first_name = self.cleaned_data['first_name']
        u.last_name = self.cleaned_data['last_name']
        u.email = self.cleaned_data['email']
        u.save()
        profile = super(UserDetailsForm, self).save(*args,**kwargs)
        return profile
Community
  • 1
  • 1
catherine
  • 22,492
  • 12
  • 61
  • 85
  • looks good, but shouldn't class Meta: model = UserDetails and not model = UserDetailsForm ? Also, suppose UserDetails has a field called phoneNumber (with a regex for \d{10}) does the form automatically take care of that as you put it (because it's linked with the UserDetails model) or do I need to modify your code above? Thanks – Orca Apr 09 '13 at 08:49
  • sorry my mistake. You have to modified it because I only put common fields for user – catherine Apr 09 '13 at 08:54
  • I see. Two more questions: 1- If I have to add the custom UserDetails fields (such as phone), where should I make a reference to it in the above code, or will django do that for me automagically because class meta now points to UserDetails? 2- What about the other user fields (password, username)? Do I have to add them to your code or Django takes care of that? – Orca Apr 09 '13 at 08:58
  • 1 - you don't have to put the phone because the form meta is already point to UserDetails. 2 - you have to add password and username the way I add first_name, last_name, and email in __init__ function. So __init__ function is for your User model and meta is for your UserDetails model – catherine Apr 09 '13 at 09:05
  • Thank you very much. Now it is clear. What about the validation of the email field for user (and making sure password1 equals password2) during registration? I assume I will do that too, right? – Orca Apr 09 '13 at 09:09
  • you can add clean_method() in the form – catherine Apr 09 '13 at 09:29
  • @catherine: I tried your solution, but it did not work, I have some issues that I posted here: http://stackoverflow.com/questions/24171103/generating-user-and-userprofle-model-from-a-single-form-in-django – eagertoLearn Jun 11 '14 at 20:29
  • I'm curious how you would populate the ModelForm from a POST: `form = UserDetailForm(request.POST)` is not possible with that constructor. – Stuart Apr 29 '16 at 00:34
6

One way you can accomplish this, if you want to keep the ModelForm for User and UserDetails separate would be to return both forms to the front-end, and make sure they are both in the same html form element (i.e. all fields will be returned when data is posted).

This works if User and UserDetails don't have any fields with the same name. To input the data in each ModelForm instance, you use the usual method:

    form_user = UserForm(request.POST, instance=request.user)
    form_user_details = UserDetailsForm(request.POST, instance=request.user.userdetails)
YacineAzmi
  • 873
  • 1
  • 9
  • 10
  • Keeping in mind that the user still hasn't registered, wouldn't request.user point to nothing (or anonymous user)? – Orca Apr 09 '13 at 08:55
  • That is correct, it would point to AnonymousUser. I was just showing a case where you would re-use the form later. – YacineAzmi Apr 09 '13 at 14:36
  • The accepted answer in the duplicate question uses this. In my opinion this is better as it leaves less potential for screw ups. – ic_fl2 Mar 15 '18 at 21:15