ajaxterm!
[invirt/packages/invirt-web.git] / code / qweb.py
1 #!/usr/bin/python2.3
2 #
3 # vim:set et ts=4 fdc=0 fdn=2 fdl=0:
4 #
5 # There are no blank lines between blocks beacause i use folding from:
6 # http://www.vim.org/scripts/script.php?script_id=515
7 #
8
9 """= QWeb Framework =
10
11 == What is QWeb ? ==
12
13 QWeb is a python based [http://www.python.org/doc/peps/pep-0333/ WSGI]
14 compatible web framework, it provides an infratructure to quickly build web
15 applications consisting of:
16
17  * A lightweight request handler (QWebRequest)
18  * An xml templating engine (QWebXml and QWebHtml)
19  * A simple name based controler (qweb_control)
20  * A standalone WSGI Server (QWebWSGIServer)
21  * A cgi and fastcgi WSGI wrapper (taken from flup)
22  * A startup function that starts cgi, factgi or standalone according to the
23    evironement (qweb_autorun).
24
25 QWeb applications are runnable in standalone mode (from commandline), via
26 FastCGI, Regular CGI or by any python WSGI compliant server.
27
28 QWeb doesn't provide any database access but it integrates nicely with ORMs
29 such as SQLObject, SQLAlchemy or plain DB-API.
30
31 Written by Antony Lesuisse (email al AT udev.org)
32
33 Homepage: http://antony.lesuisse.org/qweb/trac/
34
35 Forum: [http://antony.lesuisse.org/qweb/forum/viewforum.php?id=1 Forum]
36
37 == Quick Start (for Linux, MacOS X and cygwin) ==
38
39 Make sure you have at least python 2.3 installed and run the following commands:
40
41 {{{
42 $ wget http://antony.lesuisse.org/qweb/files/QWeb-0.7.tar.gz
43 $ tar zxvf QWeb-0.7.tar.gz
44 $ cd QWeb-0.7/examples/blog
45 $ ./blog.py
46 }}}
47
48 And point your browser to http://localhost:8080/
49
50 You may also try AjaxTerm which uses qweb request handler.
51
52 == Download ==
53
54  * Version 0.7:
55    * Source [/qweb/files/QWeb-0.7.tar.gz QWeb-0.7.tar.gz]
56    * Python 2.3 Egg [/qweb/files/QWeb-0.7-py2.3.egg QWeb-0.7-py2.3.egg]
57    * Python 2.4 Egg [/qweb/files/QWeb-0.7-py2.4.egg QWeb-0.7-py2.4.egg]
58
59  * [/qweb/trac/browser Browse the source repository]
60
61 == Documentation ==
62
63  * [/qweb/trac/browser/trunk/README.txt?format=raw Read the included documentation] 
64  * QwebTemplating
65
66 == Mailin-list ==
67
68  * Forum: [http://antony.lesuisse.org/qweb/forum/viewforum.php?id=1 Forum]
69  * 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]
70
71 QWeb Components:
72 ----------------
73
74 QWeb also feature a simple components api, that enables developers to easily
75 produces reusable components.
76
77 Default qweb components:
78
79     - qweb_static:
80         A qweb component to serve static content from the filesystem or from
81         zipfiles.
82
83     - qweb_dbadmin:
84         scaffolding for sqlobject
85
86 License
87 -------
88 qweb/fcgi.py wich is BSD-like from saddi.com.
89 Everything else is put in the public domain.
90
91
92 TODO
93 ----
94     Announce QWeb to python-announce-list@python.org web-sig@python.org
95     qweb_core
96         rename request methods into
97             request_save_files
98             response_404
99             response_redirect
100             response_download
101         request callback_generator, callback_function ?
102         wsgi callback_server_local
103         xml tags explicitly call render_attributes(t_att)?
104         priority form-checkbox over t-value (for t-option)
105
106 """
107
108 import BaseHTTPServer,SocketServer,Cookie
109 import cgi,datetime,email,email.Message,errno,gzip,os,random,re,socket,sys,tempfile,time,types,urllib,urlparse,xml.dom
110 try:
111     import cPickle as pickle
112 except ImportError:
113     import pickle
114 try:
115     import cStringIO as StringIO
116 except ImportError:
117     import StringIO
118
119 #----------------------------------------------------------
120 # Qweb Xml t-raw t-esc t-if t-foreach t-set t-call t-trim
121 #----------------------------------------------------------
122 class QWebEval:
123     def __init__(self,data):
124         self.data=data
125     def __getitem__(self,expr):
126         if self.data.has_key(expr):
127             return self.data[expr]
128         r=None
129         try:
130             r=eval(expr,self.data)
131         except NameError,e:
132             pass
133         except AttributeError,e:
134             pass
135         except Exception,e:
136             print "qweb: expression error '%s' "%expr,e
137         if self.data.has_key("__builtins__"):
138             del self.data["__builtins__"]
139         return r
140     def eval_object(self,expr):
141         return self[expr]
142     def eval_str(self,expr):
143         if expr=="0":
144             return self.data[0]
145         if isinstance(self[expr],unicode):
146             return self[expr].encode("utf8")
147         return str(self[expr])
148     def eval_format(self,expr):
149         try:
150             return str(expr%self)
151         except:
152             return "qweb: format error '%s' "%expr
153 #       if isinstance(r,unicode):
154 #           return r.encode("utf8")
155     def eval_bool(self,expr):
156         if self.eval_object(expr):
157             return 1
158         else:
159             return 0
160 class QWebXml:
161     """QWeb Xml templating engine
162     
163     The templating engine use a very simple syntax, "magic" xml attributes, to
164     produce any kind of texutal output (even non-xml).
165     
166     QWebXml:
167         the template engine core implements the basic magic attributes:
168     
169         t-att t-raw t-esc t-if t-foreach t-set t-call t-trim
170     
171     """
172     def __init__(self,x=None,zipname=None):
173         self.node=xml.dom.Node
174         self._t={}
175         self._render_tag={}
176         prefix='render_tag_'
177         for i in [j for j in dir(self) if j.startswith(prefix)]:
178             name=i[len(prefix):].replace('_','-')
179             self._render_tag[name]=getattr(self.__class__,i)
180
181         self._render_att={}
182         prefix='render_att_'
183         for i in [j for j in dir(self) if j.startswith(prefix)]:
184             name=i[len(prefix):].replace('_','-')
185             self._render_att[name]=getattr(self.__class__,i)
186
187         if x!=None:
188             if zipname!=None:
189                 import zipfile
190                 zf=zipfile.ZipFile(zipname, 'r')
191                 self.add_template(zf.read(x))
192             else:
193                 self.add_template(x)
194     def register_tag(self,tag,func):
195         self._render_tag[tag]=func
196     def add_template(self,x):
197         if hasattr(x,'documentElement'):
198             dom=x
199         elif x.startswith("<?xml"):
200             import xml.dom.minidom
201             dom=xml.dom.minidom.parseString(x)
202         else:
203             import xml.dom.minidom
204             dom=xml.dom.minidom.parse(x)
205         for n in dom.documentElement.childNodes:
206             if n.nodeName=="t":
207                 self._t[str(n.getAttribute("t-name"))]=n
208     def get_template(self,name):
209         return self._t[name]
210
211     def eval_object(self,expr,v):
212         return QWebEval(v).eval_object(expr)
213     def eval_str(self,expr,v):
214         return QWebEval(v).eval_str(expr)
215     def eval_format(self,expr,v):
216         return QWebEval(v).eval_format(expr)
217     def eval_bool(self,expr,v):
218         return QWebEval(v).eval_bool(expr)
219
220     def render(self,tname,v={},out=None):
221         if self._t.has_key(tname):
222             return self.render_node(self._t[tname],v)
223         else:
224             return 'qweb: template "%s" not found'%tname
225     def render_node(self,e,v):
226         r=""
227         if e.nodeType==self.node.TEXT_NODE or e.nodeType==self.node.CDATA_SECTION_NODE:
228             r=e.data.encode("utf8")
229         elif e.nodeType==self.node.ELEMENT_NODE:
230             pre=""
231             g_att=""
232             t_render=None
233             t_att={}
234             for (an,av) in e.attributes.items():
235                 an=str(an)
236                 if isinstance(av,types.UnicodeType):
237                     av=av.encode("utf8")
238                 else:
239                     av=av.nodeValue.encode("utf8")
240                 if an.startswith("t-"):
241                     for i in self._render_att:
242                         if an[2:].startswith(i):
243                             g_att+=self._render_att[i](self,e,an,av,v)
244                             break
245                     else:
246                         if self._render_tag.has_key(an[2:]):
247                             t_render=an[2:]
248                         t_att[an[2:]]=av
249                 else:
250                     g_att+=' %s="%s"'%(an,cgi.escape(av,1));
251             if t_render:
252                 if self._render_tag.has_key(t_render):
253                     r=self._render_tag[t_render](self,e,t_att,g_att,v)
254             else:
255                 r=self.render_element(e,g_att,v,pre,t_att.get("trim",0))
256         return r
257     def render_element(self,e,g_att,v,pre="",trim=0):
258         g_inner=[]
259         for n in e.childNodes:
260             g_inner.append(self.render_node(n,v))
261         name=str(e.nodeName)
262         inner="".join(g_inner)
263         if trim==0:
264             pass
265         elif trim=='left':
266             inner=inner.lstrip()
267         elif trim=='right':
268             inner=inner.rstrip()
269         elif trim=='both':
270             inner=inner.strip()
271         if name=="t":
272             return inner
273         elif len(inner):
274             return "<%s%s>%s%s</%s>"%(name,g_att,pre,inner,name)
275         else:
276             return "<%s%s/>"%(name,g_att)
277
278     # Attributes
279     def render_att_att(self,e,an,av,v):
280         if an.startswith("t-attf-"):
281             att,val=an[7:],self.eval_format(av,v)
282         elif an.startswith("t-att-"):
283             att,val=(an[6:],self.eval_str(av,v))
284         else:
285             att,val=self.eval_object(av,v)
286         return ' %s="%s"'%(att,cgi.escape(val,1))
287
288     # Tags
289     def render_tag_raw(self,e,t_att,g_att,v):
290         return self.eval_str(t_att["raw"],v)
291     def render_tag_rawf(self,e,t_att,g_att,v):
292         return self.eval_format(t_att["rawf"],v)
293     def render_tag_esc(self,e,t_att,g_att,v):
294         return cgi.escape(self.eval_str(t_att["esc"],v))
295     def render_tag_escf(self,e,t_att,g_att,v):
296         return cgi.escape(self.eval_format(t_att["escf"],v))
297     def render_tag_foreach(self,e,t_att,g_att,v):
298         expr=t_att["foreach"]
299         enum=self.eval_object(expr,v)
300         if enum!=None:
301             var=t_att.get('as',expr).replace('.','_')
302             d=v.copy()
303             size=-1
304             if isinstance(enum,types.ListType):
305                 size=len(enum)
306             elif isinstance(enum,types.TupleType):
307                 size=len(enum)
308             elif hasattr(enum,'count'):
309                 size=enum.count()
310             d["%s_size"%var]=size
311             d["%s_all"%var]=enum
312             index=0
313             ru=[]
314             for i in enum:
315                 d["%s_value"%var]=i
316                 d["%s_index"%var]=index
317                 d["%s_first"%var]=index==0
318                 d["%s_even"%var]=index%2
319                 d["%s_odd"%var]=(index+1)%2
320                 d["%s_last"%var]=index+1==size
321                 if index%2:
322                     d["%s_parity"%var]='odd'
323                 else:
324                     d["%s_parity"%var]='even'
325                 if isinstance(i,types.DictType):
326                     d.update(i)
327                 else:
328                     d[var]=i
329                 ru.append(self.render_element(e,g_att,d))
330                 index+=1
331             return "".join(ru)
332         else:
333             return "qweb: t-foreach %s not found."%expr
334     def render_tag_if(self,e,t_att,g_att,v):
335         if self.eval_bool(t_att["if"],v):
336             return self.render_element(e,g_att,v)
337         else:
338             return ""
339     def render_tag_call(self,e,t_att,g_att,v):
340         # TODO t-prefix
341         if t_att.has_key("import"):
342             d=v
343         else:
344             d=v.copy()
345         d[0]=self.render_element(e,g_att,d)
346         return self.render(t_att["call"],d)
347     def render_tag_set(self,e,t_att,g_att,v):
348         if t_att.has_key("eval"):
349             v[t_att["set"]]=self.eval_object(t_att["eval"],v)
350         else:
351             v[t_att["set"]]=self.render_element(e,g_att,v)
352         return ""
353
354 #----------------------------------------------------------
355 # QWeb HTML (+deprecated QWebFORM and QWebOLD)
356 #----------------------------------------------------------
357 class QWebURL:
358     """ URL helper
359     assert req.PATH_INFO== "/site/admin/page_edit"
360     u = QWebURL(root_path="/site/",req_path=req.PATH_INFO)
361     s=u.url2_href("user/login",{'a':'1'})
362     assert s=="../user/login?a=1"
363     
364     """
365     def __init__(self, root_path="/", req_path="/",defpath="",defparam={}):
366         self.defpath=defpath
367         self.defparam=defparam
368         self.root_path=root_path
369         self.req_path=req_path
370         self.req_list=req_path.split("/")[:-1]
371         self.req_len=len(self.req_list)
372     def decode(self,s):
373         h={}
374         for k,v in cgi.parse_qsl(s,1):
375             h[k]=v
376         return h
377     def encode(self,h):
378         return urllib.urlencode(h.items())
379     def request(self,req):
380         return req.REQUEST
381     def copy(self,path=None,param=None):
382         npath=self.defpath
383         if path:
384             npath=path
385         nparam=self.defparam.copy()
386         if param:
387             nparam.update(param)
388         return QWebURL(self.root_path,self.req_path,npath,nparam)
389     def path(self,path=''):
390         if not path:
391             path=self.defpath
392         pl=(self.root_path+path).split('/')
393         i=0
394         for i in range(min(len(pl), self.req_len)):
395             if pl[i]!=self.req_list[i]:
396                 break
397         else:
398             i+=1
399         dd=self.req_len-i
400         if dd<0:
401             dd=0
402         return '/'.join(['..']*dd+pl[i:])
403     def href(self,path='',arg={}):
404         p=self.path(path)
405         tmp=self.defparam.copy()
406         tmp.update(arg)
407         s=self.encode(tmp)
408         if len(s):
409             return p+"?"+s
410         else:
411             return p
412     def form(self,path='',arg={}):
413         p=self.path(path)
414         tmp=self.defparam.copy()
415         tmp.update(arg)
416         r=''.join(['<input type="hidden" name="%s" value="%s"/>'%(k,cgi.escape(str(v),1)) for k,v in tmp.items()])
417         return (p,r)
418 class QWebField:
419     def __init__(self,name=None,default="",check=None):
420         self.name=name
421         self.default=default
422         self.check=check
423         # optional attributes
424         self.type=None
425         self.trim=1
426         self.required=1
427         self.cssvalid="form_valid"
428         self.cssinvalid="form_invalid"
429         # set by addfield
430         self.form=None
431         # set by processing
432         self.input=None
433         self.css=None
434         self.value=None
435         self.valid=None
436         self.invalid=None
437         self.validate(1)
438     def validate(self,val=1,update=1):
439         if val:
440             self.valid=1
441             self.invalid=0
442             self.css=self.cssvalid
443         else:
444             self.valid=0
445             self.invalid=1
446             self.css=self.cssinvalid
447         if update and self.form:
448             self.form.update()
449     def invalidate(self,update=1):
450         self.validate(0,update)
451 class QWebForm:
452     class QWebFormF:
453         pass
454     def __init__(self,e=None,arg=None,default=None):
455         self.fields={}
456         # all fields have been submitted
457         self.submitted=False
458         self.missing=[]
459         # at least one field is invalid or missing
460         self.invalid=False
461         self.error=[]
462         # all fields have been submitted and are valid
463         self.valid=False
464         # fields under self.f for convenience
465         self.f=self.QWebFormF()
466         if e:
467             self.add_template(e)
468         # assume that the fields are done with the template
469         if default:
470             self.set_default(default,e==None)
471         if arg!=None:
472             self.process_input(arg)
473     def __getitem__(self,k):
474         return self.fields[k]
475     def set_default(self,default,add_missing=1):
476         for k,v in default.items():
477             if self.fields.has_key(k):
478                 self.fields[k].default=str(v)
479             elif add_missing:
480                 self.add_field(QWebField(k,v))
481     def add_field(self,f):
482         self.fields[f.name]=f
483         f.form=self
484         setattr(self.f,f.name,f)
485     def add_template(self,e):
486         att={}
487         for (an,av) in e.attributes.items():
488             an=str(an)
489             if an.startswith("t-"):
490                 att[an[2:]]=av.encode("utf8")
491         for i in ["form-text", "form-password", "form-radio", "form-checkbox", "form-select","form-textarea"]:
492             if att.has_key(i):
493                 name=att[i].split(".")[-1]
494                 default=att.get("default","")
495                 check=att.get("check",None)
496                 f=QWebField(name,default,check)
497                 if i=="form-textarea":
498                     f.type="textarea"
499                     f.trim=0
500                 if i=="form-checkbox":
501                     f.type="checkbox"
502                     f.required=0
503                 self.add_field(f)
504         for n in e.childNodes:
505             if n.nodeType==n.ELEMENT_NODE:
506                 self.add_template(n)
507     def process_input(self,arg):
508         for f in self.fields.values():
509             if arg.has_key(f.name):
510                 f.input=arg[f.name]
511                 f.value=f.input
512                 if f.trim:
513                     f.input=f.input.strip()
514                 f.validate(1,False)
515                 if f.check==None:
516                     continue
517                 elif callable(f.check):
518                     pass
519                 elif isinstance(f.check,str):
520                     v=f.check
521                     if f.check=="email":
522                         v=r"/^[^@#!& ]+@[A-Za-z0-9-][.A-Za-z0-9-]{0,64}\.[A-Za-z]{2,5}$/"
523                     if f.check=="date":
524                         v=r"/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/"
525                     if not re.match(v[1:-1],f.input):
526                         f.validate(0,False)
527             else:
528                 f.value=f.default
529         self.update()
530     def validate_all(self,val=1):
531         for f in self.fields.values():
532             f.validate(val,0)
533         self.update()
534     def invalidate_all(self):
535         self.validate_all(0)
536     def update(self):
537         self.submitted=True
538         self.valid=True
539         self.errors=[]
540         for f in self.fields.values():
541             if f.required and f.input==None:
542                 self.submitted=False
543                 self.valid=False
544                 self.missing.append(f.name)
545             if f.invalid:
546                 self.valid=False
547                 self.error.append(f.name)
548         # invalid have been submitted and 
549         self.invalid=self.submitted and self.valid==False
550     def collect(self):
551         d={}
552         for f in self.fields.values():
553             d[f.name]=f.value
554         return d
555 class QWebURLEval(QWebEval):
556     def __init__(self,data):
557         QWebEval.__init__(self,data)
558     def __getitem__(self,expr):
559         r=QWebEval.__getitem__(self,expr)
560         if isinstance(r,str):
561             return urllib.quote_plus(r)
562         else:
563             return r
564 class QWebHtml(QWebXml):
565     """QWebHtml
566     QWebURL:
567     QWebField:
568     QWebForm:
569     QWebHtml:
570         an extended template engine, with a few utility class to easily produce
571         HTML, handle URLs and process forms, it adds the following magic attributes:
572     
573         t-href t-action t-form-text t-form-password t-form-textarea t-form-radio
574         t-form-checkbox t-form-select t-option t-selected t-checked t-pager
575     
576     # explication URL:
577     # v['tableurl']=QWebUrl({p=afdmin,saar=,orderby=,des=,mlink;meta_active=})
578     # t-href="tableurl?desc=1"
579     #
580     # explication FORM: t-if="form.valid()"
581     # Foreach i
582     #   email: <input type="text" t-esc-name="i" t-esc-value="form[i].value" t-esc-class="form[i].css"/>
583     #   <input type="radio" name="spamtype" t-esc-value="i" t-selected="i==form.f.spamtype.value"/>
584     #   <option t-esc-value="cc" t-selected="cc==form.f.country.value"><t t-esc="cname"></option>
585     # Simple forms:
586     #   <input t-form-text="form.email" t-check="email"/>
587     #   <input t-form-password="form.email" t-check="email"/>
588     #   <input t-form-radio="form.email" />
589     #   <input t-form-checkbox="form.email" />
590     #   <textarea t-form-textarea="form.email" t-check="email"/>
591     #   <select t-form-select="form.email"/>
592     #       <option t-value="1">
593     #   <input t-form-radio="form.spamtype" t-value="1"/> Cars
594     #   <input t-form-radio="form.spamtype" t-value="2"/> Sprt
595     """
596     # QWebForm from a template
597     def form(self,tname,arg=None,default=None):
598         form=QWebForm(self._t[tname],arg,default)
599         return form
600
601     # HTML Att
602     def eval_url(self,av,v):
603         s=QWebURLEval(v).eval_format(av)
604         a=s.split('?',1)
605         arg={}
606         if len(a)>1:
607             for k,v in cgi.parse_qsl(a[1],1):
608                 arg[k]=v
609         b=a[0].split('/',1)
610         path=''
611         if len(b)>1:
612             path=b[1]
613         u=b[0]
614         return u,path,arg
615     def render_att_url_(self,e,an,av,v):
616         u,path,arg=self.eval_url(av,v)
617         if not isinstance(v.get(u,0),QWebURL):
618             out='qweb: missing url %r %r %r'%(u,path,arg)
619         else:
620             out=v[u].href(path,arg)
621         return ' %s="%s"'%(an[6:],cgi.escape(out,1))
622     def render_att_href(self,e,an,av,v):
623         return self.render_att_url_(e,"t-url-href",av,v)
624     def render_att_checked(self,e,an,av,v):
625         if self.eval_bool(av,v):
626             return ' %s="%s"'%(an[2:],an[2:])
627         else:
628             return ''
629     def render_att_selected(self,e,an,av,v):
630         return self.render_att_checked(e,an,av,v)
631
632     # HTML Tags forms
633     def render_tag_rawurl(self,e,t_att,g_att,v):
634         u,path,arg=self.eval_url(t_att["rawurl"],v)
635         return v[u].href(path,arg)
636     def render_tag_escurl(self,e,t_att,g_att,v):
637         u,path,arg=self.eval_url(t_att["escurl"],v)
638         return cgi.escape(v[u].href(path,arg))
639     def render_tag_action(self,e,t_att,g_att,v):
640         u,path,arg=self.eval_url(t_att["action"],v)
641         if not isinstance(v.get(u,0),QWebURL):
642             action,input=('qweb: missing url %r %r %r'%(u,path,arg),'')
643         else:
644             action,input=v[u].form(path,arg)
645         g_att+=' action="%s"'%action
646         return self.render_element(e,g_att,v,input)
647     def render_tag_form_text(self,e,t_att,g_att,v):
648         f=self.eval_object(t_att["form-text"],v)
649         g_att+=' type="text" name="%s" value="%s" class="%s"'%(f.name,cgi.escape(f.value,1),f.css)
650         return self.render_element(e,g_att,v)
651     def render_tag_form_password(self,e,t_att,g_att,v):
652         f=self.eval_object(t_att["form-password"],v)
653         g_att+=' type="password" name="%s" value="%s" class="%s"'%(f.name,cgi.escape(f.value,1),f.css)
654         return self.render_element(e,g_att,v)
655     def render_tag_form_textarea(self,e,t_att,g_att,v):
656         type="textarea"
657         f=self.eval_object(t_att["form-textarea"],v)
658         g_att+=' name="%s" class="%s"'%(f.name,f.css)
659         r="<%s%s>%s</%s>"%(type,g_att,cgi.escape(f.value,1),type)
660         return r
661     def render_tag_form_radio(self,e,t_att,g_att,v):
662         f=self.eval_object(t_att["form-radio"],v)
663         val=t_att["value"]
664         g_att+=' type="radio" name="%s" value="%s"'%(f.name,val)
665         if f.value==val:
666             g_att+=' checked="checked"'
667         return self.render_element(e,g_att,v)
668     def render_tag_form_checkbox(self,e,t_att,g_att,v):
669         f=self.eval_object(t_att["form-checkbox"],v)
670         val=t_att["value"]
671         g_att+=' type="checkbox" name="%s" value="%s"'%(f.name,val)
672         if f.value==val:
673             g_att+=' checked="checked"'
674         return self.render_element(e,g_att,v)
675     def render_tag_form_select(self,e,t_att,g_att,v):
676         f=self.eval_object(t_att["form-select"],v)
677         g_att+=' name="%s" class="%s"'%(f.name,f.css)
678         return self.render_element(e,g_att,v)
679     def render_tag_option(self,e,t_att,g_att,v):
680         f=self.eval_object(e.parentNode.getAttribute("t-form-select"),v)
681         val=t_att["option"]
682         g_att+=' value="%s"'%(val)
683         if f.value==val:
684             g_att+=' selected="selected"'
685         return self.render_element(e,g_att,v)
686
687     # HTML Tags others
688     def render_tag_pager(self,e,t_att,g_att,v):
689         pre=t_att["pager"]
690         total=int(self.eval_str(t_att["total"],v))
691         start=int(self.eval_str(t_att["start"],v))
692         step=int(self.eval_str(t_att.get("step","100"),v))
693         scope=int(self.eval_str(t_att.get("scope","5"),v))
694         # Compute Pager
695         p=pre+"_"
696         d={}
697         d[p+"tot_size"]=total
698         d[p+"tot_page"]=tot_page=total/step
699         d[p+"win_start0"]=total and start
700         d[p+"win_start1"]=total and start+1
701         d[p+"win_end0"]=max(0,min(start+step-1,total-1))
702         d[p+"win_end1"]=min(start+step,total)
703         d[p+"win_page0"]=win_page=start/step
704         d[p+"win_page1"]=win_page+1
705         d[p+"prev"]=(win_page!=0)
706         d[p+"prev_start"]=(win_page-1)*step
707         d[p+"next"]=(tot_page>=win_page+1)
708         d[p+"next_start"]=(win_page+1)*step
709         l=[]
710         begin=win_page-scope
711         end=win_page+scope
712         if begin<0:
713             end-=begin
714         if end>tot_page:
715             begin-=(end-tot_page)
716         i=max(0,begin)
717         while i<=min(end,tot_page) and total!=step:
718             l.append( { p+"page0":i, p+"page1":i+1, p+"start":i*step, p+"sel":(win_page==i) })
719             i+=1
720         d[p+"active"]=len(l)>1
721         d[p+"list"]=l
722         # Update v
723         v.update(d)
724         return ""
725
726 #----------------------------------------------------------
727 # QWeb Simple Controller
728 #----------------------------------------------------------
729 def qweb_control(self,jump='main',p=[]):
730     """ qweb_control(self,jump='main',p=[]):
731     A simple function to handle the controler part of your application. It
732     dispatch the control to the jump argument, while ensuring that prefix
733     function have been called.
734
735     qweb_control replace '/' to '_' and strip '_' from the jump argument.
736
737     name1
738     name1_name2
739     name1_name2_name3
740
741     """
742     jump=jump.replace('/','_').strip('_')
743     if not hasattr(self,jump):
744         return 0
745     done={}
746     todo=[]
747     while 1:
748         if jump!=None:
749             tmp=""
750             todo=[]
751             for i in jump.split("_"):
752                 tmp+=i+"_";
753                 if not done.has_key(tmp[:-1]):
754                     todo.append(tmp[:-1])
755             jump=None
756         elif len(todo):
757             i=todo.pop(0)
758             done[i]=1
759             if hasattr(self,i):
760                 f=getattr(self,i)
761                 r=f(*p)
762                 if isinstance(r,types.StringType):
763                     jump=r
764         else:
765             break
766     return 1
767
768 #----------------------------------------------------------
769 # QWeb WSGI Request handler
770 #----------------------------------------------------------
771 class QWebSession(dict):
772     def __init__(self,environ,**kw):
773         dict.__init__(self)
774         default={
775             "path" : tempfile.gettempdir(),
776             "cookie_name" : "QWEBSID",
777             "cookie_lifetime" : 0,
778             "cookie_path" : '/',
779             "cookie_domain" : '',
780             "limit_cache" : 1,
781             "probability" : 0.01,
782             "maxlifetime" : 3600,
783             "disable" : 0,
784         }
785         for k,v in default.items():
786             setattr(self,'session_%s'%k,kw.get(k,v))
787         # Try to find session
788         self.session_found_cookie=0
789         self.session_found_url=0
790         self.session_found=0
791         self.session_orig=""
792         # Try cookie
793         c=Cookie.SimpleCookie()
794         c.load(environ.get('HTTP_COOKIE', ''))
795         if c.has_key(self.session_cookie_name):
796             sid=c[self.session_cookie_name].value[:64]
797             if re.match('[a-f0-9]+$',sid) and self.session_load(sid):
798                 self.session_id=sid
799                 self.session_found_cookie=1
800                 self.session_found=1
801         # Try URL
802         if not self.session_found_cookie:
803             mo=re.search('&%s=([a-f0-9]+)'%self.session_cookie_name,environ.get('QUERY_STRING',''))
804             if mo and self.session_load(mo.group(1)):
805                 self.session_id=mo.group(1)
806                 self.session_found_url=1
807                 self.session_found=1
808         # New session
809         if not self.session_found:
810             self.session_id='%032x'%random.randint(1,2**128)
811         self.session_trans_sid="&amp;%s=%s"%(self.session_cookie_name,self.session_id)
812         # Clean old session
813         if random.random() < self.session_probability:
814             self.session_clean()
815     def session_get_headers(self):
816         h=[]
817         if (not self.session_disable) and (len(self) or len(self.session_orig)):
818             self.session_save()
819             if not self.session_found_cookie:
820                 c=Cookie.SimpleCookie()
821                 c[self.session_cookie_name] = self.session_id
822                 c[self.session_cookie_name]['path'] = self.session_cookie_path
823                 if self.session_cookie_domain:
824                     c[self.session_cookie_name]['domain'] = self.session_cookie_domain
825 #               if self.session_cookie_lifetime:
826 #                   c[self.session_cookie_name]['expires'] = TODO date localtime or not, datetime.datetime(1970, 1, 1)
827                 h.append(("Set-Cookie", c[self.session_cookie_name].OutputString()))
828             if self.session_limit_cache:
829                 h.append(('Cache-Control','no-store, no-cache, must-revalidate, post-check=0, pre-check=0'))
830                 h.append(('Expires','Thu, 19 Nov 1981 08:52:00 GMT'))
831                 h.append(('Pragma','no-cache'))
832         return h
833     def session_load(self,sid):
834         fname=os.path.join(self.session_path,'qweb_sess_%s'%sid)
835         try:
836             orig=file(fname).read()
837             d=pickle.loads(orig)
838         except:
839             return
840         self.session_orig=orig
841         self.update(d)
842         return 1
843     def session_save(self):
844         if not os.path.isdir(self.session_path):
845             os.makedirs(self.session_path)
846         fname=os.path.join(self.session_path,'qweb_sess_%s'%self.session_id)
847         try:
848             oldtime=os.path.getmtime(fname)
849         except OSError,IOError:
850             oldtime=0
851         dump=pickle.dumps(self.copy())
852         if (dump != self.session_orig) or (time.time() > oldtime+self.session_maxlifetime/4):
853             tmpname=os.path.join(self.session_path,'qweb_sess_%s_%x'%(self.session_id,random.randint(1,2**32)))
854             f=file(tmpname,'wb')
855             f.write(dump)
856             f.close()
857             if sys.platform=='win32' and os.path.isfile(fname):
858                 os.remove(fname)
859             os.rename(tmpname,fname)
860     def session_clean(self):
861         t=time.time()
862         try:
863             for i in [os.path.join(self.session_path,i) for i in os.listdir(self.session_path) if i.startswith('qweb_sess_')]:
864                 if (t > os.path.getmtime(i)+self.session_maxlifetime):
865                     os.unlink(i)
866         except OSError,IOError:
867             pass
868 class QWebSessionMem(QWebSession):
869     def session_load(self,sid):
870         global _qweb_sessions
871         if not "_qweb_sessions" in globals():
872             _qweb_sessions={}
873         if _qweb_sessions.has_key(sid):
874             self.session_orig=_qweb_sessions[sid]
875             self.update(self.session_orig)
876             return 1
877     def session_save(self):
878         global _qweb_sessions
879         if not "_qweb_sessions" in globals():
880             _qweb_sessions={}
881         _qweb_sessions[self.session_id]=self.copy()
882 class QWebSessionService:
883     def __init__(self, wsgiapp, url_rewrite=0):
884         self.wsgiapp=wsgiapp
885         self.url_rewrite_tags="a=href,area=href,frame=src,form=,fieldset="
886     def __call__(self, environ, start_response):
887         # TODO
888         # use QWebSession to provide environ["qweb.session"]
889         return self.wsgiapp(environ,start_response)
890 class QWebDict(dict):
891     def __init__(self,*p):
892         dict.__init__(self,*p)
893     def __getitem__(self,key):
894         return self.get(key,"")
895     def int(self,key):
896         try:
897             return int(self.get(key,"0"))
898         except ValueError:
899             return 0
900 class QWebListDict(dict):
901     def __init__(self,*p):
902         dict.__init__(self,*p)
903     def __getitem__(self,key):
904         return self.get(key,[])
905     def appendlist(self,key,val):
906         if self.has_key(key):
907             self[key].append(val)
908         else:
909             self[key]=[val]
910     def get_qwebdict(self):
911         d=QWebDict()
912         for k,v in self.items():
913             d[k]=v[-1]
914         return d
915 class QWebRequest:
916     """QWebRequest a WSGI request handler.
917
918     QWebRequest is a WSGI request handler that feature GET, POST and POST
919     multipart methods, handles cookies and headers and provide a dict-like
920     SESSION Object (either on the filesystem or in memory).
921
922     It is constructed with the environ and start_response WSGI arguments:
923     
924       req=qweb.QWebRequest(environ, start_response)
925     
926     req has the folowing attributes :
927     
928       req.environ standard WSGI dict (CGI and wsgi ones)
929     
930     Some CGI vars as attributes from environ for convenience: 
931     
932       req.SCRIPT_NAME
933       req.PATH_INFO
934       req.REQUEST_URI
935     
936     Some computed value (also for convenience)
937     
938       req.FULL_URL full URL recontructed (http://host/query)
939       req.FULL_PATH (URL path before ?querystring)
940     
941     Dict constructed from querystring and POST datas, PHP-like.
942     
943       req.GET contains GET vars
944       req.POST contains POST vars
945       req.REQUEST contains merge of GET and POST
946       req.FILES contains uploaded files
947       req.GET_LIST req.POST_LIST req.REQUEST_LIST req.FILES_LIST multiple arguments versions
948       req.debug() returns an HTML dump of those vars
949     
950     A dict-like session object.
951     
952       req.SESSION the session start when the dict is not empty.
953     
954     Attribute for handling the response
955     
956       req.response_headers dict-like to set headers
957       req.response_cookies a SimpleCookie to set cookies
958       req.response_status a string to set the status like '200 OK'
959     
960       req.write() to write to the buffer
961     
962     req itselfs is an iterable object with the buffer, it will also also call
963     start_response automatically before returning anything via the iterator.
964     
965     To make it short, it means that you may use
966     
967       return req
968     
969     at the end of your request handling to return the reponse to any WSGI
970     application server.
971     """
972     #
973     # This class contains part ripped from colubrid (with the permission of
974     # mitsuhiko) see http://wsgiarea.pocoo.org/colubrid/
975     #
976     # - the class HttpHeaders
977     # - the method load_post_data (tuned version)
978     #
979     class HttpHeaders(object):
980         def __init__(self):
981             self.data = [('Content-Type', 'text/html')]
982         def __setitem__(self, key, value):
983             self.set(key, value)
984         def __delitem__(self, key):
985             self.remove(key)
986         def __contains__(self, key):
987             key = key.lower()
988             for k, v in self.data:
989                 if k.lower() == key:
990                     return True
991             return False
992         def add(self, key, value):
993             self.data.append((key, value))
994         def remove(self, key, count=-1):
995             removed = 0
996             data = []
997             for _key, _value in self.data:
998                 if _key.lower() != key.lower():
999                     if count > -1:
1000                         if removed >= count:
1001                             break
1002                         else:
1003                             removed += 1
1004                     data.append((_key, _value))
1005             self.data = data
1006         def clear(self):
1007             self.data = []
1008         def set(self, key, value):
1009             self.remove(key)
1010             self.add(key, value)
1011         def get(self, key=False, httpformat=False):
1012             if not key:
1013                 result = self.data
1014             else:
1015                 result = []
1016                 for _key, _value in self.data:
1017                     if _key.lower() == key.lower():
1018                         result.append((_key, _value))
1019             if httpformat:
1020                 return '\n'.join(['%s: %s' % item for item in result])
1021             return result
1022     def load_post_data(self,environ,POST,FILES):
1023         length = int(environ['CONTENT_LENGTH'])
1024         DATA = environ['wsgi.input'].read(length)
1025         if environ.get('CONTENT_TYPE', '').startswith('multipart'):
1026             lines = ['Content-Type: %s' % environ.get('CONTENT_TYPE', '')]
1027             for key, value in environ.items():
1028                 if key.startswith('HTTP_'):
1029                     lines.append('%s: %s' % (key, value))
1030             raw = '\r\n'.join(lines) + '\r\n\r\n' + DATA
1031             msg = email.message_from_string(raw)
1032             for sub in msg.get_payload():
1033                 if not isinstance(sub, email.Message.Message):
1034                     continue
1035                 name_dict = cgi.parse_header(sub['Content-Disposition'])[1]
1036                 if 'filename' in name_dict:
1037                     # Nested MIME Messages are not supported'
1038                     if type([]) == type(sub.get_payload()):
1039                         continue
1040                     if not name_dict['filename'].strip():
1041                         continue
1042                     filename = name_dict['filename']
1043                     # why not keep all the filename? because IE always send 'C:\documents and settings\blub\blub.png'
1044                     filename = filename[filename.rfind('\\') + 1:]
1045                     if 'Content-Type' in sub:
1046                         content_type = sub['Content-Type']
1047                     else:
1048                         content_type = None
1049                     s = { "name":filename, "type":content_type, "data":sub.get_payload() }
1050                     FILES.appendlist(name_dict['name'], s)
1051                 else:
1052                     POST.appendlist(name_dict['name'], sub.get_payload())
1053         else:
1054             POST.update(cgi.parse_qs(DATA,keep_blank_values=1))
1055         return DATA
1056
1057     def __init__(self,environ,start_response,session=QWebSession):
1058         self.environ=environ
1059         self.start_response=start_response
1060         self.buffer=[]
1061
1062         self.SCRIPT_NAME = environ.get('SCRIPT_NAME', '')
1063         self.PATH_INFO = environ.get('PATH_INFO', '')
1064         # extensions:
1065         self.FULL_URL = environ['FULL_URL'] = self.get_full_url(environ)
1066         # REQUEST_URI is optional, fake it if absent
1067         if not environ.has_key("REQUEST_URI"):
1068             environ["REQUEST_URI"]=urllib.quote(self.SCRIPT_NAME+self.PATH_INFO)
1069             if environ.get('QUERY_STRING'):
1070                 environ["REQUEST_URI"]+='?'+environ['QUERY_STRING']
1071         self.REQUEST_URI = environ["REQUEST_URI"]
1072         # full quote url path before the ?
1073         self.FULL_PATH = environ['FULL_PATH'] = self.REQUEST_URI.split('?')[0]
1074
1075         self.request_cookies=Cookie.SimpleCookie()
1076         self.request_cookies.load(environ.get('HTTP_COOKIE', ''))
1077
1078         self.response_started=False
1079         self.response_gzencode=False
1080         self.response_cookies=Cookie.SimpleCookie()
1081         # to delete a cookie use: c[key]['expires'] = datetime.datetime(1970, 1, 1)
1082         self.response_headers=self.HttpHeaders()
1083         self.response_status="200 OK"
1084
1085         self.php=None
1086         if self.environ.has_key("php"):
1087             self.php=environ["php"]
1088             self.SESSION=self.php._SESSION
1089             self.GET=self.php._GET
1090             self.POST=self.php._POST
1091             self.REQUEST=self.php._ARG
1092             self.FILES=self.php._FILES
1093         else:
1094             if isinstance(session,QWebSession):
1095                 self.SESSION=session
1096             elif session:
1097                 self.SESSION=session(environ)
1098             else:
1099                 self.SESSION=None
1100             self.GET_LIST=QWebListDict(cgi.parse_qs(environ.get('QUERY_STRING', ''),keep_blank_values=1))
1101             self.POST_LIST=QWebListDict()
1102             self.FILES_LIST=QWebListDict()
1103             self.REQUEST_LIST=QWebListDict(self.GET_LIST)
1104             if environ['REQUEST_METHOD'] == 'POST':
1105                 self.DATA=self.load_post_data(environ,self.POST_LIST,self.FILES_LIST)
1106                 self.REQUEST_LIST.update(self.POST_LIST)
1107             self.GET=self.GET_LIST.get_qwebdict()
1108             self.POST=self.POST_LIST.get_qwebdict()
1109             self.FILES=self.FILES_LIST.get_qwebdict()
1110             self.REQUEST=self.REQUEST_LIST.get_qwebdict()
1111     def get_full_url(environ):
1112         # taken from PEP 333
1113         if 'FULL_URL' in environ:
1114             return environ['FULL_URL']
1115         url = environ['wsgi.url_scheme']+'://'
1116         if environ.get('HTTP_HOST'):
1117             url += environ['HTTP_HOST']
1118         else:
1119             url += environ['SERVER_NAME']
1120             if environ['wsgi.url_scheme'] == 'https':
1121                 if environ['SERVER_PORT'] != '443':
1122                     url += ':' + environ['SERVER_PORT']
1123             else:
1124                 if environ['SERVER_PORT'] != '80':
1125                     url += ':' + environ['SERVER_PORT']
1126         if environ.has_key('REQUEST_URI'):
1127             url += environ['REQUEST_URI']
1128         else:
1129             url += urllib.quote(environ.get('SCRIPT_NAME', ''))
1130             url += urllib.quote(environ.get('PATH_INFO', ''))
1131             if environ.get('QUERY_STRING'):
1132                 url += '?' + environ['QUERY_STRING']
1133         return url
1134     get_full_url=staticmethod(get_full_url)
1135     def save_files(self):
1136         for k,v in self.FILES.items():
1137             if not v.has_key("tmp_file"):
1138                 f=tempfile.NamedTemporaryFile()
1139                 f.write(v["data"])
1140                 f.flush()
1141                 v["tmp_file"]=f
1142                 v["tmp_name"]=f.name
1143     def debug(self):
1144         body=''
1145         for name,d in [
1146             ("GET",self.GET), ("POST",self.POST), ("REQUEST",self.REQUEST), ("FILES",self.FILES),
1147             ("GET_LIST",self.GET_LIST), ("POST_LIST",self.POST_LIST), ("REQUEST_LIST",self.REQUEST_LIST), ("FILES_LIST",self.FILES_LIST),
1148             ("SESSION",self.SESSION), ("environ",self.environ),
1149         ]:
1150             body+='<table border="1" width="100%" align="center">\n'
1151             body+='<tr><th colspan="2" align="center">%s</th></tr>\n'%name
1152             keys=d.keys()
1153             keys.sort()
1154             body+=''.join(['<tr><td>%s</td><td>%s</td></tr>\n'%(k,cgi.escape(repr(d[k]))) for k in keys])
1155             body+='</table><br><br>\n\n'
1156         return body
1157     def write(self,s):
1158         self.buffer.append(s)
1159     def echo(self,*s):
1160         self.buffer.extend([str(i) for i in s])
1161     def response(self):
1162         if not self.response_started:
1163             if not self.php:
1164                 for k,v in self.FILES.items():
1165                     if v.has_key("tmp_file"):
1166                         try:
1167                             v["tmp_file"].close()
1168                         except OSError:
1169                             pass
1170                 if self.response_gzencode and self.environ.get('HTTP_ACCEPT_ENCODING','').find('gzip')!=-1:
1171                     zbuf=StringIO.StringIO()
1172                     zfile=gzip.GzipFile(mode='wb', fileobj=zbuf)
1173                     zfile.write(''.join(self.buffer))
1174                     zfile.close()
1175                     zbuf=zbuf.getvalue()
1176                     self.buffer=[zbuf]
1177                     self.response_headers['Content-Encoding']="gzip"
1178                     self.response_headers['Content-Length']=str(len(zbuf))
1179                 headers = self.response_headers.get()
1180                 if isinstance(self.SESSION, QWebSession):
1181                     headers.extend(self.SESSION.session_get_headers())
1182                 headers.extend([('Set-Cookie', self.response_cookies[i].OutputString()) for i in self.response_cookies])
1183                 self.start_response(self.response_status, headers)
1184             self.response_started=True
1185         return self.buffer
1186     def __iter__(self):
1187         return self.response().__iter__()
1188     def http_redirect(self,url,permanent=1):
1189         if permanent:
1190             self.response_status="301 Moved Permanently"
1191         else:
1192             self.response_status="302 Found"
1193         self.response_headers["Location"]=url
1194     def http_404(self,msg="<h1>404 Not Found</h1>"):
1195         self.response_status="404 Not Found"
1196         if msg:
1197             self.write(msg)
1198     def http_download(self,fname,fstr,partial=0):
1199 #       allow fstr to be a file-like object
1200 #       if parital:
1201 #           say accept ranages
1202 #           parse range headers...
1203 #           if range:
1204 #               header("HTTP/1.1 206 Partial Content");
1205 #               header("Content-Range: bytes $offset-".($fsize-1)."/".$fsize);
1206 #               header("Content-Length: ".($fsize-$offset));
1207 #               fseek($fd,$offset);
1208 #           else:
1209         self.response_headers["Content-Type"]="application/octet-stream"
1210         self.response_headers["Content-Disposition"]="attachment; filename=\"%s\""%fname
1211         self.response_headers["Content-Transfer-Encoding"]="binary"
1212         self.response_headers["Content-Length"]="%d"%len(fstr)
1213         self.write(fstr)
1214
1215 #----------------------------------------------------------
1216 # QWeb WSGI HTTP Server to run any WSGI app
1217 # autorun, run an app as FCGI or CGI otherwise launch the server
1218 #----------------------------------------------------------
1219 class QWebWSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1220     def log_message(self,*p):
1221         if self.server.log:
1222             return BaseHTTPServer.BaseHTTPRequestHandler.log_message(self,*p)
1223     def address_string(self):
1224         return self.client_address[0]
1225     def start_response(self,status,headers):
1226         l=status.split(' ',1)
1227         self.send_response(int(l[0]),l[1])
1228         ctype_sent=0
1229         for i in headers:
1230             if i[0].lower()=="content-type":
1231                 ctype_sent=1
1232             self.send_header(*i)
1233         if not ctype_sent:
1234             self.send_header("Content-type", "text/html")
1235         self.end_headers()
1236         return self.write
1237     def write(self,data):
1238         try:
1239             self.wfile.write(data)
1240         except (socket.error, socket.timeout),e:
1241             print e
1242     def bufferon(self):
1243         if not getattr(self,'wfile_buf',0):
1244             self.wfile_buf=1
1245             self.wfile_bak=self.wfile
1246             self.wfile=StringIO.StringIO()
1247     def bufferoff(self):
1248         if self.wfile_buf:
1249             buf=self.wfile
1250             self.wfile=self.wfile_bak
1251             self.write(buf.getvalue())
1252             self.wfile_buf=0
1253     def serve(self,type):
1254         path_info, parameters, query = urlparse.urlparse(self.path)[2:5]
1255         environ = {
1256             'wsgi.version':         (1,0),
1257             'wsgi.url_scheme':      'http',
1258             'wsgi.input':           self.rfile,
1259             'wsgi.errors':          sys.stderr,
1260             'wsgi.multithread':     0,
1261             'wsgi.multiprocess':    0,
1262             'wsgi.run_once':        0,
1263             'REQUEST_METHOD':       self.command,
1264             'SCRIPT_NAME':          '',
1265             'QUERY_STRING':         query,
1266             'CONTENT_TYPE':         self.headers.get('Content-Type', ''),
1267             'CONTENT_LENGTH':       self.headers.get('Content-Length', ''),
1268             'REMOTE_ADDR':          self.client_address[0],
1269             'REMOTE_PORT':          str(self.client_address[1]),
1270             'SERVER_NAME':          self.server.server_address[0],
1271             'SERVER_PORT':          str(self.server.server_address[1]),
1272             'SERVER_PROTOCOL':      self.request_version,
1273             # extention
1274             'FULL_PATH':            self.path,
1275             'qweb.mode':            'standalone',
1276         }
1277         if path_info:
1278             environ['PATH_INFO'] = urllib.unquote(path_info)
1279         for key, value in self.headers.items():
1280             environ['HTTP_' + key.upper().replace('-', '_')] = value
1281         # Hack to avoid may TCP packets
1282         self.bufferon()
1283         appiter=self.server.wsgiapp(environ, self.start_response)
1284         for data in appiter:
1285             self.write(data)
1286             self.bufferoff()
1287         self.bufferoff()
1288     def do_GET(self):
1289         self.serve('GET')
1290     def do_POST(self):
1291         self.serve('GET')
1292 class QWebWSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
1293     """ QWebWSGIServer
1294         qweb_wsgi_autorun(wsgiapp,ip='127.0.0.1',port=8080,threaded=1)
1295         A WSGI HTTP server threaded or not and a function to automatically run your
1296         app according to the environement (either standalone, CGI or FastCGI).
1297
1298         This feature is called QWeb autorun. If you want to  To use it on your
1299         application use the following lines at the end of the main application
1300         python file:
1301
1302         if __name__ == '__main__':
1303             qweb.qweb_wsgi_autorun(your_wsgi_app)
1304
1305         this function will select the approriate running mode according to the
1306         calling environement (http-server, FastCGI or CGI).
1307     """
1308     def __init__(self, wsgiapp, ip, port, threaded=1, log=1):
1309         BaseHTTPServer.HTTPServer.__init__(self, (ip, port), QWebWSGIHandler)
1310         self.wsgiapp = wsgiapp
1311         self.threaded = threaded
1312         self.log = log
1313     def process_request(self,*p):
1314         if self.threaded:
1315             return SocketServer.ThreadingMixIn.process_request(self,*p)
1316         else:
1317             return BaseHTTPServer.HTTPServer.process_request(self,*p)
1318 def qweb_wsgi_autorun(wsgiapp,ip='127.0.0.1',port=8080,threaded=1,log=1,callback_ready=None):
1319     if sys.platform=='win32':
1320         fcgi=0
1321     else:
1322         fcgi=1
1323         sock = socket.fromfd(0, socket.AF_INET, socket.SOCK_STREAM)
1324         try:
1325             sock.getpeername()
1326         except socket.error, e:
1327             if e[0] == errno.ENOTSOCK:
1328                 fcgi=0
1329     if fcgi or os.environ.has_key('REQUEST_METHOD'):
1330         import fcgi
1331         fcgi.WSGIServer(wsgiapp,multithreaded=False).run()
1332     else:
1333         if log:
1334             print 'Serving on %s:%d'%(ip,port)
1335         s=QWebWSGIServer(wsgiapp,ip=ip,port=port,threaded=threaded,log=log)
1336         if callback_ready:
1337             callback_ready()
1338         try:
1339             s.serve_forever()
1340         except KeyboardInterrupt,e:
1341             sys.excepthook(*sys.exc_info())
1342
1343 #----------------------------------------------------------
1344 # Qweb Documentation
1345 #----------------------------------------------------------
1346 def qweb_doc():
1347     body=__doc__
1348     for i in [QWebXml ,QWebHtml ,QWebForm ,QWebURL ,qweb_control ,QWebRequest ,QWebSession ,QWebWSGIServer ,qweb_wsgi_autorun]:
1349         n=i.__name__
1350         d=i.__doc__
1351         body+='\n\n%s\n%s\n\n%s'%(n,'-'*len(n),d)
1352     return body
1353
1354     print qweb_doc()
1355
1356 #