django model Form. Include fields from related models

Viewed 26188

I have a model, called Student, which has some fields, and a OneToOne relationship with a user (django.contrib.auth.User).

class Student(models.Model):

    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)

Then, I have a ModelForm for that model

class StudentForm (forms.ModelForm):
    class Meta:
        model = Student

Using the fields attribute in class Meta, I've managed to show only some fields in a template. However, can I indicate which user fields to show?

Something as:

   fields =('personalInfo','user.username')

is currently not showing anything. Works with only StudentFields though/

Thanks in advance.

4 Answers

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.

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.

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 :)

Related