Reputation: 63
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
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