diff options
Diffstat (limited to 'cherrypy/tutorial')
-rw-r--r-- | cherrypy/tutorial/README.txt | 16 | ||||
-rwxr-xr-x | cherrypy/tutorial/__init__.py | 3 | ||||
-rwxr-xr-x | cherrypy/tutorial/bonus-sqlobject.py | 168 | ||||
-rw-r--r-- | cherrypy/tutorial/custom_error.html | 14 | ||||
-rw-r--r-- | cherrypy/tutorial/pdf_file.pdf | bin | 0 -> 85698 bytes | |||
-rwxr-xr-x | cherrypy/tutorial/tut01_helloworld.py | 35 | ||||
-rwxr-xr-x | cherrypy/tutorial/tut02_expose_methods.py | 32 | ||||
-rwxr-xr-x | cherrypy/tutorial/tut03_get_and_post.py | 53 | ||||
-rwxr-xr-x | cherrypy/tutorial/tut04_complex_site.py | 98 | ||||
-rwxr-xr-x | cherrypy/tutorial/tut05_derived_objects.py | 83 | ||||
-rwxr-xr-x | cherrypy/tutorial/tut06_default_method.py | 64 | ||||
-rwxr-xr-x | cherrypy/tutorial/tut07_sessions.py | 44 | ||||
-rwxr-xr-x | cherrypy/tutorial/tut08_generators_and_yield.py | 47 | ||||
-rwxr-xr-x | cherrypy/tutorial/tut09_files.py | 107 | ||||
-rwxr-xr-x | cherrypy/tutorial/tut10_http_errors.py | 81 | ||||
-rw-r--r-- | cherrypy/tutorial/tutorial.conf | 4 |
16 files changed, 849 insertions, 0 deletions
diff --git a/cherrypy/tutorial/README.txt b/cherrypy/tutorial/README.txt new file mode 100644 index 0000000..2b877e1 --- /dev/null +++ b/cherrypy/tutorial/README.txt @@ -0,0 +1,16 @@ +CherryPy Tutorials
+------------------------------------------------------------------------
+
+This is a series of tutorials explaining how to develop dynamic web
+applications using CherryPy. A couple of notes:
+
+ - Each of these tutorials builds on the ones before it. If you're
+ new to CherryPy, we recommend you start with 01_helloworld.py and
+ work your way upwards. :)
+
+ - In most of these tutorials, you will notice that all output is done
+ by returning normal Python strings, often using simple Python
+ variable substitution. In most real-world applications, you will
+ probably want to use a separate template package (like Cheetah,
+ CherryTemplate or XML/XSL).
+
diff --git a/cherrypy/tutorial/__init__.py b/cherrypy/tutorial/__init__.py new file mode 100755 index 0000000..c4e2c55 --- /dev/null +++ b/cherrypy/tutorial/__init__.py @@ -0,0 +1,3 @@ + +# This is used in test_config to test unrepr of "from A import B" +thing2 = object()
\ No newline at end of file diff --git a/cherrypy/tutorial/bonus-sqlobject.py b/cherrypy/tutorial/bonus-sqlobject.py new file mode 100755 index 0000000..c43feb4 --- /dev/null +++ b/cherrypy/tutorial/bonus-sqlobject.py @@ -0,0 +1,168 @@ +''' +Bonus Tutorial: Using SQLObject + +This is a silly little contacts manager application intended to +demonstrate how to use SQLObject from within a CherryPy2 project. It +also shows how to use inline Cheetah templates. + +SQLObject is an Object/Relational Mapper that allows you to access +data stored in an RDBMS in a pythonic fashion. You create data objects +as Python classes and let SQLObject take care of all the nasty details. + +This code depends on the latest development version (0.6+) of SQLObject. +You can get it from the SQLObject Subversion server. You can find all +necessary information at <http://www.sqlobject.org>. This code will NOT +work with the 0.5.x version advertised on their website! + +This code also depends on a recent version of Cheetah. You can find +Cheetah at <http://www.cheetahtemplate.org>. + +After starting this application for the first time, you will need to +access the /reset URI in order to create the database table and some +sample data. Accessing /reset again will drop and re-create the table, +so you may want to be careful. :-) + +This application isn't supposed to be fool-proof, it's not even supposed +to be very GOOD. Play around with it some, browse the source code, smile. + +:) + +-- Hendrik Mans <hendrik@mans.de> +''' + +import cherrypy +from Cheetah.Template import Template +from sqlobject import * + +# configure your database connection here +__connection__ = 'mysql://root:@localhost/test' + +# this is our (only) data class. +class Contact(SQLObject): + lastName = StringCol(length = 50, notNone = True) + firstName = StringCol(length = 50, notNone = True) + phone = StringCol(length = 30, notNone = True, default = '') + email = StringCol(length = 30, notNone = True, default = '') + url = StringCol(length = 100, notNone = True, default = '') + + +class ContactManager: + def index(self): + # Let's display a list of all stored contacts. + contacts = Contact.select() + + template = Template(''' + <h2>All Contacts</h2> + + #for $contact in $contacts + <a href="mailto:$contact.email">$contact.lastName, $contact.firstName</a> + [<a href="./edit?id=$contact.id">Edit</a>] + [<a href="./delete?id=$contact.id">Delete</a>] + <br/> + #end for + + <p>[<a href="./edit">Add new contact</a>]</p> + ''', [locals(), globals()]) + + return template.respond() + + index.exposed = True + + + def edit(self, id = 0): + # we really want id as an integer. Since GET/POST parameters + # are always passed as strings, let's convert it. + id = int(id) + + if id > 0: + # if an id is specified, we're editing an existing contact. + contact = Contact.get(id) + title = "Edit Contact" + else: + # if no id is specified, we're entering a new contact. + contact = None + title = "New Contact" + + + # In the following template code, please note that we use + # Cheetah's $getVar() construct for the form values. We have + # to do this because contact may be set to None (see above). + template = Template(''' + <h2>$title</h2> + + <form action="./store" method="POST"> + <input type="hidden" name="id" value="$id" /> + Last Name: <input name="lastName" value="$getVar('contact.lastName', '')" /><br/> + First Name: <input name="firstName" value="$getVar('contact.firstName', '')" /><br/> + Phone: <input name="phone" value="$getVar('contact.phone', '')" /><br/> + Email: <input name="email" value="$getVar('contact.email', '')" /><br/> + URL: <input name="url" value="$getVar('contact.url', '')" /><br/> + <input type="submit" value="Store" /> + </form> + ''', [locals(), globals()]) + + return template.respond() + + edit.exposed = True + + + def delete(self, id): + # Delete the specified contact + contact = Contact.get(int(id)) + contact.destroySelf() + return 'Deleted. <a href="./">Return to Index</a>' + + delete.exposed = True + + + def store(self, lastName, firstName, phone, email, url, id = None): + if id and int(id) > 0: + # If an id was specified, update an existing contact. + contact = Contact.get(int(id)) + + # We could set one field after another, but that would + # cause multiple UPDATE clauses. So we'll just do it all + # in a single pass through the set() method. + contact.set( + lastName = lastName, + firstName = firstName, + phone = phone, + email = email, + url = url) + else: + # Otherwise, add a new contact. + contact = Contact( + lastName = lastName, + firstName = firstName, + phone = phone, + email = email, + url = url) + + return 'Stored. <a href="./">Return to Index</a>' + + store.exposed = True + + + def reset(self): + # Drop existing table + Contact.dropTable(True) + + # Create new table + Contact.createTable() + + # Create some sample data + Contact( + firstName = 'Hendrik', + lastName = 'Mans', + email = 'hendrik@mans.de', + phone = '++49 89 12345678', + url = 'http://www.mornography.de') + + return "reset completed!" + + reset.exposed = True + + +print("If you're running this application for the first time, please go to http://localhost:8080/reset once in order to create the database!") + +cherrypy.quickstart(ContactManager()) diff --git a/cherrypy/tutorial/custom_error.html b/cherrypy/tutorial/custom_error.html new file mode 100644 index 0000000..d0f30c8 --- /dev/null +++ b/cherrypy/tutorial/custom_error.html @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<head> + <title>403 Unauthorized</title> +</head> + <body> + <h2>You can't do that!</h2> + <p>%(message)s</p> + <p>This is a custom error page that is read from a file.<p> + <pre>%(traceback)s</pre> + </body> +</html> diff --git a/cherrypy/tutorial/pdf_file.pdf b/cherrypy/tutorial/pdf_file.pdf Binary files differnew file mode 100644 index 0000000..38b4f15 --- /dev/null +++ b/cherrypy/tutorial/pdf_file.pdf diff --git a/cherrypy/tutorial/tut01_helloworld.py b/cherrypy/tutorial/tut01_helloworld.py new file mode 100755 index 0000000..ef94760 --- /dev/null +++ b/cherrypy/tutorial/tut01_helloworld.py @@ -0,0 +1,35 @@ +""" +Tutorial - Hello World + +The most basic (working) CherryPy application possible. +""" + +# Import CherryPy global namespace +import cherrypy + +class HelloWorld: + """ Sample request handler class. """ + + def index(self): + # CherryPy will call this method for the root URI ("/") and send + # its return value to the client. Because this is tutorial + # lesson number 01, we'll just send something really simple. + # How about... + return "Hello world!" + + # Expose the index method through the web. CherryPy will never + # publish methods that don't have the exposed attribute set to True. + index.exposed = True + + +import os.path +tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf') + +if __name__ == '__main__': + # CherryPy always starts with app.root when trying to map request URIs + # to objects, so we need to mount a request handler root. A request + # to '/' will be mapped to HelloWorld().index(). + cherrypy.quickstart(HelloWorld(), config=tutconf) +else: + # This branch is for the test suite; you can ignore it. + cherrypy.tree.mount(HelloWorld(), config=tutconf) diff --git a/cherrypy/tutorial/tut02_expose_methods.py b/cherrypy/tutorial/tut02_expose_methods.py new file mode 100755 index 0000000..600fca3 --- /dev/null +++ b/cherrypy/tutorial/tut02_expose_methods.py @@ -0,0 +1,32 @@ +""" +Tutorial - Multiple methods + +This tutorial shows you how to link to other methods of your request +handler. +""" + +import cherrypy + +class HelloWorld: + + def index(self): + # Let's link to another method here. + return 'We have an <a href="showMessage">important message</a> for you!' + index.exposed = True + + def showMessage(self): + # Here's the important message! + return "Hello world!" + showMessage.exposed = True + +import os.path +tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf') + +if __name__ == '__main__': + # CherryPy always starts with app.root when trying to map request URIs + # to objects, so we need to mount a request handler root. A request + # to '/' will be mapped to HelloWorld().index(). + cherrypy.quickstart(HelloWorld(), config=tutconf) +else: + # This branch is for the test suite; you can ignore it. + cherrypy.tree.mount(HelloWorld(), config=tutconf) diff --git a/cherrypy/tutorial/tut03_get_and_post.py b/cherrypy/tutorial/tut03_get_and_post.py new file mode 100755 index 0000000..283477d --- /dev/null +++ b/cherrypy/tutorial/tut03_get_and_post.py @@ -0,0 +1,53 @@ +""" +Tutorial - Passing variables + +This tutorial shows you how to pass GET/POST variables to methods. +""" + +import cherrypy + + +class WelcomePage: + + def index(self): + # Ask for the user's name. + return ''' + <form action="greetUser" method="GET"> + What is your name? + <input type="text" name="name" /> + <input type="submit" /> + </form>''' + index.exposed = True + + def greetUser(self, name = None): + # CherryPy passes all GET and POST variables as method parameters. + # It doesn't make a difference where the variables come from, how + # large their contents are, and so on. + # + # You can define default parameter values as usual. In this + # example, the "name" parameter defaults to None so we can check + # if a name was actually specified. + + if name: + # Greet the user! + return "Hey %s, what's up?" % name + else: + if name is None: + # No name was specified + return 'Please enter your name <a href="./">here</a>.' + else: + return 'No, really, enter your name <a href="./">here</a>.' + greetUser.exposed = True + + +import os.path +tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf') + +if __name__ == '__main__': + # CherryPy always starts with app.root when trying to map request URIs + # to objects, so we need to mount a request handler root. A request + # to '/' will be mapped to HelloWorld().index(). + cherrypy.quickstart(WelcomePage(), config=tutconf) +else: + # This branch is for the test suite; you can ignore it. + cherrypy.tree.mount(WelcomePage(), config=tutconf) diff --git a/cherrypy/tutorial/tut04_complex_site.py b/cherrypy/tutorial/tut04_complex_site.py new file mode 100755 index 0000000..b4d820e --- /dev/null +++ b/cherrypy/tutorial/tut04_complex_site.py @@ -0,0 +1,98 @@ +""" +Tutorial - Multiple objects + +This tutorial shows you how to create a site structure through multiple +possibly nested request handler objects. +""" + +import cherrypy + + +class HomePage: + def index(self): + return ''' + <p>Hi, this is the home page! Check out the other + fun stuff on this site:</p> + + <ul> + <li><a href="/joke/">A silly joke</a></li> + <li><a href="/links/">Useful links</a></li> + </ul>''' + index.exposed = True + + +class JokePage: + def index(self): + return ''' + <p>"In Python, how do you create a string of random + characters?" -- "Read a Perl file!"</p> + <p>[<a href="../">Return</a>]</p>''' + index.exposed = True + + +class LinksPage: + def __init__(self): + # Request handler objects can create their own nested request + # handler objects. Simply create them inside their __init__ + # methods! + self.extra = ExtraLinksPage() + + def index(self): + # Note the way we link to the extra links page (and back). + # As you can see, this object doesn't really care about its + # absolute position in the site tree, since we use relative + # links exclusively. + return ''' + <p>Here are some useful links:</p> + + <ul> + <li><a href="http://www.cherrypy.org">The CherryPy Homepage</a></li> + <li><a href="http://www.python.org">The Python Homepage</a></li> + </ul> + + <p>You can check out some extra useful + links <a href="./extra/">here</a>.</p> + + <p>[<a href="../">Return</a>]</p> + ''' + index.exposed = True + + +class ExtraLinksPage: + def index(self): + # Note the relative link back to the Links page! + return ''' + <p>Here are some extra useful links:</p> + + <ul> + <li><a href="http://del.icio.us">del.icio.us</a></li> + <li><a href="http://www.mornography.de">Hendrik's weblog</a></li> + </ul> + + <p>[<a href="../">Return to links page</a>]</p>''' + index.exposed = True + + +# Of course we can also mount request handler objects right here! +root = HomePage() +root.joke = JokePage() +root.links = LinksPage() + +# Remember, we don't need to mount ExtraLinksPage here, because +# LinksPage does that itself on initialization. In fact, there is +# no reason why you shouldn't let your root object take care of +# creating all contained request handler objects. + + +import os.path +tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf') + +if __name__ == '__main__': + # CherryPy always starts with app.root when trying to map request URIs + # to objects, so we need to mount a request handler root. A request + # to '/' will be mapped to HelloWorld().index(). + cherrypy.quickstart(root, config=tutconf) +else: + # This branch is for the test suite; you can ignore it. + cherrypy.tree.mount(root, config=tutconf) + diff --git a/cherrypy/tutorial/tut05_derived_objects.py b/cherrypy/tutorial/tut05_derived_objects.py new file mode 100755 index 0000000..3d4ec9b --- /dev/null +++ b/cherrypy/tutorial/tut05_derived_objects.py @@ -0,0 +1,83 @@ +""" +Tutorial - Object inheritance + +You are free to derive your request handler classes from any base +class you wish. In most real-world applications, you will probably +want to create a central base class used for all your pages, which takes +care of things like printing a common page header and footer. +""" + +import cherrypy + + +class Page: + # Store the page title in a class attribute + title = 'Untitled Page' + + def header(self): + return ''' + <html> + <head> + <title>%s</title> + <head> + <body> + <h2>%s</h2> + ''' % (self.title, self.title) + + def footer(self): + return ''' + </body> + </html> + ''' + + # Note that header and footer don't get their exposed attributes + # set to True. This isn't necessary since the user isn't supposed + # to call header or footer directly; instead, we'll call them from + # within the actually exposed handler methods defined in this + # class' subclasses. + + +class HomePage(Page): + # Different title for this page + title = 'Tutorial 5' + + def __init__(self): + # create a subpage + self.another = AnotherPage() + + def index(self): + # Note that we call the header and footer methods inherited + # from the Page class! + return self.header() + ''' + <p> + Isn't this exciting? There's + <a href="./another/">another page</a>, too! + </p> + ''' + self.footer() + index.exposed = True + + +class AnotherPage(Page): + title = 'Another Page' + + def index(self): + return self.header() + ''' + <p> + And this is the amazing second page! + </p> + ''' + self.footer() + index.exposed = True + + +import os.path +tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf') + +if __name__ == '__main__': + # CherryPy always starts with app.root when trying to map request URIs + # to objects, so we need to mount a request handler root. A request + # to '/' will be mapped to HelloWorld().index(). + cherrypy.quickstart(HomePage(), config=tutconf) +else: + # This branch is for the test suite; you can ignore it. + cherrypy.tree.mount(HomePage(), config=tutconf) + diff --git a/cherrypy/tutorial/tut06_default_method.py b/cherrypy/tutorial/tut06_default_method.py new file mode 100755 index 0000000..fe24f38 --- /dev/null +++ b/cherrypy/tutorial/tut06_default_method.py @@ -0,0 +1,64 @@ +""" +Tutorial - The default method + +Request handler objects can implement a method called "default" that +is called when no other suitable method/object could be found. +Essentially, if CherryPy2 can't find a matching request handler object +for the given request URI, it will use the default method of the object +located deepest on the URI path. + +Using this mechanism you can easily simulate virtual URI structures +by parsing the extra URI string, which you can access through +cherrypy.request.virtualPath. + +The application in this tutorial simulates an URI structure looking +like /users/<username>. Since the <username> bit will not be found (as +there are no matching methods), it is handled by the default method. +""" + +import cherrypy + + +class UsersPage: + + def index(self): + # Since this is just a stupid little example, we'll simply + # display a list of links to random, made-up users. In a real + # application, this could be generated from a database result set. + return ''' + <a href="./remi">Remi Delon</a><br/> + <a href="./hendrik">Hendrik Mans</a><br/> + <a href="./lorenzo">Lorenzo Lamas</a><br/> + ''' + index.exposed = True + + def default(self, user): + # Here we react depending on the virtualPath -- the part of the + # path that could not be mapped to an object method. In a real + # application, we would probably do some database lookups here + # instead of the silly if/elif/else construct. + if user == 'remi': + out = "Remi Delon, CherryPy lead developer" + elif user == 'hendrik': + out = "Hendrik Mans, CherryPy co-developer & crazy German" + elif user == 'lorenzo': + out = "Lorenzo Lamas, famous actor and singer!" + else: + out = "Unknown user. :-(" + + return '%s (<a href="./">back</a>)' % out + default.exposed = True + + +import os.path +tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf') + +if __name__ == '__main__': + # CherryPy always starts with app.root when trying to map request URIs + # to objects, so we need to mount a request handler root. A request + # to '/' will be mapped to HelloWorld().index(). + cherrypy.quickstart(UsersPage(), config=tutconf) +else: + # This branch is for the test suite; you can ignore it. + cherrypy.tree.mount(UsersPage(), config=tutconf) + diff --git a/cherrypy/tutorial/tut07_sessions.py b/cherrypy/tutorial/tut07_sessions.py new file mode 100755 index 0000000..4b1386b --- /dev/null +++ b/cherrypy/tutorial/tut07_sessions.py @@ -0,0 +1,44 @@ +""" +Tutorial - Sessions + +Storing session data in CherryPy applications is very easy: cherrypy +provides a dictionary called "session" that represents the session +data for the current user. If you use RAM based sessions, you can store +any kind of object into that dictionary; otherwise, you are limited to +objects that can be pickled. +""" + +import cherrypy + + +class HitCounter: + + _cp_config = {'tools.sessions.on': True} + + def index(self): + # Increase the silly hit counter + count = cherrypy.session.get('count', 0) + 1 + + # Store the new value in the session dictionary + cherrypy.session['count'] = count + + # And display a silly hit count message! + return ''' + During your current session, you've viewed this + page %s times! Your life is a patio of fun! + ''' % count + index.exposed = True + + +import os.path +tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf') + +if __name__ == '__main__': + # CherryPy always starts with app.root when trying to map request URIs + # to objects, so we need to mount a request handler root. A request + # to '/' will be mapped to HelloWorld().index(). + cherrypy.quickstart(HitCounter(), config=tutconf) +else: + # This branch is for the test suite; you can ignore it. + cherrypy.tree.mount(HitCounter(), config=tutconf) + diff --git a/cherrypy/tutorial/tut08_generators_and_yield.py b/cherrypy/tutorial/tut08_generators_and_yield.py new file mode 100755 index 0000000..a6fbdc2 --- /dev/null +++ b/cherrypy/tutorial/tut08_generators_and_yield.py @@ -0,0 +1,47 @@ +""" +Bonus Tutorial: Using generators to return result bodies + +Instead of returning a complete result string, you can use the yield +statement to return one result part after another. This may be convenient +in situations where using a template package like CherryPy or Cheetah +would be overkill, and messy string concatenation too uncool. ;-) +""" + +import cherrypy + + +class GeneratorDemo: + + def header(self): + return "<html><body><h2>Generators rule!</h2>" + + def footer(self): + return "</body></html>" + + def index(self): + # Let's make up a list of users for presentation purposes + users = ['Remi', 'Carlos', 'Hendrik', 'Lorenzo Lamas'] + + # Every yield line adds one part to the total result body. + yield self.header() + yield "<h3>List of users:</h3>" + + for user in users: + yield "%s<br/>" % user + + yield self.footer() + index.exposed = True + + +import os.path +tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf') + +if __name__ == '__main__': + # CherryPy always starts with app.root when trying to map request URIs + # to objects, so we need to mount a request handler root. A request + # to '/' will be mapped to HelloWorld().index(). + cherrypy.quickstart(GeneratorDemo(), config=tutconf) +else: + # This branch is for the test suite; you can ignore it. + cherrypy.tree.mount(GeneratorDemo(), config=tutconf) + diff --git a/cherrypy/tutorial/tut09_files.py b/cherrypy/tutorial/tut09_files.py new file mode 100755 index 0000000..4c8e581 --- /dev/null +++ b/cherrypy/tutorial/tut09_files.py @@ -0,0 +1,107 @@ +""" + +Tutorial: File upload and download + +Uploads +------- + +When a client uploads a file to a CherryPy application, it's placed +on disk immediately. CherryPy will pass it to your exposed method +as an argument (see "myFile" below); that arg will have a "file" +attribute, which is a handle to the temporary uploaded file. +If you wish to permanently save the file, you need to read() +from myFile.file and write() somewhere else. + +Note the use of 'enctype="multipart/form-data"' and 'input type="file"' +in the HTML which the client uses to upload the file. + + +Downloads +--------- + +If you wish to send a file to the client, you have two options: +First, you can simply return a file-like object from your page handler. +CherryPy will read the file and serve it as the content (HTTP body) +of the response. However, that doesn't tell the client that +the response is a file to be saved, rather than displayed. +Use cherrypy.lib.static.serve_file for that; it takes four +arguments: + +serve_file(path, content_type=None, disposition=None, name=None) + +Set "name" to the filename that you expect clients to use when they save +your file. Note that the "name" argument is ignored if you don't also +provide a "disposition" (usually "attachement"). You can manually set +"content_type", but be aware that if you also use the encoding tool, it +may choke if the file extension is not recognized as belonging to a known +Content-Type. Setting the content_type to "application/x-download" works +in most cases, and should prompt the user with an Open/Save dialog in +popular browsers. + +""" + +import os +localDir = os.path.dirname(__file__) +absDir = os.path.join(os.getcwd(), localDir) + +import cherrypy +from cherrypy.lib import static + + +class FileDemo(object): + + def index(self): + return """ + <html><body> + <h2>Upload a file</h2> + <form action="upload" method="post" enctype="multipart/form-data"> + filename: <input type="file" name="myFile" /><br /> + <input type="submit" /> + </form> + <h2>Download a file</h2> + <a href='download'>This one</a> + </body></html> + """ + index.exposed = True + + def upload(self, myFile): + out = """<html> + <body> + myFile length: %s<br /> + myFile filename: %s<br /> + myFile mime-type: %s + </body> + </html>""" + + # Although this just counts the file length, it demonstrates + # how to read large files in chunks instead of all at once. + # CherryPy reads the uploaded file into a temporary file; + # myFile.file.read reads from that. + size = 0 + while True: + data = myFile.file.read(8192) + if not data: + break + size += len(data) + + return out % (size, myFile.filename, myFile.content_type) + upload.exposed = True + + def download(self): + path = os.path.join(absDir, "pdf_file.pdf") + return static.serve_file(path, "application/x-download", + "attachment", os.path.basename(path)) + download.exposed = True + + +import os.path +tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf') + +if __name__ == '__main__': + # CherryPy always starts with app.root when trying to map request URIs + # to objects, so we need to mount a request handler root. A request + # to '/' will be mapped to HelloWorld().index(). + cherrypy.quickstart(FileDemo(), config=tutconf) +else: + # This branch is for the test suite; you can ignore it. + cherrypy.tree.mount(FileDemo(), config=tutconf) diff --git a/cherrypy/tutorial/tut10_http_errors.py b/cherrypy/tutorial/tut10_http_errors.py new file mode 100755 index 0000000..dfa5733 --- /dev/null +++ b/cherrypy/tutorial/tut10_http_errors.py @@ -0,0 +1,81 @@ +""" + +Tutorial: HTTP errors + +HTTPError is used to return an error response to the client. +CherryPy has lots of options regarding how such errors are +logged, displayed, and formatted. + +""" + +import os +localDir = os.path.dirname(__file__) +curpath = os.path.normpath(os.path.join(os.getcwd(), localDir)) + +import cherrypy + + +class HTTPErrorDemo(object): + + # Set a custom response for 403 errors. + _cp_config = {'error_page.403' : os.path.join(curpath, "custom_error.html")} + + def index(self): + # display some links that will result in errors + tracebacks = cherrypy.request.show_tracebacks + if tracebacks: + trace = 'off' + else: + trace = 'on' + + return """ + <html><body> + <p>Toggle tracebacks <a href="toggleTracebacks">%s</a></p> + <p><a href="/doesNotExist">Click me; I'm a broken link!</a></p> + <p><a href="/error?code=403">Use a custom error page from a file.</a></p> + <p>These errors are explicitly raised by the application:</p> + <ul> + <li><a href="/error?code=400">400</a></li> + <li><a href="/error?code=401">401</a></li> + <li><a href="/error?code=402">402</a></li> + <li><a href="/error?code=500">500</a></li> + </ul> + <p><a href="/messageArg">You can also set the response body + when you raise an error.</a></p> + </body></html> + """ % trace + index.exposed = True + + def toggleTracebacks(self): + # simple function to toggle tracebacks on and off + tracebacks = cherrypy.request.show_tracebacks + cherrypy.config.update({'request.show_tracebacks': not tracebacks}) + + # redirect back to the index + raise cherrypy.HTTPRedirect('/') + toggleTracebacks.exposed = True + + def error(self, code): + # raise an error based on the get query + raise cherrypy.HTTPError(status = code) + error.exposed = True + + def messageArg(self): + message = ("If you construct an HTTPError with a 'message' " + "argument, it wil be placed on the error page " + "(underneath the status line by default).") + raise cherrypy.HTTPError(500, message=message) + messageArg.exposed = True + + +import os.path +tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf') + +if __name__ == '__main__': + # CherryPy always starts with app.root when trying to map request URIs + # to objects, so we need to mount a request handler root. A request + # to '/' will be mapped to HelloWorld().index(). + cherrypy.quickstart(HTTPErrorDemo(), config=tutconf) +else: + # This branch is for the test suite; you can ignore it. + cherrypy.tree.mount(HTTPErrorDemo(), config=tutconf) diff --git a/cherrypy/tutorial/tutorial.conf b/cherrypy/tutorial/tutorial.conf new file mode 100644 index 0000000..6537fd3 --- /dev/null +++ b/cherrypy/tutorial/tutorial.conf @@ -0,0 +1,4 @@ +[global]
+server.socket_host = "127.0.0.1"
+server.socket_port = 8080
+server.thread_pool = 10
|