#!/usr/bin/env python
#
# vim:set et ts=4 fdc=0 fdn=2 fdl=0:
#
# There are no blank lines between blocks beacause i use folding from:
# http://www.vim.org/scripts/script.php?script_id=515
#
"""= QWeb Framework =
== What is QWeb ? ==
QWeb is a python based [http://www.python.org/doc/peps/pep-0333/ WSGI]
compatible web framework, it provides an infratructure to quickly build web
applications consisting of:
* A lightweight request handler (QWebRequest)
* An xml templating engine (QWebXml and QWebHtml)
* A simple name based controler (qweb_control)
* A standalone WSGI Server (QWebWSGIServer)
* A cgi and fastcgi WSGI wrapper (taken from flup)
* A startup function that starts cgi, factgi or standalone according to the
evironement (qweb_autorun).
QWeb applications are runnable in standalone mode (from commandline), via
FastCGI, Regular CGI or by any python WSGI compliant server.
QWeb doesn't provide any database access but it integrates nicely with ORMs
such as SQLObject, SQLAlchemy or plain DB-API.
Written by Antony Lesuisse (email al AT udev.org)
Homepage: http://antony.lesuisse.org/qweb/trac/
Forum: [http://antony.lesuisse.org/qweb/forum/viewforum.php?id=1 Forum]
== Quick Start (for Linux, MacOS X and cygwin) ==
Make sure you have at least python 2.3 installed and run the following commands:
{{{
$ wget http://antony.lesuisse.org/qweb/files/QWeb-0.7.tar.gz
$ tar zxvf QWeb-0.7.tar.gz
$ cd QWeb-0.7/examples/blog
$ ./blog.py
}}}
And point your browser to http://localhost:8080/
You may also try AjaxTerm which uses qweb request handler.
== Download ==
* Version 0.7:
* Source [/qweb/files/QWeb-0.7.tar.gz QWeb-0.7.tar.gz]
* Python 2.3 Egg [/qweb/files/QWeb-0.7-py2.3.egg QWeb-0.7-py2.3.egg]
* Python 2.4 Egg [/qweb/files/QWeb-0.7-py2.4.egg QWeb-0.7-py2.4.egg]
* [/qweb/trac/browser Browse the source repository]
== Documentation ==
* [/qweb/trac/browser/trunk/README.txt?format=raw Read the included documentation]
* QwebTemplating
== Mailin-list ==
* Forum: [http://antony.lesuisse.org/qweb/forum/viewforum.php?id=1 Forum]
* No mailing-list exists yet, discussion should happen on: [http://mail.python.org/mailman/listinfo/web-sig web-sig] [http://mail.python.org/pipermail/web-sig/ archives]
QWeb Components:
----------------
QWeb also feature a simple components api, that enables developers to easily
produces reusable components.
Default qweb components:
- qweb_static:
A qweb component to serve static content from the filesystem or from
zipfiles.
- qweb_dbadmin:
scaffolding for sqlobject
License
-------
qweb/fcgi.py wich is BSD-like from saddi.com.
Everything else is put in the public domain.
TODO
----
Announce QWeb to python-announce-list@python.org web-sig@python.org
qweb_core
rename request methods into
request_save_files
response_404
response_redirect
response_download
request callback_generator, callback_function ?
wsgi callback_server_local
xml tags explicitly call render_attributes(t_att)?
priority form-checkbox over t-value (for t-option)
"""
import BaseHTTPServer,SocketServer,Cookie
import cgi,datetime,email,email.Message,errno,gzip,os,random,re,socket,sys,tempfile,time,types,urllib,urlparse,xml.dom
try:
import cPickle as pickle
except ImportError:
import pickle
try:
import cStringIO as StringIO
except ImportError:
import StringIO
#----------------------------------------------------------
# Qweb Xml t-raw t-esc t-if t-foreach t-set t-call t-trim
#----------------------------------------------------------
class QWebEval:
def __init__(self,data):
self.data=data
def __getitem__(self,expr):
if self.data.has_key(expr):
return self.data[expr]
r=None
try:
r=eval(expr,self.data)
except NameError,e:
pass
except AttributeError,e:
pass
except Exception,e:
print "qweb: expression error '%s' "%expr,e
if self.data.has_key("__builtins__"):
del self.data["__builtins__"]
return r
def eval_object(self,expr):
return self[expr]
def eval_str(self,expr):
if expr=="0":
return self.data[0]
if isinstance(self[expr],unicode):
return self[expr].encode("utf8")
return str(self[expr])
def eval_format(self,expr):
try:
return str(expr%self)
except:
return "qweb: format error '%s' "%expr
# if isinstance(r,unicode):
# return r.encode("utf8")
def eval_bool(self,expr):
if self.eval_object(expr):
return 1
else:
return 0
class QWebXml:
"""QWeb Xml templating engine
The templating engine use a very simple syntax, "magic" xml attributes, to
produce any kind of texutal output (even non-xml).
QWebXml:
the template engine core implements the basic magic attributes:
t-att t-raw t-esc t-if t-foreach t-set t-call t-trim
"""
def __init__(self,x=None,zipname=None):
self.node=xml.dom.Node
self._t={}
self._render_tag={}
prefix='render_tag_'
for i in [j for j in dir(self) if j.startswith(prefix)]:
name=i[len(prefix):].replace('_','-')
self._render_tag[name]=getattr(self.__class__,i)
self._render_att={}
prefix='render_att_'
for i in [j for j in dir(self) if j.startswith(prefix)]:
name=i[len(prefix):].replace('_','-')
self._render_att[name]=getattr(self.__class__,i)
if x!=None:
if zipname!=None:
import zipfile
zf=zipfile.ZipFile(zipname, 'r')
self.add_template(zf.read(x))
else:
self.add_template(x)
def register_tag(self,tag,func):
self._render_tag[tag]=func
def add_template(self,x):
if hasattr(x,'documentElement'):
dom=x
elif x.startswith("%s%s%s>"%(name,g_att,pre,inner,name)
else:
return "<%s%s/>"%(name,g_att)
# Attributes
def render_att_att(self,e,an,av,v):
if an.startswith("t-attf-"):
att,val=an[7:],self.eval_format(av,v)
elif an.startswith("t-att-"):
att,val=(an[6:],self.eval_str(av,v))
else:
att,val=self.eval_object(av,v)
return ' %s="%s"'%(att,cgi.escape(val,1))
# Tags
def render_tag_raw(self,e,t_att,g_att,v):
return self.eval_str(t_att["raw"],v)
def render_tag_rawf(self,e,t_att,g_att,v):
return self.eval_format(t_att["rawf"],v)
def render_tag_esc(self,e,t_att,g_att,v):
return cgi.escape(self.eval_str(t_att["esc"],v))
def render_tag_escf(self,e,t_att,g_att,v):
return cgi.escape(self.eval_format(t_att["escf"],v))
def render_tag_foreach(self,e,t_att,g_att,v):
expr=t_att["foreach"]
enum=self.eval_object(expr,v)
if enum!=None:
var=t_att.get('as',expr).replace('.','_')
d=v.copy()
size=-1
if isinstance(enum,types.ListType):
size=len(enum)
elif isinstance(enum,types.TupleType):
size=len(enum)
elif hasattr(enum,'count'):
size=enum.count()
d["%s_size"%var]=size
d["%s_all"%var]=enum
index=0
ru=[]
for i in enum:
d["%s_value"%var]=i
d["%s_index"%var]=index
d["%s_first"%var]=index==0
d["%s_even"%var]=index%2
d["%s_odd"%var]=(index+1)%2
d["%s_last"%var]=index+1==size
if index%2:
d["%s_parity"%var]='odd'
else:
d["%s_parity"%var]='even'
if isinstance(i,types.DictType):
d.update(i)
else:
d[var]=i
ru.append(self.render_element(e,g_att,d))
index+=1
return "".join(ru)
else:
return "qweb: t-foreach %s not found."%expr
def render_tag_if(self,e,t_att,g_att,v):
if self.eval_bool(t_att["if"],v):
return self.render_element(e,g_att,v)
else:
return ""
def render_tag_call(self,e,t_att,g_att,v):
# TODO t-prefix
if t_att.has_key("import"):
d=v
else:
d=v.copy()
d[0]=self.render_element(e,g_att,d)
return self.render(t_att["call"],d)
def render_tag_set(self,e,t_att,g_att,v):
if t_att.has_key("eval"):
v[t_att["set"]]=self.eval_object(t_att["eval"],v)
else:
v[t_att["set"]]=self.render_element(e,g_att,v)
return ""
#----------------------------------------------------------
# QWeb HTML (+deprecated QWebFORM and QWebOLD)
#----------------------------------------------------------
class QWebURL:
""" URL helper
assert req.PATH_INFO== "/site/admin/page_edit"
u = QWebURL(root_path="/site/",req_path=req.PATH_INFO)
s=u.url2_href("user/login",{'a':'1'})
assert s=="../user/login?a=1"
"""
def __init__(self, root_path="/", req_path="/",defpath="",defparam={}):
self.defpath=defpath
self.defparam=defparam
self.root_path=root_path
self.req_path=req_path
self.req_list=req_path.split("/")[:-1]
self.req_len=len(self.req_list)
def decode(self,s):
h={}
for k,v in cgi.parse_qsl(s,1):
h[k]=v
return h
def encode(self,h):
return urllib.urlencode(h.items())
def request(self,req):
return req.REQUEST
def copy(self,path=None,param=None):
npath=self.defpath
if path:
npath=path
nparam=self.defparam.copy()
if param:
nparam.update(param)
return QWebURL(self.root_path,self.req_path,npath,nparam)
def path(self,path=''):
if not path:
path=self.defpath
pl=(self.root_path+path).split('/')
i=0
for i in range(min(len(pl), self.req_len)):
if pl[i]!=self.req_list[i]:
break
else:
i+=1
dd=self.req_len-i
if dd<0:
dd=0
return '/'.join(['..']*dd+pl[i:])
def href(self,path='',arg={}):
p=self.path(path)
tmp=self.defparam.copy()
tmp.update(arg)
s=self.encode(tmp)
if len(s):
return p+"?"+s
else:
return p
def form(self,path='',arg={}):
p=self.path(path)
tmp=self.defparam.copy()
tmp.update(arg)
r=''.join([''%(k,cgi.escape(str(v),1)) for k,v in tmp.items()])
return (p,r)
class QWebField:
def __init__(self,name=None,default="",check=None):
self.name=name
self.default=default
self.check=check
# optional attributes
self.type=None
self.trim=1
self.required=1
self.cssvalid="form_valid"
self.cssinvalid="form_invalid"
# set by addfield
self.form=None
# set by processing
self.input=None
self.css=None
self.value=None
self.valid=None
self.invalid=None
self.validate(1)
def validate(self,val=1,update=1):
if val:
self.valid=1
self.invalid=0
self.css=self.cssvalid
else:
self.valid=0
self.invalid=1
self.css=self.cssinvalid
if update and self.form:
self.form.update()
def invalidate(self,update=1):
self.validate(0,update)
class QWebForm:
class QWebFormF:
pass
def __init__(self,e=None,arg=None,default=None):
self.fields={}
# all fields have been submitted
self.submitted=False
self.missing=[]
# at least one field is invalid or missing
self.invalid=False
self.error=[]
# all fields have been submitted and are valid
self.valid=False
# fields under self.f for convenience
self.f=self.QWebFormF()
if e:
self.add_template(e)
# assume that the fields are done with the template
if default:
self.set_default(default,e==None)
if arg!=None:
self.process_input(arg)
def __getitem__(self,k):
return self.fields[k]
def set_default(self,default,add_missing=1):
for k,v in default.items():
if self.fields.has_key(k):
self.fields[k].default=str(v)
elif add_missing:
self.add_field(QWebField(k,v))
def add_field(self,f):
self.fields[f.name]=f
f.form=self
setattr(self.f,f.name,f)
def add_template(self,e):
att={}
for (an,av) in e.attributes.items():
an=str(an)
if an.startswith("t-"):
att[an[2:]]=av.encode("utf8")
for i in ["form-text", "form-password", "form-radio", "form-checkbox", "form-select","form-textarea"]:
if att.has_key(i):
name=att[i].split(".")[-1]
default=att.get("default","")
check=att.get("check",None)
f=QWebField(name,default,check)
if i=="form-textarea":
f.type="textarea"
f.trim=0
if i=="form-checkbox":
f.type="checkbox"
f.required=0
self.add_field(f)
for n in e.childNodes:
if n.nodeType==n.ELEMENT_NODE:
self.add_template(n)
def process_input(self,arg):
for f in self.fields.values():
if arg.has_key(f.name):
f.input=arg[f.name]
f.value=f.input
if f.trim:
f.input=f.input.strip()
f.validate(1,False)
if f.check==None:
continue
elif callable(f.check):
pass
elif isinstance(f.check,str):
v=f.check
if f.check=="email":
v=r"/^[^@#!& ]+@[A-Za-z0-9-][.A-Za-z0-9-]{0,64}\.[A-Za-z]{2,5}$/"
if f.check=="date":
v=r"/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/"
if not re.match(v[1:-1],f.input):
f.validate(0,False)
else:
f.value=f.default
self.update()
def validate_all(self,val=1):
for f in self.fields.values():
f.validate(val,0)
self.update()
def invalidate_all(self):
self.validate_all(0)
def update(self):
self.submitted=True
self.valid=True
self.errors=[]
for f in self.fields.values():
if f.required and f.input==None:
self.submitted=False
self.valid=False
self.missing.append(f.name)
if f.invalid:
self.valid=False
self.error.append(f.name)
# invalid have been submitted and
self.invalid=self.submitted and self.valid==False
def collect(self):
d={}
for f in self.fields.values():
d[f.name]=f.value
return d
class QWebURLEval(QWebEval):
def __init__(self,data):
QWebEval.__init__(self,data)
def __getitem__(self,expr):
r=QWebEval.__getitem__(self,expr)
if isinstance(r,str):
return urllib.quote_plus(r)
else:
return r
class QWebHtml(QWebXml):
"""QWebHtml
QWebURL:
QWebField:
QWebForm:
QWebHtml:
an extended template engine, with a few utility class to easily produce
HTML, handle URLs and process forms, it adds the following magic attributes:
t-href t-action t-form-text t-form-password t-form-textarea t-form-radio
t-form-checkbox t-form-select t-option t-selected t-checked t-pager
# explication URL:
# v['tableurl']=QWebUrl({p=afdmin,saar=,orderby=,des=,mlink;meta_active=})
# t-href="tableurl?desc=1"
#
# explication FORM: t-if="form.valid()"
# Foreach i
# email:
#
#
# Simple forms:
#
#
#
#
#
#
#