django form resubmitted upon refresh - django

After I submit the form for the first time and then refresh the form it gets resubmitted and and I don't want that.
Here's my form in template :
<form action = "" method = "POST"> {% csrf_token %}
{{ form.as_p }}
<input type = "submit" value = "Shout!"/>
</form>
How can I fix this ?
Here's my views:
def index(request):
shouts = Shout.objects.all()
if request.method == "POST":
form = GuestBookForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
shout = Shout(author = cd['author'], message = cd['message'])
shout.save()
form = GuestBookForm()
else:
form = GuestBookForm()
return render_to_response('guestbook/index.html', {'shouts' : shouts,
'form' : form },
context_instance = RequestContext(request))

My guess is that this is a problem in your view.
After successful submission and processing of a web form, you need to use a return HttpResponseRedirect, even if you are only redirecting to the same view. Otherwise, certain browsers (I'm pretty sure FireFox does this) will end up submitting the form twice.
Here's an example of how to handle this...
def some_view(request):
if request.method == "POST":
form = some_form(request.POST)
if form.is_valid():
# do processing
# save model, etc.
return HttpResponseRedirect("/some/url/")
return render_to_response("normal/template.html", {"form":form}, context_instance=RequestContext(request))
Given your recently added view above...
def index(request):
shouts = Shout.objects.all()
if request.method == "POST":
form = GuestBookForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
shout = Shout(author = cd['author'], message = cd['message'])
shout.save()
# Redirect to THIS view, assuming it lives in 'some app'
return HttpResponseRedirect(reverse("some_app.views.index"))
else:
form = GuestBookForm()
return render_to_response('guestbook/index.html', {'shouts' : shouts,
'form' : form },
context_instance = RequestContext(request))
That will use reverse to redirect to this same view (if thats what you are trying to do)

Try:
return redirect ('url', parameter_if_needed)
instead of
return render (request, 'name.hmtl', context)
In my case it works perfectly.

Most likely: When you refresh after submitting the form, you are showing the same form page again (without doing anything). You either need to redirect to the record page or a new page after the form has been submitted.
That way, the form becomes empty its data and will not resubmit when you refresh.

I have found a way and I think it's going to work for any website. what you have to do is add a Htmx cdn or you can call the javascript library from htmx.org like bootstrap CDN.
add this
before body tag
<script src="https://unpkg.com/htmx.org#1.6.0"></script>
add this or go to their website htmx.org
then what you have to do is go to your form tag and add this....
hx-post=" then add the path in here, where you want to redirect" something like this..
contact html
<form hx-post="/contact" hx-target="body" method="post">
</form>
you have to add a target depending on your form type. The above example is a contact form I want that contact form to stay on the same page and target its body like this hx-target="body"
views.py
return render(request, "blog/contact.html")

Use HttpResponseRedirect
create a new view(lets say thank_you) for successful message to display after form submission and return a template.
After successful form submission do return HttpResponseRedirect("/thank-you/") to the new thank-you view
from django.http import HttpResponseRedirect
def thank_you(request, template_name='thank-you.html'):
return render_to_response(template_name,locals(),context_instance=RequestContext(request))
and in urls.py
url(r'^thank-you/$','thank_you', name="thank_you")
Multiple form submission happens because when page refreshes that same url hits, which call that same view again and again and hence multiple entries saved in database. To prevent this, we are required to redirect the response to the new url/view, so that next time page refreshes it will hit that new url/view.

This solution worked for me. After form submission we have have to display a message in our template in form of popup or text in any form so though HttpResponseRedirect may prevent form resubmission but it won't deliver the message so here is what I did.
from django.contrib import messages
def index_function(request):
if request.method == "POST":
form = some_form(request.POST)
if form.is_valid():
# do processing
# save model, etc.
messages.success(request, 'Form successfully submitted') # Any message you wish
return HttpResponseRedirect("/url")
Then inside your template, you can show this message. Since this is global parameter you can display it in any HTML template like the following.
{% if messages %}
<div class="alert alert-success alert-dismissible">
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
</div>
{% endif %}

Related

Django - I'm getting "This field is required" error on UPDATING an object

When I try to use a form to edit an object that includes an image upload I get "This field is required". A similar form works fine to create the object, but when I retrieve the object and attempt to modify other fields, it fails on the image.
#-------models.py:
class Star(models.Model):
firstname = models.CharField(max_length=32)
lastname = models.CharField(max_length=32, blank=True)
portrait = models.ImageField(upload_to='images/')
#------views.py:
class StarForm(forms.ModelForm):
class Meta:
model = Star
fields = ["firstname", "lastname", "portrait"]
def staredit(request, star_id):
instance = Star.objects.get(pk=star_id)
form = StarForm(request.POST or None, instance=instance)
context = {
"form": form,
}
return render(request, "stars/edit.html", context)
def starchange(request):
form = StarForm(request.POST, request.FILES)
if form.is_valid():
newstar.save()
context = {
"message": "The form was posted",
}
return render(request, "stars/edit.html", context)
else:
context = {
"message": form.errors,
}
return render(request, "stars/edit.html", context)
#-----edit.html
<form action="/starchange" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
{{message}}
Error message:
portrait
This field is required.
You are not updating the instance, since you never have passed the instance to the view that should update it. When you make a POST requrest, the browser only submits the content of the form elements. There is no data about what has rendered the previous form, that data is lost.
You should specify the instance to update, so:
from django.shortcuts import get_object_or_404
def starchange(request, pk):
obj = get_object_or_404(Star, pk=pk)
form = StarForm(request.POST, request.FILES, instance=obj)
if form.is_valid():
form.save()
context = {
"message": "The form was posted",
}
return render(request, "stars/edit.html", context)
else:
context = {
"message": form.errors,
}
return render(request, "stars/edit.html", context)
in the urls, you thus should specify the primary key of the object to update:
urlpatterns = [
# …,
path('starchange/<int:pk>/', views.starchange, name='starchange')
]
and in the template, you should make a POST request to a view with the given instance:
<form action="{% url 'starchange' pk=form.instance.pk %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
This is one of the main reasons why often the same view is used both for a GET and POST request, since it makes it removes a lot of duplicate logic. Furthermore it is also more clean: you can use GET to retrieve the page, and POST to submit the page.
Note: In case of a successful POST request, you should make a redirect
[Django-doc]
to implement the Post/Redirect/Get pattern [wiki].
This avoids that you make the same POST request when the user refreshes the
browser.
Note: It is often better to use get_object_or_404(…) [Django-doc],
then to use .get(…) [Django-doc] directly. In case the object does not exists,
for example because the user altered the URL themselves, the get_object_or_404(…) will result in returning a HTTP 404 Not Found response, whereas using
.get(…) will result in a HTTP 500 Server Error.

Bind dynamic choices to ModelForm in Django

I'm trying to bind a dynamic list of choices to a ModelForm. The form is rendered correctly. However, when using the form with a POST Request, I get an empty form back. My goal is to save that form into the database (form.save()). Any help would be much appreciated.
Model
I'm using a multiple choice select field ( https://github.com/goinnn/django-multiselectfield )
from django.db import models
from multiselectfield import MultiSelectField
class VizInfoModel(models.Model):
tog = MultiSelectField()
vis = MultiSelectField()
Forms
class VizInfoForm(forms.ModelForm):
class Meta:
model = VizInfoModel
fields = '__all__'
def __init__(self,choice,*args,**kwargs):
super(VizInfoForm, self).__init__(*args,**kwargs)
self.fields['tog'].choices = choice
self.fields['vis'].choices = choice
View
Choices are passed from the view when instantiating the form.
def viz_details(request):
options = []
headers = request.session['headers']
for header in headers :
options.append((header, header))
if request.method == 'POST':
form = VizInfoForm(options, request.POST)
#doesnt' get into the if statement since form is empty!
#choices are not bounded to the model although the form is perfectly rendered
if form.is_valid():
form.save()
return HttpResponseRedirect('/upload')
else:
#this works just fine
form = VizInfoForm(options)
return render(request, 'uploads/details.html', {'form': form})
Template
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>Choose variables to toggle between</p>
{{ form.tog }}
<br></br>
<p>Choose variable to be visualized</p>
{{ form.vis }}
<br></br>
<button type="submit">Submit</button>
</form>
You're saying Django doesn't get into your if request.method == 'POST' block.
This tells us that you're not sending your request through the POST method. Your template probably has an error in it, maybe you haven't specified the method on your form, or you made your button to just be a link instead of a submit ?
Show your template so we can say more, unless this was enough to solve your question !

Django - Form element's default value error

there are two links in one page[admin page]. both goes to same form page[ Add Post ]. in admin page ; you can click to 'new post' link or you can type your title in a textbox which is connected to same page[Add Post]. in that page ; i have a form which have a checkbox field.
isdraft = forms.BooleanField(required=False,initial=True)
as you can see ; i set the field's default value True. if i click the 'new post' at admin page ; to come that form page ; it works great. checkbox comes to me as True. but if i use other way [ typing title in textbox ] checkbox comes to me as False. i couldn't figure that out.
edit : i think the question is complicated. not clear. if any additional data needed ; i can add it.
edit :
admin.html :
<h1>PostsNew Post</h1> #this works!
<form action="{% url add_post %}" method="POST">{% csrf_token %}
{{ form.title }} <!-- this is textbox -->
{{ form.isdraft.as_hidden }} <!-- when i add this line form works correctly. but i get 'please fill all fields' warning when redirected to add_post page. -->
</form>
edit2 :
add_post view:
#login_required(login_url='/login/')
def add_post(request):
template_name = 'add.html'
owner = request.user
if request.method == "POST":
form = addForm(request.POST)
if form.is_valid():
titleform = form.cleaned_data['title']
bodyform = form.cleaned_data['body']
checkform = form.cleaned_data['isdraft']
n = Post(title=titleform, body=bodyform, isdraft=checkform, owner=owner)
n.save()
messages.add_message(request, messages.SUCCESS,
'New post created successfully!')
return HttpResponseRedirect('/admin/post/add/')
else:
messages.add_message(request, messages.WARNING,
'Please fill in all fields!')
else:
form = addForm()
return render_to_response(template_name, {'form': form, 'owner':owner,},context_instance=RequestContext(request))`
admin view :
#login_required(login_url='/login/')
def admin_view(request):
if request.session.get('loggedin') == "djangoo":
form = newDraft() # textbox in admin page
return render_to_response('admin.html', {'form':form },context_instance=RequestContext(request))
else:
return HttpResponseRedirect('/login/')
addForm :
class addForm(forms.Form):
title = forms.CharField(max_length=100,
widget=forms.TextInput(attrs={'placeholder':'Title here',}))
body = forms.CharField(widget=forms.Textarea(
attrs={'placeholder':'Write post here','rows':'25','cols':'90',}))
isdraft = forms.BooleanField(required=False,initial=True)
initial only works when the form is unbound.
When you click the new post link, you are doing a get request on the page, so the form is unbound and the initial value is used.
When you enter a title and submit, I assume you are doing a post request on the page. Therefore the form is bound, and the initial value will not be used.
I'm afraid I don't understand the question completely, and you haven't show much code, so I can't suggest any work arounds for you. Hope you get it working.
Update following edits to your question
When the data comes from the add_post view, don't create a bound form, because then the data will be validated and you'll get the error messages.
Instead, fetch the title from the post data, and use that to create an initial dictionary to instantiate your addForm with.
You need a way to tell whether the post request came from the admin or add post view. You could do this by adding another hidden field to the addForm.
action = forms.CharField(widget=forms.HiddenInput, initial="addform")
Then change your add_post view to something like:
if request.method == 'POST':
if request.POST.get('action') == 'addform':
form = addForm(initial={'title': request.POST.get('title'), 'isdraft': True})
else:
# your existing code for handling a request post

Django - Using context_processor

I want to put a login form everywhere in my webpage so I added a context_processor and I included it in base.html file. The problem now is I cannot see the form.
Here is my context_processors.py:
def global_login_form(request):
if request.method == 'POST':
formLogin = LoginForm(data=request.POST)
if formLogin.is_valid():
from django.contrib.auth import login
login(request, formLogin.get_user())
...
else:
formLogin = LoginForm()
return {'formLogin': formLogin}
And here are the diferents htmls I tried in base.html trying to invoke the form:
<form action="/myapp/login/" method="post">
{% csrf_token %}
{{global_login_form}}
</form>
<form action="/myapp/login/" method="post">
{% csrf_token %}
{{global_login_form.as_p}}
</form>
<form action="/myapp/login/" method="post">
{% csrf_token %}
{{request.formLogin}}
</form>
first time I load the page, the context_process returns {'formLogin': formLogin} (cause formLogin is LoginForm()) but I cannot see the form while inspecting the html. It is not there... but I can see the csrf_token so I think I'm not invoking the context properly.
Just it case (maybe the order is incorrect), here is settings.py:
TEMPLATE_CONTEXT_PROCESSORS = (
"myapp.context_processors.global_login_form",
"django.core.context_processors.request",
"django.contrib.auth.context_processors.auth",
)
Any ideas?
I believe OP has incorrectly assumed that the template context variable is going to match the function name of the context processor.
OP's context processor global_login_form() injects formLogin to the template context. Therefore, in the templates the form should be referenced as, for example, {{ formLogin.as_p }}.
you must have the form variable in every views, or you should implement a templatetag instead. example:
https://docs.djangoproject.com/en/dev/howto/custom-template-tags/#writing-custom-template-tags
from django import template
from django.contrib.auth.forms import AuthenticationForm
register = template.Library()
#register.inclusion_tag('registration/login.html', takes_context=True)
def login(context):
"""
the login form
{% load login %}{% login %}
"""
request = context.get('request', None)
if not request:
return
if request.user.is_authenticated():
return dict(formLogin=AuthenticationForm())
return dict(user=request.user)
I want to use everywhere on my site gives a signal that you might need a template context processor.
def global_login_form(request):
next = request.GET.get('next', '/')
login_form = AuthenticationForm(initial={'next': next})
return {'login_form': login_form}
Having this and adding this context processor to TEMPLATE_CONTEXT_PROCESSORS section in settings you can use {{global_login_form}} in your templates.
Probably you'll have more or less something like this
....
<form action="{% url login_view_name %}" method="POST">
{{global_login_form.as_p}}
</form>
....
I used this pattern in one of my projects and it worked pretty fine. Only note here that in case if form is not validated it is better to display form in errors on separated page. Here's an example of the view:
def login(request):
if request.method == "POST":
login_form =LoginForm(data=request.POST)
if login_form.is_valid():
next = login_form.cleaned_data.get('next', '/')
login(request, login_form.get_user())
return HttpResponseRedirect(next)
return render_to_response(
'users/login/login_page.html',
{'form': login_form},
context_instance=RequestContext(request),
)
class MyForm(forms.Form):
username = forms.CharField(widget=forms.TextInput(attrs={'class':'login_text'}))
password = forms.CharField(widget=forms.TextInput(attrs={'class':'password_text'}))
This may be your solution. Css is passed as an attribute of the form field, so you don't need to explicitly declare it into your html.
Don't try to put in POST something that wasn't really POSTed. If you need to get some information to the templatetag, just use the context.
Finally I used a middleware to process the request, instead of using a context_processor. Also, I deleted my login view and I changed the form action to "." so the login functionality is in the middleware.
I followed this question to find the solution.

Pass an initial value to a Django form field

Django newbie question....
I'm trying to write a search form and maintain the state of the input box between the search request and the search results.
Here's my form:
class SearchForm(forms.Form):
q = forms.CharField(label='Search: ', max_length=50)
And here's my views code:
def search(request, q=""):
if (q != ""):
q = q.strip()
form = SearchForm(initial=q)
#get results here...
return render_to_response('things/search_results.html',
{'things': things, 'form': form, 'query': q})
elif (request.method == 'POST'): # If the form has been submitted
form = SearchForm(request.POST)
if form.is_valid():
q = form.cleaned_data['q']
# Process the data in form.cleaned_data
return HttpResponseRedirect('/things/search/%s/' % q) # Redirect after POST
else:
form = SearchForm()
return render_to_response('things/search.html', {
'form': form,
})
else:
form = SearchForm()
return render_to_response('things/search.html', {
'form': form,
})
But this gives me the error:
Caught an exception while rendering: 'unicode' object has no attribute 'get'
How can I pass the initial value? Various things I've tried seem to interfere with the request.POST parameter.
Several things are not good here...
1) The recommended thing after a POST is to redirect. This avoids the infamous popup saying that you are resubmitting the form when using the back button.
2) You don't need to say if request.method == 'POST', just if request.POST. That makes your code easier to read.
3) The view generally looks something like:
def myview(request):
# Some set up operations
if request.POST:
form=MyForm(request.POST)
if form.is_valid():
# some other operations and model save if any
# redirect to results page
form=MyForm()
#render your form template
That is not to say that there can't be much simpler and much more complicated views. But that is the gist of a view: if request is post process the form and redirect; if request is get render the form.
I don't know why you are getting an unicode error. I can only think that it is related to one of your models that you don't provide.
The error, as spookylukey mentions is in his comment, most likely is caused by you submitting a string instead of a dict to the initial parameter.
I really recommend the django documentation, in particular the tutorial., but there is also the very nice Django Book.
All that said, I think you want something like:
def search(request, q=None):
if request.POST:
form = SearchForm(request.POST)
if form.is_valid():
q = form.cleaned_data['q']
url=reverse('search_results', args=(q,))
return HttpResponseRedirect(url)
if q is None:
form = SearchForm()
else:
form = SearchForm(initial={'q': q})
return render_to_response('things/search.html', {
'form': form,
})
Notice that the parameter to initial is a dict of the field values of your form.
Hope that helps.
Django forms are not particularly helpful for your use case. Also, for a search page, it's much better to use a GET form and maintain state in the URL. The following code is much shorter, simpler and conforms far better to HTTP standards:
def search(request):
q = request.GET.get('q','').strip()
results = get_some_results(q)
render_to_response("things/search.html", {'q': q, 'results': results})
The template:
<form method="GET" action=".">
<p><input type="text" value="{{ q }}" /> <input type="submit" value="Search" /></p>
{% if q %}
{% if results %}
Your results...
{% else %}
No results
{% endif %}
{% endif %}
</form>