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