How to handle direct file upload with GAE/python? - python

Client side (javascript) uploads the application with XMLHttpRequest:
var req = new XMLHttpRequest();
req.open('POST', my_app_url, false);
req.setRequestHeader("Content-Length", req.length);
req.sendAsBinary(req.binary);
I use datastore on the server side (not blobstore).
How can I save uploaded file to the datastore? I've found that ServletFileUpload can be used with Java. But how to do the same with Python?

You should use self.request.body
class YourUploadHandler(webapp.RequestHandler):
def post(self):
your_binary_content = self.request.body

If you mean on the appengine side, you just have to have a blobproperty. So something like...
class SomeEntity(db.Model):
file_data = db.BlobProperty()
class AddData(webapp.RequestHandler)
def post(self):
data = self.request.get("filedata")
e = SomeEntity(file_data = db.Blob(data))
e.put()
As a note, I'm not sure if the code you posted above to send the request is correct, but you can upload the file with a simple html form, something like this:
<form action="/url_to_adddata_handler/" method="post">
<input type="file" name="filedata">
<input type="submit" value="Submit">
</form>

Related

Flask form appends form data to url on submit

I have a HTML form
<form>
<input type="text" name="keyword" id="keyword" />
<input type="submit">Submit</input>
on submits I want to pass form data to flask app
requests.py
def search_news(keyword):
search_news_url = 'https://newsapi.org/v2/everything?q={}&language=en&apiKey={}'.format(keyword,api_key)
with urllib.request.urlopen(search_news_url) as url:
search_news_data = url.read()
search_news_response = json.loads(search_news_data)
search_news_results = None
if search_news_response['articles']:
search_news_list = search_news_response['articles']
search_news_results = process_search_results(search_news_list)
return search_news_results
def process_search_results(search_news_list):
news_results = []
for search_results_item in search_news_list:
author = search_results_item.get('author')
title = search_results_item.get('title')
description = search_results_item.get('description')
url = search_results_item.get('url')
urlToImage = search_results_item.get('urlToImage')
publishedAt = search_results_item.get('publishedAt')
content = search_results_item.get('content')
totalResults = search_results_item.get('totalResults')
if content:
news_results_object = Everything(author,title,description,url,urlToImage,publishedAt,content,totalResults)
news_results.append(news_results_object)
return news_results
views.py
from ..requests import get_everything,search_news
....
#main.route('/')
def index():
everything = get_everything()
title = 'News Highlight'
searching_news = request.args.get('keyword')
if searching_news:
redirect(url_for('.search',keyword = searching_news))
return render_template('index.html',title = title,everything = everything)
....
#main.route('/search/<keyword>')
def search(keyword):
keyword_list = keyword.split(" ")
keyword_format = '%20'.join(keyword_list)
searched_news = search_news(keyword_format)
title = f'Search results for {keyword} '
return render_template('search.html',searched_news = searched_news)
on form submits it appends form data to url like this:
http://127.0.0.1:5000/?keyword=game+of+thrones
I've tried using post methods but i get a server does not support method error. Can anyone help please.
but when I append link like this:
http://127.0.0.1:5000/search/game%20%of%thrones
the app searches and displays results
By default, form data is submitted via the URL's query string if you don't tell it to behave differently (the method defaults to GET).
Post
If you want to send the data to flask using a POST request, this is the way to go:
Make sure to tell your HTML form to use method="POST" and tell it where to send it's data via the action attribute:
<form method="post" action="/search">
<input type="text" name="keyword" id="keyword" />
<input type="submit">Submit</input>
</form>
Tell your server-side view to accept POST requests and fetch the sent data from request.form. In views.py use
#main.route('/search/' methods=['POST'])
def search():
keyword = request.form['keyword']
...
Get
If you want to use a GET request and pass the data via query string set your form's method to get
<form method="get" action="/search">
<input type="text" name="keyword" id="keyword" />
<input type="submit">Submit</input>
</form>
On submit, your browser will append the values entered in the input field to the URL as a query string: ?keyword=<whatever-you-entered>.
Tell your server-side view to accept GET requests and fetch query string data via request.args.get(). In views.py use
#main.route('/search/' methods=['GET'])
def search():
keyword = request.args.get('keyword')
...
MDN has got a nice article with more details around sending and retreiving form data with HTML and Python, this might be worth a read, too.

can't get post parameter in multipart form

