I have a form which allows users to select several parameters to allow faceted querying of data. As there is no data entry going on here I want the form to post to GET and I have a another view with a different template which displays the results.
I want the form to validate as normal so that if a required field is not completed the corresponding errors are displayed. At the moment my process looks like this (simplified):
my search view:
def search(request):
...
context['form'] = GraphForm()
...
return render(request, 'search.html', context)
my results view:
def results(request):
if 'submit' in request.GET:
# process GET variables as query
...
return render(request, 'results.html', context)
my search.html template:
<form action="{% url results %}" method="get">{% csrf_token %}
{% for field in form %}
<div class="field_wrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
<input type="submit" name="submit" value="Query" />
</form>
Given that the form submits to another url with separate view code, what is the best way to go about validating (highlighting errors), and ensuring I have my GET data?
Any help much appreciated.
This might be a little late, but I think the following will work while maintaining similarity to 'POST' workflow:
Instead of having two different views for searching and displaying results, just have one view. The normal codepath described for post forms can then be followed. Instead of using request.method == 'POST' to detect form submission, we instead use 'submit' in request.GET. If using javascript to submit the form, make sure that 'submit' is included in the GET data or use a hidden field to detect form submission.
views.py
def search(request):
context_dict = {}
if 'submit' in request.GET:
form = GraphForm(request.GET)
if form.is_valid():
#do search and add results to context
#If you don't want to use a single view,
# you would redirect to results view here.
results = get_results(**form.cleaned_date)
context_dict['results'] = results
else:
form = GraphForm()
context_dict['form'] = form
return render(request, 'search.html', context_dict)
search.html
<form action="{% url 'search' %}" method="get">
{{form}}
<input type="submit" name="submit" value="Query" />
</form>
{% if results %}
{% include 'results.html' %}
{% endif %}
You should be able to pass request.GET just like request.POST to the form. The form simply accepts a data dictionary. It doesn't care where that comes from. Have you already tried that?
Use JavaScript/jQuery for form validation. All you need to do is add an id to form, and in the corresponding Javascript, do something like
document.getElementById("#form").onsubmit = checkForm();
or using jQuery
$("#form").submit(checkForm);
where checkForm() returns true upon successful validation, and false otherwise. (Note that, if you do not return false, form submission will continue as usual.)
Which fields you check for/validate can also change by using Django's templates.
Related
Playing around with FileFiled and trying to update a form. I think my problem comes from the views.py.
I have a template where I can see a product, and I have the option to update the product by clicking on the update button.
When clicking on the update button, I am redirected to a form where I can update the product.
template
{% for TermsAndConditions in commercial_list %}
<a class="btn btn-outline-secondary" href="{% url 'update-commercial' TermsAndConditions.id %}">Update
</a>
{% endfor %}
templateform.py
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<h6>Upload terms</h6>
{{ form.attachment| as_crispy_field}}
{% if url %}
<p>Uploaded file {{url}}</p>
{%endif%}
<button type="submit" value="Submit" id="profile-btn" class="btn btn-primary custom-btn">Update</button>
</form>
My normal upload form (without FileField) looks like this:
views (without FileField)
def update_commercial(request, termsandconditions_id):
commercials = TermsAndConditions.objects.get(pk=termsandconditions_id)
form = TermsAndConditionsForm(request.POST or None, instance=commercials )
if form.is_valid():
form.save()
return redirect('list-commercial')
return render(request, 'main/update_commercial.html',{'form':form,'commercials':commercials})
With the addition of the FileField, I thought I would do something like this:
views (with FileField)
def update_commercial(request, termsandconditions_id):
commercials = TermsAndConditions.objects.get(pk=termsandconditions_id)
form = TermsAndConditionsForm(request.POST or None, request.FILES ,instance=commercials ) #the change being request.FILES
if form.is_valid():
form.save()
return redirect('list-commercial')
return render(request, 'main/update_commercial.html',{'form':form,'commercials':commercials})
Problem is, when I do that the update button in the template becomes invalid (it doesn't redirect to the update form).
I think my problem is how to handle the or None and request.FILES together. I tried different combinations but none have worked. Hoping someone might be able to shed some light.
(I have add the models and url files, as I dont think these are the problem and didnt want to make this post longer than it should be, but feel free to let me know)
Ok, figured it out in the end.
Posting the answer just in case for anyone who needs it.
I needed to repeat or None for request.FILES.
def update_commercial(request, termsandconditions_id):
commercials = TermsAndConditions.objects.get(pk=termsandconditions_id)
form = TermsAndConditionsForm(request.POST or None, request.FILES or None,instance=commercials )
if form.is_valid():
form.save()
return redirect('list-commercial')
return render(request, 'main/update_commercial.html',{'form':form,'commercials':commercials})
I have the following code and I'm submitting a form. When I hit the submit button, my form validation prints out False. I've checked and made sure I'm including everything from different posts, but I can't get it to validate. Is there anything I'm doing wrong?
#app.route('/index.html', methods=['GET', 'POST'])
def index():
user = {'nickname': 'Rafa'}
form = FilterForm()
print("about to validate", file=sys.stderr)
if form.validate_on_submit():
print("validated", file=sys.stderr)
filters_array = form.filter.split(',')
streaming(filters_array)
response = {"response", "yes"}
redirect("/authenticate")
return render_template('index.html',
title="Home",
user=user,
form=form)
class FilterForm(Form):
filter = StringField('filter', validators=[DataRequired()])
Here is my Jinja file
{% block content %}
<h1> I have successfully navigated to the title pagee </h1>
<h1> Hello, {{user.nickname}}!</h1>
<h1> Get Tweets </h1>
<p> Please enter a comma delimited list of filters</p>
<form action="" method="post" name="login">
{{form.filter(size=80)}}
<input type="submit" value="Get Tweets!">
</form>
{% endblock %}
FilterForm should not be indented at the same level as def index(). More importantly, you don't have a csrf_token in your form. Which will prevent it from validating.
Add this to your form:
{{ form.csrf_token }}
Lastly, when validating with wtforms, the errors are populated in the form object. So after an if validate, try printing form.errors and you'll find out exactly what is wrong.
Another requirement is that when you use form.validate_on_submit, you have to make sure that you had use all fields of your form model.
I have found some syntax error in your code, maybe that will cause the problem you have met.
first, the problem in your decorator app.route:
app.route('/index')
second, in your html file:
form action='/index'
Extreme noob here. I have been trying to create a simple form in Django where the user can select from the models that are present in the database and then click submit (whereupon I will then return a list of objects).
However, I am getting the following error: 'ModelBase' object is not iterable
Note all I am trying to achieve so far is to actually render the form.
Here is the code:
HTML
<form action="." method="">
{% csrf_token %}
{% for field in output %}
{{ output.as_p }}
{% endfor %}
<input type="submit" value="Save" />
</form>
Forms.py
from projectdb.models import *
class TablesForm(forms.Form):
models = models.get_models()
select = forms.ChoiceField(choices=models)
Views.py
def output_form(request):
form = TablesForm()
return render(request, 'projectdb/output.html', {'output': form})
Much obliged for any help!
Some object is not iterable errors will be fixed by adding .all() where you're doing the foreach loop. If the loop is in the template, try .all only
In a view:
for something in array.all():
In a template:
{% for field in output.all %}
And everytime I do a form in Django my method on the form is empty method="". This let's you return to the same view and process your data there. If you have errors on the form you can redirect to the same form but with the pre-saved data that the user has wrote.
Hope it helps.
So, I am having this form where I display a list of all my app models in a drop-down list and expect from the user to choose one so as to display its fields. Below is my form and the models() method, which creates the list of models to be passed as argument to my ChoiceField.
*forms.py*
class dbForm(forms.Form):
model_classes_field = forms.ChoiceField(choices=models(), required=True,)
def models():
apps = get_app('Directories')
m_id = 0
for model in get_models(apps):
m_id += 1
model_classes.append({
'model_name': model._meta.verbose_name,
'model_id': m_id,
'model_table': model._meta.db_table,
'model_object': model.objects.all()
})
return model_classes
In my views.py I tried handling the POST data but unfortunately for some reason the form was not valid and I couldn't manipulate any data. Furthermore form.errors does not display anything at all.
*views.py*
def index(request):
if request.method == 'POST': # If the form has been submitted...
form = dbForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
model_classes_field = form.cleaned_data['model_classes_field']
return HttpResponseRedirect('/list/') # Redirect after POST
else:
print "form: ", form.errors
else:
form = dbForm() # An unbound form
print "form: ", form.errors
print "not valid form"
return render(request, 'Directories/index.html', {
'form': form,
})
Furthermore, in the template whenever i try to submit the form it returns an error message "too many values to unpack" and does not redirect me to the next template (list.html).
*index.html*
{% block content%}
<div id="content" align="center">
<h2> Welcome! this is Directories app! </h2>
<form action="" method="post">{% csrf_token %}
{{ form.model_classes_field.errors }}
<label for="id_model_classes_field">Choose from one of the existing tables:</label>
<select id="id_model_classes_field" name="model_classes_field">
{% for choice in form.fields.model_classes_field.choices %}
<option name="m_id" value="{{ choice.model_table }}">{{choice.model_id}}: {{choice.model_name}}</option>
{% endfor %}
</select> <br />
<input type="submit" value="Change" name="_change" />
</form>
</div>
<div id="bottom">
</div>
{% endblock %}
The only workaround i found on this is to fill the form action with the template to be redirected at (i.e. action = "list") instead of doing it in the views with return HttpResponseRedirect('/list/') . However, I believe that this does not solve the issue since still the form is not valid and i cannot process data with form.cleaned_data. It's peculiar though that the post data is sent even though the form is not valid.
*
EDIT: Solution
I changed my models() method as such:
def models():
apps = get_app('Directories')
for model in get_models(apps):
model_classes.append( (model._meta.verbose_name, model._meta.db_table), )
return model_classes
so I included a tuple as instructed by #Rohan and after making a slight alteration to my index.html:
...
{% for choice in form.fields.model_classes_field.choices %}
<option name="m_id" value="{{choice.0}}">{{choice.0}}</option>
{% endfor %}
...
form is valid and can now process my data.
*
Value of choices should be list/tuple containing items with exactly 2 elements. But you are creating list of dicts which might be causing the issue.
for model in get_models(apps):
m_id += 1
model_classes.append({
'model_name': model._meta.verbose_name,
'model_id': m_id,
'model_table': model._meta.db_table,
'model_object': model.objects.all()
})
So you may want to update models() method to return appropriate list/tuple.
I have a form like so:
class MyForm(forms.Form):
site = forms.ChoiceField(choices=SITE_CHOICES, label=ugettext_lazy('Site'),)
...
params = forms.MultipleChoiceField(
choices=PARAM_CHOICES,
label=ugettext_lazy('Select Parameters'),
widget=forms.CheckboxSelectMultiple()
)
And in my template:
<form action="{% url results %}" method="get">{% csrf_token %}
{% for field in myform %}
<div class="field_wrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
<input type="submit" name="submit" value="{% trans 'Query' %}" />
</form>
My problem is that when I submit the form as GET the variables are like the following:
site=1¶ms=foo¶ms=bar¶ms=something&submit=Query
my params variable is clearly being overwritten by the last choice? How can get access to the submitted data as separate variables?
Any help appreciated.
Using Django forms
You should be using Django's form handling with a POST which would majke things easier. Here goes:
if request.method == 'GET':
form = MyFormClass()
else:
form = MyFormClass(request.POST)
if form.is_valid():
do_something_with(form.cleaned_data['params'])
return redirect('somewhere')
return render_to_response('my_template.html', RequestContext(request, {'form':form}))
Notes on using GET vs POST with forms
It's useless to include {% csrf_token %} if you're going to GET the form (Absolutely no csrf validation is done with GET requests, which make sense, as GET requests are supposed to be non-data-altering.
Anyway, if you're really going to GET the page, you can still use the same logic as written before, with a little tuning:
form = MyFormClass(request.GET)
if form.is_valid():
do_something_with(form.cleaned_data['params'])
return render_to_response('some_template.html', {'stuff':some_stuff})
return render_to_response('form_submission_page.html', {'form':form})
Last thing, using GET to submit data is usually bad practice, unless you're creating some search function or altering display (pagination & all).
Using request.GET
Now, if for some reason you don't want to use Django forms, you can still get around the problem and retrieve your params, you simply need to use the QueryDict.getlist instead of using the QueryDict.get method.
Here goes:
my_data = request.GET.getlist('params')
Documentation
Don't forget to check out the Django documentation on QueryDicts and on forms
And use {% csrf_token %} in get request is a bad practice.
Use form.is_valid() and form.cleaned_data['params'].