form action not working in django - python

I have django 1.4 and I am following a tutorial which uses an older version of django. Its a simple tutorial which creates a wiki app with Page as model.
The problem is that the view function corresponding to a POST method in a form is not getting invoked.
This is the content in the urls.py:
url(r'^wikicamp/(?P<page_name>[^/]+)/edit/$', 'wiki.views.edit_page'),
url(r'^wikicamp/(?P<page_name>[^/]+)/save/$', 'wiki.views.save_page'),
url(r'^wikicamp/(?P<page_name>[^/]+)/$', 'wiki.views.view_page'),
This is the content of the template edit.html:
<from method = "get" action="/wikicamp/{{page_name}}/save/">
{% csrf_token %}
<textarea name = "content" rows="20" cols="60">
{{content}}
</textarea>
<br/>
<input type="submit" value="Save Page"/>
</form>
this is link to save
And this is the content in views.py:
def edit_page(request, page_name):
try:
page = Page.objects.get(pk=page_name)
content = page.content
except Page.DoesNotExist:
content = ""
return render_to_response("edit.html", {"page_name":page_name, "content":content}, context_instance=RequestContext(request))
def save_page(request, page_name):
return HttpResponse("You're looking at the page %s." % page_name)
I initially I was getting csrf related error and I then tried all the fixes provided in https://docs.djangoproject.com/en/dev/ref/contrib/casrf/ and followed many many stackoverflow question related to POST and django. Now nothing happens when I click the 'Save Page' button, nothing! Not even any request being sent from the form (Using firebug to track the HTTP request and response)

You have a typo in your HTML: from instead of form.
You may realize this, but that code won't really save anything. I'm not sure what blog you are following, but you would be better-off following the official Django tutorial in the documentation, then reading the forms docs.

You may need to change method to "POST" in your form.
<from method = "get" action="/wikicamp/{{page_name}}/save/">
to
<form method = "post" action="/wikicamp/{{page_name}}/save/">

There are some spelling mistakes, such as from instead of form.
Also the form is malformed.
Change:
this is link to save
to
<input type="submit" value="Save Page" />
And thirdly, change the method= "get"to method="POST".
The entire form should look like this
<form method = "POST" action="/wikicamp/{{page_name}}/save/">
{% csrf_token %}
<textarea name = "content" rows="20" cols="60">
{{content}}
</textarea>
<br/>
<input type="submit" value="Save Page"/>
</form>
Also what #DanielRoseman said. But hey, it might come further down the road.

Related

Flask Redirect is Duplicating POST request

I'm doing a simple shopping website with a order confirmation page but I'm finding that there are duplicate POST requests to my /confim-order route. I have a home page that redirects on POST:
#views.route('/', methods=['GET', 'POST'])
#login_required
def home():
if request.method == 'POST':
# save information from the form for later use
session['text'] = request.form.get('samples')
session['note'] = request.form.get('note')
return redirect(url_for('views.confirm_order'))
return render_template("home.html", user=current_user)
My order confirmation function:
#views.route('/confirm-order', methods=['GET', 'POST'])
#login_required
def confirm_order():
if request.method == 'POST':
text = session['text']
note = session['note']
session.pop('text', None)
session.pop('note', None)
create_order(current_user, text, note)
return redirect(url_for('views.home'))
elif request.method == 'GET':
text = session['text']
note = session['note']
sample_list = get_samples(text, note)
return render_template("confirm.html", user=current_user, sample_list=sample_list)
There's no JavaScript in the HTML template. What's in confirm.html is essentially:
<form method="POST">
<div class="form-group">
<label for="cc">Credit Card</label>
<input type="text" class="form-control" id="cc" name="cc" placeholder="Credit Card Number" />
</div>
Click button to place order:
<p></p>
<div align="right">
<button type="submit" id="submit_btn" class="btn btn-success">Place Order</button>
</div>
</form>
This is what I see: sometimes, clicking submit works fine. Most times clicking submit results in two POST requests to confirm_order() and then I get a "This site can’t be reached" message in my browser at http://localhost:5000/confirm-order. I've been at this for almost an entire day. I put some print statements that seem to suggest the first POST to /confirm-order is initiated correctly from the template rendered we reach /confirm-order by GET from home: /. The second POST to /confirm-order came immediately after first the POST from within POST of /confirm-order. When that happens, I get the "This site can't be reached" message and I find that duplicate orders have been created.
I've searched online and most people that have duplicate POST issues are using JavaScript along with the form submission button. But my page doesn't use Javascript. If someone sees what's wrong, any help is greatly appreciated. Thank you.
EDIT: Here's the create_order() function in case something in there is causing the problem:
def create_order(user, text, note):
new_order = Order(user_id=current_user.id, text=text,
status='submitted', note=note)
db.session.add(new_order)
db.session.commit()
I'm not 100% sure this is the issue but it's the most likely thing I can think of.
I think there are some issues in your html since you don't specify the url for action. I would also use an input tag rather than button for submit.
More generally, I would also recommend following a few of the things mentioned here. So use the data in request.form rather than in the session object since you can control and validate that more explicitly within Flask (for instance if this is going into production you may want to implement WTF Forms for security reasons to prevent CSRF).
<form action="/confirm-order" method="post">
<div class="form-group">
<label for="po">Purchase Order</label>
<div class="form-group">
<label for="cc">Credit Card</label>
<input type="text" class="form-control" id="cc" name="cc" placeholder="Credit Card Number" />
</div>
Click button to place order:
<p></p>
<div align="right">
<input type="submit" id="submit_btn" class="btn btn-success">Place Order</input>
</div>
</form>

