Django form submission - avoid page redirect - django

I am using a django form to update some details. once the save changes is clicked the ajax call is made, view executed, ajax response sent, success code executed n the page reloads again. n on reload as form is no longer valid, the else part of from.is_valid() is executed.
if form.is_valid:
#do something
return HttpResponse(simplejson.dumps(response), mimetype='application/json')
else:
#do something
return render_to_response('ts.html', {'form': form}, context_instance = RequestContext(request))
I want to avoid the page reload on the successful form submission and jus return ajax response. How can achieve that?
I have changed my code and here is the view, template and jquery.
if request.POST:
if form.valid():
if credentials.correct():
resp = {'success': True}
return HttpResponse(simplejson.dumps(resp), mimetype='application/json')
else:
resp = {'success': False}
return HttpResponse(simplejson.dumps(resp), mimetype='application/json')
else:
print "error in form"
return render(request, 'manage_accounts.html', {'creds': creds, 'form': form})
else:
form = SomeForm()
return render(request, 'manage_accounts.html', {'creds': creds, 'form': form})
Template
<form action="/accounts/" method="POST" id="bitlychangeform">
<div id="ajaxwrapper">
{% csrf_token %}
{{ form.non_field_errors }}
<!--{% include "Change_bitly_form.html" %}-->
{{ form.as_p }}
<div class="clearfix">
<label></label>
<div class="input" style="float:right">
<input type="submit" id="save" value="Save Changes" class="btn btn-primary "/>
</div>
</div>
</div>
</form>
Jquery:
$(function(){
var form = $('#bitlychangeform');
form.submit(function(e) {
jQuery("#save").attr('disabled', true)
jQuery("#ajaxwrapper").load(
form.attr('action') + ' #ajaxwrapper',
form.serializeArray(),
function(data) {
jQuery("#save").attr('disabled', false)
alert(data);
});
return false;
e.preventDefault();
});
});
The page reload doesn't occur when i use Django form as {{ form.as_p }} but when i use a "custom template for form with fields as password char fields with ** and editable only after one clicks edit button", the form.valid() returns false.
I require that the django form functions as my custom template for form as i desire. Can anyone guide me.

You can handle this in your JavaScript by returning false on submission of form.
form.submit(){
#do something
#make AJAX call
#do something
return false;
}

I would guess you didn't override the default form submission behavior on the front end, and you are submitting your form normally.
Make sure that you supress the default behavior of form submission.
This page
provides a great example under templates/contact/form.html

Related

Django form errorlist appears even when there is no attempt to submit

My page when requested through GET appears already with error "This field is required.", which is not a desirable behavior. I want this error appears when user actually attempts to submit a form without a data required.
This is my form Python class:
class Place_A_Bid_Form(forms.Form):
listing = forms.CharField(widget=forms.TextInput(attrs={"type":"hidden"}))
bid = forms.IntegerField(widget=forms.NumberInput(attrs={"class":"form-control"}),
min_value=1)
Form's html:
<form action="{% url 'place_a_bid' listing.title %}">
{% csrf_token %}
<div class="form-row">
<div class="col">
{{ form }}
</div>
<div class="col">
<input class="btn btn-primary" type="submit" value="Place a bid">
</div>
</div>
</form>
And view:
def details(request, listing):
listing_obj = Listing.objects.get(title=listing)
form = Place_A_Bid_Form({"listing":listing_obj.title})
categories = Category.objects.all()
user = User.objects.get(username=request.user.username)
if listing_obj in user.created_listings.all():
return render(request, "auctions/details.html", {
"listing": listing_obj,
"categories": categories,
"creator": True
})
elif listing_obj in user.watchlist.all():
return render(request, "auctions/details.html", {
"listing": listing_obj,
"categories": categories,
"in_watchlist": True,
"form": form
})
else:
return render(request, "auctions/details.html", {
"listing": listing_obj,
"categories": categories,
"in_watchlist": False,
"form": form
})
What am I doing wrong?
Thanks in advance.
There is an error in that your form has no method, so it defaults to GET. It should be
<form action="{% url 'place_a_bid' listing.title %}" method="post">
Now why are you getting errors even if the form is not being submitted?
This form = Place_A_Bid_Form({"listing":listing_obj.title}) creates a bound form, which will give the error since bid is required and missing.
Perhaps what you intended is to create an unbound form with initial data, like this:
form = Place_A_Bid_Form(initial={"listing":listing_obj.title})
This will create the same looking form on your html, but since it's not bound, it will not give any errors, unless it is submitted with the missing bid field.
Check out the docs on bound and unbound forms, and this answer about how to provide initial data to each.

Ajax redirects to the url with preventDefault()

