diff options
Diffstat (limited to 'www/users_guide/webware.rst')
-rw-r--r-- | www/users_guide/webware.rst | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/www/users_guide/webware.rst b/www/users_guide/webware.rst new file mode 100644 index 0000000..75237ed --- /dev/null +++ b/www/users_guide/webware.rst @@ -0,0 +1,598 @@ +Using Cheetah with Webware +========================== + +(webware) + +{ Webware for Python} is a 'Python-Powered Internet Platform' that +runs servlets in a manner similar to Java servlets. { WebKit} is +the name of Webware's application server. For more details, please +visit http://webware.sourceforge.net/. + +All comments below refer to the official version of Webware, the +DamnSimple! offshoot at ?, and the now-abandoned +WebwareExperimental implementation at +http://sourceforge.net/projects/expwebware/, except where noted. +All the implementations are 95% identical to the servlet writer: +their differences lie in their internal structure and configuration +files. One difference is that the executable you run to launch +standard Webware is called {AppServer}, whereas in +WebwareExperimental it's called {webkit}. But to servlets they're +both "WebKit, Webware's application server", so it's one half dozen +to the other. In this document, we generally use the term { WebKit} +to refer to the currently-running application server. + +Installing Cheetah on a Webware system +-------------------------------------- + +(webware.installing) + +Install Cheetah after you have installed Webware, following the +instructions in chapter gettingStarted. + +The standard Cheetah test suite ('cheetah test') does not test +Webware features. We plan to build a test suite that can run as a +Webware servlet, containing Webware-specific tests, but that has +not been built yet. In the meantime, you can make a simple template +containing something like "This is a very small template.", compile +it, put the \*.py template module in a servlet directory, and see +if Webware serves it up OK. + +{ You must not have a Webware context called "Cheetah".} If you do, +Webware will mistake that directory for the Cheetah module +directory, and all template-servlets will bomb out with a +"ImportError: no module named Template". (This applies only to the +standard Webware; WebwareExperimental does not have contexts.) + +If Webware complains that it cannot find your servlet, make sure +'.tmpl' is listed in 'ExtensionsToIgnore' in your +'Application.config' file. + +Containment vs Inheritance +-------------------------- + +(webware.background) + +Because Cheetah's core is flexible, there are many ways to +integrate it with Webware servlets. There are two broad strategies: +the { Inheritance approach} and the { Containment approach}. The +difference is that in the Inheritance approach, your template +object { is} the servlet, whereas in the Containment approach, the +servlet is not a template but merely { uses} template(s) for +portion(s) of its work. + +The Inheritance approach is recommended for new sites because it's +simpler, and because it scales well for large sites with a +site->section->subsection->servlet hierarchy. The Containment +approach is better for existing servlets that you don't want to +restructure. For instance, you can use the Containment approach to +embed a discussion-forum table at the bottom of a web page. + +However, most people who use Cheetah extensively seem to prefer the +Inheritance approach because even the most analytical servlet needs +to produce { some} output, and it has to fit the site's look and +feel { anyway}, so you may as well use a template-servlet as the +place to put the output. Especially since it's so easy to add a +template-servlet to a site once the framework is established. So we +recommend you at least evaluate the effort that would be required +to convert your site framework to template superclasses as +described below, vs the greater flexibility and manageability it +might give the site over the long term. You don't necessarily have +to convert all your existing servlets right away: just build common +site templates that are visually and behaviorally compatible with +your specification, and use them for new servlets. Existing +servlets can be converted later, if at all. + +Edmund Liam is preparing a section on a hybrid approach, in which +the servlet is not a template, but still calls template(s) in an +inheritance chain to produce the output. The advantage of this +approach is that you aren't dealing with {Template} methods and +Webware methods in the same object. + +The Containment Approach +~~~~~~~~~~~~~~~~~~~~~~~~ + +(webware.containment) + +In the Containment approach, your servlet is not a template. +Instead, it it makes its own arrangements to create and use +template object(s) for whatever it needs. The servlet must +explicitly call the template objects' {.respond()} (or +{.\_\_str\_\_()}) method each time it needs to fill the template. +This does not present the output to the user; it merely gives the +output to the servlet. The servlet then calls its +{#self.response().write()} method to send the output to the user. + +The developer has several choices for managing her templates. She +can store the template definition in a string, file or database and +call {Cheetah.Template.Template} manually on it. Or she can put the +template definition in a \*.tmpl file and use { cheetah compile} +(section howWorks.cheetah-compile) to convert it to a Python class +in a \*.py module, and then import it into her servlet. + +Because template objects are not thread safe, you should not store +one in a module variable and allow multiple servlets to fill it +simultaneously. Instead, each servlet should instantiate its own +template object. Template { classes}, however, are thread safe, +since they don't change once created. So it's safe to store a +template class in a module global variable. + +The Inheritance Approach +~~~~~~~~~~~~~~~~~~~~~~~~ + +(webware.inheritance) + +In the Inheritance approach, your template object doubles as as +Webware servlet, thus these are sometimes called { +template-servlets}. { cheetah compile} (section +howWorks.cheetah-compile) automatically creates modules containing +valid Webware servlets. A servlet is a subclass of Webware's +{WebKit.HTTPServlet} class, contained in a module with the same +name as the servlet. WebKit uses the request URL to find the +module, and then instantiates the servlet/template. The servlet +must have a {.respond()} method (or {.respondToGet()}, +{.respondToPut()}, etc., but the Cheetah default is {.respond()}). +Servlets created by {cheetah compile} meet all these requirements. + +(Cheetah has a Webware plugin that automatically converts a {.tmpl +servlet file} into a {.py servlet file} when the {.tmpl servlet +file} is requested by a browser. However, that plugin is currently +unavailable because it's being redesigned. For now, use {cheetah +compile} instead.) + +What about logic code? Cheetah promises to keep content (the +placeholder values), graphic design (the template definition and is +display logic), and algorithmic logic (complex calculations and +side effects) separate. How? Where do you do form processing? + +The answer is that your template class can inherit from a pure +Python class containing the analytical logic. You can either use +the {#extends} directive in Cheetah to indicate the superclass(es), +or write a Python {class} statement to do the same thing. See the +template {Cheetah.Templates.SkeletonPage.tmpl} and its pure Python +class {Cheetah.Templates.\_SkeletonPage.py} for an example of a +template inheriting logic code. (See sections +inheritanceEtc.extends and inheritanceEtc.implements for more +information about {#extends} and {#implements}. They have to be +used a certain right way.) + +If {#WebKit.HTTPServlet} is not available, Cheetah fakes it with a +dummy class to satisfy the dependency. This allows servlets to be +tested on the command line even on systems where Webware is not +installed. This works only with servlets that don't call back into +WebKit for information about the current web transaction, since +there is no web transaction. Trying to access form input, for +instance, will raise an exception because it depends on a live web +request object, and in the dummy class the request object is +{None}. + +Because Webware servlets must be valid Python modules, and +"cheetah compile" can produce only valid module names, if you're +converting an existing site that has .html filenames with hyphens +(-), extra dots (.), etc, you'll have to rename them (and possibly +use redirects). + +Site frameworks +--------------- + +(webware.siteFrameworks) + +Web sites are normally arranged hierarchically, with certain +features common to every page on the site, other features common to +certain sections or subsections, and others unique to each page. +You can model this easily with a hierarchy of classes, with +specific servlets inheriting from their more general superclasses. +Again, you can do this two ways, using Cheetah's { Containment} +approach or { Inheritance} approach. + +In the Inheritance approach, parents provide {#block}s and children +override them using {#def}. Each child {#extend}s its immediate +parent. Only the leaf servlets need to be under WebKit's document +root directory. The superclass servlets can live anywhere in the +filesystem that's in the Python path. (You may want to modify your +WebKit startup script to add that library directory to your +{PYTHONPATH} before starting WebKit.) + +Section libraries.templates.skeletonPage contains information on a +stock template that simplifies defining the basic HTML structure of +your web page templates. + +In the Containment approach, your hierarchy of servlets are not +templates, but each uses one or more templates as it wishes. +Children provide callback methods to to produce the various +portions of the page that are their responsibility, and parents +call those methods. Webware's {WebKit.Page} and +{WebKit.SidebarPage} classes operate like this. + +Note that the two approaches are not compatible! {WebKit.Page} was +not designed to intermix with {Cheetah.Templates.SkeletonPage}. +Choose either one or the other, or expect to do some integration +work. + +If you come up with a different strategy you think is worth noting +in this chapter, let us know. + +Directory structure +------------------- + +(webware.directoryStructure) + +Here's one way to organize your files for Webware+Cheetah. + +:: + + www/ # Web root directory. + site1.example.com/ # Site subdirectory. + apache/ # Web server document root (for non-servlets). + www/ # WebKit document root. + index.py # http://site1.example.com/ + index.tmpl # Source for above. + servlet2.py # http://site1.example.com/servlet2 + servlet2.tmpl # Source for above. + lib/ # Directory for helper classes. + Site.py # Site superclass ("#extends Site"). + Site.tmpl # Source for above. + Logic.py # Logic class inherited by some template. + webkit.config # Configuration file (for WebwareExperimental). + Webware/ # Standard Webware's MakeAppWorkDir directory. + AppServer # Startup program (for standard Webware). + Configs/ # Configuration directory (for standard Webware). + Application.config + # Configuration file (for standard Webware). + site2.example.org/ # Another virtual host on this computer.... + +Initializing your template-servlet with Python code +--------------------------------------------------- + +(webware.calculations) + +If you need a place to initialize variables or do calculations for +your template-servlet, you can put it in an {.awake()} method +because WebKit automatically calls that early when processing the +web transaction. If you do override {.awake()}, be sure to call the +superclass {.awake} method. You probably want to do that first so +that you have access to the web transaction data {Servlet.awake} +provides. You don't have to worry about whether your parent class +has its own {.awake} method, just call it anyway, and somebody up +the inheritance chain will respond, or at minimum {Servlet.awake} +will respond. Section tips.callingSuperclassMethods gives examples +of how to call a superclass method. + +As an alternative, you can put all your calculations in your own +method and call it near the top of your template. ({#silent}, +section output.silent). + +Form processing +--------------- + +(webware.form) + +There are many ways to display and process HTML forms with Cheetah. +But basically, all form processing involves two steps. + + +#. Display the form. + +#. In the next web request, read the parameters the user submitted, + check for user errors, perform any side effects (e.g., + reading/writing a database or session data) and present the user an + HTML response or another form. + + +The second step may involve choosing between several templates to +fill (or several servlets to redirect to), or a big +if-elif-elif-else construct to display a different portion of the +template depending on the situation. + +In the oldest web applications, step 1 and step 2 were handled by +separate objects. Step 1 was a static HTML file, and step 2 was a +CGI script. Frequently, a better strategy is to have a single +servlet handle both steps. That way, the servlet has better control +over the entire situation, and if the user submits unacceptable +data, the servlet can redisplay the form with a "try again" error +message at the top and and all the previous input filled in. The +servlet can use the presence or absence of certain CGI parameters +(e.g., the submit button, or a hidden mode field) to determine +which step to take. + +One neat way to build a servlet that can handle both the form +displaying and form processing is like this: + + +#. Put your form HTML into an ordinary template-servlet. In each + input field, use a placeholder for the value of the {VALUE=} + attribue. Place another placeholder next to each field, for that + field's error message. + +#. Above the form, put a {$processFormData} method call. + +#. Define that method in a Python class your template {#extend}s. + (Or if it's a simple method, you can define it in a {#def}.) The + method should: + + + #. Get the form input if any. + + #. If the input variable corresponding to the submit field is + empty, there is no form input, so we're showing the form for the + first time. Initialize all VALUE= variables to their default value + (usually ""), and all error variables to "". Return "", which will + be the value for {$processFormData}. + + #. If the submit variable is not empty, fill the VALUE= variables + with the input data the user just submitted. + + #. Now check the input for errors and put error messages in the + error placeholders. + + #. If there were any user errors, return a general error message + string; this will be the value for {$processFormData}. + + #. If there were no errors, do whatever the form's job is (e.g., + update a database) and return a success message; this will be the + value for {$processFormData}. + + +#. The top of the page will show your success/failure message (or + nothing the first time around), with the form below. If there are + errors, the user will have a chance to correct them. After a + successful submit, the form will appear again, so the user can + either review their entry, or change it and submit it again. + Depending on the application, this may make the servlet update the + same database record again, or it may generate a new record. + + +{FunFormKit} is a third-party Webware package that makes it easier +to produce forms and handle their logic. It has been successfully +been used with Cheetah. You can download FunFormKit from +http://colorstudy.net/software/funformkit/ and try it out for +yourself. + +Form input, cookies, session variables and web server variables +--------------------------------------------------------------- + +(webware.input) + +General variable tips that also apply to servlets are in section +tips.placeholder. + +To look up a CGI GET or POST parameter (with POST overriding): + +:: + + $request.field('myField') + self.request().field('myField') + +These will fail if Webware is not available, because {$request} +(aka {self.request()} will be {None} rather than a Webware +{WebKit.Request} object. If you plan to read a lot of CGI +parameters, you may want to put the {.fields} method into a local +variable for convenience: + +:: + + #set $fields = $request.fields + $fields.myField + +But remember to do complicated calculations in Python, and assign +the results to simple variables in the searchList for display. +These {$request} forms are useful only for occasions where you just +need one or two simple request items that going to Python for would +be overkill. + +To get a cookie or session parameter, subsitute "cookie" or +"session" for "field" above. To get a dictionary of all CGI +parameters, substitute "fields" (ditto for "cookies"). To verify a +field exists, substitute "hasField" (ditto for "hasCookie"). + +Other useful request goodies: + +:: + + ## Defined in WebKit.Request + $request.field('myField', 'default value') + $request.time ## Time this request began in Unix ticks. + $request.timeStamp ## Time in human-readable format ('asctime' format). + ## Defined in WebKit.HTTPRequest + $request.hasField.myField ## Is a CGI parameter defined? + $request.fields ## Dictionary of all CGI parameters. + $request.cookie.myCookie ## A cookie parameter (also .hasCookie, .cookies). + $request.value.myValue ## A field or cookie variable (field overrides) + ## (also .hasValue). + $request.session.mySessionVar # A session variable. + $request.extraURLPath ## URL path components to right of servlet, if any. + $request.serverDictionary ## Dict of environmental vars from web server. + $request.remoteUser ## Authenticated username. HTTPRequest.py source + ## suggests this is broken and always returns None. + $request.remoteAddress ## User's IP address (string). + $request.remoteName ## User's domain name, or IP address if none. + $request.urlPath ## URI of this servlet. + $request.urlPathDir ## URI of the directory containing this servlet. + $request.serverSidePath ## Absolute path of this servlet on local filesystem. + $request.serverURL ## URL of this servlet, without "http://" prefix, + ## extra path info or query string. + $request.serverURLDir ## URL of this servlet's directory, without "http://". + $log("message") ## Put a message in the Webware server log. (If you + ## define your own 'log' variable, it will override + ## this; use $self.log("message") in that case. + +.webInput() +~~~~~~~~~~~ + +(webware.webInput) + +From the method docstring: + +:: + + def webInput(self, names, namesMulti=(), default='', src='f', + defaultInt=0, defaultFloat=0.00, badInt=0, badFloat=0.00, debug=False): + + This method places the specified GET/POST fields, cookies or session variables + into a dictionary, which is both returned and put at the beginning of the + searchList. It handles: + * single vs multiple values + * conversion to integer or float for specified names + * default values/exceptions for missing or bad values + * printing a snapshot of all values retrieved for debugging + All the 'default*' and 'bad*' arguments have "use or raise" behavior, meaning + that if they're a subclass of Exception, they're raised. If they're anything + else, that value is substituted for the missing/bad value. + + The simplest usage is: + + #silent $webInput(['choice']) + $choice + + dic = self.webInput(['choice']) + write(dic['choice']) + + Both these examples retrieves the GET/POST field 'choice' and print it. If you + leave off the "#silent", all the values would be printed too. But a better way + to preview the values is + + #silent $webInput(['name'], $debug=1) + + because this pretty-prints all the values inside HTML <PRE> tags. + + Since we didn't specify any coversions, the value is a string. It's a "single" + value because we specified it in 'names' rather than 'namesMulti'. Single + values work like this: + * If one value is found, take it. + * If several values are found, choose one arbitrarily and ignore the rest. + * If no values are found, use or raise the appropriate 'default*' value. + + Multi values work like this: + * If one value is found, put it in a list. + * If several values are found, leave them in a list. + * If no values are found, use the empty list ([]). The 'default*' + arguments are *not* consulted in this case. + + Example: assume 'days' came from a set of checkboxes or a multiple combo box + on a form, and the user chose "Monday", "Tuesday" and "Thursday". + + #silent $webInput([], ['days']) + The days you chose are: #slurp + #for $day in $days + $day #slurp + #end for + + dic = self.webInput([], ['days']) + write("The days you chose are: ") + for day in dic['days']: + write(day + " ") + + Both these examples print: "The days you chose are: Monday Tuesday Thursday". + + By default, missing strings are replaced by "" and missing/bad numbers by zero. + (A "bad number" means the converter raised an exception for it, usually because + of non-numeric characters in the value.) This mimics Perl/PHP behavior, and + simplifies coding for many applications where missing/bad values *should* be + blank/zero. In those relatively few cases where you must distinguish between + ""/zero on the one hand and missing/bad on the other, change the appropriate + 'default*' and 'bad*' arguments to something like: + * None + * another constant value + * $NonNumericInputError/self.NonNumericInputError + * $ValueError/ValueError + (NonNumericInputError is defined in this class and is useful for + distinguishing between bad input vs a TypeError/ValueError + thrown for some other reason.) + + Here's an example using multiple values to schedule newspaper deliveries. + 'checkboxes' comes from a form with checkboxes for all the days of the week. + The days the user previously chose are preselected. The user checks/unchecks + boxes as desired and presses Submit. The value of 'checkboxes' is a list of + checkboxes that were checked when Submit was pressed. Our task now is to + turn on the days the user checked, turn off the days he unchecked, and leave + on or off the days he didn't change. + + dic = self.webInput([], ['dayCheckboxes']) + wantedDays = dic['dayCheckboxes'] # The days the user checked. + for day, on in self.getAllValues(): + if not on and wantedDays.has_key(day): + self.TurnOn(day) + # ... Set a flag or insert a database record ... + elif on and not wantedDays.has_key(day): + self.TurnOff(day) + # ... Unset a flag or delete a database record ... + + 'source' allows you to look up the variables from a number of different + sources: + 'f' fields (CGI GET/POST parameters) + 'c' cookies + 's' session variables + 'v' "values", meaning fields or cookies + + In many forms, you're dealing only with strings, which is why the + 'default' argument is third and the numeric arguments are banished to + the end. But sometimes you want automatic number conversion, so that + you can do numeric comparisons in your templates without having to + write a bunch of conversion/exception handling code. Example: + + #silent $webInput(['name', 'height:int']) + $name is $height cm tall. + #if $height >= 300 + Wow, you're tall! + #else + Pshaw, you're short. + #end if + + dic = self.webInput(['name', 'height:int']) + name = dic[name] + height = dic[height] + write("%s is %s cm tall." % (name, height)) + if height > 300: + write("Wow, you're tall!") + else: + write("Pshaw, you're short.") + + To convert a value to a number, suffix ":int" or ":float" to the name. The + method will search first for a "height:int" variable and then for a "height" + variable. (It will be called "height" in the final dictionary.) If a numeric + conversion fails, use or raise 'badInt' or 'badFloat'. Missing values work + the same way as for strings, except the default is 'defaultInt' or + 'defaultFloat' instead of 'default'. + + If a name represents an uploaded file, the entire file will be read into + memory. For more sophisticated file-upload handling, leave that name out of + the list and do your own handling, or wait for Cheetah.Utils.UploadFileMixin. + + This mixin class works only in a subclass that also inherits from + Webware's Servlet or HTTPServlet. Otherwise you'll get an AttributeError + on 'self.request'. + + EXCEPTIONS: ValueError if 'source' is not one of the stated characters. + TypeError if a conversion suffix is not ":int" or ":float". + +More examples +------------- + +(webware.examples) + +Example A - a standalone servlet +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Example B - a servlet under a site framework +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Example C - several servlets with a common template +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Other Tips +---------- + +(webware.otherTips) + +If your servlet accesses external files (e.g., via an {#include} +directive), remember that the current directory is not necessarily +directory the servlet is in. It's probably some other directory +WebKit chose. To find a file relative to the servlet's directory, +prefix the path with whatever {self.serverSidePath()} returns (from +{Servlet.serverSidePath()}. + +If you don't understand how {#extends} and {#implements} work, and +about a template's main method, read the chapter on inheritance +(sections inheritanceEtc.extends and inheritanceEtc.implements). +This may help you avoid buggy servlets. + + |