nova/tools/ajaxterm/qweb.py
2010-09-17 19:08:22 -07:00

1357 lines
49 KiB
Python

#!/usr/bin/python2.3
#
# 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("<?xml"):
import xml.dom.minidom
dom=xml.dom.minidom.parseString(x)
else:
import xml.dom.minidom
dom=xml.dom.minidom.parse(x)
for n in dom.documentElement.childNodes:
if n.nodeName=="t":
self._t[str(n.getAttribute("t-name"))]=n
def get_template(self,name):
return self._t[name]
def eval_object(self,expr,v):
return QWebEval(v).eval_object(expr)
def eval_str(self,expr,v):
return QWebEval(v).eval_str(expr)
def eval_format(self,expr,v):
return QWebEval(v).eval_format(expr)
def eval_bool(self,expr,v):
return QWebEval(v).eval_bool(expr)
def render(self,tname,v={},out=None):
if self._t.has_key(tname):
return self.render_node(self._t[tname],v)
else:
return 'qweb: template "%s" not found'%tname
def render_node(self,e,v):
r=""
if e.nodeType==self.node.TEXT_NODE or e.nodeType==self.node.CDATA_SECTION_NODE:
r=e.data.encode("utf8")
elif e.nodeType==self.node.ELEMENT_NODE:
pre=""
g_att=""
t_render=None
t_att={}
for (an,av) in e.attributes.items():
an=str(an)
if isinstance(av,types.UnicodeType):
av=av.encode("utf8")
else:
av=av.nodeValue.encode("utf8")
if an.startswith("t-"):
for i in self._render_att:
if an[2:].startswith(i):
g_att+=self._render_att[i](self,e,an,av,v)
break
else:
if self._render_tag.has_key(an[2:]):
t_render=an[2:]
t_att[an[2:]]=av
else:
g_att+=' %s="%s"'%(an,cgi.escape(av,1));
if t_render:
if self._render_tag.has_key(t_render):
r=self._render_tag[t_render](self,e,t_att,g_att,v)
else:
r=self.render_element(e,g_att,v,pre,t_att.get("trim",0))
return r
def render_element(self,e,g_att,v,pre="",trim=0):
g_inner=[]
for n in e.childNodes:
g_inner.append(self.render_node(n,v))
name=str(e.nodeName)
inner="".join(g_inner)
if trim==0:
pass
elif trim=='left':
inner=inner.lstrip()
elif trim=='right':
inner=inner.rstrip()
elif trim=='both':
inner=inner.strip()
if name=="t":
return inner
elif len(inner):
return "<%s%s>%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(['<input type="hidden" name="%s" value="%s"/>'%(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: <input type="text" t-esc-name="i" t-esc-value="form[i].value" t-esc-class="form[i].css"/>
# <input type="radio" name="spamtype" t-esc-value="i" t-selected="i==form.f.spamtype.value"/>
# <option t-esc-value="cc" t-selected="cc==form.f.country.value"><t t-esc="cname"></option>
# Simple forms:
# <input t-form-text="form.email" t-check="email"/>
# <input t-form-password="form.email" t-check="email"/>
# <input t-form-radio="form.email" />
# <input t-form-checkbox="form.email" />
# <textarea t-form-textarea="form.email" t-check="email"/>
# <select t-form-select="form.email"/>
# <option t-value="1">
# <input t-form-radio="form.spamtype" t-value="1"/> Cars
# <input t-form-radio="form.spamtype" t-value="2"/> Sprt
"""
# QWebForm from a template
def form(self,tname,arg=None,default=None):
form=QWebForm(self._t[tname],arg,default)
return form
# HTML Att
def eval_url(self,av,v):
s=QWebURLEval(v).eval_format(av)
a=s.split('?',1)
arg={}
if len(a)>1:
for k,v in cgi.parse_qsl(a[1],1):
arg[k]=v
b=a[0].split('/',1)
path=''
if len(b)>1:
path=b[1]
u=b[0]
return u,path,arg
def render_att_url_(self,e,an,av,v):
u,path,arg=self.eval_url(av,v)
if not isinstance(v.get(u,0),QWebURL):
out='qweb: missing url %r %r %r'%(u,path,arg)
else:
out=v[u].href(path,arg)
return ' %s="%s"'%(an[6:],cgi.escape(out,1))
def render_att_href(self,e,an,av,v):
return self.render_att_url_(e,"t-url-href",av,v)
def render_att_checked(self,e,an,av,v):
if self.eval_bool(av,v):
return ' %s="%s"'%(an[2:],an[2:])
else:
return ''
def render_att_selected(self,e,an,av,v):
return self.render_att_checked(e,an,av,v)
# HTML Tags forms
def render_tag_rawurl(self,e,t_att,g_att,v):
u,path,arg=self.eval_url(t_att["rawurl"],v)
return v[u].href(path,arg)
def render_tag_escurl(self,e,t_att,g_att,v):
u,path,arg=self.eval_url(t_att["escurl"],v)
return cgi.escape(v[u].href(path,arg))
def render_tag_action(self,e,t_att,g_att,v):
u,path,arg=self.eval_url(t_att["action"],v)
if not isinstance(v.get(u,0),QWebURL):
action,input=('qweb: missing url %r %r %r'%(u,path,arg),'')
else:
action,input=v[u].form(path,arg)
g_att+=' action="%s"'%action
return self.render_element(e,g_att,v,input)
def render_tag_form_text(self,e,t_att,g_att,v):
f=self.eval_object(t_att["form-text"],v)
g_att+=' type="text" name="%s" value="%s" class="%s"'%(f.name,cgi.escape(f.value,1),f.css)
return self.render_element(e,g_att,v)
def render_tag_form_password(self,e,t_att,g_att,v):
f=self.eval_object(t_att["form-password"],v)
g_att+=' type="password" name="%s" value="%s" class="%s"'%(f.name,cgi.escape(f.value,1),f.css)
return self.render_element(e,g_att,v)
def render_tag_form_textarea(self,e,t_att,g_att,v):
type="textarea"
f=self.eval_object(t_att["form-textarea"],v)
g_att+=' name="%s" class="%s"'%(f.name,f.css)
r="<%s%s>%s</%s>"%(type,g_att,cgi.escape(f.value,1),type)
return r
def render_tag_form_radio(self,e,t_att,g_att,v):
f=self.eval_object(t_att["form-radio"],v)
val=t_att["value"]
g_att+=' type="radio" name="%s" value="%s"'%(f.name,val)
if f.value==val:
g_att+=' checked="checked"'
return self.render_element(e,g_att,v)
def render_tag_form_checkbox(self,e,t_att,g_att,v):
f=self.eval_object(t_att["form-checkbox"],v)
val=t_att["value"]
g_att+=' type="checkbox" name="%s" value="%s"'%(f.name,val)
if f.value==val:
g_att+=' checked="checked"'
return self.render_element(e,g_att,v)
def render_tag_form_select(self,e,t_att,g_att,v):
f=self.eval_object(t_att["form-select"],v)
g_att+=' name="%s" class="%s"'%(f.name,f.css)
return self.render_element(e,g_att,v)
def render_tag_option(self,e,t_att,g_att,v):
f=self.eval_object(e.parentNode.getAttribute("t-form-select"),v)
val=t_att["option"]
g_att+=' value="%s"'%(val)
if f.value==val:
g_att+=' selected="selected"'
return self.render_element(e,g_att,v)
# HTML Tags others
def render_tag_pager(self,e,t_att,g_att,v):
pre=t_att["pager"]
total=int(self.eval_str(t_att["total"],v))
start=int(self.eval_str(t_att["start"],v))
step=int(self.eval_str(t_att.get("step","100"),v))
scope=int(self.eval_str(t_att.get("scope","5"),v))
# Compute Pager
p=pre+"_"
d={}
d[p+"tot_size"]=total
d[p+"tot_page"]=tot_page=total/step
d[p+"win_start0"]=total and start
d[p+"win_start1"]=total and start+1
d[p+"win_end0"]=max(0,min(start+step-1,total-1))
d[p+"win_end1"]=min(start+step,total)
d[p+"win_page0"]=win_page=start/step
d[p+"win_page1"]=win_page+1
d[p+"prev"]=(win_page!=0)
d[p+"prev_start"]=(win_page-1)*step
d[p+"next"]=(tot_page>=win_page+1)
d[p+"next_start"]=(win_page+1)*step
l=[]
begin=win_page-scope
end=win_page+scope
if begin<0:
end-=begin
if end>tot_page:
begin-=(end-tot_page)
i=max(0,begin)
while i<=min(end,tot_page) and total!=step:
l.append( { p+"page0":i, p+"page1":i+1, p+"start":i*step, p+"sel":(win_page==i) })
i+=1
d[p+"active"]=len(l)>1
d[p+"list"]=l
# Update v
v.update(d)
return ""
#----------------------------------------------------------
# QWeb Simple Controller
#----------------------------------------------------------
def qweb_control(self,jump='main',p=[]):
""" qweb_control(self,jump='main',p=[]):
A simple function to handle the controler part of your application. It
dispatch the control to the jump argument, while ensuring that prefix
function have been called.
qweb_control replace '/' to '_' and strip '_' from the jump argument.
name1
name1_name2
name1_name2_name3
"""
jump=jump.replace('/','_').strip('_')
if not hasattr(self,jump):
return 0
done={}
todo=[]
while 1:
if jump!=None:
tmp=""
todo=[]
for i in jump.split("_"):
tmp+=i+"_";
if not done.has_key(tmp[:-1]):
todo.append(tmp[:-1])
jump=None
elif len(todo):
i=todo.pop(0)
done[i]=1
if hasattr(self,i):
f=getattr(self,i)
r=f(*p)
if isinstance(r,types.StringType):
jump=r
else:
break
return 1
#----------------------------------------------------------
# QWeb WSGI Request handler
#----------------------------------------------------------
class QWebSession(dict):
def __init__(self,environ,**kw):
dict.__init__(self)
default={
"path" : tempfile.gettempdir(),
"cookie_name" : "QWEBSID",
"cookie_lifetime" : 0,
"cookie_path" : '/',
"cookie_domain" : '',
"limit_cache" : 1,
"probability" : 0.01,
"maxlifetime" : 3600,
"disable" : 0,
}
for k,v in default.items():
setattr(self,'session_%s'%k,kw.get(k,v))
# Try to find session
self.session_found_cookie=0
self.session_found_url=0
self.session_found=0
self.session_orig=""
# Try cookie
c=Cookie.SimpleCookie()
c.load(environ.get('HTTP_COOKIE', ''))
if c.has_key(self.session_cookie_name):
sid=c[self.session_cookie_name].value[:64]
if re.match('[a-f0-9]+$',sid) and self.session_load(sid):
self.session_id=sid
self.session_found_cookie=1
self.session_found=1
# Try URL
if not self.session_found_cookie:
mo=re.search('&%s=([a-f0-9]+)'%self.session_cookie_name,environ.get('QUERY_STRING',''))
if mo and self.session_load(mo.group(1)):
self.session_id=mo.group(1)
self.session_found_url=1
self.session_found=1
# New session
if not self.session_found:
self.session_id='%032x'%random.randint(1,2**128)
self.session_trans_sid="&amp;%s=%s"%(self.session_cookie_name,self.session_id)
# Clean old session
if random.random() < self.session_probability:
self.session_clean()
def session_get_headers(self):
h=[]
if (not self.session_disable) and (len(self) or len(self.session_orig)):
self.session_save()
if not self.session_found_cookie:
c=Cookie.SimpleCookie()
c[self.session_cookie_name] = self.session_id
c[self.session_cookie_name]['path'] = self.session_cookie_path
if self.session_cookie_domain:
c[self.session_cookie_name]['domain'] = self.session_cookie_domain
# if self.session_cookie_lifetime:
# c[self.session_cookie_name]['expires'] = TODO date localtime or not, datetime.datetime(1970, 1, 1)
h.append(("Set-Cookie", c[self.session_cookie_name].OutputString()))
if self.session_limit_cache:
h.append(('Cache-Control','no-store, no-cache, must-revalidate, post-check=0, pre-check=0'))
h.append(('Expires','Thu, 19 Nov 1981 08:52:00 GMT'))
h.append(('Pragma','no-cache'))
return h
def session_load(self,sid):
fname=os.path.join(self.session_path,'qweb_sess_%s'%sid)
try:
orig=file(fname).read()
d=pickle.loads(orig)
except:
return
self.session_orig=orig
self.update(d)
return 1
def session_save(self):
if not os.path.isdir(self.session_path):
os.makedirs(self.session_path)
fname=os.path.join(self.session_path,'qweb_sess_%s'%self.session_id)
try:
oldtime=os.path.getmtime(fname)
except OSError,IOError:
oldtime=0
dump=pickle.dumps(self.copy())
if (dump != self.session_orig) or (time.time() > oldtime+self.session_maxlifetime/4):
tmpname=os.path.join(self.session_path,'qweb_sess_%s_%x'%(self.session_id,random.randint(1,2**32)))
f=file(tmpname,'wb')
f.write(dump)
f.close()
if sys.platform=='win32' and os.path.isfile(fname):
os.remove(fname)
os.rename(tmpname,fname)
def session_clean(self):
t=time.time()
try:
for i in [os.path.join(self.session_path,i) for i in os.listdir(self.session_path) if i.startswith('qweb_sess_')]:
if (t > os.path.getmtime(i)+self.session_maxlifetime):
os.unlink(i)
except OSError,IOError:
pass
class QWebSessionMem(QWebSession):
def session_load(self,sid):
global _qweb_sessions
if not "_qweb_sessions" in globals():
_qweb_sessions={}
if _qweb_sessions.has_key(sid):
self.session_orig=_qweb_sessions[sid]
self.update(self.session_orig)
return 1
def session_save(self):
global _qweb_sessions
if not "_qweb_sessions" in globals():
_qweb_sessions={}
_qweb_sessions[self.session_id]=self.copy()
class QWebSessionService:
def __init__(self, wsgiapp, url_rewrite=0):
self.wsgiapp=wsgiapp
self.url_rewrite_tags="a=href,area=href,frame=src,form=,fieldset="
def __call__(self, environ, start_response):
# TODO
# use QWebSession to provide environ["qweb.session"]
return self.wsgiapp(environ,start_response)
class QWebDict(dict):
def __init__(self,*p):
dict.__init__(self,*p)
def __getitem__(self,key):
return self.get(key,"")
def int(self,key):
try:
return int(self.get(key,"0"))
except ValueError:
return 0
class QWebListDict(dict):
def __init__(self,*p):
dict.__init__(self,*p)
def __getitem__(self,key):
return self.get(key,[])
def appendlist(self,key,val):
if self.has_key(key):
self[key].append(val)
else:
self[key]=[val]
def get_qwebdict(self):
d=QWebDict()
for k,v in self.items():
d[k]=v[-1]
return d
class QWebRequest:
"""QWebRequest a WSGI request handler.
QWebRequest is a WSGI request handler that feature GET, POST and POST
multipart methods, handles cookies and headers and provide a dict-like
SESSION Object (either on the filesystem or in memory).
It is constructed with the environ and start_response WSGI arguments:
req=qweb.QWebRequest(environ, start_response)
req has the folowing attributes :
req.environ standard WSGI dict (CGI and wsgi ones)
Some CGI vars as attributes from environ for convenience:
req.SCRIPT_NAME
req.PATH_INFO
req.REQUEST_URI
Some computed value (also for convenience)
req.FULL_URL full URL recontructed (http://host/query)
req.FULL_PATH (URL path before ?querystring)
Dict constructed from querystring and POST datas, PHP-like.
req.GET contains GET vars
req.POST contains POST vars
req.REQUEST contains merge of GET and POST
req.FILES contains uploaded files
req.GET_LIST req.POST_LIST req.REQUEST_LIST req.FILES_LIST multiple arguments versions
req.debug() returns an HTML dump of those vars
A dict-like session object.
req.SESSION the session start when the dict is not empty.
Attribute for handling the response
req.response_headers dict-like to set headers
req.response_cookies a SimpleCookie to set cookies
req.response_status a string to set the status like '200 OK'
req.write() to write to the buffer
req itselfs is an iterable object with the buffer, it will also also call
start_response automatically before returning anything via the iterator.
To make it short, it means that you may use
return req
at the end of your request handling to return the reponse to any WSGI
application server.
"""
#
# This class contains part ripped from colubrid (with the permission of
# mitsuhiko) see http://wsgiarea.pocoo.org/colubrid/
#
# - the class HttpHeaders
# - the method load_post_data (tuned version)
#
class HttpHeaders(object):
def __init__(self):
self.data = [('Content-Type', 'text/html')]
def __setitem__(self, key, value):
self.set(key, value)
def __delitem__(self, key):
self.remove(key)
def __contains__(self, key):
key = key.lower()
for k, v in self.data:
if k.lower() == key:
return True
return False
def add(self, key, value):
self.data.append((key, value))
def remove(self, key, count=-1):
removed = 0
data = []
for _key, _value in self.data:
if _key.lower() != key.lower():
if count > -1:
if removed >= count:
break
else:
removed += 1
data.append((_key, _value))
self.data = data
def clear(self):
self.data = []
def set(self, key, value):
self.remove(key)
self.add(key, value)
def get(self, key=False, httpformat=False):
if not key:
result = self.data
else:
result = []
for _key, _value in self.data:
if _key.lower() == key.lower():
result.append((_key, _value))
if httpformat:
return '\n'.join(['%s: %s' % item for item in result])
return result
def load_post_data(self,environ,POST,FILES):
length = int(environ['CONTENT_LENGTH'])
DATA = environ['wsgi.input'].read(length)
if environ.get('CONTENT_TYPE', '').startswith('multipart'):
lines = ['Content-Type: %s' % environ.get('CONTENT_TYPE', '')]
for key, value in environ.items():
if key.startswith('HTTP_'):
lines.append('%s: %s' % (key, value))
raw = '\r\n'.join(lines) + '\r\n\r\n' + DATA
msg = email.message_from_string(raw)
for sub in msg.get_payload():
if not isinstance(sub, email.Message.Message):
continue
name_dict = cgi.parse_header(sub['Content-Disposition'])[1]
if 'filename' in name_dict:
# Nested MIME Messages are not supported'
if type([]) == type(sub.get_payload()):
continue
if not name_dict['filename'].strip():
continue
filename = name_dict['filename']
# why not keep all the filename? because IE always send 'C:\documents and settings\blub\blub.png'
filename = filename[filename.rfind('\\') + 1:]
if 'Content-Type' in sub:
content_type = sub['Content-Type']
else:
content_type = None
s = { "name":filename, "type":content_type, "data":sub.get_payload() }
FILES.appendlist(name_dict['name'], s)
else:
POST.appendlist(name_dict['name'], sub.get_payload())
else:
POST.update(cgi.parse_qs(DATA,keep_blank_values=1))
return DATA
def __init__(self,environ,start_response,session=QWebSession):
self.environ=environ
self.start_response=start_response
self.buffer=[]
self.SCRIPT_NAME = environ.get('SCRIPT_NAME', '')
self.PATH_INFO = environ.get('PATH_INFO', '')
# extensions:
self.FULL_URL = environ['FULL_URL'] = self.get_full_url(environ)
# REQUEST_URI is optional, fake it if absent
if not environ.has_key("REQUEST_URI"):
environ["REQUEST_URI"]=urllib.quote(self.SCRIPT_NAME+self.PATH_INFO)
if environ.get('QUERY_STRING'):
environ["REQUEST_URI"]+='?'+environ['QUERY_STRING']
self.REQUEST_URI = environ["REQUEST_URI"]
# full quote url path before the ?
self.FULL_PATH = environ['FULL_PATH'] = self.REQUEST_URI.split('?')[0]
self.request_cookies=Cookie.SimpleCookie()
self.request_cookies.load(environ.get('HTTP_COOKIE', ''))
self.response_started=False
self.response_gzencode=False
self.response_cookies=Cookie.SimpleCookie()
# to delete a cookie use: c[key]['expires'] = datetime.datetime(1970, 1, 1)
self.response_headers=self.HttpHeaders()
self.response_status="200 OK"
self.php=None
if self.environ.has_key("php"):
self.php=environ["php"]
self.SESSION=self.php._SESSION
self.GET=self.php._GET
self.POST=self.php._POST
self.REQUEST=self.php._ARG
self.FILES=self.php._FILES
else:
if isinstance(session,QWebSession):
self.SESSION=session
elif session:
self.SESSION=session(environ)
else:
self.SESSION=None
self.GET_LIST=QWebListDict(cgi.parse_qs(environ.get('QUERY_STRING', ''),keep_blank_values=1))
self.POST_LIST=QWebListDict()
self.FILES_LIST=QWebListDict()
self.REQUEST_LIST=QWebListDict(self.GET_LIST)
if environ['REQUEST_METHOD'] == 'POST':
self.DATA=self.load_post_data(environ,self.POST_LIST,self.FILES_LIST)
self.REQUEST_LIST.update(self.POST_LIST)
self.GET=self.GET_LIST.get_qwebdict()
self.POST=self.POST_LIST.get_qwebdict()
self.FILES=self.FILES_LIST.get_qwebdict()
self.REQUEST=self.REQUEST_LIST.get_qwebdict()
def get_full_url(environ):
# taken from PEP 333
if 'FULL_URL' in environ:
return environ['FULL_URL']
url = environ['wsgi.url_scheme']+'://'
if environ.get('HTTP_HOST'):
url += environ['HTTP_HOST']
else:
url += environ['SERVER_NAME']
if environ['wsgi.url_scheme'] == 'https':
if environ['SERVER_PORT'] != '443':
url += ':' + environ['SERVER_PORT']
else:
if environ['SERVER_PORT'] != '80':
url += ':' + environ['SERVER_PORT']
if environ.has_key('REQUEST_URI'):
url += environ['REQUEST_URI']
else:
url += urllib.quote(environ.get('SCRIPT_NAME', ''))
url += urllib.quote(environ.get('PATH_INFO', ''))
if environ.get('QUERY_STRING'):
url += '?' + environ['QUERY_STRING']
return url
get_full_url=staticmethod(get_full_url)
def save_files(self):
for k,v in self.FILES.items():
if not v.has_key("tmp_file"):
f=tempfile.NamedTemporaryFile()
f.write(v["data"])
f.flush()
v["tmp_file"]=f
v["tmp_name"]=f.name
def debug(self):
body=''
for name,d in [
("GET",self.GET), ("POST",self.POST), ("REQUEST",self.REQUEST), ("FILES",self.FILES),
("GET_LIST",self.GET_LIST), ("POST_LIST",self.POST_LIST), ("REQUEST_LIST",self.REQUEST_LIST), ("FILES_LIST",self.FILES_LIST),
("SESSION",self.SESSION), ("environ",self.environ),
]:
body+='<table border="1" width="100%" align="center">\n'
body+='<tr><th colspan="2" align="center">%s</th></tr>\n'%name
keys=d.keys()
keys.sort()
body+=''.join(['<tr><td>%s</td><td>%s</td></tr>\n'%(k,cgi.escape(repr(d[k]))) for k in keys])
body+='</table><br><br>\n\n'
return body
def write(self,s):
self.buffer.append(s)
def echo(self,*s):
self.buffer.extend([str(i) for i in s])
def response(self):
if not self.response_started:
if not self.php:
for k,v in self.FILES.items():
if v.has_key("tmp_file"):
try:
v["tmp_file"].close()
except OSError:
pass
if self.response_gzencode and self.environ.get('HTTP_ACCEPT_ENCODING','').find('gzip')!=-1:
zbuf=StringIO.StringIO()
zfile=gzip.GzipFile(mode='wb', fileobj=zbuf)
zfile.write(''.join(self.buffer))
zfile.close()
zbuf=zbuf.getvalue()
self.buffer=[zbuf]
self.response_headers['Content-Encoding']="gzip"
self.response_headers['Content-Length']=str(len(zbuf))
headers = self.response_headers.get()
if isinstance(self.SESSION, QWebSession):
headers.extend(self.SESSION.session_get_headers())
headers.extend([('Set-Cookie', self.response_cookies[i].OutputString()) for i in self.response_cookies])
self.start_response(self.response_status, headers)
self.response_started=True
return self.buffer
def __iter__(self):
return self.response().__iter__()
def http_redirect(self,url,permanent=1):
if permanent:
self.response_status="301 Moved Permanently"
else:
self.response_status="302 Found"
self.response_headers["Location"]=url
def http_404(self,msg="<h1>404 Not Found</h1>"):
self.response_status="404 Not Found"
if msg:
self.write(msg)
def http_download(self,fname,fstr,partial=0):
# allow fstr to be a file-like object
# if parital:
# say accept ranages
# parse range headers...
# if range:
# header("HTTP/1.1 206 Partial Content");
# header("Content-Range: bytes $offset-".($fsize-1)."/".$fsize);
# header("Content-Length: ".($fsize-$offset));
# fseek($fd,$offset);
# else:
self.response_headers["Content-Type"]="application/octet-stream"
self.response_headers["Content-Disposition"]="attachment; filename=\"%s\""%fname
self.response_headers["Content-Transfer-Encoding"]="binary"
self.response_headers["Content-Length"]="%d"%len(fstr)
self.write(fstr)
#----------------------------------------------------------
# QWeb WSGI HTTP Server to run any WSGI app
# autorun, run an app as FCGI or CGI otherwise launch the server
#----------------------------------------------------------
class QWebWSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def log_message(self,*p):
if self.server.log:
return BaseHTTPServer.BaseHTTPRequestHandler.log_message(self,*p)
def address_string(self):
return self.client_address[0]
def start_response(self,status,headers):
l=status.split(' ',1)
self.send_response(int(l[0]),l[1])
ctype_sent=0
for i in headers:
if i[0].lower()=="content-type":
ctype_sent=1
self.send_header(*i)
if not ctype_sent:
self.send_header("Content-type", "text/html")
self.end_headers()
return self.write
def write(self,data):
try:
self.wfile.write(data)
except (socket.error, socket.timeout),e:
print e
def bufferon(self):
if not getattr(self,'wfile_buf',0):
self.wfile_buf=1
self.wfile_bak=self.wfile
self.wfile=StringIO.StringIO()
def bufferoff(self):
if self.wfile_buf:
buf=self.wfile
self.wfile=self.wfile_bak
self.write(buf.getvalue())
self.wfile_buf=0
def serve(self,type):
path_info, parameters, query = urlparse.urlparse(self.path)[2:5]
environ = {
'wsgi.version': (1,0),
'wsgi.url_scheme': 'http',
'wsgi.input': self.rfile,
'wsgi.errors': sys.stderr,
'wsgi.multithread': 0,
'wsgi.multiprocess': 0,
'wsgi.run_once': 0,
'REQUEST_METHOD': self.command,
'SCRIPT_NAME': '',
'QUERY_STRING': query,
'CONTENT_TYPE': self.headers.get('Content-Type', ''),
'CONTENT_LENGTH': self.headers.get('Content-Length', ''),
'REMOTE_ADDR': self.client_address[0],
'REMOTE_PORT': str(self.client_address[1]),
'SERVER_NAME': self.server.server_address[0],
'SERVER_PORT': str(self.server.server_address[1]),
'SERVER_PROTOCOL': self.request_version,
# extention
'FULL_PATH': self.path,
'qweb.mode': 'standalone',
}
if path_info:
environ['PATH_INFO'] = urllib.unquote(path_info)
for key, value in self.headers.items():
environ['HTTP_' + key.upper().replace('-', '_')] = value
# Hack to avoid may TCP packets
self.bufferon()
appiter=self.server.wsgiapp(environ, self.start_response)
for data in appiter:
self.write(data)
self.bufferoff()
self.bufferoff()
def do_GET(self):
self.serve('GET')
def do_POST(self):
self.serve('GET')
class QWebWSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
""" QWebWSGIServer
qweb_wsgi_autorun(wsgiapp,ip='127.0.0.1',port=8080,threaded=1)
A WSGI HTTP server threaded or not and a function to automatically run your
app according to the environement (either standalone, CGI or FastCGI).
This feature is called QWeb autorun. If you want to To use it on your
application use the following lines at the end of the main application
python file:
if __name__ == '__main__':
qweb.qweb_wsgi_autorun(your_wsgi_app)
this function will select the approriate running mode according to the
calling environement (http-server, FastCGI or CGI).
"""
def __init__(self, wsgiapp, ip, port, threaded=1, log=1):
BaseHTTPServer.HTTPServer.__init__(self, (ip, port), QWebWSGIHandler)
self.wsgiapp = wsgiapp
self.threaded = threaded
self.log = log
def process_request(self,*p):
if self.threaded:
return SocketServer.ThreadingMixIn.process_request(self,*p)
else:
return BaseHTTPServer.HTTPServer.process_request(self,*p)
def qweb_wsgi_autorun(wsgiapp,ip='127.0.0.1',port=8080,threaded=1,log=1,callback_ready=None):
if sys.platform=='win32':
fcgi=0
else:
fcgi=1
sock = socket.fromfd(0, socket.AF_INET, socket.SOCK_STREAM)
try:
sock.getpeername()
except socket.error, e:
if e[0] == errno.ENOTSOCK:
fcgi=0
if fcgi or os.environ.has_key('REQUEST_METHOD'):
import fcgi
fcgi.WSGIServer(wsgiapp,multithreaded=False).run()
else:
if log:
print 'Serving on %s:%d'%(ip,port)
s=QWebWSGIServer(wsgiapp,ip=ip,port=port,threaded=threaded,log=log)
if callback_ready:
callback_ready()
try:
s.serve_forever()
except KeyboardInterrupt,e:
sys.excepthook(*sys.exc_info())
#----------------------------------------------------------
# Qweb Documentation
#----------------------------------------------------------
def qweb_doc():
body=__doc__
for i in [QWebXml ,QWebHtml ,QWebForm ,QWebURL ,qweb_control ,QWebRequest ,QWebSession ,QWebWSGIServer ,qweb_wsgi_autorun]:
n=i.__name__
d=i.__doc__
body+='\n\n%s\n%s\n\n%s'%(n,'-'*len(n),d)
return body
print qweb_doc()
#