Webware, FunFormKit och Cheetah Template
Erik Forsberg, Lysator ACS![Lysator Logo](/pictures/lyslogo.gif)
forsberg@lysator.liu.se
http://www.lysator.liu.se/~forsberg/
Agenda
- Webware
- Cheetah Template
- Webware + Cheetah Template
- FunFormKit
- WebWare + FunFormKit + Cheetah Template
Uppmaning och ursäkt
- Ställ frågor!
- Ja, jag vet. Ibland är det svengelska..
Vad är Webware?
- Tomcat, fast s/Java/Python/.
- Applikationsserver.
- Servlets i Python anropas via Webben.
- Objektorienterad arkitektur (Halleluja!)
- Python-style license.
Hur fungerar det?
- Apache eller annan webserver tar emot requesten.
- Vidarebefordrar till persistent Pythonprocess via localhost:8086.
- Pythonprocessen behandlar och skickar tillbaka output.
- Samma tanke som Tomcat bakom webserver (mod_jk/mod_jserv).
- Webservern kan med fördel servera statiskt innehåll.
- Never underestimate the power of mod_rewrite :-).
Webserverintegration
- Apache 1.3.x eller 2.0.x
- OpenSA.
- Xitami.
- IIS.
- via CGI.
I 0.8 finns även en egen webserver.
Uppbyggnad
En bunt "Kits".
- WebKit
- MiscUtils
- TaskKit - för att schedulera jobb vid bestämda tidpunkter.
- FunFormKit - från tredjepart.
- MiddleKit - kommer jag bara nämna.
- PSP - kommer jag inte att prata om.
- WebUtils - kodning/avkodning av HTML etc.
WebKit
Hjärtat av Webware. Snabb och lättanvänd applikationsserver.
- Multitrådad.
- Fungerar under både Unix och Windows.
- Stabil.
Servlets
- Pythonklasser i moduler med samma namn
- FooPage ska vara klassen FooPage i modulen FooPage.
- Servlets måste ärva från WebKit.Servlet eller dess två subklasser
- WebKit.HTTPServlet
- WebKit.Page
Jag brukar ärva från WebKit.Page eller någon av dess bekvämlighetssubklasser.
- Vanligt är att implementera en siteglobal SitePage som alla Servlets ärver från.
Exempelservlet
from WebKit.Page import Page
class TestServlet(Page):
def writeContent(self):
self.write("Hello, World!")
TestServlet
Contexts
- Servlets bor i Context.
- Ett Context är ett pythonpaket, dvs det finns en __init__.py
- __init__.py kan vara tom. Precis som med vanliga pythonpaket.
- Application.config i katalogen Configs mappar mellan URL och Context.
- Servleten med namnet Main anropas för / i ett Context.
Hjälpmoduler
- Hjälpmoduler placeras lämpligen i ett eget paket.
- ..så att de inte av misstag anropas som servlets.
- SitePage etc.
Arbetskataloger
- WebKit kan köras direkt från installationskatalogen.
- Bättre är att skapa en arbetskatalog.
- Enklare vid uppgraderingar.
- Möjligt att köra flera Webware.
Exempel på arbetskatalog
hostname:erik /s/lysmemb % ls -l
404Text.txt | | | Texten som visas vid 404 |
AppServer | | | Unix-varianten av AppServer |
AppServer.bat | | | Windows-varianten av AppServer |
Cache/ | | | Gissa! |
Cans/ | | | Kvarleva från gammal idé |
Configs/ | | | Konfigurationsfiler |
| | Application.config | Innehåller bland annat Contextdefinitioner |
| | AppServer.config | Diverse runtimekonfiguration för AppServer |
ErrorMsgs/ | | | Vid exception lagras felsidan här |
Launch.py | | | Används av AppServer.bat |
Logs/ | | | Index över felen i ErrorMsgs |
MyContext/ | | | Ett exempelcontext. Måste definieras i Application.config |
NTService.py | | | Används om man kör AppServer som en service i Windows. |
OneShot.cgi | | | Startar upp WebKit, kör requesten och dödar. Rekommenderas inte längre. |
Sessions/ | | | Disklagrade sessioner. |
WebKit.cgi | | | CGI-script som pratar med WebKit på port 8086 |
address.text | | | Anger adressen WebKit lyssnar på. |
appserverpid.txt | | | Anger pid för AppServer. |
AppServer, WebKit.cgi, OneShot.cgi
- AppServer är den persistenta pythonprocessen.
- WebKit.cgi kan användas i stället för mod_webkit
- OneShot.cgi kan användas under utveckling.
- Bättre att använda AppServer med autoreload.
Debug
- print hamnar på AppServers stdout
- Application.config/IncludeFancyTracebacks ger en massa extra info efter din sida.
Requestens väg genom WebKit
- Givet Apache på localhost.
- Givet mod_webkit konfigurerad så att /WK skickas till WebKit.
- Givet ett Context inkonfigurerat i URLspace som 'UppLYSning'.
- http://localhost/WK/UppLYSning/TestServlet
- http://localhost/WK/ får Apache att skicka requesten till WebKit
- /UppLYSning/ får WebKit att aktivera UppLYSning
- TestServlet aktiverar servleten vid namn TestServlet
- En instans av TestServlet hämtas från en pool av sådana. Alternativt skapas en ny.
- Ett transaktionsobjekt skapas.
- Metoderna nedan anropas på instansen av TestServlet
- Servlet.awake(transaction)
- Servlet.respond(transaction)
- Servlet.sleep(transaction)
- Instansen av TestServlet returneras till poolen.
Transaktionsobjektet
- Request
- Response
- Servlet
- Session
- Application
HTTPRequest
- self.request()
- .field(name, [default])
- .hasField(name)
- .fields()
- .cookie(name, [default])
- .hasCookie(name, [default])
- .cookies()
- .value(name, [default])
- .hasValue(name)
- .values(name)
HTTPResponse
- self.response()
- .write(text)
- .setHeader(name, value)
- .flush()
- .sendRedirect(url)
Page : bekvämlighetsmetoder
- .write() - samma sak som .response().write()
- .writeln() - som .write(), men med \n på slutet.
- .forward()
- .includeURL()
- .callMethodOfServlet()
Page : metoder som anropas under en request
- .respond() anropar normalt .writeHTML()
- .writeHTML() anropar
- .writeDoctType()
- .writeln('<html>')
- .writeHead()
- .writeBody()
- .writeln('</html>')
Page : .writeBody()
- .writeBody() anropar:
- .write('<body %s>' % self.htBodyArgs())
- .writeBodyParts() som anropar:
- .writeContent() - metoden du oftast själv implementerar
- .write('</body>')
Actions
Ett sätt att associera olika submitknappar med olika servletmetoder.
<input name="_action_add" type="submit" value="Add Widget">
- Implementera .actions():
def actions(self):
return ['add', 'delete']
- .respond() kollar efter ett fält vid namn _action_ACTIONNAME
- Din metod anropas efter utskrift av <html> och <head>.
Sessioner
- Lagra användarspecifik data från ett anrop till ett annat.
- Automatexpirerar efter ett antal minuter som sätts i Application.config
- Lagras antingen i minnet eller på disk. Oftast i kombination.
- Interface:
- self.session().value(name, [default])
- self.session().hasValue(name)
- self.session().values()
- self.session().setValue(name, value)
- Alla sessioner sparas på disk när AppServer stoppas.
- AppServer kan alltså startas om utan att paja sessioner.
- Användaren hålls reda på med Cookie (_SID_) eller kodning i URL.
- Application.config/useAutomaticPathSessions
Tracebacks
- WebKit spottar ur sig en schysst felrapport vid exceptions.
- Den kan också maila dem till dig.
Felsida
Varning: Opedagogiskt (felaktigt) exempel följer :-)
from WebKit.Page import Page
class ErrorServlet(Page):
def writeContent(self):
slf.write("Hello, World!<p>")
ErrorServlet
Evig sanning
- Dina användare kommer inte att rapportera fel.
- Utnyttja att Webware kan skicka mail med felrapporter!
Administrationswebsidor
- Inte så användbara, egentligen.
- Visar lite loggar.
- Omladdning av moduler etc.
- Bör troligen stängas av på produktionsservers.
Inloggning i Webware
from SitePage import SitePage
import time
class SecurePage(SitePage):
def __init__(self):
SitePage.__init__(self)
def awake(self, trans):
SitePage.awake(self,trans)
if self.session().lastAccessTime() +\
self.session().timeout() < time.time():
self.session().expiring()
if self.setting('RequireLogin'):
if not trans.session().value('user', None) \
or trans.session().isExpired():
trans.response().sendRedirect("Login")
if trans.request().hasField("logout"):
self.session().values().clear()
trans.response().sendRedirect("Main")
def respond(self, trans):
if self.session().value('user', None) or \
not self.setting('RequireLogin'):
SitePage.respond(self, trans)
def defaultConfig(self):
return {'RequireLogin': 1}
Web Services
- WebKit har stöd för XML-RPC.
- Jag vägrar ta upp det här ämnet! :-)
Något om MiscUtils
- Configurable
- DBPool
- DataTable
- etc.
Saknas: dokumentation :(.
MiscUtils : Configurable
- Samlar dina sitespecifika konfigurationsvariabler på ett ställe.
- Ärv från Configurable i din servlet
- Definiera configFilename som returnerar platsen för konfigfil.
- Hämta saker ur konfigfilen med self.setting()
Konfigurationsfilformat:
{
'dbHost':'localhost',
'dbUser':'invuser',
'db':'inv',
'dbPass':ettlösenord',
'TemplateDirectory':'InvTemplates',
'PublicMenu': [["Förstasidan","Main"]],
'PrivateMenu': [["Lista","List"]],
'WriteMenu': [["Lägg till","Edit"]],
'LoginMenu': {"login": ["Logga in","Login"],
"logout": ["Logga ut","Main?logout=1"]},
'WriteGroups': ['root','styrelse', 'tilde'],
}
Webware : Version
Webware 0.8 släpptes 2003-02-09
- All adapters have been moved to Webware/WebKit/Adapters
- LRWPAdapter added, for use with the Xitami web server
- New setCookie method in HTTPResponse. New method supports setting the expire time, path (which defaults to "/") and security flag. The expire time can be set with constants "ONCLOSE", "NOW", or "NEVER"; or you can give an integer timestamp, tuple of integers (as used in the time module), or string identifier (like "+1w" for 1 week in the future).
- Added an experimental HTTPAdapter.py which serves directly as an HTTP server.
- Added an optional AutoReload setting to enable reloading the server whenever source files, including servlets and PSP files are changed.
- Fix so PSP works with python 2.3a1.
- Includes an Apache 2 mod_webkit adapter, and pre-compiled DLL for windows.
- Fixed a problem where escapes in PSP source were not being processed correctly.
- Restored 0.7 behavior of Page.writeDocType() to output 4.01 Transitional. See the release notes for more info.
- Upgraded documentation
- Improvements to profilling.
- Numerous bug fixes.
Cheetah Template
- Templatesystem för att generera all sorts text.
- HTML.
- Javascript.
- Mail.
- text/plain.
- You-Name-It.
Cheetah Template : Inspiration
- DTML (Zope)
- Zope Page Templates
- YAPTU (Yet Another Python Templating Utility)
- itpl
- Quixote's Python Template Language
- Velocity
- Webmacro
- Smarty
- PHPLib's Template class
- Template Toolkit (Perl)
- Skunkweb's STML
Cheetah Template : Features
- Variabelexpansion
- Enklare programkonstruktioner.
- Arv
- Tillgång till hela Python.
Cheetah Template : Dokumentation
- 90-sidors användarguide.
- 57-sidors utvecklarguide.
- Wiki.
Cheetah Template : Hur använder man det?
- Kompilering till python-fil.
- Import från fil.
Cheetah Template : Kompilering av template till .py
- Verktyget cheetah används för att kompilera template -> .py
- Genererade .py kan användas som Webwareservlets, givet rätt arv.
- Genererade .py kan köras och har hjälpfunktion(!)
- Variabler kan hämtas från miljö eller picklad datafil.
Cheetah Template : Import från fil.
- Templates kan importeras som filer och skrivas ut med str()
t = Template(file="Members".tmpl,
searchList = [{'othermembers':othermembers,
'societymembers':societymembers,
'districtmembers':districtmembers}])
print t
Cheetah Template : Grundläggande syntax
- ## Single line comment
- #* multi line comment *#
- $placeholder - Variabler etc.
- #expression
Cheetah Template : Variabelaccess
- NameMapper - ett försök att göra variabelaccess enkelt för ickeprogrammerare.
- self.customers()[$ID].address()['city']
- $customers()[$ID].address()['city']
- $customers()[$ID].address().city
- $customers()[$ID].address.city
- $customers()[$ID].address.city
- $customers[$ID].address.city
Cheetah Template : Enkla programkonstruktioner
- Cheeath fattar alla giltiga Pythonexpressions.
- Indentering och : används dock inte.
- Multiradsdirektiv har slutdirektiv, #end for
<table>
#for $row in $societymembers
<tr>
<td align="left"><a href="Member?id=${row.id}">${row.name}</a></td>
</tr>
#end for
</table>
Cheetah Template : Pythonkod i Template
I en Cheetahtemplate har du tillgång till hela Python
Here is my #echo ', '.join(['silly']*5) # example.
Here is my silly, silly, silly, silly, silly example.
Cheetah Template : Caching
- $var ${var} Dynamiskt.
- $*var2 $*{var2} Statiskt, uppdateras bara vid startup.
- $*5*var3 $*5*{var3} Dynamiskt, men uppdateras bara var femte minut.
..eller
#cache
$a $b
#end cache
Cache kan dessutom uppdateras på kommando.
Cheetah Template : Utdatafilter
- Cheetah filtrerar output från placeholders.
- Standardfiltret ersätter bara None med ''.
- Andra filter finns:
- Du kan definiera egna filter.
Cheetah Template : Import
Cheetah Template : Arv
Templates kan ärva av varandra, och av andra pythonfiler
parent.tmpl
#block foo
Det här är innehållet i blocket foo
#end block foo
#block bar
Blocket bar
#end block bar
child.tmpl
#extends parent
#block foo
Child har ett annat innehåll i foo..
#end block foo
Cheetah Template : Arv - körexempel
hostname:erik ~/dev/Cheetah % cheetah compile parent.tmpl child.tmpl
Compiling parent.tmpl -> parent.py (backup parent.py_bak)
Compiling child.tmpl -> child.py (backup child.py_bak)
hostname:erik ~/dev/Cheetah % python parent.py
Det här är innehållet i blocket foo
Blocket bar
hostname:erik ~/dev/Cheetah % python child.py
Child har ett annat innehåll i foo..
Blocket bar
hostname:erik ~/dev/Cheetah %
FunFormKit
"FunFormKit is the most fun, family-oriented form validation and generation package for Webware in the entire world!"
- Form Validation
- Value Conversion
- HTML generation
- HTML widgets
- Quoting
- Threading
FunFormKit : Form Validation
- Generering av <FORM>
- Validering av indata från <FORM>
- Felrapportering och ihågkomst av ifyllda värden vid fel.
FunFormKit : Ett litet exempel
from WebKit.Page import Page
from FunFormKit.Form import FormServlet, FormDefinition
from FunFormKit import Field
import random
formDef = FormDefinition("CCWinner",
[Field.TextField("randmax", maxLength=5, size=3,
description="Upper border of random number wanted"),
Field.SubmitButton("submit",
description="Randomize me!",
methodToInvoke="randomize_me")],
)
class CCWinner(Page, FormServlet):
def __init__(self):
Page.__init__(self)
FormServlet.__init__(self, [formDef])
def title(self):
return "A random example.."
def randomize_me(self, fields):
maxrandnr = int(fields['randmax'])
randnr = random.randrange(0, maxrandnr)
self.write("Your random number between 0 and %d is %d<p>" % (maxrandnr,
randnr))
def writeContent(self):
submitted, data = self.processForm()
rf = self.renderableForm()
self.write(rf.htFormTable(bgcolor="#ddddff"))
FunFormKit : Ett litet exempel
- Filen sparas i CCWinner.py (samma namn som klassen).
- FunFormKit genererar HTML åt oss.
- CCWinner
FunFormKit : Fälttyper
- SubmitButton
- ImageSubmit
- HiddenField
- SecureHiddenField
- CompoundField
- TextField
- TextAreaField
- PasswordField
- MD5PasswordField
- TimeMD5PasswordField
- SelectField
- OrderingField
- OrderingDeletingField
- RadioField
|
- MultiSelectField
- MultiCheckboxField
- CheckboxField
- DateField
- FileField
- TextareaFileField
- ImageFileUploadField
- FileUploadField
- StaticText
- ColorPickerField
- VerifyField
- PasswordVerifyField
- CityStateZipField
- CreditCardField
|
FunFormKit : Value Conversion/Validation
- Konverterar redan i ett tidigt stadium fält till något annat.
- Konvertering av siffervärden till int/float.
- Konvertering av användarnamn till användarid.
- Möjlighet att kontrollera värden mot en mall.
Value Conversion/Validation : Ett Exempel
from WebKit.Page import Page
from FunFormKit.Form import FormServlet, FormDefinition
from FunFormKit import Field
from FunFormKit.Validator import ValidatorConverter, InvalidField
import random
class IntValidator(ValidatorConverter):
def convert(self, num):
try:
return int(num)
except ValueError:
raise InvalidField, "Field must be a number!"
formDef = FormDefinition("CCConvertedWinner",
[Field.TextField("randmax", maxLength=5, size=3,
description="Upper border of random"\
"number wanted",
validators=[IntValidator()]),
Field.TextField("useless", maxLength=200, size=20,
description="A useless field"),
Field.SubmitButton("submit",
description="Randomize me!",
methodToInvoke="randomize_me")],
)
class CCConvertedWinner(Page, FormServlet):
def __init__(self):
Page.__init__(self)
FormServlet.__init__(self, [formDef])
def title(self):
return "A random example.."
def randomize_me(self, fields):
maxrandnr = int(fields['randmax'])
randnr = random.randrange(0, maxrandnr)
self.write("Your random number between 0 and %d is %d<p>" % (maxrandnr,
randnr))
def writeContent(self):
submitted, data = self.processForm()
rf = self.renderableForm()
self.write(rf.htFormTable(bgcolor="#ddddff"))
Value Conversion/Validation : Ett Exempel
- Vi ser till att fältet blir en integer i ett tidigt stadium.
- Vi kan ge ett vettigt felmeddelande, på rätt ställe.
- Resten av fälten förblir ifyllda.
- CCConvertedWinner
Value Conversion/Validation : Två detaljer
- Egenskrivna Validators ska:
- Ärva från ValidatorConverter
- Ha en metod convert(self, value) om konvertering ska ske.
- Returnera värdet om konvertering gick bra.
- raise InvalidField, "Error Message" om värdet inte gick att konvertera.
- Ha en metod validate(self, value) som returnerar en felsträng eller None
Value Conversion/Validation : Färdiga validatorer
- MaxLength
- MinLength
- NotEmpty
- Empty
- Regex
- PlainText
- InList
- DictionaryConverter
- IndexListConverter
|
- DateValidator
- AsInt
- AsNumber
- AsList
- Email
- StateProvince
- PhoneNumber
- DateConverter
- PostalCode
|
FunFormKit : Form Validators
- En Validator kan också validera ett helt form (inte bara ett fält).
- Typexempel: Kreditkortsvalidering (nummer och typ versus datum etc.)
- Ett annat exempel:
class ValidatePassword(FormValidator):
def __init__(self, servlet):
self.servlet = servlet
def validate(self, fieldDict):
if self.servlet.comparePassword(fieldDict["username"][0],
fieldDict["password"]):
return None
else:
return {'password':self.servlet.setting('InvalidPasswordMessage')}
Koden som använder den:
formDef = FormDefinition('Login',
[Field.TextField('username', maxLength=255,
size=20,
validators=\
[UsernameToUserID(self)]),
Field.PasswordField('password', size=10,
maxLength=20),
Field.SubmitButton('submit',
description='Login',
methodToInvoke='login'),
],
formValidators=[ValidatePassword(self)])
När vi kommer till login vet vi att användaren loggat in ordentligt
def login(self, fields):
self.session().setValue('user', fields["username"][0])
self.session().setValue('username', fields["username"][1])
self.response().sendRedirect("Main")
FunFormKit : HTML generation
- FunFormKit kan generera HTML-koden för dina fält.
- Ett sätt har vi redan sett - htFormTable().
- htFormLayout() - möjlighet att bestämma:
- layout.
- CSS-klass.
- Bakgrundsfärg.
- Mellanrum.
- Läs manulen, jag har inte använt den själv.. :-)
- Fält kan också hämtas ut som strängar. Används vid integration med templatesystem..
FunFormKit och Cheetah, tillsammans
Genom att hämta ut fält som strängar från FunFormKit kan man integrera med Webware.
Servlet:
from WebKit.Page import Page
from FunFormKit.Form import FormServlet, FormDefinition
from FunFormKit import Field
from Cheetah.Template import Template
from FunFormKit.Validator import Email
class FFF_Cheetah(Page, FormServlet):
def __init__(self):
formDef = FormDefinition("FFF_Cheetah",
[Field.TextField("name",
maxLength=255,
size=20
),
Field.TextField("email", maxLength=255,
size=25,
validators=[Email()]),
Field.SubmitButton("submit",
description="Send!")])
Page.__init__(self)
FormServlet.__init__(self, [formDef])
def writeContent(self):
submitted, data = self.processForm()
if not submitted:
rf = self.renderableForm()
t = Template(file="/s/waresite/Templates/FFF_Cheetah.tmpl",
searchList=[{'rf':rf}])
self.write(str(t))
else:
self.write("Your name and email is %(name)s <%(email)s>" % data)
- Notera: Annan metod för att hämta resultatet.
- Ej nödvändigt med methodToInvoke på submit-fält.
FunFormKit och Cheetah, tillsammans
Template (FFF_Cheetah.tmpl):
## mode: html
$rf.start
<TABLE>
<TR>
<TD><B>Name: </B></TD>
<TD>$rf.name $rf.name.error</TD>
</TR>
<TR>
<TD><B>Email: </B></TD>
<TD>$rf.email $rf.email.error</TD>
</TR>
<TR>
<TD> </TD>
<TD>$rf.submit $rf.submit.error</TD>
</TR>
</TABLE>
$rf.end
- Meningslöst exempel - nästan samma resultat som htFormTable :-)
- Du bestämmer!
- Ska gå att få till med Inheritance också.
- FFF_Cheetah
Webware @ Lysator
- http://webware.lysator.liu.se/<username>
- Kontext måste skapas explicit för varje användare.
- Skript för att göra det saknas tyvärr för tillfället :-(
Länkar
Kent vill ha ett randomnummer!
CCConvertedWinner