Django delete item use case : good practice?

Little general explanation.
I'm pretty newbie in Django, I have a little knowledge, but nothing as experience. The code that I want ask about is working, but I have a question about good/bad practice. Does my approach is good or at least not bad?
Little use case explanation.
I have a site with the items. There is a functionality to add the items and now I want to add a possibility to delete the item.
Use case : on the page of item user clicks on the button Delete, we will show a page with details about this item and the button "Confirm delete" at the bottom. If user click on the button I delete this item from database.
So
I create in urls.py
path('item/delete/<int:id>/', views.delete_item, {}, 'delete_item'),
I create in views.py
def delete_item(request,id):
if id :
cur_item = get_object_or_404(Item, pk=id)
else :
raise Http404
if request.POST:
try :
post_item_id=int(request.POST["pk"])
except ValueError :
messages.error(request,"Wrong id number")
return render(request, 'myapp/item_delete_form.html', {'cur_item': cur_item})
if (request.POST["delete"] == "yes") and (post_item_id == id):
Item.objects.filter(pk=id).delete()
# Delete was successful, so redirect to another page
redirect_url = reverse('items')
return redirect(redirect_url)
else:
messages.error(request, "id number in form not the same as in URL")
return render(request, 'myapp/item_delete_form.html', {'cur_item' : cur_item})
return render(request, 'myapp/item_delete_form.html', {'cur_item' : cur_item})
I do not use Django Forms as for me it's not the real Django form (i.e. it's not linked to a Model). As mention by #akx in the comments I could always create a Form based directly on forms.Form. But it seems also useless for me as in fact there is almost no data in my form.
Instead I just create generic form in the template.
In templates/myapp/item_delete_form.html
First part of template get cur_item and show it. And then :
<form method="post">
{% csrf_token %}
<input type="hidden" value="{{ cur_item.id }}" name="pk">
<input class="btn btn-default btn-danger" name="delete" type="submit" value="yes"/>
</form>
Finally here the small test to check it :
def test_delete_item(self):
test_serial = "111-111"
new_item = Item(serial_number=test_serial)
new_item.save()
url = reverse("delete_item", kwargs={'id':new_item.id})
resp = self.client.get(url)
confirm_data = {'pk': new_item.id,'delete': 'yes'}
resp = self.client.post(url, confirm_data)
self.assertEqual(Item.objects.all().count(), 0)
Whilst I'll be very appreciate to have and comments about my realisation, here are some practical questions.
Does the practice to analyze request.POST list is not the "bad practice"?
As I do not use Django forms I could not do form.is_valid. Is it ok?
Are there some pitfalls that I should think about (for example does user have the rights to delete this item, but for the moment user system is not developed so I could do nothing about that)?
Does my test is relevant?
And finally, if my realization is not a good or acceptable practice, how should I do my use case ?
You can save yourself quite some trouble by just using Django's default DeleteView CBV:
urls.py
path('item/delete/<int:pk>/', DeleteItemView.as_view(), name='delete_item'),
views.py
class DeleteItemView(DeleteView):
model = Item
template_name = "myapp/item_delete_form.html"
success_url = reverse_lazy('items')
item_delete_form.html
<form method="post">
{% csrf_token %}
<input class="btn btn-default btn-danger" name="delete" type="submit" value="yes" />
</form>
In fact, you don't even necessarily need a views.py. Just setting the arguments for the CBV in urls.py would do...
path(
"item/delete/<int:pk>/",
DeleteView.as_view(
model=Item,
template_name="myapp/item_delete_form.html",
success_url=reverse_lazy("items"),
),
name="delete_item",
)
would be equivalent.

