├── .gitignore
├── ACKS
├── CHANGES
├── LICENSE
├── MANIFEST
├── MANIFEST.in
├── PKG-INFO
├── README
├── TODO
├── doc
├── INSTALL.html
├── INSTALL.txt
├── Makefile
├── PTL.html
├── PTL.txt
├── ZPL.txt
├── default.css
├── demo.html
├── demo.txt
├── form2conversion.html
├── form2conversion.txt
├── multi-threaded.html
├── multi-threaded.txt
├── programming.html
├── programming.txt
├── session-mgmt.html
├── session-mgmt.txt
├── static-files.html
├── static-files.txt
├── ua_test.py
├── upgrading.html
├── upgrading.txt
├── upload.html
├── upload.txt
├── utest_html.py
├── web-server.html
├── web-server.txt
├── web-services.html
├── web-services.txt
├── widgets.html
└── widgets.txt
├── quixote
├── __init__.py
├── _py_htmltext.py
├── config.py
├── demo
│ ├── __init__.py
│ ├── demo.cgi
│ ├── demo.conf
│ ├── demo_scgi.py
│ ├── demo_scgi.sh
│ ├── forms.ptl
│ ├── integer_ui.py
│ ├── pages.ptl
│ ├── run_cgi.py
│ ├── session.ptl
│ ├── session_demo.cgi
│ ├── upload.cgi
│ └── widgets.ptl
├── errors.py
├── fcgi.py
├── form
│ ├── __init__.py
│ ├── form.py
│ └── widget.py
├── form2
│ ├── __init__.py
│ ├── compatibility.py
│ ├── css.py
│ ├── form.py
│ └── widget.py
├── html.py
├── http_request.py
├── http_response.py
├── ihooks.py
├── mod_python_handler.py
├── ptl_compile.py
├── ptl_import.py
├── ptlc_dump.py
├── publish.py
├── qwip.py
├── qx_distutils.py
├── sendmail.py
├── server
│ ├── __init__.py
│ ├── medusa_http.py
│ └── twisted_http.py
├── session.py
├── upload.py
└── util.py
├── setup.py
├── src
├── Makefile
├── _c_htmltext.c
├── cimport.c
└── setup.py
└── test
├── __init__.py
├── base.py
├── test_config.py
├── test_drop_dup_value.py
├── test_json_request.py
├── test_qwip.py
└── test_upload.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.egg-info/*
2 | *.pyc
3 | *~
4 | build/*
5 | dist/*
6 |
--------------------------------------------------------------------------------
/ACKS:
--------------------------------------------------------------------------------
1 | Acknowledgements
2 | ================
3 |
4 | The Quixote developer team would like to thank everybody who
5 | contributed in any way, with code, hints, bug reports, ideas, moral
6 | support, endorsement, or even complaints. Listed in alphabetical
7 | order:
8 |
9 | David Ascher
10 | Anton Benard
11 | Titus Brown
12 | Oleg Broytmann
13 | David M. Cooke
14 | Jonathan Corbet
15 | Herman Cuppens
16 | Toby Dickenson
17 | Ray Drew
18 | Jim Dukarm
19 | Quinn Dunkan
20 | Robin Dunn (original author of fcgi.py)
21 | Jon Dyte
22 | David Edwards
23 | Graham Fawcett
24 | Jim Fulton (original author of the *Request and *Response classes)
25 | David Goodger
26 | Neal M. Holtz
27 | Kaweh Kazemi
28 | Shahms E. King
29 | A.M. Kuchling
30 | Erno Kuusela
31 | Nicola Larosa
32 | Hamish Lawson
33 | Patrick K. O'Brien
34 | Brendan T O'Connor
35 | Ed Overly
36 | Paul Richardson
37 | Jeff Rush
38 | Neil Schemenauer
39 | Jason Sibre
40 | Gregory P. Smith
41 | Mikhail Sobolev
42 | Johann Visagie
43 | Greg Ward
44 | The whole gang at the Zope Corporation
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | CNRI OPEN SOURCE LICENSE AGREEMENT
2 |
3 | IMPORTANT: PLEASE READ THE FOLLOWING AGREEMENT CAREFULLY. BY
4 | COPYING, INSTALLING OR OTHERWISE USING QUIXOTE-1.2 SOFTWARE, YOU
5 | ARE DEEMED TO HAVE AGREED TO THE TERMS AND CONDITIONS OF THIS
6 | LICENSE AGREEMENT.
7 |
8 | 1. This LICENSE AGREEMENT is between Corporation for National
9 | Research Initiatives, having an office at 1895 Preston White
10 | Drive, Reston, VA 20191 ("CNRI"), and the Individual or
11 | Organization ("Licensee") copying, installing or otherwise using
12 | Quixote-1.2 software in source or binary form and its associated
13 | documentation ("Quixote-1.2").
14 |
15 | 2. Subject to the terms and conditions of this License Agreement,
16 | CNRI hereby grants Licensee a nonexclusive, royalty-free, world-
17 | wide license to reproduce, analyze, test, perform and/or display
18 | publicly, prepare derivative works, distribute, and otherwise use
19 | Quixote-1.2 alone or in any derivative version, provided,
20 | however, that CNRI's License Agreement and CNRI's notice of
21 | copyright, i.e., "Copyright (c) 2004 Corporation for National
22 | Research Initiatives; All Rights Reserved" are retained in
23 | Quixote-1.2 alone or in any derivative version prepared by
24 | Licensee.
25 |
26 | 3. In the event Licensee prepares a derivative work that is based on
27 | or incorporates Quixote-1.2 or any part thereof, and wants to
28 | make the derivative work available to others as provided herein,
29 | then Licensee hereby agrees to include in any such work a brief
30 | summary of the changes made to Quixote-1.2.
31 |
32 | 4. CNRI is making Quixote-1.2 available to Licensee on an "AS IS"
33 | basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
34 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO
35 | AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY
36 | OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF QUIXOTE-
37 | 1.2 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
38 |
39 | 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF
40 | QUIXOTE-1.2 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES
41 | OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE
42 | USING QUIXOTE-1.2, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF
43 | THE POSSIBILITY THEREOF.
44 |
45 | 6. This License Agreement will automatically terminate upon a
46 | material breach of its terms and conditions.
47 |
48 | 7. This License Agreement shall be governed by and interpreted in
49 | all respects by the law of the State of Virginia, excluding
50 | Virginia's conflict of law provisions. Nothing in this License
51 | Agreement shall be deemed to create any relationship of agency,
52 | partnership, or joint venture between CNRI and Licensee. This
53 | License Agreement does not grant permission to use CNRI
54 | trademarks or trade name in a trademark sense to endorse or
55 | promote products or services of Licensee, or any third party.
56 |
57 | 8. By copying, installing or otherwise using Quixote-1.2, Licensee
58 | agrees to be bound by the terms and conditions of this License
59 | Agreement.
60 |
61 |
--------------------------------------------------------------------------------
/MANIFEST:
--------------------------------------------------------------------------------
1 | ACKS
2 | CHANGES
3 | LICENSE
4 | MANIFEST
5 | MANIFEST.in
6 | README
7 | TODO
8 | __init__.py
9 | _py_htmltext.py
10 | config.py
11 | errors.py
12 | fcgi.py
13 | html.py
14 | http_request.py
15 | http_response.py
16 | mod_python_handler.py
17 | ptl_compile.py
18 | ptl_import.py
19 | ptlc_dump.py
20 | publish.py
21 | qx_distutils.py
22 | sendmail.py
23 | session.py
24 | setup.py
25 | upload.py
26 | util.py
27 | ./__init__.py
28 | ./_py_htmltext.py
29 | ./config.py
30 | ./errors.py
31 | ./fcgi.py
32 | ./html.py
33 | ./http_request.py
34 | ./http_response.py
35 | ./mod_python_handler.py
36 | ./ptl_compile.py
37 | ./ptl_import.py
38 | ./ptlc_dump.py
39 | ./publish.py
40 | ./qx_distutils.py
41 | ./sendmail.py
42 | ./session.py
43 | ./upload.py
44 | ./util.py
45 | ./demo/__init__.py
46 | ./demo/demo_scgi.py
47 | ./demo/forms.ptl
48 | ./demo/integer_ui.py
49 | ./demo/pages.ptl
50 | ./demo/run_cgi.py
51 | ./demo/session.ptl
52 | ./demo/widgets.ptl
53 | ./form/__init__.py
54 | ./form/form.py
55 | ./form/widget.py
56 | ./form2/__init__.py
57 | ./form2/compatibility.py
58 | ./form2/css.py
59 | ./form2/form.py
60 | ./form2/widget.py
61 | ./server/__init__.py
62 | ./server/medusa_http.py
63 | ./server/twisted_http.py
64 | demo/__init__.py
65 | demo/demo.cgi
66 | demo/demo.conf
67 | demo/demo_scgi.py
68 | demo/demo_scgi.sh
69 | demo/forms.ptl
70 | demo/integer_ui.py
71 | demo/pages.ptl
72 | demo/run_cgi.py
73 | demo/session.ptl
74 | demo/session_demo.cgi
75 | demo/upload.cgi
76 | demo/widgets.ptl
77 | doc/INSTALL.html
78 | doc/INSTALL.txt
79 | doc/Makefile
80 | doc/PTL.html
81 | doc/PTL.txt
82 | doc/ZPL.txt
83 | doc/default.css
84 | doc/demo.html
85 | doc/demo.txt
86 | doc/form2conversion.html
87 | doc/form2conversion.txt
88 | doc/multi-threaded.html
89 | doc/multi-threaded.txt
90 | doc/programming.html
91 | doc/programming.txt
92 | doc/session-mgmt.html
93 | doc/session-mgmt.txt
94 | doc/static-files.html
95 | doc/static-files.txt
96 | doc/upgrading.html
97 | doc/upgrading.txt
98 | doc/upload.html
99 | doc/upload.txt
100 | doc/web-server.html
101 | doc/web-server.txt
102 | doc/web-services.html
103 | doc/web-services.txt
104 | doc/widgets.html
105 | doc/widgets.txt
106 | form/__init__.py
107 | form/form.py
108 | form/widget.py
109 | form2/__init__.py
110 | form2/compatibility.py
111 | form2/css.py
112 | form2/form.py
113 | form2/widget.py
114 | server/__init__.py
115 | server/medusa_http.py
116 | server/twisted_http.py
117 | src/Makefile
118 | src/_c_htmltext.c
119 | src/cimport.c
120 | src/setup.py
121 | test/__init__.py
122 | test/ua_test.py
123 | test/utest_html.py
124 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | global-include *.py *.ptl
2 | include README LICENSE MANIFEST.in MANIFEST CHANGES TODO ACKS
3 | include doc/*.txt doc/*.css doc/Makefile
4 | recursive-include doc *.html
5 | include demo/*.cgi demo/*.conf demo/*.sh
6 | include src/*.c src/Makefile
7 |
--------------------------------------------------------------------------------
/PKG-INFO:
--------------------------------------------------------------------------------
1 | Metadata-Version: 1.0
2 | Name: Quixote
3 | Version: 1.2
4 | Summary: A highly Pythonic Web application framework
5 | Home-page: http://www.mems-exchange.org/software/quixote/
6 | Author: MEMS Exchange
7 | Author-email: quixote@mems-exchange.org
8 | License: CNRI Open Source License (see LICENSE.txt)
9 | Provides: quixote-1.2
10 | Provides: quixote.demo-1.2
11 | Provides: quixote.form-1.2
12 | Provides: quixote.form2-1.2
13 | Provides: quixote.server-1.2
14 | Download-URL: http://www.mems-exchange.org/software/files/quixote/Quixote-1.2.tar.gz
15 | Description: UNKNOWN
16 | Platform: UNKNOWN
17 | Classifier: Development Status :: 5 - Production/Stable
18 | Classifier: Environment :: Web Environment
19 | Classifier: License :: OSI Approved :: Python License (CNRI Python License)
20 | Classifier: Intended Audience :: Developers
21 | Classifier: Operating System :: Unix
22 | Classifier: Operating System :: Microsoft :: Windows
23 | Classifier: Operating System :: MacOS :: MacOS X
24 | Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
25 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Quixote
2 | =======
3 |
4 | Quixote is yet another framework for developing Web applications in
5 | Python. The design goals were:
6 |
7 | 1) To allow easy development of Web applications where the
8 | emphasis is more on complicated programming logic than
9 | complicated templating.
10 |
11 | 2) To make the templating language as similar to Python as possible,
12 | in both syntax and semantics. The aim is to make as many of the
13 | skills and structural techniques used in writing regular Python
14 | code applicable to Web applications built using Quixote.
15 |
16 | 3) No magic. When it's not obvious what to do in
17 | a certain case, Quixote refuses to guess.
18 |
19 | If you view a web site as a program, and web pages as subroutines,
20 | Quixote just might be the tool for you. If you view a web site as a
21 | graphic design showcase, and each web page as an individual work of art,
22 | Quixote is probably not what you're looking for.
23 |
24 | An additional requirement was that the entire system had to be
25 | implementable in a week or two. The initial version of Quixote was
26 | indeed cranked out in about that time -- thank you, Python!
27 |
28 | We've tried to reuse as much existing code as possible:
29 |
30 | * The HTTPRequest and HTTPResponse classes are distantly
31 | derived from their namesakes in Zope, but we've removed
32 | huge amounts of Zope-specific code.
33 |
34 | * The quixote.fcgi module is derived from Robin Dunn's FastCGI module,
35 | available at
36 | http://alldunn.com/python/#fcgi
37 |
38 | Quixote requires Python 2.1 or greater to run. We only test Quixote
39 | with Python 2.3, but it should still work with 2.1 and 2.2.
40 |
41 | For installation instructions, see the doc/INSTALL.txt file (or
42 | http://www.mems-exchange.org/software/quixote/doc/INSTALL.html).
43 |
44 | If you're switching to a newer version of Quixote from an older
45 | version, please refer to doc/upgrading.txt for explanations of any
46 | backward-incompatible changes.
47 |
48 |
49 | Overview
50 | ========
51 |
52 | Quixote works by using a Python package to store all the code and HTML
53 | for a Web-based application. There's a simple framework for
54 | publishing code and objects on the Web, and the publishing loop can be
55 | customized by subclassing the Publisher class. You can think of it as
56 | a toolkit to build your own smaller, simpler version of Zope,
57 | specialized for your application.
58 |
59 | An application using Quixote is a Python package containing .py and
60 | .ptl files.
61 |
62 | webapp/ # Root of package
63 | __init__.py
64 | module1.py
65 | module2.py
66 | pages1.ptl
67 | pages2.ptl
68 |
69 | PTL, the Python Template Language, is used to mix HTML with Python code.
70 | More importantly, Python can be used to drive the generation of HTML.
71 | An import hook is defined so that PTL files can be imported just like
72 | Python modules. The basic syntax of PTL is Python's, with a few small
73 | changes:
74 |
75 | def plain [text] barebones_header(title=None,
76 | description=None):
77 | """
78 |
79 | %s
80 | """ % html_quote(str(title))
81 | if description:
82 | '' % html_quote(description)
83 |
84 | ''
85 |
86 | See doc/PTL.txt for a detailed explanation of PTL.
87 |
88 |
89 | Quick start
90 | ===========
91 |
92 | For instant gratification, see doc/demo.txt. This explains how to get
93 | the Quixote demo up and running, so you can play with Quixote without
94 | actually having to write any code.
95 |
96 |
97 | Documentation
98 | =============
99 |
100 | All the documentation is in the doc/ subdirectory, in both text and
101 | HTML. Or you can browse it online from
102 | http://www.mems-exchange.org/software/quixote/doc/
103 |
104 | Recommended reading:
105 |
106 | demo.txt getting the Quixote demo up and running, and
107 | how the demo works
108 | programming.txt the components of a Quixote application: how
109 | to write your own Quixote apps
110 | PTL.txt the Python Template Language, used by Quixote
111 | apps to generate web pages
112 | web-server.txt how to configure your web server for Quixote
113 |
114 | Optional reading (more advanced or arcane stuff):
115 |
116 | session-mgmt.txt session management: how to track information
117 | across requests
118 | static-files.txt making static files and CGI scripts available
119 | upload.txt how to handle HTTP uploads with Quixote
120 | upgrading.txt info on backward-incompatible changes that may
121 | affect applications written with earlier versions
122 | widgets.txt reference documentation for the Quixote Widget
123 | classes (which underly the form library)
124 | web-services.txt how to write web services using Quixote and
125 | XML-RPC
126 |
127 |
128 | Authors, copyright, and license
129 | ===============================
130 |
131 | Copyright (c) 2000-2003 CNRI.
132 |
133 | Quixote was primarily written by Andrew Kuchling, Neil Schemenauer, and
134 | Greg Ward.
135 |
136 | Overall, Quixote is covered by the CNRI Open Source License Agreement;
137 | see LICENSE for details.
138 |
139 | Portions of Quixote are derived from Zope, and are also covered by the
140 | ZPL (Zope Public License); see ZPL.txt.
141 |
142 | Full acknowledgments are in the ACKS file.
143 |
144 |
145 | Availability, home page, and mailing lists
146 | ==========================================
147 |
148 | The Quixote home page is:
149 | http://www.mems-exchange.org/software/quixote/
150 |
151 | You'll find the latest stable release there. The current development
152 | code is also available via CVS; for instructions, see
153 | http://www.mems-exchange.org/software/quixote/cvs.html
154 |
155 | Discussion of Quixote occurs on the quixote-users mailing list:
156 | http://mail.mems-exchange.org/mailman/listinfo/quixote-users/
157 |
158 | To follow development at the most detailed level by seeing every CVS
159 | checkin, join the quixote-checkins mailing list:
160 | http://mail.mems-exchange.org/mailman/listinfo/quixote-checkins/
161 |
162 |
163 | --
164 | A.M. Kuchling
165 | Neil Schemenauer
166 | Greg Ward
167 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/douban/douban-quixote/20cda948580802ee4205b72fe46728de9dda5253/TODO
--------------------------------------------------------------------------------
/doc/INSTALL.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Installing Quixote
8 |
9 |
10 |
11 |
We strongly recommend that you remove any old Quixote version before
27 | installing a new one. First, find out where your old Quixote
28 | installation is:
If you are using Python 2.0 or 2.1 then you need to install the
40 | compiler package from the Python source distribution. The
41 | compiler package is for parsing Python source code and generating
42 | Python bytecode, and the PTL compiler is built on top of it. With
43 | Python 2.0 and 2.1, this package was included in Python's source
44 | distribution, but not installed as part of the standard library.
45 |
Assuming your Python source distribution is in /tmp/Python-2.1.2:
(Obviously, you'll have to adjust this to reflect your Python version
51 | and where you kept the source distribution after installing Python.)
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/doc/INSTALL.txt:
--------------------------------------------------------------------------------
1 | Installing Quixote
2 | ==================
3 |
4 | Best-case scenario
5 | ------------------
6 |
7 | If you are using Python 2.2 or later, and have never installed Quixote
8 | with your current version of Python, you're in luck. Just run
9 |
10 | python setup.py install
11 |
12 | and you're done. Proceed to the demo documentation to learn how to get
13 | Quixote working.
14 |
15 | If you're using an older Python, or if you're upgrading from an older
16 | Quixote version, read on.
17 |
18 |
19 | Upgrading from an older Quixote version
20 | ---------------------------------------
21 |
22 | We strongly recommend that you remove any old Quixote version before
23 | installing a new one. First, find out where your old Quixote
24 | installation is:
25 |
26 | python -c "import os, quixote; print os.path.dirname(quixote.__file__)"
27 |
28 | and then remove away the reported directory. (If the import fails, then
29 | you don't have an existing Quixote installation.)
30 |
31 | Then proceed as above:
32 |
33 | python setup.py install
34 |
35 |
36 | Using Quixote with Python 2.0 or 2.1
37 | ------------------------------------
38 |
39 | If you are using Python 2.0 or 2.1 then you need to install the
40 | ``compiler`` package from the Python source distribution. The
41 | ``compiler`` package is for parsing Python source code and generating
42 | Python bytecode, and the PTL compiler is built on top of it. With
43 | Python 2.0 and 2.1, this package was included in Python's source
44 | distribution, but not installed as part of the standard library.
45 |
46 | Assuming your Python source distribution is in ``/tmp/Python-2.1.2``::
47 |
48 | cd /tmp/Python-2.1.2/Tools/compiler
49 | python setup.py install
50 |
51 | (Obviously, you'll have to adjust this to reflect your Python version
52 | and where you kept the source distribution after installing Python.)
53 |
54 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | #
2 | # Makefile to convert Quixote docs to HTML
3 | #
4 | # $Id: Makefile 20217 2003-01-16 20:51:53Z akuchlin $
5 | #
6 |
7 | TXT_FILES = $(wildcard *.txt)
8 | HTML_FILES = $(filter-out ZPL%,$(TXT_FILES:%.txt=%.html))
9 |
10 | RST2HTML = /www/python/bin/rst2html
11 | RST2HTML_OPTS = -o us-ascii
12 |
13 | DEST_HOST = staging.mems-exchange.org
14 | DEST_DIR = /www/www-docroot/software/quixote/doc
15 |
16 | SS = default.css
17 |
18 | %.html: %.txt
19 | $(RST2HTML) $(RST2HTML_OPTS) $< $@
20 |
21 | all: $(HTML_FILES)
22 |
23 | clean:
24 | rm -f $(HTML_FILES)
25 |
26 | install:
27 | rsync -vptgo *.html $(SS) $(DEST_HOST):$(DEST_DIR)
28 |
29 | local-install:
30 | dir=`pwd` ; \
31 | cd $(DEST_DIR) && ln -sf $$dir/*.html $$dir/$(SS) .
32 |
--------------------------------------------------------------------------------
/doc/PTL.txt:
--------------------------------------------------------------------------------
1 | PTL: Python Template Language
2 | =============================
3 |
4 | Introduction
5 | ------------
6 |
7 | PTL is the templating language used by Quixote. Most web templating
8 | languages embed a real programming language in HTML, but PTL inverts
9 | this model by merely tweaking Python to make it easier to generate
10 | HTML pages (or other forms of text). In other words, PTL is basically
11 | Python with a novel way to specify function return values.
12 |
13 | Specifically, a PTL template is designated by inserting a ``[plain]``
14 | or ``[html]`` modifier after the function name. The value of
15 | expressions inside templates are kept, not discarded. If the type is
16 | ``[html]`` then non-literal strings are passed through a function that
17 | escapes HTML special characters.
18 |
19 |
20 | Plain text templates
21 | --------------------
22 |
23 | Here's a sample plain text template::
24 |
25 | def foo [plain] (x, y = 5):
26 | "This is a chunk of static text."
27 | greeting = "hello world" # statement, no PTL output
28 | print 'Input values:', x, y
29 | z = x + y
30 | """You can plug in variables like x (%s)
31 | in a variety of ways.""" % x
32 |
33 | "\n\n"
34 | "Whitespace is important in generated text.\n"
35 | "z = "; z
36 | ", but y is "
37 | y
38 | "."
39 |
40 | Obviously, templates can't have docstrings, but otherwise they follow
41 | Python's syntactic rules: indentation indicates scoping, single-quoted
42 | and triple-quoted strings can be used, the same rules for continuing
43 | lines apply, and so forth. PTL also follows all the expected semantics
44 | of normal Python code: so templates can have parameters, and the
45 | parameters can have default values, be treated as keyword arguments,
46 | etc.
47 |
48 | The difference between a template and a regular Python function is that
49 | inside a template the result of expressions are saved as the return
50 | value of that template. Look at the first part of the example again::
51 |
52 | def foo [plain] (x, y = 5):
53 | "This is a chunk of static text."
54 | greeting = "hello world" # statement, no PTL output
55 | print 'Input values:', x, y
56 | z = x + y
57 | """You can plug in variables like x (%s)
58 | in a variety of ways.""" % x
59 |
60 | Calling this template with ``foo(1, 2)`` results in the following
61 | string::
62 |
63 | This is a chunk of static text.You can plug in variables like x (1)
64 | in a variety of ways.
65 |
66 | Normally when Python evaluates expressions inside functions, it just
67 | discards their values, but in a ``[plain]`` PTL template the value is
68 | converted to a string using ``str()`` and appended to the template's
69 | return value. There's a single exception to this rule: ``None`` is the
70 | only value that's ever ignored, adding nothing to the output. (If this
71 | weren't the case, calling methods or functions that return ``None``
72 | would require assigning their value to a variable. You'd have to write
73 | ``dummy = list.sort()`` in PTL code, which would be strange and
74 | confusing.)
75 |
76 | The initial string in a template isn't treated as a docstring, but is
77 | just incorporated in the generated output; therefore, templates can't
78 | have docstrings. No whitespace is ever automatically added to the
79 | output, resulting in ``...text.You can ...`` from the example. You'd
80 | have to add an extra space to one of the string literals to correct
81 | this.
82 |
83 | The assignment to the ``greeting`` local variable is a statement, not an
84 | expression, so it doesn't return a value and produces no output. The
85 | output from the ``print`` statement will be printed as usual, but won't
86 | go into the string generated by the template. Quixote directs standard
87 | output into Quixote's debugging log; if you're using PTL on its own, you
88 | should consider doing something similar. ``print`` should never be used
89 | to generate output returned to the browser, only for adding debugging
90 | traces to a template.
91 |
92 | Inside templates, you can use all of Python's control-flow statements::
93 |
94 | def numbers [plain] (n):
95 | for i in range(n):
96 | i
97 | " " # PTL does not add any whitespace
98 |
99 | Calling ``numbers(5)`` will return the string ``"1 2 3 4 5 "``. You can
100 | also have conditional logic or exception blocks::
101 |
102 | def international_hello [plain] (language):
103 | if language == "english":
104 | "hello"
105 | elif language == "french":
106 | "bonjour"
107 | else:
108 | raise ValueError, "I don't speak %s" % language
109 |
110 |
111 | HTML templates
112 | --------------
113 |
114 | Since PTL is usually used to generate HTML documents, an ``[html]``
115 | template type has been provided to make generating HTML easier.
116 |
117 | A common error when generating HTML is to grab data from the browser
118 | or from a database and incorporate the contents without escaping
119 | special characters such as '<' and '&'. This leads to a class of
120 | security bugs called "cross-site scripting" bugs, where a hostile user
121 | can insert arbitrary HTML in your site's output that can link to other
122 | sites or contain JavaScript code that does something nasty (say,
123 | popping up 10,000 browser windows).
124 |
125 | Such bugs occur because it's easy to forget to HTML-escape a string,
126 | and forgetting it in just one location is enough to open a hole. PTL
127 | offers a solution to this problem by being able to escape strings
128 | automatically when generating HTML output, at the cost of slightly
129 | diminished performance (a few percent).
130 |
131 | Here's how this feature works. PTL defines a class called
132 | ``htmltext`` that represents a string that's already been HTML-escaped
133 | and can be safely sent to the client. The function ``htmlescape(string)``
134 | is used to escape data, and it always returns an ``htmltext``
135 | instance. It does nothing if the argument is already ``htmltext``.
136 | Both ``htmltext`` and ``htmlescape`` are available from the global
137 | namespace in PTL modules.
138 |
139 | If a template function is declared ``[html]`` instead of ``[text]``
140 | then two things happen. First, all literal strings in the function
141 | become instances of ``htmltext`` instead of Python's ``str``. Second,
142 | the values of expressions are passed through ``htmlescape()`` instead
143 | of ``str()``.
144 |
145 | ``htmltext`` type is like the ``str`` type except that operations
146 | combining strings and ``htmltext`` instances will result in the string
147 | being passed through ``htmlescape()``. For example::
148 |
149 | >>> from quixote.html import htmltext
150 | >>> htmltext('a') + 'b'
151 |
152 | >>> 'a' + htmltext('b')
153 |
154 | >>> htmltext('a%s') % 'b'
155 |
156 | >>> response = 'green eggs & ham'
157 | >>> htmltext('The response was: %s') % response
158 |
159 |
160 | Note that calling ``str()`` strips the ``htmltext`` type and should be
161 | avoided since it usually results in characters being escaped more than
162 | once. While ``htmltext`` behaves much like a regular string, it is
163 | sometimes necessary to insert a ``str()`` inside a template in order
164 | to obtain a genuine string. For example, the ``re`` module requires
165 | genuine strings. We have found that explicit calls to ``str()`` can
166 | often be avoided by splitting some code out of the template into a
167 | helper function written in regular Python.
168 |
169 | It is also recommended that the ``htmltext`` constructor be used as
170 | sparingly as possible. The reason is that when using the htmltext
171 | feature of PTL, explicit calls to ``htmltext`` become the most likely
172 | source of cross-site scripting holes. Calling ``htmltext`` is like
173 | saying "I am absolutely sure this piece of data cannot contain malicious
174 | HTML code injected by a user. Don't escape HTML special characters
175 | because I want them."
176 |
177 | Note that literal strings in template functions declared with
178 | ``[html]`` are htmltext instances, and therefore won't be escaped.
179 | You'll only need to use ``htmltext`` when HTML markup comes from
180 | outside the template. For example, if you want to include a file
181 | containing HTML::
182 |
183 | def output_file [html] ():
184 | '' # does not get escaped
185 | htmltext(open("myfile.html").read())
186 | ''
187 |
188 | In the common case, templates won't be dealing with HTML markup from
189 | external sources, so you can write straightforward code. Consider
190 | this function to generate the contents of the ``HEAD`` element::
191 |
192 | def meta_tags [html] (title, description):
193 | '%s' % title
194 | '\n' % description
195 |
196 | There are no calls to ``htmlescape()`` at all, but string literals
197 | such as ``%s`` have all be turned into ``htmltext``
198 | instances, so the string variables will be automatically escaped::
199 |
200 | >>> t.meta_tags('Catalog', 'A catalog of our cool products')
201 | Catalog
202 | \n'>
203 | >>> t.meta_tags('Dissertation on ',
204 | ... 'Discusses the "LINK" and "META" tags')
205 | Dissertation on <HEAD>
206 | \n'>
208 | >>>
209 |
210 | Note how the title and description have had HTML-escaping applied to them.
211 | (The output has been manually pretty-printed to be more readable.)
212 |
213 | Once you start using ``htmltext`` in one of your templates, mixing
214 | plain and HTML templates is tricky because of ``htmltext``'s automatic
215 | escaping; plain templates that generate HTML tags will be
216 | double-escaped. One approach is to just use HTML templates throughout
217 | your application. Alternatively you can use ``str()`` to convert
218 | ``htmltext`` instances to regular Python strings; just be sure the
219 | resulting string isn't HTML-escaped again.
220 |
221 | Two implementations of ``htmltext`` are provided, one written in pure
222 | Python and a second one implemented as a C extension. Both versions
223 | have seen production use.
224 |
225 |
226 | PTL modules
227 | -----------
228 |
229 | PTL templates are kept in files with the extension .ptl. Like Python
230 | files, they are byte-compiled on import, and the byte-code is written to
231 | a compiled file with the extension ``.ptlc``. Since vanilla Python
232 | doesn't know anything about PTL, Quixote provides an import hook to let
233 | you import PTL files just like regular Python modules. The standard way
234 | to install this import hook is by calling the ``enable_ptl()`` function::
235 |
236 | from quixote import enable_ptl
237 | enable_ptl()
238 |
239 | (Note: if you're using ZODB, always import ZODB *before* installing the
240 | PTL import hook. There's some interaction which causes importing the
241 | TimeStamp module to fail when the PTL import hook is installed; we
242 | haven't debugged the problem.)
243 |
244 | Once the import hook is installed, PTL files can be imported as if they
245 | were Python modules. If all the example templates shown here were put
246 | into a file named ``foo.ptl``, you could then write Python code that did
247 | this::
248 |
249 | from foo import numbers
250 | def f():
251 | return numbers(10)
252 |
253 | You may want to keep this little function in your ``PYTHONSTARTUP``
254 | file::
255 |
256 | def ptl():
257 | try:
258 | import ZODB
259 | except ImportError:
260 | pass
261 | from quixote import enable_ptl
262 | enable_ptl()
263 |
264 | This is useful if you want to interactively play with a PTL module.
265 |
266 |
267 | $Id: PTL.txt 25237 2004-09-30 18:28:31Z nascheme $
268 |
--------------------------------------------------------------------------------
/doc/ZPL.txt:
--------------------------------------------------------------------------------
1 | Zope Public License (ZPL) Version 2.0
2 | -----------------------------------------------
3 |
4 | This software is Copyright (c) Zope Corporation (tm) and
5 | Contributors. All rights reserved.
6 |
7 | This license has been certified as open source. It has also
8 | been designated as GPL compatible by the Free Software
9 | Foundation (FSF).
10 |
11 | Redistribution and use in source and binary forms, with or
12 | without modification, are permitted provided that the
13 | following conditions are met:
14 |
15 | 1. Redistributions in source code must retain the above
16 | copyright notice, this list of conditions, and the following
17 | disclaimer.
18 |
19 | 2. Redistributions in binary form must reproduce the above
20 | copyright notice, this list of conditions, and the following
21 | disclaimer in the documentation and/or other materials
22 | provided with the distribution.
23 |
24 | 3. The name Zope Corporation (tm) must not be used to
25 | endorse or promote products derived from this software
26 | without prior written permission from Zope Corporation.
27 |
28 | 4. The right to distribute this software or to use it for
29 | any purpose does not give you the right to use Servicemarks
30 | (sm) or Trademarks (tm) of Zope Corporation. Use of them is
31 | covered in a separate agreement (see
32 | http://www.zope.com/Marks).
33 |
34 | 5. If any files are modified, you must cause the modified
35 | files to carry prominent notices stating that you changed
36 | the files and the date of any change.
37 |
38 | Disclaimer
39 |
40 | THIS SOFTWARE IS PROVIDED BY ZOPE CORPORATION ``AS IS''
41 | AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
42 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
43 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
44 | NO EVENT SHALL ZOPE CORPORATION OR ITS CONTRIBUTORS BE
45 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
46 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
48 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
49 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
50 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
51 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
52 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
53 | DAMAGE.
54 |
55 |
56 | This software consists of contributions made by Zope
57 | Corporation and many individuals on behalf of Zope
58 | Corporation. Specific attributions are listed in the
59 | accompanying credits file.
60 |
--------------------------------------------------------------------------------
/doc/default.css:
--------------------------------------------------------------------------------
1 | /*
2 | Cascading style sheet for the Quixote documentation.
3 | Just overrides what I don't like about the standard docutils
4 | stylesheet.
5 |
6 | $Id: default.css 20217 2003-01-16 20:51:53Z akuchlin $
7 | */
8 |
9 | @import url(/misc/docutils.css);
10 |
11 | pre.literal-block, pre.doctest-block {
12 | margin-left: 1em ;
13 | margin-right: 1em ;
14 | background-color: #f4f4f4 }
15 |
16 | tt { background-color: transparent }
17 |
--------------------------------------------------------------------------------
/doc/multi-threaded.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Multi-Threaded Quixote Applications
8 |
9 |
10 |
11 |
12 |
Multi-Threaded Quixote Applications
13 |
Starting with Quixote 0.6, it's possible to write multi-threaded Quixote
14 | applications. In previous versions, Quixote stored the current
15 | HTTPRequest object in a global variable, meaning that processing
16 | multiple requests in the same process simultaneously was impossible.
17 |
However, the Publisher class as shipped still can't handle multiple
18 | simultaneous requests; you'll need to subclass Publisher to make it
19 | re-entrant. Here's a starting point:
The StaticFile class makes an individual filesystem file (possibly
18 | a symbolic link) available. You can also specify the MIME type and
19 | encoding of the file; if you don't specify this, the MIME type will be
20 | guessed using the standard Python mimetypes.guess_type() function.
21 | The default action is to not follow symbolic links, but this behaviour
22 | can be changed using the follow_symlinks parameter.
23 |
The following example publishes a file with the URL .../stylesheet_css:
24 |
25 | # 'stylesheet_css' must be in the _q_exports list
26 | _q_exports = [ ..., 'stylesheet_css', ...]
27 |
28 | stylesheet_css = StaticFile(
29 | "/htdocs/legacy_app/stylesheet.css",
30 | follow_symlinks=1, mime_type="text/css")
31 |
32 |
If you want the URL of the file to have a .css extension, you use
33 | the external to internal name mapping feature of _q_exports. For
34 | example:
Publishing a directory is similar. The StaticDirectory class
42 | makes a complete filesystem directory available. Again, the default
43 | behaviour is to not follow symlinks. You can also request that the
44 | StaticDirectory object cache information about the files in
45 | memory so that it doesn't try to guess the MIME type on every hit.
54 |
55 |
56 |
--------------------------------------------------------------------------------
/doc/static-files.txt:
--------------------------------------------------------------------------------
1 | Examples of serving static files
2 | ================================
3 |
4 | The ``quixote.util`` module includes classes for making files and
5 | directories available as Quixote resources. Here are some examples.
6 |
7 |
8 | Publishing a Single File
9 | ------------------------
10 |
11 | The ``StaticFile`` class makes an individual filesystem file (possibly
12 | a symbolic link) available. You can also specify the MIME type and
13 | encoding of the file; if you don't specify this, the MIME type will be
14 | guessed using the standard Python ``mimetypes.guess_type()`` function.
15 | The default action is to not follow symbolic links, but this behaviour
16 | can be changed using the ``follow_symlinks`` parameter.
17 |
18 | The following example publishes a file with the URL ``.../stylesheet_css``::
19 |
20 | # 'stylesheet_css' must be in the _q_exports list
21 | _q_exports = [ ..., 'stylesheet_css', ...]
22 |
23 | stylesheet_css = StaticFile(
24 | "/htdocs/legacy_app/stylesheet.css",
25 | follow_symlinks=1, mime_type="text/css")
26 |
27 |
28 | If you want the URL of the file to have a ``.css`` extension, you use
29 | the external to internal name mapping feature of ``_q_exports``. For
30 | example::
31 |
32 | _q_exports = [ ..., ('stylesheet.css', 'stylesheet_css'), ...]
33 |
34 |
35 |
36 | Publishing a Directory
37 | ----------------------
38 |
39 | Publishing a directory is similar. The ``StaticDirectory`` class
40 | makes a complete filesystem directory available. Again, the default
41 | behaviour is to not follow symlinks. You can also request that the
42 | ``StaticDirectory`` object cache information about the files in
43 | memory so that it doesn't try to guess the MIME type on every hit.
44 |
45 | This example publishes the ``notes/`` directory::
46 |
47 | _q_exports = [ ..., 'notes', ...]
48 |
49 | notes = StaticDirectory("/htdocs/legacy_app/notes")
50 |
51 |
52 |
--------------------------------------------------------------------------------
/doc/ua_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Test Quixote's ability to parse the "User-Agent" header, ie.
4 | # the 'guess_browser_version()' method of HTTPRequest.
5 | #
6 | # Reads User-Agent strings on stdin, and writes Quixote's interpretation
7 | # of each on stdout. This is *not* an automated test!
8 |
9 | import sys, os
10 | from copy import copy
11 | from quixote.http_request import HTTPRequest
12 |
13 | env = copy(os.environ)
14 | file = sys.stdin
15 | while 1:
16 | line = file.readline()
17 | if not line:
18 | break
19 | if line[-1] == "\n":
20 | line = line[:-1]
21 |
22 | env["HTTP_USER_AGENT"] = line
23 | req = HTTPRequest(None, env)
24 | (name, version) = req.guess_browser_version()
25 | if name is None:
26 | print "%s -> ???" % line
27 | else:
28 | print "%s -> (%s, %s)" % (line, name, version)
29 |
--------------------------------------------------------------------------------
/doc/upgrading.txt:
--------------------------------------------------------------------------------
1 | Upgrading code from older versions of Quixote
2 | =============================================
3 |
4 | This document lists backward-incompatible changes in Quixote, and
5 | explains how to update application code to work with the newer
6 | version.
7 |
8 | Changes from 0.6.1 to 1.0
9 | -------------------------
10 |
11 | Sessions
12 | ********
13 |
14 | A leading underscore was removed from the ``Session`` attributes
15 | ``__remote_address``, ``__creation_time``, and ``__access_time``. If
16 | you have pickled ``Session`` objects you will need to upgrade them
17 | somehow. Our preferred method is to write a script that unpickles each
18 | object, renames the attributes and then re-pickles it.
19 |
20 |
21 |
22 | Changes from 0.6 to 0.6.1
23 | -------------------------
24 |
25 | ``_q_exception_handler`` now called if exception while traversing
26 | *****************************************************************
27 |
28 | ``_q_exception_handler`` hooks will now be called if an exception is
29 | raised during the traversal process. Quixote 0.6 had a bug that caused
30 | ``_q_exception_handler`` hooks to only be called if an exception was
31 | raised after the traversal completed.
32 |
33 |
34 |
35 | Changes from 0.5 to 0.6
36 | -----------------------
37 |
38 | ``_q_getname`` renamed to ``_q_lookup``
39 | ***************************************
40 |
41 | The ``_q_getname`` special function was renamed to ``_q_lookup``,
42 | because that name gives a clearer impression of the function's
43 | purpose. In 0.6, ``_q_getname`` still works but will trigger a
44 | warning.
45 |
46 |
47 | Form Framework Changes
48 | **********************
49 |
50 | The ``quixote.form.form`` module was changed from a .ptl file to a .py
51 | file. You should delete or move the existing ``quixote/`` directory
52 | in ``site-packages`` before running ``setup.py``, or at least delete
53 | the old ``form.ptl`` and ``form.ptlc`` files.
54 |
55 | The widget and form classes in the ``quixote.form`` package now return
56 | ``htmltext`` instances. Applications that use forms and widgets will
57 | likely have to be changed to use the ``[html]`` template type to avoid
58 | over-escaping of HTML special characters.
59 |
60 | Also, the constructor arguments to ``SelectWidget`` and its subclasses have
61 | changed. This only affects applications that use the form framework
62 | located in the ``quixote.form`` package.
63 |
64 | In Quixote 0.5, the ``SelectWidget`` constructor had this signature::
65 |
66 | def __init__ (self, name, value=None,
67 | allowed_values=None,
68 | descriptions=None,
69 | size=None,
70 | sort=0):
71 |
72 | ``allowed_values`` was the list of objects that the user could choose,
73 | and ``descriptions`` was a list of strings that would actually be
74 | shown to the user in the generated HTML.
75 |
76 | In Quixote 0.6, the signature has changed slightly::
77 |
78 | def __init__ (self, name, value=None,
79 | allowed_values=None,
80 | descriptions=None,
81 | options=None,
82 | size=None,
83 | sort=0):
84 |
85 | The ``quote`` argument is gone, and the ``options`` argument has been
86 | added. If an ``options`` argument is provided, ``allowed_values``
87 | and ``descriptions`` must not be supplied.
88 |
89 | The ``options`` argument, if present, must be a list of tuples with
90 | 1,2, or 3 elements, of the form ``(value:any, description:any,
91 | key:string)``.
92 |
93 | * ``value`` is the object that will be returned if the user chooses
94 | this item, and must always be supplied.
95 |
96 | * ``description`` is a string or htmltext instance which will be
97 | shown to the user in the generated HTML. It will be passed
98 | through the htmlescape() functions, so for an ordinary string
99 | special characters such as '&' will be converted to '&'.
100 | htmltext instances will be left as they are.
101 |
102 | * If supplied, ``key`` will be used in the value attribute
103 | of the option element (``
A _q_exception_handler method, if present, is
67 | called when a PublishError exception is raised. It
68 | can do whatever it likes to provide a friendly page.
69 |
70 |
Here's the exception that was raised:
71 | %s (%s).
82 | """
83 | htmltext(request.dump_html())
84 | """
85 |
86 |
87 | """
88 |
--------------------------------------------------------------------------------
/quixote/demo/run_cgi.py:
--------------------------------------------------------------------------------
1 | # This is a simple script that makes it easy to write one file CGI
2 | # applications that use Quixote. To use, add the following line to the top
3 | # of your CGI script:
4 | #
5 | # #!/usr/local/bin/python /run_cgi.py
6 | #
7 | # Your CGI script becomes the root namespace and you may use PTL syntax
8 | # inside the script. Errors will go to stderr and should end up in the server
9 | # error log.
10 | #
11 | # Note that this will be quite slow since the script will be recompiled on
12 | # every hit. If you are using Apache with mod_fastcgi installed you should be
13 | # able to use .fcgi as an extension instead of .cgi and get much better
14 | # performance. Maybe someday I will write code that caches the compiled
15 | # script on the filesystem. :-)
16 |
17 | import sys
18 | import new
19 | from quixote import enable_ptl, ptl_compile, Publisher
20 |
21 | enable_ptl()
22 | filename = sys.argv[1]
23 | root_code = ptl_compile.compile_template(open(filename), filename)
24 | root = new.module("root")
25 | root.__file__ = filename
26 | root.__name__ = "root"
27 | exec root_code in root.__dict__
28 | p = Publisher(root)
29 | p.publish_cgi()
30 |
--------------------------------------------------------------------------------
/quixote/demo/session.ptl:
--------------------------------------------------------------------------------
1 | # quixote.demo.session
2 | #
3 | # Application code for the Quixote session management demo.
4 | # Driver script is session_demo.cgi.
5 |
6 | __revision__ = "$Id: session.ptl 23532 2004-02-20 22:38:57Z dbinger $"
7 |
8 | from quixote import get_session_manager
9 | from quixote.errors import QueryError
10 |
11 | _q_exports = ['login', 'logout']
12 |
13 |
14 | # Typical stuff for any Quixote app.
15 |
16 | def page_header [html] (title):
17 | '''\
18 |
19 | %s
20 |
21 |
%s
22 | ''' % (title, title)
23 |
24 | def page_footer [html] ():
25 | '''\
26 |
27 |
28 | '''
29 |
30 |
31 | # We include the login form on two separate pages, so it's been factored
32 | # out to a separate template.
33 |
34 | def login_form [html] ():
35 | '''
36 |
40 | '''
41 |
42 |
43 | def _q_index [html] (request):
44 | page_header("Quixote Session Management Demo")
45 |
46 | session = request.session
47 |
48 | # All Quixote sessions have the ability to track the user's identity
49 | # in session.user. In this simple application, session.user is just
50 | # a string which the user enters directly into this form. In the
51 | # real world, you would of course use a more sophisticated form of
52 | # authentication (eg. enter a password over an SSL connection), and
53 | # session.user might be an object with information about the user
54 | # (their email address, password hash, preferences, etc.).
55 |
56 | if session.user is None:
57 | '''
58 |
You haven\'t introduced yourself yet.
59 | Please tell me your name:
60 | '''
61 | login_form()
62 | else:
63 | '
Hello, %s. Good to see you again.
\n' % session.user
64 |
65 | '''
66 | You can now:
67 |
\n'
73 |
74 | # The other piece of information we track here is the number of
75 | # requests made in each session; report that information for the
76 | # current session here.
77 | """\
78 |
Your session is %s
79 | You have made %d request(s) (including this one) in this session.
80 | """ % (repr(session), session.num_requests)
81 |
82 | # The session manager is the collection of all sessions managed by
83 | # the current publisher, ie. in this process. Poking around in the
84 | # session manager is not something you do often, but it's really
85 | # handy for debugging/site administration.
86 | mgr = get_session_manager()
87 | session_ids = mgr.keys()
88 | '''
89 |
The current session manager is %s
90 | It has %d session(s) in it right now:
91 |
92 |
session id
user
num requests
93 | ''' % (repr(mgr), len(session_ids))
94 | for sess_id in session_ids:
95 | sess = mgr[sess_id]
96 | ('
%s
%s
%d
\n'
97 | % (sess.id,
98 | sess.user and sess.user or "none",
99 | sess.num_requests))
100 | '
\n'
101 |
102 | page_footer()
103 |
104 |
105 | # The login() template has two purposes: to display a page with just a
106 | # login form, and to process the login form submitted either from the
107 | # index page or from login() itself. This is a fairly common idiom in
108 | # Quixote (just as it's a fairly common idiom with CGI scripts -- it's
109 | # just cleaner with Quixote).
110 |
111 | def login [html] (request):
112 | page_header("Quixote Session Demo: Login")
113 | session = request.session
114 |
115 | # We seem to be processing the login form.
116 | if request.form:
117 | user = request.form.get("name")
118 | if not user:
119 | raise QueryError("no user name supplied")
120 |
121 | session.user = user
122 |
123 | '
Welcome, %s! Thank you for logging in.
\n' % user
124 | 'back to start\n'
125 |
126 | # No form data to process, so generate the login form instead. When
127 | # the user submits it, we'll return to this template and take the
128 | # above branch.
129 | else:
130 | '
Please enter your name here:
\n'
131 | login_form()
132 |
133 | page_footer()
134 |
135 |
136 | # logout() just expires the current session, ie. removes it from the
137 | # session manager and instructs the client to forget about the session
138 | # cookie. The only code necessary is the call to
139 | # SessionManager.expire_session() -- the rest is just user interface.
140 |
141 | def logout [html] (request):
142 | page_header("Quixote Session Demo: Logout")
143 | session = request.session
144 | if session.user:
145 | '
\n'
151 | page_footer()
152 |
--------------------------------------------------------------------------------
/quixote/demo/session_demo.cgi:
--------------------------------------------------------------------------------
1 | #!/www/python/bin/python
2 |
3 | # Demonstrate Quixote session management, along with the application
4 | # code in session.ptl (aka quixote.demo.session).
5 |
6 | __revision__ = "$Id: session_demo.cgi 21182 2003-03-17 21:46:52Z gward $"
7 |
8 | import os
9 | from stat import ST_MTIME
10 | from time import time
11 | from cPickle import load, dump
12 | from quixote import enable_ptl
13 | from quixote.session import Session, SessionManager
14 | from quixote.publish import SessionPublisher
15 |
16 | class DemoSession (Session):
17 | """
18 | Session class that tracks the number of requests made within a
19 | session.
20 | """
21 |
22 | def __init__ (self, request, id):
23 | Session.__init__(self, request, id)
24 | self.num_requests = 0
25 |
26 | def start_request (self, request):
27 |
28 | # This is called from the main object publishing loop whenever
29 | # we start processing a new request. Obviously, this is a good
30 | # place to track the number of requests made. (If we were
31 | # interested in the number of *successful* requests made, then
32 | # we could override finish_request(), which is called by
33 | # the publisher at the end of each successful request.)
34 |
35 | Session.start_request(self, request)
36 | self.num_requests += 1
37 |
38 | def has_info (self):
39 |
40 | # Overriding has_info() is essential but non-obvious. The
41 | # session manager uses has_info() to know if it should hang on
42 | # to a session object or not: if a session is "dirty", then it
43 | # must be saved. This prevents saving sessions that don't need
44 | # to be saved, which is especially important as a defensive
45 | # measure against clients that don't handle cookies: without it,
46 | # we might create and store a new session object for every
47 | # request made by such clients. With has_info(), we create the
48 | # new session object every time, but throw it away unsaved as
49 | # soon as the request is complete.
50 | #
51 | # (Of course, if you write your session class such that
52 | # has_info() always returns true after a request has been
53 | # processed, you're back to the original problem -- and in fact,
54 | # this class *has* been written that way, because num_requests
55 | # is incremented on every request, which makes has_info() return
56 | # true, which makes SessionManager always store the session
57 | # object. In a real application, think carefully before putting
58 | # data in a session object that causes has_info() to return
59 | # true.)
60 |
61 | return (self.num_requests > 0) or Session.has_info(self)
62 |
63 | is_dirty = has_info
64 |
65 |
66 | class DirMapping:
67 | """A mapping object that stores values as individual pickle
68 | files all in one directory. You wouldn't want to use this in
69 | production unless you're using a filesystem optimized for
70 | handling large numbers of small files, like ReiserFS. However,
71 | it's pretty easy to implement and understand, it doesn't require
72 | any external libraries, and it's really easy to browse the
73 | "database".
74 | """
75 |
76 | def __init__ (self, save_dir=None):
77 | self.set_save_dir(save_dir)
78 | self.cache = {}
79 | self.cache_time = {}
80 |
81 | def set_save_dir (self, save_dir):
82 | self.save_dir = save_dir
83 | if save_dir and not os.path.isdir(save_dir):
84 | os.mkdir(save_dir, 0700)
85 |
86 | def keys (self):
87 | return os.listdir(self.save_dir)
88 |
89 | def values (self):
90 | # This is pretty expensive!
91 | return [self[id] for id in self.keys()]
92 |
93 | def items (self):
94 | return [(id, self[id]) for id in self.keys()]
95 |
96 | def _gen_filename (self, session_id):
97 | return os.path.join(self.save_dir, session_id)
98 |
99 | def __getitem__ (self, session_id):
100 |
101 | filename = self._gen_filename(session_id)
102 | if (self.cache.has_key(session_id) and
103 | os.stat(filename)[ST_MTIME] <= self.cache_time[session_id]):
104 | return self.cache[session_id]
105 |
106 | if os.path.exists(filename):
107 | try:
108 | file = open(filename, "rb")
109 | try:
110 | print "loading session from %r" % file
111 | session = load(file)
112 | self.cache[session_id] = session
113 | self.cache_time[session_id] = time()
114 | return session
115 | finally:
116 | file.close()
117 | except IOError, err:
118 | raise KeyError(session_id,
119 | "error reading session from %s: %s"
120 | % (filename, err))
121 | else:
122 | raise KeyError(session_id,
123 | "no such file %s" % filename)
124 |
125 | def get (self, session_id, default=None):
126 | try:
127 | return self[session_id]
128 | except KeyError:
129 | return default
130 |
131 | def has_key (self, session_id):
132 | return os.path.exists(self._gen_filename(session_id))
133 |
134 | def __setitem__ (self, session_id, session):
135 | filename = self._gen_filename(session.id)
136 | file = open(filename, "wb")
137 | print "saving session to %s" % file
138 | dump(session, file, 1)
139 | file.close()
140 |
141 | self.cache[session_id] = session
142 | self.cache_time[session_id] = time()
143 |
144 | def __delitem__ (self, session_id):
145 | filename = self._gen_filename(session_id)
146 | if os.path.exists(filename):
147 | os.remove(filename)
148 | if self.cache.has_key(session_id):
149 | del self.cache[session_id]
150 | del self.cache_time[session_id]
151 | else:
152 | raise KeyError(session_id, "no such file: %s" % filename)
153 |
154 |
155 | # This is mostly the same as the standard boilerplate for any Quixote
156 | # driver script. The main difference is that we have to instantiate a
157 | # session manager, and use SessionPublisher instead of the normal
158 | # Publisher class. Just like demo.cgi, we use demo.conf to setup log
159 | # files and ensure that error messages are more informative than secure.
160 |
161 | # You can use the 'shelve' module to create an alternative persistent
162 | # mapping to the DirMapping class above.
163 | #import shelve
164 | #sessions = shelve.open("/tmp/quixote-sessions")
165 |
166 | enable_ptl()
167 | sessions = DirMapping(save_dir="/tmp/quixote-session-demo")
168 | session_mgr = SessionManager(session_class=DemoSession,
169 | session_mapping=sessions)
170 | app = SessionPublisher('quixote.demo.session', session_mgr=session_mgr)
171 | app.read_config("demo.conf")
172 | app.setup_logs()
173 | app.publish_cgi()
174 |
--------------------------------------------------------------------------------
/quixote/demo/upload.cgi:
--------------------------------------------------------------------------------
1 | #!/www/python/bin/python
2 |
3 | # Simple demo of HTTP upload with Quixote. Also serves as an example
4 | # of how to put a (simple) Quixote application into a single file.
5 |
6 | __revision__ = "$Id: upload.cgi 21182 2003-03-17 21:46:52Z gward $"
7 |
8 | import os
9 | import stat
10 | from quixote import Publisher
11 | from quixote.html import html_quote
12 |
13 | _q_exports = ['receive']
14 |
15 | def header (title):
16 | return '''\
17 | %s
18 |
19 | ''' % title
20 |
21 | def footer ():
22 | return '\n'
23 |
24 | def _q_index (request):
25 | return header("Quixote Upload Demo") + '''\
26 |
35 | ''' + footer()
36 |
37 | def receive (request):
38 | result = []
39 | name = request.form.get("name")
40 | if name:
41 | result.append("
Thanks, %s!
" % html_quote(name))
42 |
43 | upload = request.form.get("upload")
44 | size = os.stat(upload.tmp_filename)[stat.ST_SIZE]
45 | if not upload.base_filename or size == 0:
46 | title = "Empty Upload"
47 | result.append("
It took you %.1f sec to fill out and submit the form
\n"
124 | % (now - form_time))
125 |
126 | """\
127 |
128 |
129 | """
130 |
--------------------------------------------------------------------------------
/quixote/errors.py:
--------------------------------------------------------------------------------
1 | """quixote.errors
2 | $HeadURL: svn+ssh://svn/repos/trunk/quixote/errors.py $
3 | $Id$
4 |
5 | Exception classes used by Quixote
6 | """
7 | from quixote.html import htmltext, htmlescape
8 |
9 | __revision__ = "$Id$"
10 |
11 |
12 | class PublishError(Exception):
13 | """PublishError exceptions are raised due to some problem with the
14 | data provided by the client and are raised during the publishing
15 | process. Quixote will abort the current request and return an error
16 | page to the client.
17 |
18 | public_msg should be a user-readable message that reveals no
19 | inner workings of your application; it will always be shown.
20 |
21 | private_msg will only be shown if the config option SECURE_ERRORS is
22 | false; Quixote uses this to give you more detail about why the error
23 | occurred. You might want to use it for similar, application-specific
24 | information. (SECURE_ERRORS should always be true in a production
25 | environment, since these details about the inner workings of your
26 | application could conceivably be useful to attackers.)
27 |
28 | The formatting done by the Quixote versions of these exceptions is
29 | very simple. Applications will probably wish to raise application
30 | specific subclasses which do more sophisticated formatting or provide
31 | a _q_except handler to format the exception.
32 |
33 | """
34 |
35 | status_code = 400 # bad request
36 | title = "Publishing error"
37 | description = "no description"
38 |
39 | def __init__(self, public_msg=None, private_msg=None):
40 | self.public_msg = public_msg
41 | self.private_msg = private_msg # cleared if SECURE_ERRORS is true
42 |
43 | def __str__(self):
44 | return self.private_msg or self.public_msg or "???"
45 |
46 | def format(self, request):
47 | msg = htmlescape(self.title)
48 | if not isinstance(self.title, htmltext):
49 | msg = str(msg) # for backwards compatibility
50 | if self.public_msg:
51 | msg = msg + ": " + self.public_msg
52 | if self.private_msg:
53 | msg = msg + ": " + self.private_msg
54 | return msg
55 |
56 |
57 | class TraversalError(PublishError):
58 | """
59 | Raised when a client attempts to access a resource that does not
60 | exist or is otherwise unavailable to them (eg. a Python function
61 | not listed in its module's _q_exports list).
62 |
63 | path should be the path to the requested resource; if not
64 | supplied, the current request object will be fetched and its
65 | get_path() method called.
66 | """
67 |
68 | status_code = 404 # not found
69 | title = "Page not found"
70 | description = ("The requested link does not exist on this site. If "
71 | "you arrived here by following a link from an external "
72 | "page, please inform that page's maintainer.")
73 |
74 | def __init__(self, public_msg=None, private_msg=None, path=None):
75 | PublishError.__init__(self, public_msg, private_msg)
76 | if path is None:
77 | import quixote
78 | path = quixote.get_request().get_path()
79 | self.path = path
80 |
81 | def format(self, request):
82 | msg = htmlescape(self.title) + ": " + self.path
83 | if not isinstance(self.title, htmltext):
84 | msg = str(msg) # for backwards compatibility
85 | if self.public_msg:
86 | msg = msg + ": " + self.public_msg
87 | if self.private_msg:
88 | msg = msg + ": " + self.private_msg
89 | return msg
90 |
91 | class RequestError(PublishError):
92 | """
93 | Raised when Quixote is unable to parse an HTTP request (or its CGI
94 | representation). This is a lower-level error than QueryError -- it
95 | either means that Quixote is not smart enough to handle the request
96 | being passed to it, or the user-agent is broken and/or malicious.
97 | """
98 | status_code = 400
99 | title = "Invalid request"
100 | description = "Unable to parse HTTP request."
101 |
102 |
103 | class QueryError(PublishError):
104 | """Should be raised if bad data was provided in the query part of a
105 | URL or in the content of a POST request. What constitutes bad data is
106 | solely application dependent (eg: letters in a form field when the
107 | application expects a number).
108 | """
109 |
110 | status_code = 400
111 | title = "Invalid query"
112 | description = ("An error occurred while handling your request. The "
113 | "query data provided as part of the request is invalid.")
114 |
115 |
116 |
117 | class AccessError(PublishError):
118 | """Should be raised if the client does not have access to the
119 | requested resource. Usually applications will raise this error from
120 | an _q_access method.
121 | """
122 |
123 | status_code = 403
124 | title = "Access denied"
125 | description = ("An error occurred while handling your request. "
126 | "Access to the requested resource was not permitted.")
127 |
128 |
129 |
130 | class SessionError(PublishError):
131 | """Raised when a session cookie has an invalid session ID. This
132 | could be either a broken/malicious client or an expired session.
133 | """
134 |
135 | status_code = 400
136 | title = "Expired or invalid session"
137 | description = ("Your session is invalid or has expired. "
138 | "Please reload this page to start a new session.")
139 |
140 | def __init__(self, public_msg=None, private_msg=None, session_id=None):
141 | PublishError.__init__(self, public_msg, private_msg)
142 | self.session_id = session_id
143 |
144 | def format(self, request):
145 | from quixote import get_session_manager
146 | get_session_manager().revoke_session_cookie(request)
147 | msg = PublishError.format(self, request)
148 | if self.session_id:
149 | msg = msg + ": " + self.session_id
150 | return msg
151 |
152 |
153 | def default_exception_handler(request, exc):
154 | """(request : HTTPRequest, exc : PublishError) -> string
155 |
156 | Format a PublishError exception as a web page. This is the default
157 | handler called if no '_q_exception_handler' function was found while
158 | traversing the path.
159 | """
160 | return htmltext("""\
161 |
163 |
164 | Error: %s
165 |
166 |