I have the following form shown in a modal of a bootstrap page
the aim is to upload a file with a hidden id
the application is based on Google App Engine, Python/Webapp2
<form action="someUrl" role="form" method="POST" enctype="multipart/form-data">
<input type="hidden" name="entityId" value="{{datastoreEntity.key.id()}}"/>
<input name="importFile" type="file" multiple>
<input type="text" class="form-control" readonly>
<input type="submit" name="submit" value="Import">
</form>
the problem is in the related RequestHandler (server side) where I can retrieve the file with
raw_file = self.request.POST.multi['importFile'].file
But I can't get the id (which is correctly generated by Jinja2 - checked in the page source). I have already tried with
self.request.get('entityId')
self.request.POST['entityId']
self.request.POST.multi['entityId']
I can't reproduce the problem as you relate it in the simplest of ways. I've copied that template into form.html, direct / to MainHandler, and net of the usual preparations (imports, jinja_environment with a FileLoader in the current dir, etc, I have):
class It(ndb.Model):
name = ndb.StringProperty()
class MainHandler(webapp2.RequestHandler):
def get(self):
dse = It(name="Willy")
dsek = dse.put()
datastoreEntity = dsek.get()
template = jinja_environment.get_template('form.html')
self.response.write(template.render(dict(
datastoreEntity=datastoreEntity,
)))
def post(self):
self.response.headers['Content-Type'] = 'text/plain'
self.response.write(self.request.get('entityId'))
Visiting /, picking a file, and clicking the Import button, I see on my browser: 5066549580791808 -- which seems to be a typical datastore entity ID, as desired.
Please "interpolate" between this toy super-simplified version, and the no doubt much more complex things you're trying to do, and edit your Q to show us the very simplest way you can reproduce your problem, thanks!
Uploading via file inputs in App Engine requires that the app use a path that's obtained from the blobstore API. The doc for that is here. So you probably want to do something like
upload_url = blobstore.create_upload_url('/someUrl')
and then inject that into your template so that the form can use it
<form action="{{upload_url}}" ...
Once the file upload is complete, blobstore will redirect to /someUrl, where you process the rest of the form, including .file.

Get uploaded file name with Google App Engine Request class

I have an HTML form that allows file uploads. I need to send the uploaded file via email. I am following the Google appengine Request class example.
from google.appengine.ext import webapp
from google.appengine.api import mail
class MyRequestHandler(webapp.RequestHandler):
def get(self):
self.response.out.write('''
<html>
<body>
<form method="post">
<p>File: <input type="file" name="upload" /></p>
<p><input type="submit" /></p>
</form>
</body>
</html>
''')
def post(self):
uploaded_file = self.request.get("upload")
uploaded_name = "file.doc"
message = mail.EmailMessage(sender="Me <me#mydomain.com>",
subject="Email from me")
message.to = "Me <me#mydomain.com>"
message.body = "See attached file."
message.attachments = [(uploaded_name, uploaded_file)]
message.send()
The above code works. I need to replace the hardcoded file name (file.doc) in the message.attachments tuple with the actual name of the file uploaded. I have two questions:
How do I get the uploaded file name from the webapp Request class?
Where can I find the documentation that describes how to do this? I have done a bunch of searching and can not find reference material or examples.
UPDATE:
Looks like the solution is:
if uploaded_file != "":
uploaded_name = self.request.params["upload"].filename
...as posted
here.
Instead of going self.request.get("upload"), try examining the self.request.POST["upload"] dict.
Specifically, self.request.POST['upload'] will return a cgi.FieldStorage instance. The attribute you are looking for is filename. Also of interest would be mimetype - and of course data.
For a complete how-to on handling uploads, take a look at this ancient post over at Nick Johnson's blog.
For an under-the-hood look at how webapp2 request data works, consult this portion of webapp2 docs.

Getting error: Could not find session for *blob_key*