FileRequiredValidator() doesn't work when using MultipleFileField() in my form

My UploadForm class:
from app import app
from flask_wtf.file import FileRequired, FileAllowed
from wtforms.fields import MultipleFileField
from wtforms.validators import InputRequired,DataRequired
class UploadForm(FlaskForm):
.
.
.
roomImage = MultipleFileField('Room',validators=[FileAllowed(['jpg', 'png'], 'Image only!'), FileRequired('File was empty!')] )
.
.
.#there are other fields here which are not relevant to the problem at hand
HTML Template
{% extends "base.html" %}
{% block content %}
<h1>Upload Your Images</h1>
<form action="/" enctype="multipart/form-data" method="post" >
{{ form.csrf_token }}
Room<br />
{{form.roomImage()}}
.
.
. <MORE THINGS THAT I HAVE EDITED OUT>
{{form.submit()}}
<br/>
{% if form.errors %}
{{ form.errors }}
{% endif %}
</form>
{% endblock %}
hosts.py to run the check for validation
def upload_image():#NEEDS HEAVY FIXING
"""home page to return the web page to upload documents"""
form = UploadForm()
if form.validate_on_submit():
Using VS's debugging tools, I find that form.validate_on_submit() doesn't work and always fails validation and I get this error on my html page.
{'roomImage': ['File was empty!']}
There is another MultipleFileField control with almost the exact same code.
This issue does not happen when I use FileField to upload one file. The documentation on this is very limited and all I had to go on was this. I don't really know how to solve this issue. I have searched extensively for finding example involving MultipleFileField but they are not using any validation. A thread on Github that I can't find anymore suggested using OptionalValidator, but then that is not an option for me and even that didn't work.
Can someone suggest me a solution?
EDIT:
Even the FileAllowed() validator does not seem to work.
This works for me (found on GitHub "between the lines"):
multi_file = MultipleFileField("Upload File(s)", validators=[DataRequired()])
However
FileAllowed(["xml", "jpg"])
is ignored and does not work for me.
EDIT:
No, sadly, it does not work... It returns True for form.validate() and for form.validate_on_submit() but when you pass no files, by deleting
required=""
from
<input id="multi_file" multiple="" name="multi_file" required="" type="file">
and submit a form, it still evaluate that as True.
So problem sill persist in full, as described...
Regarding FileAllowed validator , it is not working because FileAllowed validator expect one FileStorage object , but MultipleFileField is sending a list of Filestorage objects , which is why it is not working . You have to implement a MultiFileAllowed validator yourself. For More details StackOverflow answer explained with example.
This approach seems to solve the one part of the problem and it uses j-query, which I was unfamiliar with until this point.
So since I am a novice when it comes to web-dev, I was looking for a python based approach to this one. I don't think it exists just yet.
Will update when I fix the multiple file upload issue.
Jsfiddle link
jQuery.validator.setDefaults({
debug: true,
success: "valid"
});
$( "#myform" ).validate({
rules: {
":file": {
required: true,
accept: "image/*"
}
}
});
and
<form id="myform">
<label for="field">Required, image files only: </label>
<input type="file" class="left" id="field" name="field" multiple>
<br/>
<input type="submit" value="Validate!">
</form>
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/jquery.validation/1.16.0/jquery.validate.min.js"></script>
<script src="https://cdn.jsdelivr.net/jquery.validation/1.16.0/additional-methods.min.js"></script>
Since I was using wtforms, I tried something called a validation class and inline validators that seems to do the trick.
"""
Validation Class for RoomUpload
"""
class MyRoomValidator(object):
def __init__(self, message=None):
if not message:
message = u"You need to upload the images with the category as well"
self.message = message
def __call__(self, form, field):
print(len(form.roomImage.data))
print((form.roomTypeSelect.data))
if (not form.roomImage.data and form.roomTypeSelect.data == -1) or(form.roomImage.data and form.roomTypeSelect.data == -1) or (not form.roomImage.data and form.roomTypeSelect.data != -1):#check for all possible combinations
raise ValidationError(self.message)
class RoomUpload(FlaskForm):
"""
This is meant for the sub-form for room details upload
containing the details for room and its subtype
roomImage:Upload the room image
roomTypeSelect:The subcategory for room category
The Form will only be submitted if the images are uploaded and
the RoomType will be selected
"""
def validate_roomTypeSelect(form, field):#Inline Validator to ensure if default choice is not chosen
print(field.data)
if field.data == -1:
raise ValidationError('Need to Select Room Type')
def validate_roomImage(form,field):
for roomFile in field.data:
print(roomFile.filename)
if isanImageFile(roomFile.filename) == False:
raise ValidationError("Error!!! Not an Image File ")
roomImage = MultipleFileField(u"Room",validators=[MyRoomValidator(),FileAllowed],id = "roomImage")
roomTypeSelect= SelectField("Room Type",choices=roomTypes,coerce=int,id="roomTypeSelect")
roomSubmit = SubmitField("Upload the images of the room")

