I am using a Django form wizard to enter data into a form page, and then display it in a confirmation page. However, when I try to call self.get_cleaned_data_for_step(step_name), I get a "'MyForm' object has no attribute 'cleaned_data'." I know this can happen if the form fails to validate, so I overrode the is_valid method in my form class to always return True, just as a test, but I still get this error. My relevant code is below:
forms.py
...
class MealForm(forms.Form):
modifications = forms.CharField()
def __init__(self, *args, **kwargs):
menu_items = kwargs.pop('menu_items')
super(MealForm, self).__init__(*args, **kwargs)
for item in menu_items:
self.fields[str(item.name)] = forms.IntegerField(widget=forms.NumberInput(attrs={'value': 0}))
def is_valid(self):
return True
urls.py
...
url(r'^(?P<url>[-\w]+)/meal/$',
login_required(views.MealFormWizard.as_view(views.MealFormWizard.FORMS)), name="meal"),
views.py
...
class MealFormWizard(SessionWizardView):
FORMS = [('meal_form', MealForm),
('meal_form_confirmation', MealFormConfirmation)]
TEMPLATES = {'meal_form': 'restaurant/createMeal.html',
'meal_form_confirmation': 'restaurant/confirmation.html'}
def get_form_kwargs(self, step=None):
kwargs = {}
url = self.kwargs['url']
restaurant = Restaurant.objects.get(url=url)
menu_items = MenuItem.objects.filter(restaurant=restaurant)
if step == 'meal_form':
kwargs['menu_items'] = menu_items
return kwargs
def get_context_data(self, form, **kwargs):
context = super(MealFormWizard, self).get_context_data(form=form, **kwargs)
if self.steps.current == 'meal_form':
context.update({...objects/vars...})
if self.steps.current == 'meal_form_confirmation':
cd = self.get_cleaned_data_for_step('meal_form') **This is where my error occurs**
createMeal.html
...
<form action="" method="post">
{% csrf_token %}
{{ wizard.management_form }}
{{ wizard.form }}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.next }}">Submit Meal</button>
</form>
Upon submitting the form in createMeal.html I should be able to access the cleaned data for the previous step in the get_context_data method of my MealFormWizard class in views.py via a call to self.get_cleaned_data_for_step('meal_form'). However, this is not the case, but I am not sure where I went wrong.
Overriding is_valid like that won't work - if you follow the code you will see that the form's cleaned_data attribute is set by the normal is_valid method.
The docs say that if the form is invalid, then get_cleaned_data_for_step will return None, you need to write your code so it can handle this.
In case this is helpful to anyone. My problem was that in my createMeal.html, my button was simply taking the wizard to the next step, bypassing any validation. The proper solution is to make a simple submit button to submit the form, at which point the wizard will then validate the form, and if it is valid, it will move on to the next step.
Related
This is the forms.py file:
class MyForm(forms.Form):
data = forms.CharField(required=True)
def clean(self):
#super(MyForm, self).clean()
if any(self.errors):
print self.errors
return
value = self.cleaned_data['data']
if int(value)%2 != 0:
print "invalid data"
raise forms.ValidationError(_("Please enter an even number"))
This is my view.py file:
def home(request):
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
return HttpResponse("thanks")
else:
form = MyForm()
context = {
'form': form,
}
return render(request, 'todayhome.html', context)
And this is my todayhome.html file:
<form method="post" action="">{% csrf_token %}
{{form.as_p}}
{{authorformset}}
<input type="submit" value="submit" name="submit"/>
</form>
What I don't understand is :
Do I need to call
super(MyForm, self).clean()
inside 'clean' method of forms.py explicitly, or will it be called automatically once I do
self.errors?
If I remove
if any(self.errors):
print self.errors
return
from the 'clean' method of forms.py file and submit the form empty, it renders shows
'KeyError (data)'
instead of showing 'This field is required.' Why is it so ?
Do I need to call super(MyForm, self).clean() inside 'clean' method of forms.py explicitly, or will it be called automatically once I do
You need to call it explicitly. That is the method which populates the self.errors attribute, when some error is found. Using self.errors you're just accessing that attribute, not calling any method.
For your second question, it shows KeyError while accessing self.cleaned_data['data'] (in case when you call super clean method), because when a particular key is added to self.errors, it is not added to self.cleaned_data. You need to check the key existence or self.errors first, before accessing any cleaned_data key.
I have created a Class view in views.py of the django application.
class HelloTemplate(TemplateView):
template_name = "index.html"
def get_context_data(self, **kwargs):
context = super(HelloTemplate, self).get_context_data(**kwargs)
return context
Now I have a form defined in the html page:
<form method="get">
<input type="text" name="q">
<input type="text" name="q1">
<input type="submit" value="Search">
</form>
As you can see, I am submitting the form on the same page.
Now I want to get the form submitted values in my HelloTemplate class. I don't want to create another class or methods outside the existing class.
Also, I would like to send an error message to the html form if data is not validated in the django.
I don't know how to do this, please help me out.
You need to define get (because your form defined with get method <form method="get">) method in view class:
class HelloTemplate(TemplateView):
template_name = "index.html"
def get_context_data(self, **kwargs):
context = super(HelloTemplate, self).get_context_data(**kwargs)
return context
def get(self, request, *args, **kwargs):
q = request.GET.get('q')
error = ''
if not q:
error = "error message"
return render(request, self.template_name, {'error': error})
More information in django docs here Introduction to Class-based views
There's only one value, and it's in request.GET['q'].
Quick response, I can show you what I did a while ago for a review form (for people to create a new review, one of my models):
def review_form_view(request):
c = {}
c.update(csrf(request))
a = Review()
if request.method == 'POST':
review_form = Review_Form(request.POST, instance=a)
if review_form.is_valid():
a = review_form.save()
return HttpResponseRedirect('../dest_form_complete')
pass
else:
review_form = Review_Form(instance=a)
return render_to_response('../review_form.html', {
'review_form': review_form,
}, context_instance=RequestContext(request))
If you have a user model, comment model, etc. you can probably use something similar to this. Very (very) roughly put, the request is the input that the user fills out in the form, 'POST' is the method called that lets the server know you are adding entries to your database, and is_valid() validates the data according to your models.py parameters (can name be NULL? Is age an integer? etc).
Take a look at https://docs.djangoproject.com/en/dev/topics/forms/ as well for more examples and explanation.
I am currently beginning web development using django. In my application, I want a form with a varied number of questions and their choices to be presented.
In models.py, a table is create to store the questions
class QuizItems(models.Model):
question = models.CharField(max_length=255)
choices = SeparatedValuesField(token="$")
answer = models.IntegerField()
In form.py, I overload the __init__ method in Form class so as to pass qchoose, a list of QuizItems instances to create the form fields.
def choiceItem(question):
return [(unicode(idx), q) for idx, q in enumerate(question.choices)]
class QuizForm(forms.Form):
def __init__(self, qchoose, *args, **kwargs):
super(QuizForm, self).__init__(*args, **kwargs)
for q in qchoose:
self.fields[str(q.id)] = forms.ChoiceField(required=True,
label=q.question, widget=forms.RadioSelect(),choices=choiceItem(q))
Then in view.py
if request.method == 'POST':
idlst = request.POST.keys()
else:
# qchoose is a list of quizitems
form = QuizForm(qchoose)
In quiz.html
{% for field in form %}
<li><b> {{ field.label }} </b></li>
<ul> {{ field }} </ul>
{% endfor %}
I want to get idlst, the list of question id, that I can get the correct answers from. It works fine when all the choicefields are filled. The problem is if there is any choicefield value is empty, I won't get its key. I think since the request.POST is a dictionary, it is supposed to return all the keys even if its value is empty.
Could anyone help me what is wrong with my code or anything missing? Thank you!
You're supposed to use the form on POST as well, then call is_valid() and access the form's cleaned_data dict.
I'm trying to receive a POST then generate a form with new fields and pass along the values I received in the previous POST as hidden variables. I've done a lot of searching in documentation and can't seem to find anything that connects the two sides of this flow. I'm using Django 1.4 w/ Python 2.7.
views.py
from django.shortcuts import render_to_response
from gateway_interface.forms import newForm
def requestNewForm(request):
if (request.method == "POST"):
form = newForm(request)
return render_to_response('myTemplate.html', {'form' : form})
forms.py
from django import forms
class newForm(forms.Form):
def __init__(self, request):
my_passed_variable = request.POST['pass_variable']
a_new_variable = forms.CharField(max_length = 25)
my_passed_variable = forms.CharField(widget = forms.HiddenInput())
myTemplate.html
<form action="/myNextDjangoView/" method="post">
<div class="fieldWrapper">
I need this value: {{ form.a_new_variable }} <br>
</div>
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<input type="submit" value="Submit">
</form>
I must be doing something fundamentally wrong. If I use the for loop in the template none of the visible fields show on the page. Nothing I've tried has caused the hidden fields to populate.
Any suggestions? Perhaps I'm missing an import somewhere? Is there something I need to import in forms.py to allow for the use of HiddenInput()?
EDIT 1:
I've modified forms.py to look like this:
form django import forms
class newForm(forms.Form):
a_new_variable = forms.CharField(max_length = 25)
my_passed_variable = forms.CharField(widget = forms.HiddenInput())
def __init__(self, *args, **kwargs):
super(newForm, self).__init__(*args, **kwargs)
This has not changed my output. I still get the same form with no fields showing (hidden or visible). What I need to do is instantiate with an initialization dictionary. (I think?) Where the dictionary contains the name and values for all the hidden fields.
initial_dict = { 'my_passed_variable' : request.POST.get('pass_variable') }
form = newForm(initial = initial_dict)
EDIT 2:
Using the initialization dictionary was a step in the right direction! I am now able to see the visible fields in my form but the hidden fields are still not populating.
views.py
from django.shortcuts import render_to_response
from gateway_interface.forms import newForm
def requestNewForm(request):
if (request.method == "POST"):
initial_dict = { 'my_passed_variable' : request.POST.get('pass_variable') }
form = newForm(initial = initial_dict)
return render_to_response('myTemplate.html', {'form' : form})
EDIT 3:
I've got it working. Thanks to Jordan Reiter for pushing me in the right direction. It turns out the problem was almost entirely the caching of my browser after EDIT 1 above. I moved to Chrome's incognito mode and everything just worked.
There is a definite problem with this code:
from django import forms
class newForm(forms.Form):
def __init__(self, request):
my_passed_variable = request.POST['pass_variable']
a_new_variable = forms.CharField(max_length = 25)
my_passed_variable = forms.CharField(widget = forms.HiddenInput())
First, it's incredibly confusing for there to be two variables with identical names (although one of them is self.my_passed_variable available throughout the form and the other is just my_passed_variable available in __init__ only). I can't help but think you're trying to tie the two variables together somehow, but you're not. Worst/best case scenario (if you rewrote my_passed_variable = request.POST['pass_variable'] as self.my_passed_variable = request.POST['pass_variable']) you're overwriting the value for form field object with a string.
Second, I'm assuming you snipped out a bunch of code from the __init__ function. You're missing the super which actually makes this a form. As it stands, the form object is not going to be instantiated correctly.
If you're trying to do what I think you're trying to do, you want to rewrite it this way:
from django import forms
class newForm(forms.Form):
# first, I'm going to put the fields at the top, I think that's more standard
a_new_variable = forms.CharField(max_length = 25)
my_passed_variable = forms.CharField(widget = forms.HiddenInput())
def __init__(self, request, *args, **kwargs):
super(newForm, self).__init__(*args, **kwargs)
self.fields['my_passed_variable'].initial = request.POST.get('pass_variable') # don't assume the variable is present!
I'm not able to find the proper syntax for doing what I want to do. I want to do something if a name/value pair is not present. Here is the code in my view:
if (!request.POST['number']):
# do something
What is the proper way to accomplish something like the above? I am getting a syntax error when I try this.
#Thomas gave you the generic way, but there is a shortcut for the particular case of getting a default value when a key does not exist.
number = request.POST.get('number', 0)
This is equivalent to:
if 'number' not in request.POST:
number = 0
else:
number = request.POST['number']
Most logically:
if not 'number' in request.POST:
Python convention:
if 'number' not in request.POST:
Both work in exactly the same way.
What I have used many times is the following in my view:
def some_view(request):
foobar = False
if request.GET.get('foobar'):
foobar = True
return render(request, 'some_template.html',{
'foobar': foobar,
})
Then, in my template I can use the following URL syntax to set foobar:
Link Name
Also, since we returned the foobar variable from the view above, we can use that in the template with other logic blocks (great for navigation!):
<li class="nav-item">
{% if foobar %}
<a class="nav-link active" ....
{% else %}
<a class="nav-link" ....
{% endif %}
</li>
Hope it helps,
You can use a custom decorator to achieve this and throw an error if the field's requested fields are not sent from the front-end.
from typing import List
from rest_framework import status
from rest_framework.response import Response
def required_fields(dataKey: str, fields: List):
def decorator_func(og_func, *args, **kwargs):
def wrapper_func(request, *args, **kwargs):
data = None
if dataKey == 'data':
data = request.data
elif dataKey == 'GET':
data = request.GET
for field in fields:
if field not in data:
return Response('invalid fields', status=status.HTTP_400_BAD_REQUEST)
return og_func(request, *args, **kwargs)
return wrapper_func
return decorator_func
And now you can do:
#api_view(['POST'])
#required_field('data',['field1', 'field2']) # use 'GET' instead of 'data' to check for a GET request.
def some_view(request):
data = request.data
... do something