I'm using Django for an app that allows you to search and save recipes. I'm trying to have a form on a search results page that allows you to click 'save' and creates an object to the database without reloading the page. In the current setup when I click on the 'save' button for the form with id="add_form" I get redirected to a new url (add/) even though I've included preventDefault().
I thought this was due to me including the form action={%url 'add' %} however, I've tried removing the form action but that gave me a multiValueDictKeyError for raw_input = request.POST['ingredients'] so I thought it was calling the incorrect view function views.recipes instead of views.add. I've read some other ajax guides that explicitly state a form action so I thought that would help with the incorrect view function being called.
views.py
def recipes(request):
if request.method == 'POST':
# check for dietary restrictions
restrictions = request.POST.getlist('restrictions')
# format input ingredients
raw_input = request.POST['ingredients']
...
return render(request, 'recipes/recipes.html', {'results': results})
else:
return render(request, 'recipes/recipes.html', {'error': 'Please search for a recipe'})
#csrf_protect
def add(request):
if request.method == 'POST':
title = request.POST['title']
image = request.POST['image']
source = request.POST['source']
user = request.user
try:
recipe = Recipe.objects.get(image=image, title=title, source=source)
except ObjectDoesNotExist:
recipe = Recipe.objects.create(image=image, title=title, source=source)
finally:
recipe.users.add(user)
recipe.save()
return JsonResponse({'success': True})
else:
return JsonResponse({'success': False})
urls.py
urlpatterns = [
path('add/', views.add, name='add'),
path('', views.recipes, name='recipes'),
]
html file
{% block content %}
...
{% if user.is_authenticated %}
<form action="{% url 'add' %}" id="add_form" method="POST">
{% csrf_token %}
<input type="hidden" id="image" name="image" value="{{ result.image }}">
<input type="hidden" id="title" name="title" value="{{ result.title }}">
<input type="hidden" id="source" name="source" value="{{ result.source }}">
<button type="submit" name="recipe" class="btn btn-sm btn-outline-secondary">Save</button>
</form>
{% endif %}
{% endblock %}
{% block javascript %}
<script type="text/javascript">
$(document).ready(function() {
$("#add_form").on('submit', function(e) {
e.preventDefault();
console.log( "form submitted");
$.ajax({
type:'POST',
async: 'false',
cache: 'false',
url:'{% url 'add' %}',
data:{
image:$('#image').val(),
title:$('#title').val(),
source:$('#source').val(),
},
success:function(){
alert("Saved Recipe")
}
})
})
})
</script>
I'm expecting any clicks to the save button to stay on the same page without a reload and to call views.add which will save/create an object to the database
Change the url to $(this).attr('action'). It will automatically get the url from the form instead of explicitly defining it again while making ajax call.
$.ajax({
type:'POST',
async: 'false',
cache: 'false',
url: $(this).attr('action'),,
data:{
image:$('#image').val(),
title:$('#title').val(),
source:$('#source').val(),
},
success:function(){
alert("Saved Recipe")
}
})

Django forms - rendering form and redirecting

