Warning: Can't synchronize with repository "(default)" (Unsupported version control system "svn": No module named svn). Look in the Trac log for more information.

Changes between Version 8 and Version 9 of FileUploadTutorial


Ignore:
Timestamp:
06/29/07 10:17:53 (12 years ago)
Author:
Chris Arndt
Comment:

migration notice

Legend:

Unmodified
Added
Removed
Modified
  • FileUploadTutorial

    v8 v9  
    1 = File Upload Tutorial = 
    2 TurboGears 0.8a3 
    3  
    4 This is a follow up to the existing 20 minute wiki: http://turbogears.org/docs/wiki20/index.html. At this point, it is assumed that you have: 
    5  * installed TG 
    6  * completed the previous tutorial 
    7  * are ready to add to that tutorial 
    8  
    9 Here is what this tutorial is all about: File Uploads. After completing the above tutorial, I thought, 'gee, it'd be cool if I could upload files to the wiki'. And the way things go, one thing leads to another, and I wanted to download them, too. This introduces some issues, like: 
    10  * ui for displaying the files (we'll use templates for all the common stuff) 
    11  * ui for uploading/downloading the files 
    12  * state issues : we don't want to upload a file to a page that doesn't exist yet 
    13  
    14 Rather than stepwise you thru my thought process and re-build this app like a real tutorial, I'll present the major issues along with the major chunks of code to address them.  
    15  
    16 BTW - you don't have to copy and paste from this page, there are zip files attached...just grab the real thing, amigo. 
    17  
    18 -Todd 
    19  
    20 == Update the DB Tables == 
    21 We need to update the database to add a new table, the uploaded files table. This is a many to many relationship in my example. We'll have to drop our old tables and re-create these for the relationships to match. Here we go: 
    22  
    23  1. stop the server 
    24  1. drop the existing tables 
    25    * tg-admin sql drop page 
    26    * tg-admin sql drop uploadedfile 
    27  1. your new model.py 
    28  {{{ 
    29 from sqlobject import * 
    30 from turbogears.database import PackageHub 
    31  
    32 hub = PackageHub("toddswiki") 
    33 __connection__ = hub 
    34  
    35 # class YourDataClass(SQLObject): 
    36 #     pass 
    37  
    38 class Page(SQLObject): 
    39         pagename = StringCol(alternateID=True, length=30) 
    40         data = StringCol() 
    41         attached_files = RelatedJoin('UploadedFile') 
    42  
    43 class UploadedFile(SQLObject): 
    44         filename = StringCol(alternateID=True, length=100) 
    45         abspath = StringCol() 
    46         size = IntCol() 
    47         referenced_in_pages = RelatedJoin('Page') 
     1{{{ 
     2#!rst 
     3.. note:: This page has been migrated to http://docs.turbogears.org/1.0/FileUploadTutorial. 
    484}}} 
    49  1. create the new tables 
    50    * tg-admin sql create 
    51  
    52 == Add the 'upload' Method == 
    53 Here is the meat and potatoes. We add the upload method and a bunch of state logic. The idea is simple: we want to upload files and have them relate to a page. If a user uploads a previously uploaded file, we skip the upload, but we add the relationship to the db. So, multiple pages can reference a single uploaded file. And a single page can reference many files. Let's store these files in an app configured location: 
    54  
    55  1. Add upload directory to the cfg file 
    56  #def.cfg 
    57  {{{ 
    58 [global] 
    59  
    60 # Upload dir 
    61 wiki.uploads="./uploads" 
    62 }}} 
    63  1. Add upload dir check to controller, create it if nec. 
    64  #controller.py 
    65  {{{ 
    66 #default upload dir to ./uploads 
    67 UPLOAD_DIR = cherrypy.config.get("wiki.uploads", os.path.join(os.getcwd(),"uploads")) 
    68 if not os.path.exists(UPLOAD_DIR): 
    69     os.makedirs(UPLOAD_DIR)  
    70 }}} 
    71  1. Add the 'upload' method (remember meat and potatoes) 
    72  #controller.py 
    73  {{{ 
    74         @turbogears.expose() 
    75         def upload(self, upload_file, pagename, new, **keywords): 
    76                 try: 
    77                         p = Page.byPagename(pagename) 
    78                 except SQLObjectNotFound: 
    79                         turbogears.flash("Must save page first") 
    80                         raise cherrypy.HTTPRedirect(turbogears.url("/%s" % pagename)) 
    81                  
    82                 data = upload_file.file.read() 
    83                 target_file_name = os.path.join(os.getcwd(),UPLOAD_DIR,upload_file.filename) 
    84                 try: 
    85                         u =  UploadedFile.byFilename(upload_file.filename) 
    86                         turbogears.flash("File already uploaded: %s is already at %s" %  (upload_file.filename, target_file_name)) 
    87                 except SQLObjectNotFound: 
    88                         #The Parameter 'w' here should change to 'wb' when you Upload Pictures on Windows 
    89                         # because 'wb' means binary. If you use 'w' there whould be earlier then expected an error. 
    90                         # 'w' conveft linefeeds to Windows-Style and the images are broken-down! 
    91                         f = open(target_file_name, 'w') 
    92                         f.write(data) 
    93                         f.close() 
    94                         turbogears.flash("File uploaded successfully: %s saved as : %s" % (upload_file.filename, target_file_name)) 
    95                         u = UploadedFile(filename=upload_file.filename, abspath=target_file_name, size=0) 
    96                          
    97                 Page.byPagename(pagename).addUploadedFile(u) 
    98                 raise cherrypy.HTTPRedirect(turbogears.url("/%s" % pagename)) 
    99 }}} 
    100  1. Note: the above uses the db to determine the page state. If the page doesn't exist in the db, then we cannot relate an uploaded file to it: 
    101  {{{ 
    102                 try: 
    103                         p = Page.byPagename(pagename) 
    104                 except SQLObjectNotFound: 
    105                         turbogears.flash("Must save page first") 
    106                         raise cherrypy.HTTPRedirect(turbogears.url("/%s" % pagename)) 
    107 }}} 
    108  
    109 == Add 'upload' to the page.kid == 
    110  1. Here's the form that calls the upload method (note we use a 'post'). Pretty simple. I have the hidden variable 'new' in here b/c I was going to use that for state tracking in the same way that the original tutorial did. For some reason, this wasn't working for me so I use the database instead, see above comment. 
    111  #page.kid 
    112  {{{ 
    113         <form action="upload" method="post" enctype="multipart/form-data"> 
    114                 <input type="hidden" name="pagename" py:attrs="value=pagename"/> 
    115                 <input type="hidden" name="new" value="${new}"/> 
    116                 filename: <input type="file" name="upload_file"/><br/> 
    117                 <input type="submit" name="submit_upload" value="Upload"/> 
    118         </form> 
    119 }}} 
    120  
    121 == Add the goo to display the file listings == 
    122  1. Here is the code: 
    123  {{{ 
    124         <ul> 
    125                 Attached Files: 
    126                 <li py:for="filename in uploads"><a href="/download?filename=${filename}" py:content="filename">Filenamehere.</a></li> 
    127         </ul> 
    128 }}} 
    129  1. Add this code to page.kid and edit.kid  
    130  1. I've appended to this tutorial my notes for generalizing this with templates (see below) 
    131  
    132 == Add the 'download' method == 
    133  1. cherrypy.lib.cptools.serveFile makes this super simple: 
    134  #controller.py 
    135  {{{ 
    136         @turbogears.expose() 
    137         def download(self, filename): 
    138                 uf = UploadedFile.byFilename(filename) 
    139                 return cherrypy.lib.cptools.serveFile(uf.abspath, "application/x-download", "attachment", uf.filename) 
    140 }}} 
    141  
    142 == Add error handling to the index method, just in case publish parts barfs == 
    143  #controller.py 
    144  {{{ 
    145         @turbogears.expose(html="toddswiki.templates.page") 
    146         def index(self, pagename="FrontPage"): 
    147                 try: 
    148                         page = Page.byPagename(pagename) 
    149                         uploads = [item.filename for item in page.attached_files] 
    150                 except SQLObjectNotFound: 
    151                         raise cherrypy.HTTPRedirect(turbogears.url("/notfound",pagename=pagename)) 
    152                 try: 
    153                         content = publish_parts(page.data, writer_name="html")["html_body"] 
    154                 except: 
    155                         content = page.data 
    156  
    157                 root = str(turbogears.url("/")) 
    158                 content = wikiwords.sub(r'<a href="%s\1">\1</a>' % root, content) 
    159                 content = content.encode("utf8") 
    160                 return dict(data=content, pagename=page.pagename, uploads=uploads) 
    161 }}} 
    162  
    163 == Generalize the File listing with Kid Templates (inheritance/matching) == 
    164 Make the following changes to get this working: 
    165  
    166 #master.kid 
    167 {{{ 
    168 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    169 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
    170 <?python import sitetemplate ?> 
    171 <html 
    172 xmlns="http://www.w3.org/1999/xhtml"xmlns:py="http://purl.org/kid/ns#" 
    173 py:extends="sitetemplate, 'includes.kid'" 
    174 }}} 
    175  
    176 #page.kid / edit.kid 
    177 {{{ 
    178         <div py:replace="attached_files('${filename}')"/> 
    179 }}} 
    180  
    181 #includes.kid 
    182 {{{ 
    183 <ul py:def="attached_files(filename)"> 
    184                 Attached Files: 
    185                 <li py:for="filename in uploads"><a 
    186 href="./download?filename=${filename}" 
    187 py:content="filename">Filenamehere.</a></li> 
    188 </ul>  
    189 }}} 
    190  
    191 For more details, see: 
    192  * http://kid.lesscode.org/language.html#template-reuse-py-extends 
    193  * http://lesscode.org/projects/kid/wiki/IncludeSectionFromOtherTemplateRecipe