Django: ValidationError is stuck - django

My ValidationError seems to be stuck somewhere!
The routine clean() seems to be running as expected since I am not being forwared to success_url, but the error message does not show up.
forms.py
from django import forms
from django.core.exceptions import ValidationError
class ContactForm(forms.ModelForm):
def clean(self):
raise forms.ValidationError('basic error')
class Meta:
model = Person
views.py
class ContactView(generic.edit.FormView):
def form_valid(self, form):
template_name = 'union/contact.html'
form_class = ContactForm # class of union/forms.py
success_url = '/union/VIP_confirm'
context_object_name = 'contact'
def get_context_data(self, **kwargs):
context = super(ContactView, self).get_context_data(**kwargs)
context['count'] = countcal()
return context
def form_valid(self, form):
if True:
model_id = form.save()
person_id = model_id.pk
return HttpResponseRedirect(
reverse('union:VIP_save', kwargs={'person_id': person_id}))
union/contact.html template for the ContactView
<form action="{% url 'union:ContactView' %}" method="post">
{% csrf_token %}
{% for field in form %}
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% endfor %}
<input type="submit" value="abschicken" />
<!-- regular value submission -->
</form>
I followed the official docs and several other sources but to no success as to find out what the problem is or where it lies. To solve this, do you need additional infos about my app?
EDIT: I seem to miss something in the template, since the test error message (basic error) shows up if the template just uses the form-placeholder {{form}}
Using Django 1.7.1
EDIT EDIT:
OK. The comments pointed me to the right direction. I missed {{form.errors}}.

Related

how to add form as data in class-base views?

I used to send form as content in function-base-view and I could use for loop to simply write down fields and values like:
{% for x in field %}
<p>{{ x.label_tag }} : {{ x.value }} </p>
I don't remember whole the way so maybe I wrote it wrong but is there anyway to do this with class-based-views, because when I have many fields its really hard to write them 1by1
Not entirely sure if this is what you need. But still I will try to answer. I took an example with class AuthorDetail(FormMixin, DetailView) as a basis. In get_context_data saved the form itself. In the template, first I displayed the form, then the value from the bbs model and requested
form.message.label_tag. To get the tag, I looked at this documentation.
In the class, replace the Rubric model with your own. In the template path: bboard/templ.html replace bboard with the name of your application where your templates are located.
views.py
class Dw(FormMixin, DetailView):
model = Rubric
template_name = 'bboard/templ.html'
form_class = TestForm
context_object_name = 'bbs'
def get_context_data(self, **kwargs):
context = super(Dw, self).get_context_data(**kwargs)
context['form'] = self.get_form()
print(77777, context['form']['message'].label_tag())
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
forms.py
class TestForm(forms.Form):
message = forms.CharField()
templates
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="adding">
</form>
<h2>{{ bbs }}</h2>
<h2>{{ form.message.label_tag }}</h2>
urls.py
urlpatterns = [
path('<int:pk>/', Dw.as_view(), name='aaa'),
]

Why FormView not saving data within a DetailView?

I have a DetailView Based on a model ( A ) and on the same template I have a ModelFormView from a model B which has FK to model (A)
The data from form doesn't get saved to the database.
This is the DetailView:
class LocationView(DetailView):
template_name = "base/stocks/location.html"
model = LocationStock
def get_context_data(self, **kwargs):
context = super(LocationView, self).get_context_data(**kwargs)
context['form'] = OutsModelForm
return context
def get_object(self):
id_ = self.kwargs.get("id")
return get_object_or_404(LocationStock, id=id_)
This is the FormView:
class OutsAdd(FormView):
form_class = OutsModelForm
success_url = reverse_lazy('base:dashboard')
def form_valid(self, form):
return super().form_valid(form)
This is the url.py:
path('locations/<int:id>', LocationView.as_view(), name='location-detail'),
path('locations/outs', require_POST(OutsAdd.as_view()), name='outs-add'),
This is the template:
<form method="POST" action="{% url 'outs-add' %}" >
<div class="modal-content">
{% csrf_token %}
{% render_field form.quantity placeholder="Quantity"%}
{% render_field form.id_year placeholder="Year"%}
{% render_field form.id_location placeholder="ID Location"%}
</div>
<div class="modal-footer">
<input class="modal-close waves-effect waves-green btn-flat" type="submit" value="Save">
</div>
</form>
The data gets POSTED in the /locations/outs but is not saving to the actual database.How can I save it ?
The functionality of Django's FormView is really only meant to display a form on a GET request, show form errors in the case of form_invalid, and redirect to a new URL if the form is valid. In order to persist the data to the database you have two options. First you can simply call form.save() in your FormView:
class OutsAdd(FormView):
form_class = OutsModelForm
success_url = reverse_lazy('base:dashboard')
def form_valid(self, form):
form.save()
return super().form_valid(form)
Or, you can use the generic CreateView. Django's CreateView is similar to a FormView except it assumes it's working with a ModelForm and calls form.save() for you behind the scenes.

