source: package_branches/invirt-web/ajaxterm-rebased/code/qweb.py @ 2756

Last change on this file since 2756 was 2747, checked in by broder, 15 years ago

Import upstream ajaxterm 0.10

File size: 49.1 KB
RevLine 
[2747]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
13QWeb is a python based [http://www.python.org/doc/peps/pep-0333/ WSGI]
14compatible web framework, it provides an infratructure to quickly build web
15applications 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
25QWeb applications are runnable in standalone mode (from commandline), via
26FastCGI, Regular CGI or by any python WSGI compliant server.
27
28QWeb doesn't provide any database access but it integrates nicely with ORMs
29such as SQLObject, SQLAlchemy or plain DB-API.
30
31Written by Antony Lesuisse (email al AT udev.org)
32
33Homepage: http://antony.lesuisse.org/qweb/trac/
34
35Forum: [http://antony.lesuisse.org/qweb/forum/viewforum.php?id=1 Forum]
36
37== Quick Start (for Linux, MacOS X and cygwin) ==
38
39Make 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
48And point your browser to http://localhost:8080/
49
50You 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
71QWeb Components:
72----------------
73
74QWeb also feature a simple components api, that enables developers to easily
75produces reusable components.
76
77Default 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
86License
87-------
88qweb/fcgi.py wich is BSD-like from saddi.com.
89Everything else is put in the public domain.
90
91
92TODO
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
108import BaseHTTPServer,SocketServer,Cookie
109import cgi,datetime,email,email.Message,errno,gzip,os,random,re,socket,sys,tempfile,time,types,urllib,urlparse,xml.dom
110try:
111    import cPickle as pickle
112except ImportError:
113    import pickle
114try:
115    import cStringIO as StringIO
116except ImportError:
117    import StringIO
118
119#----------------------------------------------------------
120# Qweb Xml t-raw t-esc t-if t-foreach t-set t-call t-trim
121#----------------------------------------------------------
122class 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
160class 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#----------------------------------------------------------
357class 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)
418class 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)
451class 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
555class 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
564class 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#----------------------------------------------------------
729def 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#----------------------------------------------------------
771class 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
868class 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()
882class 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)
890class 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
900class 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
915class 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#----------------------------------------------------------
1219class 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')
1292class 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)
1318def 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#----------------------------------------------------------
1346def 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#
Note: See TracBrowser for help on using the repository browser.