[Django]-Django model Form. Include fields from related models

31πŸ‘

A common practice is to use 2 forms to achieve your goal.

  • A form for the User Model:

    class UserForm(forms.ModelForm):
        ... Do stuff if necessary ...
        class Meta:
            model = User
            fields = ('the_fields', 'you_want')
    
  • A form for the Student Model:

    class StudentForm (forms.ModelForm):
        ... Do other stuff if necessary ...
        class Meta:
            model = Student
            fields = ('the_fields', 'you_want')
    
  • Use both those forms in your view (example of usage):

    def register(request):
        if request.method == 'POST':
            user_form = UserForm(request.POST)
            student_form = StudentForm(request.POST)
            if user_form.is_valid() and student_form.is_valid():
                user_form.save()
                student_form.save()
    
  • Render the forms together in your template:

    <form action="." method="post">
        {% csrf_token %}
        {{ user_form.as_p }}
        {{ student_form.as_p }}
        <input type="submit" value="Submit">
    </form>
    

Another option would be for you to change the relationship from OneToOne to ForeignKey (this completely depends on you and I just mention it, not recommend it) and use the inline_formsets to achieve the desired outcome.

πŸ‘€John Moutafis

8πŸ‘

Both answers are correct: Inline Formsets make doing this easy.

Be aware, however, that the inline can only go one way: from the model that has the foreign key in it. Without having primary keys in both (bad, since you could then have A -> B and then B -> A2), you cannot have the inline formset in the related_to model.

For instance, if you have a UserProfile class, and want to be able to have these, when shown, have the User object that is related shown as in inline, you will be out of luck.

You can have custom fields on a ModelForm, and use this as a more flexible way, but be aware that it is no longer β€˜automatic’ like a standard ModelForm/inline formset.

4πŸ‘

An alternative method that you could consider is to create a custom user model by extending the AbstractUser or AbstractBaseUser models rather than using a one-to-one link with a Profile model (in this case the Student model). This would create a single extended User model that you can use to create a single ModelForm.

For instance one way to do this would be to extend the AbstractUser model:

from django.contrib.auth.models import AbstractUser

class Student(AbstractUser):

    phone = models.CharField(max_length = 25 )
    birthdate = models.DateField(null=True) 
    gender = models.CharField(max_length=1,choices = GENDER_CHOICES) 
    city = models.CharField(max_length = 50)
    personalInfo = models.TextField()
    # user = models.OneToOneField(User,unique=True)   <= no longer required

In settings.py file, update the AUTH_USER_MODEL

AUTH_USER_MODEL = 'appname.models.Student'

update the model in your Admin:

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

admin.site.register(Student, UserAdmin)

Then you can use a single ModelForm that has both the additional fields you require as well as the fields in the original User model. In forms.py

from .models import Student    

class StudentForm (forms.ModelForm):
    class Meta:
        model = Student
        fields = ['personalInfo', 'username']

A more complicated way would be to extend the AbstractBaseUser, this is described in detail in the docs.

However, I’m not sure whether creating a custom user model this way in order to have a convenient single ModelForm makes sense for your use case. This is a design decision you have to make since creating custom User models can be a tricky exercise.

πŸ‘€Kevin L.

1πŸ‘

From my understanding you want to update the username field of auth.User which is OneToOne relation with Student, this is what I would do…

class StudentForm (forms.ModelForm):

username = forms.Charfield(label=_('Username'))
class Meta:
    model = Student
    fields = ('personalInfo',)

def clean_username(self):
    # using clean method change the value
    # you can put your logic here to update auth.User
    username = self.cleaned_data('username')
    # get AUTH USER MODEL
    in_db = get_user_model()._default_manager.update_or_create(username=username)

hope this helps πŸ™‚

πŸ‘€Sanchit

Leave a comment