Welcome!
Everything is fine.
File Upload¶
File upload is an important feature of many web apps. Think about uploading profile pictures or posting pictures of your vacation or breakfast to Facebook.
Plan¶
- Announcements
- Basics of file upload, both on front-end and back-end
- Quiz Questions
- Breakout sessions
Announcements¶
- Grading status
File Upload Story¶
First, a story ....
What are the important points for today?
- File Upload is an important feature: images, documents, etc. Your projects should try to include this in some way.
- File Upload is fraught with peril.
- New kind of form input and form encoding type (ENCTYPE).
- Sometimes need to learn about MIME types, such as "image/jpeg"
- Two major places to store them:
- The filesystem, meaning regular files; store pathnames in the database. This is usually more convenient and efficient. We'll focus on this.
- The database (BLOBS). Do this if you either cannot save to the filesystem or are extremely security conscious (maybe people are supposed to be uploading executable scripts, but you need to keep them safe). We'll punt this topic this semester, but let me know if you personally want to know more.
- Images in the filesystem are connected to entities by storing the
filename as an attribute (column), or having a pattern like the
filename is
<nm>.jpg
. Images in the database are easily connected, though you do need to be careful in displaying them to your terminal. - Serving images from the filesystem is easy: just generate a URL pointing to the file. Flask/Apache does the rest. (Flask requires a bit more, but not difficult.) Serving images from BLOBs means additional handlers that pull out the data and return it as the response along with a MIME header.
- File upload is accomplished by
- ENCTYPE="multipart/form-data"
- INPUT TYPE=FILE
- POST method
- Flask applications run as you, but the uploaded files are either
in memory or saved to a temporary location. An object similar to
Python's built-in
file
object is returned; it has a useful.save()
method to save the contents in a pathname of your specification. - Responses should supply the correct MIME type for any file. Determining the correct MIME type is best done with the python-magic and python-magic
Summary¶
- File upload is an important feature of many web databases, not just FaceBook and Yelp. You should consider its usefulness in your project.
- File upload can be a security risk, so you should be careful.
Specific technology pieces:
- form enctype:
<FORM ENCTYPE="MULTIPART/FORM-DATA" ...>
- form inputs:
<INPUT TYPE=FILE NAME=name-of-file-input>
- folders: you'll probably create a folder for uploaded files, say,
uploads
, next totemplates
andstatic
. You could, of course, have different folders for different kinds of files, etc. - files: you'll need a naming scheme for your uploaded files.
- You could use a counter:
fileNNN.jpg
- You could use an ID:
fileNM.jpg
- You could use a timestamp:
file-2022-04-01-23-01-12.jpg
(your phone's camera does that)
- You could use a counter:
- You'll probably store the filename in an appropriate database table, unless the naming scheme allows the filename to be inferred from other data.
- Flask's
request
object has afiles
dictionary, containing all the uploaded files, as Pythonfile
objects. file
objects have a.save()
method, allowing you to save the file to your desired pathname- You'll construct your pathname from the uploads folder and the
chosen filename using
os.path.join(folder, file)
from the Pythonos
module. - Avoid user data in the filename/pathname, though that is possible.
- Use
secure_filename
if there's any user data in the filename from werkzeug.utils import secure_filename
- To deliver files to the browser, use
send_from_directory(uploaddir, filename)
- The browser requests files (e.g. images) in a separate request, so you have to have a route/endpoing to respond to those requests.
Detailed Review¶
Review of File Upload and how to use them in Flask.
- the
request.files
dictionary lets you get a file object for each upload. - each file has properties like
.filename
- The
.save()
method allows you to save the file to a name you specify. - The
secure_filename
function strips off "../" and other dangerous stuff send_from_directory
lets you serve these static files from a directory you specify.
File Upload Example¶
Here's the main Python code (app.py
) for today:
'''This example implements file upload to the filesystem'''
from flask import (Flask, render_template, make_response, request, redirect,
url_for, session, flash, send_from_directory, Response)
from werkzeug.utils import secure_filename
app = Flask(__name__)
import sys, os, random
import imghdr
import cs304dbi as dbi
app.secret_key = ''.join([ random.choice(('ABCDEFGHIJKLMNOPQRSTUVXYZ' +
'abcdefghijklmnopqrstuvxyz' +
'0123456789'))
for i in range(20) ])
# This gets us better error messages for certain common request errors
app.config['TRAP_BAD_REQUEST_ERRORS'] = True
# new for file upload
app.config['UPLOADS'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 1*1024*1024 # 1 MB
@app.route('/')
def index():
return render_template('base.html')
@app.route('/pic/<nm>')
def pic(nm):
conn = dbi.connect()
curs = dbi.dict_cursor(conn)
numrows = curs.execute(
'''select filename from picfile where nm = %s''',
[nm])
if numrows == 0:
flash('No picture for {}'.format(nm))
return redirect(url_for('index'))
row = curs.fetchone()
return send_from_directory(app.config['UPLOADS'],row['filename'])
@app.route('/pics/')
def pics():
conn = dbi.connect()
curs = dbi.dict_cursor(conn)
curs.execute('''select nm,name,filename
from picfile inner join person using (nm)''')
pics = curs.fetchall()
return render_template('all_pics.html',n=len(pics),pics=pics)
@app.route('/upload/', methods=["GET", "POST"])
def file_upload():
if request.method == 'GET':
return render_template('form.html',src='',nm='')
else:
try:
nm = int(request.form['nm']) # may throw error
f = request.files['pic']
user_filename = f.filename
ext = user_filename.split('.')[-1]
filename = secure_filename('{}.{}'.format(nm,ext))
pathname = os.path.join(app.config['UPLOADS'],filename)
f.save(pathname)
conn = dbi.connect()
curs = dbi.dict_cursor(conn)
curs.execute(
'''insert into picfile(nm,filename) values (%s,%s)
on duplicate key update filename = %s''',
[nm, filename, filename])
conn.commit()
flash('Upload successful')
# decided to just re-render the form (rather than use
# post-redirect-get) so that we can display the uploaded
# picture.
return render_template('form.html',
src=url_for('pic',nm=nm),
nm=nm)
except Exception as err:
flash('Upload failed {why}'.format(why=err))
return render_template('form.html',src='',nm='')
if __name__ == '__main__':
if len(sys.argv) > 1:
# arg, if any, is the desired port number
port = int(sys.argv[1])
assert(port>1024)
else:
port = os.getuid()
dbi.conf() # connect to personal database
app.debug = True
app.run('0.0.0.0',port)
Here's a link to the upload folder.
Quiz Questions¶
More than half are feeling comfortable with this material, which is great, but there's a significant number who aren't.
I'll answer your quiz questions
Optional Demo¶
There's a file upload video in the videos collection. But I will do a live demo today.
cd ~/cs304
source ~/cs304/venv/bin/activate
cp -r ~cs304/pub/downloads/upload/ upload
cd upload
Here are the steps of the demo:
- Activate the venv if you haven't already
- Use the
create-table-picfile.sql
file to create a tablepicfile
that stores the name of the picture file associated with a person (usingnm
) - Use the
insert-picfiles.py
file to insert all the files that are already in theuploads
folder - Run
app.py
and try the links:/pic/<nm>
to see a single picture/pics/
to see all pictures (view source to see the embedded URLs, all generated byurl_for()
!)/upload/
to upload an nm/file pair. Do view source to see the<form>
element.
- Optionally, do
mysql < show-picfiles.sql
in the terminal to see the current contents - Optionally, use the
delete-image.sh
command if you want to delete a particular image (say to re-run an upload). Feel free to read that file to see how it works.
Commands to copy/paste:
mysql < create-table-picfile.sql
python insert-picfiles.py
mysql < show-picfiles.sql
python app.py
Breakouts¶
- You can work on CRUD
- You can work on your project draft
Anonymous Comment¶
I got this yesterday evening from the Anonymous Feedback form:
This is somewhat of a vent, and I apologize in advance because it’ll likely be rather lengthy. I’m really frustrated by the amount of generative AI being used by my classmates, and I’m not really sure where else to unload this, since I don’t want my partners to face consequences. I’ve noticed in my last 2 pair assignments that my partners turn to AI at the first instance of trouble instead of checking older notes on the site or even just googling it. It’s so immensely frustrating for us both to be confused and then notice my partner trying to be subtle about turning their screen away from me as I see ChatGPT open in their tabs. Or when I have asked classmates before for help with assignments and can immediately tell the code they wrote was not written by them, and then feel uncomfortable with their help. I’m sure this is a concern professors have as well and I have no idea how to remedy it, but I’m just so unbelievably tired of working really hard on assignments and having to explain tons of concepts to my partners who won’t take 5 minutes to reread notes. I’m not the tattle tale type, and I really don’t mind helping my partners out or even explaining concepts they don’t understand well. But it’s entirely different when there’s no effort on their end, and it’s evident they just want to get the work done as fast as possible, even if it means neither of us understand what’s going on.
- Thanks for taking the time to send this.
- I truly sympathize with your frustration and fatigue
- I'm having lots of reactions, some emotional and some practical
- I'm still figuring that out
- I'd like to hear from all of you. Maybe not right now, but send me an email over the weekend or use the anonymous feedback form
- We'll talk again on Tuesday.
Summary¶
- Provide file upload if it's useful and relevant
- Worry about security, but don't panic