Django.fun

Django Form not Saving The field I added

I am following a Django tutorial on Youtube, I added a bio field in the UserUpdateForm. There is a slot for me to edit the bio on change_profile.html but when I press the update button it updates everything else except for the bio.

from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from .models import Profile

class UserRegisterForm(UserCreationForm):
    email = forms.EmailField()

    class Meta:
        model = User
        fields = ['username', 'email', 'password1', 'password2']

class UserUpdateForm(forms.ModelForm):
    email = forms.EmailField()
    # What I added
    bio = forms.CharField(required=False)

    class Meta:
        model = User
        fields = ['username', 'email', 'bio']

class ProfileUpdateForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['image']

The function that saves the forms

@login_required
def change_profile(request):
    if request.method == 'POST':
        u_form = UserUpdateForm(request.POST, instance=request.user)
        p_form = ProfileUpdateForm(request.POST, request.FILES, instance=request.user.profile)
        if u_form.is_valid() and p_form.is_valid():
            u_form.save()
            p_form.save()
            messages.success(request, 'Profile Updated')
            return redirect('profile')
    else:
        u_form = UserUpdateForm(instance=request.user)
        p_form = ProfileUpdateForm(instance=request.user.profile)
    context = {
        'u_form' : u_form,
        'p_form' : p_form
    }
    return render(request, 'users/change_profile.html', context)

The change_profile.html

{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block title %}Change Profile{% endblock title %}
{% block content %}
    <div class="content-section">
        <form method="post" enctype="multipart/form-data">
            {% csrf_token %}
            <fieldset class="form-group">
                <legend class="border-bottom mb-4">Edit Profile</legend>
                {{ u_form|crispy }}
                {{ p_form|crispy }}
            </fieldset>
            <div class="form-group">
                <button class="btn btn-outline-info" type="submit">Update</button>
            </div>
        </form>
    </div>
{% endblock content %}

And the profile.html

{% extends "blog/base.html" %}
{% block title %}Profile{% endblock title %}
{% block content %}
    <div class="content-section">
        <div class="media">
            <img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
            <div class="media-body">
                <h2 class="account-heading">{{ user.username }}</h2>
                <p class="text-secondary">{{ user.email }}</p>
                <p class="article-content">{{ user.bio }}</p>
            </div>
        </div>
        <a class="ml-2" href="{% url 'change_profile' %}">Edit Profile</a>
{% endblock content %}

You could have simply override the the User model and add your custom fields, then you don't need to add extra fields in your form. Check this example:

from django.contrib.auth.models import User


class UserProfile(models.Model):

    user = models.OneToOneField(User)
    bio = models.TextField()

    def __str__(self):
        return unicode(self.user)

Make sure you add mention your custom User model in settings:

AUTH_USER_MODEL ='your_app.UserProfile'

It's because the default User model has no attribute called bio, so there's nowhere to store the value you're getting from the form. You need to add it to the model first. You can create a custom user model, but since you already have a Profile model, you can store bio along with image:

class Profile(models.Model):
   user = models.OneToOneField(User, on_delete=models.CASCADE)
   image = models.ImageField()
   bio = models.CharField(max_length=225, blank=True, null=True)

And in forms.py add the new field to the field list:

class ProfileUpdateForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ('image', 'bio')