Overriding get_context_data is blocking some context data going to the template

I am inheriting from CreateView to create a form for a model.
class NewBlogView(CreateView):
form_class = BlogForm
template_name = 'blog_settings.html'
def form_valid(self, form):
blog_obj = form.save(commit=False)
blog_obj.owner = self.request.user
blog_obj.slug = slugify(blog_obj.title)
blog_obj.save()
return HttpResponseRedirect(reverse('home'))
Here is my template code:
{% extends 'base.html' %}
{% block content %}
<h1>Create New User</h1>
<form action='' method='post'>{% csrf_token %}
{{ form.as_p }}
<input type='submit' value='Create Account' />
</form>
{% endblock %}
At this moment everything is working as expected, but when I am overriding get_context_data() my title field is disappearing.
class NewBlogView(CreateView):
form_class = BlogForm
template_name = 'blog_settings.html'
def form_valid(self, form):
blog_obj = form.save(commit=False)
blog_obj.owner = self.request.user
blog_obj.slug = slugify(blog_obj.title)
blog_obj.save()
return HttpResponseRedirect(reverse('home'))
def get_context_data(self, **kwargs):
ctx = super(NewBlogView, self).get_context_data(**kwargs)
print(ctx)
return ctx
I am thinking although I am running the original get_context_data() form the function that I am inheriting, there is something that is going wrong when it comes to taking the field name from the form_class. Can someone help with that confusion that I have?

How to use context with class in CreateView in django?

How to use context with class in CreateView in django?
Before i have:
#views.py
from django.views.generic import CreateView
from cars.models import *
def CreateCar(CreateView):
info_sended = False
if request.method == 'POST':
form = FormCar(request.POST, request.FILES)
if form.is_valid():
info_sended = True
form.save()
else:
form = FormCar()
ctx = {'form': form, 'info_sended':info_sended}
return render_to_response("create_car.html", ctx,
context_instance=RequestContext(request))
Now, a have, and try:
class CreateCar(CreateView):
info_sended = False
template_name = 'create_car.html'
model = Car
success_url = 'create_car' #urls name
def form_valid(self, form):
info_sended = True
ctx = {'form': form, 'info_sended':info_sended}
return super(CreateCar, self).form_valid(form)
My html page is:
<!-- create_car.html -->
{% extends 'base.html' %}
{% block content %}
{% if info_sended %}
<p>Data saved successfully</p>
<p>Show List</p>
{% else %}
<form class="form-horizontal" action="" method="post">
{% csrf_token %}
{% include "form.html" %}
<div class="col-md-offset-1">
<button class="btn btn-primary" type="submit">Add</button>
</div>
</form>
{% endif %}
{% endblock %}
You should define get_context_data() method in your class view. Update your code as
from django.shortcuts import render
class CreateCar(CreateView):
info_sended = False
template_name = 'create_car.html'
model = Car
success_url = 'create_car' #urls name
def form_valid(self, form):
self.info_sended = True
# Instead of return this HttpResponseRedirect, return an
# new rendered page
super(CreateCar, self).form_valid(form)
return render(self.request, self.template_name,
self.get_context_data(form=form))
def get_context_data(self, **kwargs):
ctx = super(CreateCar, self).get_context_data(**kwargs)
ctx['info_sended'] = self.info_sended
return ctx
You have to use get_context_data
class CreateCar(CreateView):
info_sended = False
template_name = 'create_car.html'
model = Car
success_url = 'create_car' #urls name
def form_valid(self, form):
self.info_sended = True
return super(CreateCar, self).form_valid(form)
def get_context_data(self, **kwargs):
ctx = super(CreateCar, self).get_context_data(**kwargs)
ctx['info_sended'] = self.info_sended
return ctx
If you see the django source CreateView inherits from BaseCreateView this one inherits from ModelFormMixin in turn this one inherits from FormMixin and this one inherits from ContextMixin and the only method this one defines is get_context_data.
Hope this helps you.
PD: This may be a bit confusing for better understanding of inheritance in Python feel free of read this article about MRO.
Since your are creating a new instance of a Car, there is no context for get_context_data because there is no object yet. I didn't test using Mixin to get the context from another class as suggested above, but that seems reasonable. However, if I can assume you want to use the basic CreateView, UpdateView and DeleteView, then I solved this by assuming I will have no context for CreateView. Then in my template I used an if to make the decision, such as:
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value={% if not buttonword %}Save{% else %}{{ buttonword }}{% endif %}>
</form>
In DeleteView I include:
context['buttonword'] = 'Delete'
In UpdateView I include:
context['buttonword'] = 'Update'
As I said, I do not set buttonword in CreateView. Hence, when the template logic is done, if buttonword is assigned, the word in it shows up in the button, otherwise Save shows up on the button.