Im trying to make a form where users can upload a file. And I have a form that takes the users arguments and posts them to the UploadHandler that is mapped to the url that is passed as parameter to the renderfunction. It works up to the point that it renders the form, but when submitting the file it redirects me to a blank page and the console shows me the error:
ERROR 2013-03-17 11:53:30,769 dev_appserver_blobstore.py:404] Could not find session for ahhkZXZ-a3Vyc3NhbW1hbmZhdHRuaW5nYXJyGwsSFV9fQmxvYlVwbG9hZFNlc3Npb25fXxgxDA
INFO 2013-03-17 11:53:30,779 dev_appserver.py:3104] "POST /_ah/upload/ahhkZXZ- a3Vyc3NhbW1hbmZhdHRuaW5nYXJyGwsSFV9fQmxvYlVwbG9hZFNlc3Npb25fXxgxDA HTTP/1.1" 404 -
Im still new at this but I can't seem to find whats going on. I understand that the mappings somehow f***s up, but still I don't understand why it doesn't redirect me to the correct handler (SummaryHandler)?
The class that serves the form for file upload:
class CreateHandler(BaseHandler):
def get(self):
self.render('create.html', upload_url = blobstore.create_upload_url('/upload'))
The html-form ('create.html'):
<h2 class="main-title">Upload a file!</h2>
<form action="{{upload_url}}" method="post" enctype="multipart/form-data">
<label>
<div>Upload:</div>
<input type="file" name="file" accept="application/pdf"><br>
</label>
<div class="error">
{{file_error}}
</div>
<br>
<input type="submit" name="submit" value="Submit">
</form>
The uploadhandler that takes the form args:
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
upload_files = self.get_uploads('file')
blob_info = upload_files[0]
#validation of form-vars
have_error=False
file_data=dict()
if not (blob_info.content_type == 'application/pdf'):
file_data['file_error']="You can only upload files in pdf-format."
have_error=True
if have_error:
self.render('create.html', **file_data)
else:
#'parent' is just some stuff for organising the database.
#Not important in this context
the_file = A_File(parent = summary_key(),
file_key=str(blob_info.key()))
the_file.put()
if the_file:
self.redirect('/summary/%s' % str(the_file.key().id()))
else:
_error="Could not find the file"
self.render('welcome.html', error=_error)
The application handlers and their mappings:
app = webapp2.WSGIApplication([
(r'/', WelcomeHandler),
(r'/create', CreateHandler),
(r'/upload', UploadHandler),
(r'/summary/(\d+)', SummaryHandler) #I have not included this handler in the text
], debug=True)
It might be the fact that this session expired or it was used. The urls that created using create_upload_url have an expiration (I think 10 min). Before actually posting you might want to refresh that URL using JavaScript.
Also if for any reason the file was uploaded in the Blobstore you won't be able to use the same URL again, it is only good for one request (could have multiple files though).
In your example, try to remove all the file specific checks, try to upload something and check through the administrative console in the Datastore Viewer if you have any blobs or session keys. This can be accessed through this URL:
http://localhost:8080/_ah/admin/datastore

Tie a Blob to a Datastore entity

I have been banging my head against the wall on this one, for some reason I am having trouble tying the different aspects of Google App Engine together to make this work.
Basically I want to let a user upload a photo to the Blobstore, which I have working in the below code, and then I want to put the BlobKey into a list which will be stored in a database entity. So here is my code to upload the image and where in here can I get the BlobKey so that I can store it?
class MainHandler(BlogHandler):
def get(self):
upload_url = blobstore.create_upload_url('/upload')
self.response.out.write('<html><body>')
self.response.out.write('<form action="%s" method="POST" enctype="multipart/form-data">' % upload_url)
self.response.out.write("""Upload File: <input type="file" name="file"><br> <input type="submit" name="submit" value="Submit"> </form></body></html>""")
#there is a lot more code in here where I get all the following info but it isn't relevant
location_db = Location(
description=description,
submitter=submitter,
user_id=user_id,
title=title,
locationtype = locationtype)
#This is what I would like to do but I don't know where to get thr BlobKey
location_db.blobRefs.append(BlobKey)
location_db.put()
for b in blobstore.BlobInfo.all():
self.response.out.write('<li>' + str(b.filename) + '')
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
upload_files = self.get_uploads('file')
blob_info = upload_files[0]
self.redirect('/main')
class ServeHandler(blobstore_handlers.BlobstoreDownloadHandler):
def get(self, blob_key):
blob_key = str(urllib.unquote(blob_key))
if not blobstore.get(blob_key):
self.error(404)
else:
self.send_blob(blobstore.BlobInfo.get(blob_key), save_as=True)
Here:
blob_info = upload_files[0]
self.redirect('/serve/%s' % blob_info.key())
I think it's that
blob_info.key()
you are missing. Grab that, stuff it into your list. Docs also note:
In this handler, you can store the blob key with the rest of your application's data model. The blob key itself remains accessible from the blob info entity in the datastore.
https://developers.google.com/appengine/docs/python/blobstore/overview#Serving_a_Blob

Categories