1357 lines
49 KiB
Python
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="&%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()
|
||
|
|
||
|
#
|