mengu on web programming.

TurboGears File Uploads

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:
Select File:

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. :)
Did you enjoy this post? You should follow me on twitter here.

Comments

V Patel said on 30/04/2010 11:22 AM
Thanks, very useful. However is this method robust for large file sizes? I have large XML files I need to receive as input and so I'm not sure if this is a good method to save the file on the server.

Mengu Kagan said on 30/04/2010 12:44 PM
Hi, Thanks for the feedback. I've got lots of visitors getting what they want from this blog, yet only 1/1500 provide feedback. :) I don't see a problem with keeping the files in the database. However if you don't like this approach you can save the files to your file system instead. So you can create a file with the same name in a directory where you wish to save the uploaded files and then write the content you get to that file.

Maxim said on 06/05/2010 16:42 PM
Briefly, coherently, clear. My compliments! That's what I was looking for. Very useful. May I ask You a question: Is there any way to check for file size before upload started? For example I'd like to forbid uploading image which size is more than one megabyte. Thank You.

Maxim said on 15/05/2010 09:22 AM
Well, that was a stupid question. There is no way except using AJAX. Sorry.

Mengu Kagan said on 15/05/2010 18:11 PM
Hi Maxim, Thank you for the compliments. :) I was thinking on how this can be achieved but as far as i know, there is no way to find out the file size before the user submits it.

Leave a Response

No HTML allowed. You can use markdown.
Name*:
E-Mail* (not published):
Web site:
Response: