R. Manes
R. Manes

Reputation: 63

Django / Bootstrap Error: Parameter "form" should contain a valid Django Form

I have a Django app called 'Accounts' that extends the Django default User model. I want each user to be able to update their account, which is a model called UserProfile using the generic.UpdateView. I get the following error when I go to the UpdateView URL:

BootstrapError at /accounts/user-profile/5/edit/ Parameter "form" should contain a valid Django Form.

I've included my code below. Thank you!

models.py

from django.db import models
from django.contrib import auth

# Create your models here.

class User(auth.models.User,auth.models.PermissionsMixin):
    readonly_fields = ('id','pk')

    def __str__(self):
        return self.username

class UserProfile(models.Model):
    user = models.OneToOneField(auth.models.User,on_delete=models.CASCADE)
    join_date = models.DateTimeField(auto_now=True)
    profile_pic = models.ImageField(upload_to='profile_pics',default='media/block-m.png')
    skills = models.TextField()
    major = models.CharField(max_length=128)
    grad_year = models.CharField(max_length=4)
    clubs = models.TextField() #make FK to Clubs
    opt_in = models.BooleanField(default=True)

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

forms.py

from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from .models import UserProfile
from django import forms

class UserCreateForm(UserCreationForm):

    class Meta:
        model = get_user_model()
        fields = ('first_name','last_name','username','email','password1','password2')

    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        # self.fields['username'].label = 'Username'
        # self.fields['email'].label = 'Email Address'
        # self.fields['password1'].label = 'Password'
        # self.fields['password2'].label = 'Confirm Password'

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        fields = ('profile_pic','grad_year','opt_in')

views.py

from django.shortcuts import render,redirect
from django.contrib import messages
from django.contrib.auth.mixins import(
    LoginRequiredMixin,
    PermissionRequiredMixin
)
from django.urls import reverse,reverse_lazy
from django.db import IntegrityError
from django.shortcuts import get_object_or_404
from django.views import generic
from .models import User,UserProfile
from .forms import UserCreateForm,UserProfileForm
import easygui
from . import models

from . import forms

# Create your views here.


class ListAccounts(generic.ListView):
    model = models.UserProfile

class DetailAccounts(generic.DetailView):
    model = models.UserProfile

class UpdateAccounts(generic.UpdateView):
    fields = ('grad_year',)
    model = models.UserProfile

def SignUp(request):

    registered=False

    if request.method == "POST":
        user_create_form = UserCreateForm(data=request.POST)
        user_profile_form = UserProfileForm(data=request.POST)

        if user_create_form.is_valid() and user_profile_form.is_valid():
            user = user_create_form.save()

            user_profile = user_profile_form.save(commit=False)
            user_profile.user = user


            if 'profile_pic' in request.FILES:
                user_profile.profile_pic = request.FILES['profile_pic']

            user_profile.save()

            registered = True

            return redirect('/accounts/login/')

        else:
            print(user_create_form.errors,user_profile_form.errors)
    else:
        user_create_form = UserCreateForm()
        user_profile_form = UserProfileForm()

    return render(request,'accounts/signup.html',
        {'user_create_form':user_create_form,
         'user_profile_form':user_profile_form,
         'registered':registered})

urls.py

from django.contrib.auth import views as auth_views
from . import views

app_name = 'accounts'

urlpatterns = [
    path('login/',auth_views.LoginView.as_view(template_name='accounts/login.html'),name='login'),
    path('logout',auth_views.LogoutView.as_view(),name='logout'),
    path('',views.ListAccounts.as_view(),name='all'),
    path('signup/',views.SignUp,name='signup'),
    path('user-profile/<int:pk>/',views.DetailAccounts.as_view(),name='detail'),
    path('user-profile/<int:pk>/edit/',views.UpdateAccounts.as_view(),name='user_update'),
    path('match/',views.Match.as_view(),name='match'),
]

userprofile_form.html

{% extends 'base.html' %}
{% load bootstrap4 %}

{% block content %}

<div class="container login">
    <h2>Update</h2>
    <form action="{% url 'accounts:all' %}" enctype="multipart/form-data" method="POST">
      {% csrf_token %}
      {% bootstrap_form user_profile_form layout='inline' %}
      <input type="submit" class="btn" style="background:#f86041; color:white;" value="Update">
    </form>
</div>

{% endblock %}

Upvotes: 5

Views: 1251

Answers (1)

tim-mccurrach
tim-mccurrach

Reputation: 6815

I'll try and explain the steps I took to try and solve this, so you can better understand how to solve it for yourself:

First thing, you need to understand is why the error is being raised. It's a BootstrapError not a standard error, so you'll want to look at the source-code for django-bootstrap4 to see why it gets raised. Looking at the source code (just search) you'll find this:

        if not isinstance(form, BaseForm):
            raise BootstrapError('Parameter "form" should contain a valid Django Form.')

Try and work through the code from where you call your template tag, to where this error is raised (looking at the traceback might also help you here) to understand what all the various variables are equal to.

In your case form will be equal to user_profile_form, and the error is being raised because user_profile_form is not an instance of BaseForm (imported from django.forms.forms). So you can go back and look at user_profile_form. Well it's an instance of UserProfileForm which is in turn a subclass of ModelForm. But ModelForm is a subclass of BaseForm. Hmmm... this seems a bit strange. Why is user_profile_form not an instance of BaseForm when it should be.

At this point, first check for things like spelling errors? Maybe check if your middleware could be playing with the form at all. Also, try simplifing things, just render the form {{ user_profile_form }}... you would find this doesn't work. So the problem seems to be with user_profile_form?

Aha... we've passed down user_profile_form to the template in the view, SignUp. But that's not the view we're looking at here!!! The view we're considering is the UpdateAccounts view. This is a generic.UpdateView, and if we check out the django docs (always a good idea) and look at the example we see the form is passed down to the template as just form.

Replace the line:

      {% bootstrap_form user_profile_form layout='inline' %}

with

      {% bootstrap_form form layout='inline' %}

In the template userprofile_form.html, and it should all work swimmingly :)

As you can see, I went down some blind alleys, before I stumbled across the (perhaps more obvious) correct solution. But I hope this is helpful.

Upvotes: 2

Related Questions