how to submit a form and formset at the same time

I am trying to render a form and a formset at once.
The formset is working fine (i think), but the form is not validating (as if there was no data being posted)
i have tried playing with the button but its main submit function comes through js.
the forms all work independently but not when submitted togetehr so it seem like the problem is in the views
here is the code:
views.py
from django.shortcuts import render, render_to_response
from django.http import HttpResponseRedirect
from forms import LessonForm, AddMaterialForm
from models import Lesson, SUBJECT_OPTIONS, Materials, MATERIAL_TYPES
from django.forms.formsets import formset_factory
def Create_Lesson(request):
AddMaterials=formset_factory(AddMaterialForm, extra=9)
if request.method == "POST": # If the form has been submitted...
lesson = LessonForm(request.POST, prefix="lesson") # A form bound to the POST data
formset = AddMaterials(request.POST, request.FILES) # A form bound to the POST data
if lesson.is_valid() and formset.is_valid(): # All validation rules pass
lesson = lesson.save(commit=False)
lesson.creator = request.user
lesson.save()
for form in formset:
form = form.save(commit=False)
form.lesson = lesson.pk
form.save()
return render(request, 'index.html',)
else:
lesson= LessonForm(prefix='lesson') # An unbound form
formset = AddMaterials()
return render(request, 'create_lesson/create.html', {
'form': lesson,'formset':formset
})
.html
<form id="create_lesson_form" method="post" action="">
<h2>1: Create Your Lesson</h2>
{{ form.non_field_errors }}
<label for="subject"><span>Subject</span></label>
{{form.subject}}
{{ form.subject.errors }}
<label for="title"><span>Title</span></label>
<input type="text" id="title" name="name" placeholder="Give it a name"/>
{{ form.name.errors }}
<label class="error" for="title" id="title_error">You must choose a title!</label>
<label for="subtitle"><span>Subtitle</span></label>
<input type="text" id="subtitle" name="subtitle" placeholder="Type your subtitle here"/>
{{ form.subtitle.errors }}
<label class="error" for="subtitle" id="subtitle_error">are you sure you want to leave subtititle blank?</label>
<label for="description"><span>Description</span></label>
<textarea id="description" name= "description" cols="42" rows="5" placeholder="why is it important? this can be a longer description"'></textarea>
{{ form.description.errors }}
<label class="error" for="description" id="description_error">are you sure you want to leave the description blank?</label>
<label for="success" id="Goals_title"><span>Goals</span></label>
<textarea id="success" name="success" cols="42" rows="5" placeholder="explain what sucess might look like for someone doing this lesson...what does mastery look like?" '></textarea>
{{ form.success.errors }}
<label class="error" for="success" id="success_error">are you sure you want to leave the goals blank?</label>
{{ form.directions.errors }}
<label class="error" for="directions" id="directions_error">are you sure you do not want to include dierections?</label>
<label for="directions" id="Directions_title"><span>Directions</span></label>
<textarea id="directions" name="directions" cols="42" rows="5" placeholder="you can add simple directions here" '></textarea><br>
</form>
<form id="add_elements_form" method="post" action="">
{% csrf_token %}
{{ formset.as_p}}
<button type='submit' id='finish'>Finish Editing Lesson</button>
</form>
This will submit the form and the formset at the same time.
//When your uploading files or images don't forget to put "multipart/form-data"
// in your form.
//To connect formset in your form, don't forget to put the model in the formset
// for instance.
//In this you can add many lines as you want or delete it.
forms.py
class LessonForm(forms.ModelForm):
class Meta:
model = Lesson
MaterialsFormset = inlineformset_factory(Lesson, Materials,
fields=('field_name', 'field_name'), can_delete=True)
views.py
def view_name(request):
form = LessonForm()
formset = MaterialsFormset(instance=Lesson())
if request.method == 'POST':
form = LessonForm(request.POST)
if form.is_valid():
lesson = form.save()
formset = MaterialsFormset(request.POST, request.FILES,
instance=lesson)
if formset.is_valid():
formset.save()
return render(request, 'index.html',)
return render(request, "page.html", {
'form': form, 'formset': formset
})
html
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
{{ formset.as_p }}
<input type="submit" value="Save"/>
</form>
You only need one form tag. If you expect to receive all of the data at the same time, you need to wrap all of the fields with one form tag.
Now that django 4 is out it's possible to do the same thing within the form itself using the Reusable templates. I prefer this solution because it is then simpler to reuse complex forms without messing with the views.
For the record, here is how I do it
The 2 related models:
# models.py
from django.db import models
class Recipe(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Ingredient(models.Model):
name = models.CharField(max_length=100)
quantity = models.FloatField()
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name="ingredients")
def __str__(self):
return self.name
The forms bound together:
# forms.py
from django import forms
from .models import Recipe, Ingredient
class IngredientForm(forms.ModelForm):
class Meta:
model = Ingredient
exclude = ('recipe',)
IngredientFormSet = forms.inlineformset_factory(Recipe, Ingredient, form=IngredientForm)
class RecipeForm(forms.ModelForm):
class Meta:
model = Recipe
fields = '__all__'
template_name = 'recipe_form.html'
def __init__(self, *args, **kwargs):
"""Initialize the formset with its fields."""
self.formset = IngredientFormSet(*args, **kwargs)
super().__init__(*args, **kwargs)
def get_context(self):
"""Add the formset to the context for rendering."""
context = super().get_context()
context['formset'] = self.formset
return context
def save(self, commit=True):
"""Bind both models together."""
instance = super().save(commit=False)
self.formset.instance = instance
if self.formset.is_valid():
instance.save()
self.formset.save()
return instance
The template for the form:
<!-- templates/recipe_form.html -->
<p>Recipe: {{ form.name }}</p> <!-- calling "form" creates a rendering recursion -->
<p>Ingredients:</p>
{{ formset.management_form }}
<ul>
{% for elt in formset %}
<li>{{ elt }}</li>
{% endfor %}
</ul>
And the view using it:
# views.py
from django.views.generic import TemplateView
from .forms import RecipeForm
class RecipeView(TemplateView):
template_name = 'recipe.html'
def get_context_data(self):
context = super().get_context_data()
context['form'] = RecipeForm()
return context
def post(self, *args, **kwargs):
form = RecipeForm(self.request.POST)
if form.is_valid():
form.save()
else:
raise Exception('Something bad happened!')
return self.get(*args, **kwargs)
With a very basic template:
<!-- templates/recipe.html -->
<form action="." method="post">
{% csrf_token %}
{{ form }}
<button type="submit">Submit</button>
</form>
And finally you're good to go:
# tests.py
from django.test import TestCase
from django.urls import reverse
from .models import Recipe
class TestFormSet(TestCase):
def test_new_recipe(self):
data = {
"name": "quiche",
"ingredients-TOTAL_FORMS": 3,
"ingredients-INITIAL_FORMS": 0,
"ingredients-0-name": 'bacon bits',
"ingredients-0-quantity": 200,
"ingredients-1-name": 'eggs',
"ingredients-1-quantity": 4,
"ingredients-2-name": 'cream',
"ingredients-2-quantity": 150,
}
r = self.client.post(reverse('recipe'), data=data)
self.assertEqual(Recipe.objects.first().ingredients.count(),3)
$ python manage.py test
OK
Hopefully it will be useful to somebody.