I am trying to create an 'add user' feature which will simply add the user you've selected from a dropdown as your connection. I am using ModelChoiceField from Django Forms so that I may get the existing users from my User model in the dropdown.
forms.py
from django import forms
from django.contrib.auth.models import User
class NetworkForm(forms.Form):
user_id = forms.ModelChoiceField(queryset=User.objects.all(), label='',
widget=forms.Select(attrs={'class': 'all_users'}))
views.py
#login_required
def index(request):
user_list = User.objects.exclude(username=request.user)
return render(request, 'chat/index.html', {'user_list': user_list})
For now I am just printing the form to see output
#login_required
def add_user(request):
form = NetworkForm()
if request.method == 'POST':
form = NetworkForm(request.POST)
if form.is_valid():
print form
return redirect(request.META['HTTP_REFERER'])
errors = form.errors or None
return render(request, 'chat/index.html', {
'form': form,
'errors': errors,
})
index.html
<div class="row">
<form action="{% url 'chat:add_user' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-warning" value="{{ user_id }}" style="float: left;">Submit</button>
</form>
</div>
urls.py
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^add_user/$', views.add_user, name='add_user'),]
The way it is being rendered currently is: I have my main index page where I don't see any dropdown, i.e.,
When I click on the submit button, it moves me to index/add_user, where I get a dropdown with user (with a warning "this field is required) and a submit button again.
Finally, when I choose a user in this new page and hit submit, finally the form is printed, which I want ultimately.
What I would want it, have the complete form with dropdown in the index page itself and remain there when I submit the form. I will then hook that to show user the users they have added beneath that form itself ('print' is just for debugging purpose - not a good way I've heard though).
I understand the post request will have to go to add_user page and I can redirect back from that. I have tried various alternatives for the past 6 hours, nothing works yet. Apologies for a long one, giving out information as much as possible. Thanks very much guys. You are awesome.
Edit
Have been now rendering the form in index page (suggestion from #fazil-zaid ), but the issue remains as in only 'submit' button appears on index initially, unless when I hit submit after which the dropdown and submit appears. Again, on clicking the second time, the form is submitted.
Edit-2
I am thinking that:
<form action="{% url 'chat:index' %}" method="post">{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-warning" value="{{ user_id }}" style="float: left;">Submit</button>
</form>
this might be where the problem is, as per current logic unless user takes form's action, i.e., clicks the button {{ form.as_p }} will not appear. Then I tried:
{{ form.as_p }}
<form action="{% url 'chat:index' %}" method="post">{% csrf_token %}
<button class="btn btn-warning" value="{{ user_id }}" style="float: left;">Submit</button>
</form>
Doesn't work still. POST request doesn't send any data (understandably so).
If you want the form to be in the index page, then you could include it in the index view itself.
def index(request):
if request.method == 'POST':
form = NetworkForm(request.POST)
if form.is_valid():
#do what you wanna do
#with the form data.
else:
form = NetworkForm()
render(request, 'chat/index.html', { 'form': form})
In the template,
<div class="row">
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-warning" value="{{ user_id }}" style="float: left;">Submit</button>
</form>
</div>
You are not rendering another template, but the same 'index.html'. Then, multiple view for that is just redundant. Index page could contain the form and render itself. From what I understand, there's no need of redirections.
There's no need of add_user view if you're showing the form in the index page itself.
For your issue, try changing the "class" attribute of the form fields, maybe something like this,
class NetworkForm(forms.Form):
user_id = forms.ModelChoiceField(queryset=User.objects.all(), widget=forms.Select(attrs={'class': 'form-control'}))
Solution:
When the page is called in the first instance using GET, the form is not valid as it seeks a POST method. Hence, all the method need to be changed to POST in the view, i.e.,
#login_required
def index(request):
user_list = User.objects.exclude(username=request.user)
form = NetworkForm()
if request.method == 'POST':
form = NetworkForm(request.POST)
if form.is_valid():
print form.data
return redirect(request.META['HTTP_REFERER'])
return render(request, 'chat/index.html', {
'user_list': user_list,
'form': form,
})
Earlier, index was using a GET to render data to index page, and using a POST to use the form. Now, everything works fine.
Special shout-out to #fazil-zaid for the heads-up since you mentioned to include everything in the index view itself, rather than making a separate view for form. Your code pointed that out in a way in addition to Stack here.

django view redirects to URL it shouldn't

I have the following view
def edit_booking(request, pk=None):
if not request.user.is_authenticated:
raise Http404
agent = Agent.objects.get(user=request.user)
booking = get_object_or_404(Booking, pk=pk)
form = BookingForm(request.POST or None, instance=booking)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
return HttpResponseRedirect(instance.get_absolute_url())
elif form.errors:
messages.error(request,"There was a problem, please try again")
context = {
"form": form,
}
return render(request,'booking_form.html', context)
I use the following urls.py
urlpatterns = [
url(r'^booking/create', create_booking, name="create-booking"),
url(r'^booking/$', booking_list, name="booking-list"),
url(r'^booking/(?P<pk>\d+)/$', booking_detail, name="booking-detail"),
url(r'^booking/(?P<pk>\d+)/edit', edit_booking, name="edit-booking"),
]
For some reason when I try to submit the form after editing some booking (e.g. http://127.0.0.1:8000/booking/24/edit) I am automatically redirected to (http://127.0.0.1:8000/booking/24/).
As far as I can tell django is not processing any further code in the view. I tried to figure out with simple print("something") to see where in the code it ends up but it just goes to the url right away as soon as I submit from the template. For completeness sake this is the template:
{% extends 'base.html' %}
<div class="col-sm-6 col-sm-offset 3">
{% block content %}
<form method="POST" action=".">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save"/>
</form>
{% endblock %}
</div>
".", which you used as the action of the form, is interpreted by browsers as "the base of the current path directory". Since you have not used a trailing slash in your /edit URL, the browser submits the form to the nearest base, ie /booking/24.
You should always use a trailing slash:
url(r'^booking/create/$', create_booking, name="create-booking"),
url(r'^booking/$', booking_list, name="booking-list"),
url(r'^booking/(?P<pk>\d+)/$', booking_detail, name="booking-detail"),
url(r'^booking/(?P<pk>\d+)/edit/$', edit_booking, name="edit-booking"),
You need to check for the request method otherwise it will redirect on initial form rendering because django uses the same view for initial rendering and submitting the form.
if request.method == 'POST':
if form.is_valid():
instance = form.save(commit=False)
instance.save()
return HttpResponseRedirect(instance.get_absolute_url())
elif form.errors:
messages.error(request,"There was a problem, please try again")
else:
context = {
"form": form,
}
return render(request,'booking_form.html', context)

How to redirect to previous page in Django after POST request

I face a problem which I can't find a solution for. I have a button in navbar which is available on all pages and it is a button responsible for creating some content.
View that links with button:
def createadv(request):
uw = getuw(request.user.username)
if request.method =='POST':
form = AdverForm(request.POST, request.FILES)
if form.is_valid():
form.instance.user = request.user
form.save()
return HttpResponseRedirect('/', {'username': request.user.username, 'uw': uw})
args = {}
args.update(csrf(request))
args['username'] = request.user.username
args['form'] = AdverForm()
args['uw'] = uw
return render_to_response('createadv.html', args)
If you can see now I always redirect to main page '/' after creating content but I want to go back to the page with which I launched the creation of content.
You can add a next field to your form, and set it to request.path. After you processed your form you can redirect to the value of this path.
template.html
<form method="POST">
{% csrf_token %}
{{ form }}
<input type="hidden" name="next" value="{{ request.path }}">
<button type="submit">Let's Go</button>
</form>
views.py
next = request.POST.get('next', '/')
return HttpResponseRedirect(next)
This is roughly what django.contrib.auth does for the login form if I remember well.
If you pass through an intermediate page, you can pass the 'next' value via the querystring:
some_page.html
Go to my form!
template.html
<form method="POST">
{% csrf_token %}
{{ form }}
<input type="hidden" name="next" value="{{ request.GET.next }}">
<button type="submit">Let's Go</button>
</form>
You can use the HTTP_REFERER value:
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
Note that this will not work if the client disabled sending referrer information (for example, using a private/incognito browser Window). In such a case it will redirect to /.
You can use this
return redirect(request.META.get('HTTP_REFERER'))
Make sure to import this
from django.shortcuts import redirect
My favorite way to do that is giving the request.path as GET parameter to the form.
It will pass it when posting until you redirect.
In Class-Based-Views (FormView, UpdateView, DeleteView or CreateView) you can directly use it as success_url.
Somewhere i read that it's bad practise to mix GET and POST but the simplicity of this makes it to an exception for me.
Example urls.py:
urlpatterns = [
path('', HomeView.as_view(), name='home'),
path('user/update/', UserUpdateView.as_view(), name='user_update'),
]
Link to the form inside of the template:
Update User
Class-Based-View:
class UserUpdateView(UpdateView):
...
def get_success_url(self):
return self.request.GET.get('next', reverse('home'))
In your function based view you can use it as follows:
def createadv(request):
uw = getuw(request.user.username)
if request.method =='POST':
form = AdverForm(request.POST, request.FILES)
if form.is_valid():
form.instance.user = request.user
form.save()
next = request.GET.get('next', reverse('home'))
return HttpResponseRedirect(next)
args = {}
args.update(csrf(request))
args['username'] = request.user.username
args['form'] = AdverForm()
args['uw'] = uw
return render_to_response('createadv.html', args)
you could do this easily with a simple one-liner JS
<button onclick="history.back()">Go Back</button>
This will take you back to the previous page of your history list.
If you don't have a history
https://www.w3schools.com/jsref/met_his_back.asp
Use HTTP_REFERER value:
for use in func return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
for use in template Go Back
In case this helps someone I got this to work in class based UpdateView
template
<form class="form" method="POST">
{% csrf_token %}
<!-- hidden form field -->
<input type="hidden" id="previous_page" name="previous_page"
value="/previous/page/url">
<!-- any other form fields -->
{{ form.name|as_crispy_field }}
{{ form.address|as_crispy_field }}
<!-- form submit button -->
<button class="btn btn-primary" type="submit" id="submit">Submit</button>
</form>
<!-- JS to insert previous page url in hidden input field -->
<script>
prev = document.getElementById("previous_page");
prev.value = document.referrer;
</script>
views.py
class ArticleUpdateView(generic.UpdateView):
model = Article
form_class = ArticleForm
template_name = 'repo/article_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
# if form is valid get url of previous page from hidden input field
# and assign to success url
self.success_url = self.request.POST.get('previous_page')
return super().form_valid(form)
The view now redirects you back to the page where you had clicked the "Update/Edit" button. Any URL query parameters are also preserved.