Django form action "." reloads same page with missing slug

I'm following a basic tutorial which adds commenting to a blog post detail page. I am navigating to the detail page with the absolute_url method and it works perfectly fine.
def get_absolute_url(self):
return reverse('blog:post_detail',
args=[self.publish.year,
self.publish.month,
self.publish.day,
self.slug])
Here is a sample url created by the get_absolute_url
http://localhost:8000/blog/2019/5/2/second-post
However, when I submit the form within the detail page with the action=".", it only returns the date parameters and missing the slug part.
<form action="." method="post">
{% csrf_token %}
{{ comment_form.as_p }}
<p><input type="submit" value="Add comment"></p>
</form>
Here is the returned url
http://localhost:8000/blog/2019/5/2/
adding action="{{ post.get_absolute_url }}" seems to solve the problem but the book I am following Django 2 By Example tells it should just work fine with action="."
I'm new to Django and Development so thank you for your help and understanding if the question is noob in any way :)
You didn't show your URL patterns, but they should all end with /. So the original URL of http://localhost:8000/blog/2019/5/2/second-post should be http://localhost:8000/blog/2019/5/2/second-post/. For example, the pattern might be:
path('blog/<int:year>/<int:month>/<int:day>/<slug:slug>/', views.blog, 'blog'),
which ends with a slash and so the generated path will also end with a slash. Then posting to "." will work properly.

Django post request doing nothing

So i'm not even sure how to search for someone who had the same thing happen to them.
I'm working on a django website and my form won't post to my database, instead, i get redirected to a URL containing the information that was in the forms, like this:
<form id="form">
<input type="hidden" id="compinp" name="compinp">
<input maxlength="20" onkeyup="showpost()" name="title" id="titleinput">
{{ captcha }}
</form>
Where compinp is some other data that gets posted, {{ captcha }} is a reCaptcha checkbox that works just fine, and when everything is filled in and getting posted, instead of running the post function from views.py, instead i get redirected to this:
http://localhost:8000/newentry/?compinp=XXXX&title=XXXX&g-recaptcha-response="xxxx-xxxx-xxxx"
It gets posted via jQuery through a button outside of the form, though i tried to add a submit button inside it and got the exact same thing.
The views.py function that handles that looks like this:
def newentry(request):
if request.method == "GET" and request.user.is_authenticated():
#creating objects for the view, works fine too
return render(request, "newentry.html",
{"champlist": complist, "captcha": captcha})
elif request.method == "POST" and request.user.is_authenticated():
captcha = Captcha(request.POST)
title = request.POST.get("title", False)
compname = request.POST.get("compinp", False)
comp = Comp.objects.get(title=compname)
if captcha.is_valid() and title and compname:
newe= entry_generator(request.user, title, comp)
newe.save()
return redirect('/')
else:
return redirect('/')
else:
handle_home(request.method, request.user)
This view tries to post models from another app in the same project, if that makes it any different.
I had added a print attempt at the right after the request check for post it didn't print anything.
Not sure what other info i can give to help, if you want any, just ask (:
You need to add the form method post:
<form id="form" method="post">
<input type="hidden" id="compinp" name="compinp">
<input maxlength="20" onkeyup="showpost()" name="title" id="titleinput">
{{ captcha }}
</form>

Categories