TurboGears File Uploads
2010-03-04 08:13:40 | 5 Comments
I have seen people searching for a solution for file uploads with TurboGears. As always, once you have the idea, you can get it all working. I had no idea about what TurboGears sends for a file so I have created a simple upload form. I inspected the result and I saw there is a file attribute which gives me the entire file information and content that I can use. So once I had the way in my mind, I started developing the model.
In the model, all I need was a file name field and a file content field. In TurboGears 2.0.3, you can easily create your models since it is providing you the DeclarativeBase itself which is why you don't need to create tables first and then map them to a class. So I have created my model like this:
# -*- coding: utf-8 -*-
"""Sample model module."""
from sqlalchemy import *
from sqlalchemy.orm import mapper, relation
from sqlalchemy import Table, ForeignKey, Column
from sqlalchemy.types import Integer, Unicode, BLOB
#from sqlalchemy.orm import relation, backref
from fileupload.model import DeclarativeBase, metadata, DBSession
class UserFile(DeclarativeBase):
__tablename__ = 'userfile'
id = Column(Integer, primary_key=True)
filename = Column(Unicode(255), nullable=False)
filecontent = Column(BLOB)
def __init__(self, filename, filecontent):
self.filename = filename
self.filecontent = filecontent
After creating my model, I have written my index and save actions to let the user send a file and save user files to the database.
This is the HTML form for uploading the files:
And these are the controller actions for users to send the file, for system to save the file.
@expose('fileupload.templates.index')
def index(self):
current_files = DBSession.query(UserFile).all()
return dict(current_files=current_files)
@expose()
def save(self, userfile):
forbidden_files = [".js", ".htm", ".html", ".mp3"]
for forbidden_file in forbidden_files:
if userfile.filename.find(forbidden_file) != -1:
return redirect("/")
filecontent = userfile.file.read()
new_file = UserFile(filename=userfile.filename, filecontent=filecontent)
DBSession.add(new_file)
DBSession.flush()
redirect("/view/"+str(new_file.id))
This save action basically checks if the user file is forbidden or not and then according to the result, it saves the file to the database and redirects to that file.
The redirect is to the "view" action in which I should explicitly set the content-type. I also find the file, if there is no file it redirects to the index page, I set the content-type header and display the file content. However since it's not good to display .zip, .rar, .pdf files I thought its better to dispose them as attachments so I have added content-types in a dictionary as display and download. If a file ends with a file name that is in display then display it otherwise user will download it. So here is the view action:
@expose(content_type=CUSTOM_CONTENT_TYPE)
def view(self, fileid):
try:
userfile = DBSession.query(UserFile).filter_by(id=fileid).one()
except:
redirect("/")
content_types = {
'display': {'.png': 'image/jpeg', '.jpeg':'image/jpeg', '.jpg':'image/jpeg', '.gif':'image/jpeg', '.txt': 'text/plain'},
'download': {'.pdf':'application/pdf', '.zip':'application/zip', '.rar':'application/x-rar-compressed'}
}
for file_type in content_types['display']:
if userfile.filename.endswith(file_type):
response.headers["Content-Type"] = content_types['display'][file_type]
for file_type in content_types['download']:
if userfile.filename.endswith(file_type):
response.headers["Content-Type"] = content_types['download'][file_type]
response.headers["Content-Disposition"] = 'attachment; filename="'+userfile.filename+'"'
if userfile.filename.find(".") == -1:
response.headers["Content-Type"] = "text/plain"
return userfile.filecontent
In order to be able to use "CUSTOM_CONTENT_TYPE" I needed to import it from tg.controllers in the header. I also thought it would be good if there was a delete action so here it is:
@expose()
def delete(self, fileid):
try:
userfile = DBSession.query(UserFile).filter_by(id=fileid).one()
except:
return redirect("/")
DBSession.delete(userfile)
return redirect("/")
You can browse the full source at [my github repositories](http://github.com/mengu/turbogears-file-upload) of this simple application. I hope this will be helpful to people who are in need of an example to work with or improve. You can always improve this application. Since I didn't do security work on this, I'd like to hear your ideas how to implement security on this application.
Good luck and don't forget to let me know what you think. :)
Check my latest project compector.com where you can post references about your former employers and see what others have said about the future employers. Sign up now and post a reference and share it on twitter or facebook.


Comments
Leave a Response