[Sqlalchemy-commits] [1331] zblog: moving zblog demo to sa tree
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-04-24 01:17:27
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><style type="text/css"><!-- #msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; } #msg dt { float: left; width: 6em; font-weight: bold; } #msg dt:after { content:':';} #msg dl, #msg dt, #msg ul, #msg li { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; } #msg dl a { font-weight: bold} #msg dl a:link { color:#fc3; } #msg dl a:active { color:#ff0; } #msg dl a:visited { color:#cc6; } h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; } #msg ul, pre { overflow: auto; } #patch { width: 100%; } #patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;} #patch .propset h4, #patch .binary h4 {margin:0;} #patch pre {padding:0;line-height:1.2em;margin:0;} #patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;} #patch .propset .diff, #patch .binary .diff {padding:10px 0;} #patch span {display:block;padding:0 10px;} #patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, .info {color:#888;background:#fff;} --></style> <title>[1331] zblog: moving zblog demo to sa tree</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1331</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-23 20:17:08 -0500 (Sun, 23 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>moving zblog demo to sa tree</pre> <h3>Added Paths</h3> <ul> <li>zblog/trunk/</li> <li><a href="#zblogtrunkREADME">zblog/trunk/README</a></li> <li>zblog/trunk/bin/</li> <li><a href="#zblogtrunkbinserverpy">zblog/trunk/bin/server.py</a></li> <li>zblog/trunk/components/</li> <li><a href="#zblogtrunkcomponentscomponentsmyc">zblog/trunk/components/components.myc</a></li> <li><a href="#zblogtrunkcomponentsdatamyc">zblog/trunk/components/data.myc</a></li> <li><a href="#zblogtrunkcomponentsformmyc">zblog/trunk/components/form.myc</a></li> <li><a href="#zblogtrunkcomponentstoolbarmyc">zblog/trunk/components/toolbar.myc</a></li> <li>zblog/trunk/config/</li> <li><a href="#zblogtrunkconfigserver_configpy">zblog/trunk/config/server_config.py</a></li> <li>zblog/trunk/data/</li> <li>zblog/trunk/htdocs/</li> <li>zblog/trunk/htdocs/admin/</li> <li><a href="#zblogtrunkhtdocsadminblogmyt">zblog/trunk/htdocs/admin/blog.myt</a></li> <li><a href="#zblogtrunkhtdocsadminindexmyt">zblog/trunk/htdocs/admin/index.myt</a></li> <li><a href="#zblogtrunkhtdocsadminusermyt">zblog/trunk/htdocs/admin/user.myt</a></li> <li>zblog/trunk/htdocs/ajax/</li> <li><a href="#zblogtrunkhtdocsajaxmyghtyjaxjs">zblog/trunk/htdocs/ajax/myghtyjax.js</a></li> <li><a href="#zblogtrunkhtdocsajaxmyghtyjaxmyt">zblog/trunk/htdocs/ajax/myghtyjax.myt</a></li> <li><a href="#zblogtrunkhtdocsautohandler">zblog/trunk/htdocs/autohandler</a></li> <li>zblog/trunk/htdocs/blog/</li> <li><a href="#zblogtrunkhtdocsblogformsmyt">zblog/trunk/htdocs/blog/forms.myt</a></li> <li><a href="#zblogtrunkhtdocsblogindexmyt">zblog/trunk/htdocs/blog/index.myt</a></li> <li><a href="#zblogtrunkhtdocsblogpostmyt">zblog/trunk/htdocs/blog/post.myt</a></li> <li><a href="#zblogtrunkhtdocsblogpostcommentmyt">zblog/trunk/htdocs/blog/postcomment.myt</a></li> <li><a href="#zblogtrunkhtdocsblogviewsmyt">zblog/trunk/htdocs/blog/views.myt</a></li> <li>zblog/trunk/htdocs/bootstrap/</li> <li><a href="#zblogtrunkhtdocsbootstrapcompletemyt">zblog/trunk/htdocs/bootstrap/complete.myt</a></li> <li><a href="#zblogtrunkhtdocsbootstrapindexmyt">zblog/trunk/htdocs/bootstrap/index.myt</a></li> <li><a href="#zblogtrunkhtdocsfaviconico">zblog/trunk/htdocs/favicon.ico</a></li> <li><a href="#zblogtrunkhtdocsglobaljs">zblog/trunk/htdocs/global.js</a></li> <li><a href="#zblogtrunkhtdocsindexmyt">zblog/trunk/htdocs/index.myt</a></li> <li><a href="#zblogtrunkhtdocsloginmyt">zblog/trunk/htdocs/login.myt</a></li> <li><a href="#zblogtrunkhtdocsregistermyt">zblog/trunk/htdocs/register.myt</a></li> <li><a href="#zblogtrunkhtdocsstylecss">zblog/trunk/htdocs/style.css</a></li> <li>zblog/trunk/lib/</li> <li>zblog/trunk/lib/zblog/</li> <li><a href="#zblogtrunklibzblog__init__py">zblog/trunk/lib/zblog/__init__.py</a></li> <li>zblog/trunk/lib/zblog/controller/</li> <li><a href="#zblogtrunklibzblogcontroller__init__py">zblog/trunk/lib/zblog/controller/__init__.py</a></li> <li>zblog/trunk/lib/zblog/controller/blog/</li> <li><a href="#zblogtrunklibzblogcontrollerblog__init__py">zblog/trunk/lib/zblog/controller/blog/__init__.py</a></li> <li><a href="#zblogtrunklibzblogcontrollerblogcommentspy">zblog/trunk/lib/zblog/controller/blog/comments.py</a></li> <li><a href="#zblogtrunklibzblogcontrollerblogindexpy">zblog/trunk/lib/zblog/controller/blog/index.py</a></li> <li><a href="#zblogtrunklibzblogcontrollerbootstrappy">zblog/trunk/lib/zblog/controller/bootstrap.py</a></li> <li><a href="#zblogtrunklibzblogcontrollerfrontpy">zblog/trunk/lib/zblog/controller/front.py</a></li> <li><a href="#zblogtrunklibzblogcontrollerindexpy">zblog/trunk/lib/zblog/controller/index.py</a></li> <li><a href="#zblogtrunklibzblogcontrollerloginpy">zblog/trunk/lib/zblog/controller/login.py</a></li> <li>zblog/trunk/lib/zblog/controller/manage/</li> <li><a href="#zblogtrunklibzblogcontrollermanage__init__py">zblog/trunk/lib/zblog/controller/manage/__init__.py</a></li> <li><a href="#zblogtrunklibzblogcontrollermanageblogpy">zblog/trunk/lib/zblog/controller/manage/blog.py</a></li> <li><a href="#zblogtrunklibzblogcontrollermanageindexpy">zblog/trunk/lib/zblog/controller/manage/index.py</a></li> <li><a href="#zblogtrunklibzblogcontrollermanageuserpy">zblog/trunk/lib/zblog/controller/manage/user.py</a></li> <li>zblog/trunk/lib/zblog/database/</li> <li><a href="#zblogtrunklibzblogdatabase__init__py">zblog/trunk/lib/zblog/database/__init__.py</a></li> <li><a href="#zblogtrunklibzblogdatabasemapperspy">zblog/trunk/lib/zblog/database/mappers.py</a></li> <li><a href="#zblogtrunklibzblogdatabasetablespy">zblog/trunk/lib/zblog/database/tables.py</a></li> <li>zblog/trunk/lib/zblog/domain/</li> <li><a href="#zblogtrunklibzblogdomain__init__py">zblog/trunk/lib/zblog/domain/__init__.py</a></li> <li><a href="#zblogtrunklibzblogdomainactionspy">zblog/trunk/lib/zblog/domain/actions.py</a></li> <li><a href="#zblogtrunklibzblogdomainblogpy">zblog/trunk/lib/zblog/domain/blog.py</a></li> <li><a href="#zblogtrunklibzblogdomainuserpy">zblog/trunk/lib/zblog/domain/user.py</a></li> <li>zblog/trunk/lib/zblog/util/</li> <li><a href="#zblogtrunklibzblogutil__init__py">zblog/trunk/lib/zblog/util/__init__.py</a></li> <li><a href="#zblogtrunklibzblogutilformpy">zblog/trunk/lib/zblog/util/form.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="zblogtrunkREADME"></a> <div class="addfile"><h4>Added: zblog/trunk/README (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/README 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/README 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,129 @@ </span><ins>+Zblog Demo Application + +Yes, its a blog. Probably not a blog app you'd want to use anywhere, although +it could be taken as a starting point to building a more featured application. +Its pretty much a plain white interface that lets you create users, blogs, +blog posts, and comments, with everything reading fresh from the database, +i.e. no file generation or caching or anything. + +The point of it is to illustrate a fairly powerful application layout, which +includes Ajax functionality, basic SQLAlchemy integration, a rudimentary +security framework, and form-handling paradigms that work across regular and +Ajax-style layouts. + +ARCHITECTURE +------------ +The blog application is basically providing a management interface to the +following graph of objects: + + Users + | + +----Blogs <-TopicAssociations-> Topics + | + +----Posts + | + +---Comments<--+ + + | + | | + +-------+ + +The application is laid out something like this: + +Server Application Persistence +------ --------------------- ------------ +bin/server.py ---> lib/zblog/__init__.py --> lib/zblog/database/tables.py +config/server_config.py config/config.py lib/zblog/database/mappers.py + + | + v +View Controller Domain +----- ----------- -------- +htdocs/*.myt <-- lib/zblog/controller/__init__.py <-- lib/zblog/domain/user.py +components/*.myc lib/zblog/controller/front.py lib/zblog/domain/blog.py + lib/zblog/controller/*.py lib/zblog/domain/actions.py + lib/zblog/util/form.py + +Not included above are a few modules in the "bootstrap" category, which are +involved with auto-configuring the application and creating its database +tables when it is run for the first time. + +Server +------ +The server modules deal with running the application. bin/server.py is a basic +Myghty HTTPServerHandler runner, which reads the interpreter configuration +from config/server_config.py. Other runners, such as WSGI, Paste, mod_python, +etc. can be used instead with server_config.py. + +Application +----------- +The base application includes the zblog __init__.py module, which provides +some initialization functions and the ability for other packages to add +themselves to the overall startup process, and the config.py configuration +which includes application-wide config. This file is generated by the initial +"bootstrap" runner. + +Persistence +----------- +tables.py defines the SQLAlchemy table metadata for all tables, which is also +used to create the tables, and mappers.py defines the datamapping +relationships from the tables to the domain objects. + +Domain +------ +this is the center of the app, user.py includes a User object as well as some +basic crypt() functions for passwords, and blog.py includes all the rest of +the objects represented. actions.py defines a set of Action objects which +correspond to specific user actions and states. A single method access() +provides a yes/no response indicating if a particular action is allowed. + +Controller +---------- +The base controller class is in __init__.py, and an application-wide "front" +controller is in front.py. All HTTP requests, including Ajax calls, go through +the front controller. The front controller then locates the actual controller +to be called, examines its security attributes which may also include an +actions.Action object, and either passes along or raises a 403 return code. + +The security attributes are set via a decorator, and this decorator is the +primary method of marking a particular controller method as publically +accessible. The front controller uses its own "resolver context" in order to +locate the secondary controller object; this context is configured in +config/server_config.py. + +Each controller then handles its appropriate section, and makes heavy use of +the Form objects defined in util/form.py in order to send information about +form status to views. + +View +---- +All the Myghty templates, HTML, JS, CSS, including the Myghtyjax files. Form +handling is largely assisted by the components/components.myc package which +provides formfield rendering methods that integrate with the util/form.py +objects. + +RUNNING THE DEMO +---------------- +1. We're using decorators, so you should use Python 2.4 or greater. +2. insure that SQLAlchemy is installed (http://www.sqlalchemy.org/). +3. insure that this latest source tree of Myghty is either installed or in the +PYTHONPATH. +4. insure that either pysqlite, psygopg/Postgres, mysqldb/mysql is installed (or +Oracle/cx_Oracle, but not tested as much). +5. run the server: python ./bin/server.py +6. Go to the URL: http://localhost:8080/ +7. A "bootstrap" page should appear. enter an admin username, password, and +set up database parameters (foolproof defaults are provided for SQLite), then +press "connect" to try connecting to the database. +8. when connect is successful, press "create config file". this should bounce +to a textual screen of table create statements. it also writes a config file +in ./config/config.py . +9. to login, press "login" and login as the administrator user. +10. create a few test users, and a few "blogs". Then maybe a few posts inside +the blogs. +11. the console where the server.py is running should be dumping all the HTTP +requests as well as all the SQL calls and their results. The logging of SQL +calls can be switched off in the config/config.py file. +12. watch what happens when you create a user, then create some blogs for that +user. then delete the user. All the blogs, posts, comments owned by that user +(and comment replies) get deleted too ! amazing. the mapping relationships are +completely defined within lib/zblog/database/mappers.py. </ins></span></pre></div> <a id="zblogtrunkbinserverpy"></a> <div class="addfile"><h4>Added: zblog/trunk/bin/server.py (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/bin/server.py 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/bin/server.py 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,32 @@ </span><ins>+#!/usr/local/bin/python + +"""myghty HTTPServerHandler runner.""" + +import myghty.http.HTTPServerHandler as HTTPServerHandler +import sys, os, re +from myghty.resolver import * + +root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../')) +sys.path.insert(0, os.path.join(root, 'lib')) + +interpreter_config = { + 'root' : root +} +execfile(os.path.join(root, 'config/server_config.py'), globals(), interpreter_config) +interp = HTTPServerHandler.HSHandler(**interpreter_config) + +port = 8080 +httpd = HTTPServerHandler.HTTPServer( + port = port, + + handlers = [ + {r'.*(?:/|\.myt)$' : interp}, + ], + + docroot = [{'.*' : os.path.join(root, 'htdocs')}], + +) + +print "Listening on port %d" % port +httpd.serve_forever() + </ins></span></pre></div> <a id="zblogtrunkcomponentscomponentsmyc"></a> <div class="addfile"><h4>Added: zblog/trunk/components/components.myc (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/components/components.myc 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/components/components.myc 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,228 @@ </span><ins>+<%global> + import string, re +</%global> + +<%method user> + <%init> + return m.get_session().get('user', None) + </%init> +</%method> + +<%method securehref> + <%args> + href + action + </%args> + <%init> + user = m.get_session().get('user', None) + </%init> +% if action.access(user, **ARGS): + <a href="<% href %>"><% m.content() %></a> +% else: + <span class="grey"><% m.content() %></span> +% + +</%method> + +<%method entryform> + <%args> + name=None + action=None + onsubmit=None + ajaxtarget=None + form=None + </%args> + <%init> + if form is not None: + name = form.name + m.attributes[(self.owner.id, 'form')] = form + if ajaxtarget is not None: + onsubmit = m.comp("SELF:ajaxaction", target=ajaxtarget, form=form, name=name) + ";return false;" + </%init> + <form method="post" name="<% name %>" action="<% action %>" onsubmit="<% onsubmit %>"> + <&|SELF:fields&> + <% m.content() %> + </&> + </form> + <%cleanup> + try: + del m.attributes[(self.owner.id, 'form')] + except KeyError: + pass + </%cleanup> +</%method> + +<%method ajaxaction> + <%args> + target + form=None + name=None + </%args> + <%init> + if form is not None: + if name is None: + name = form.name + formargs = m.scomp('SELF:formargs', form=form) + else: + formargs = '' + return target + "(%s)" % formargs + </%init> +</%method> + +<%method formargs trim="both"> + <%args> + form + name=None + </%args> +getargs(document.<% name or form.name %>, <% string.join(["'%s'" % f.displayname for f in form], ',') %>) +</%method> + + +<%method fields> + <table> + <% m.content() %> + </table> +</%method> + +<%method field> + <%args> + type + name=None + field=None + </%args> + <%init> + label = m.content() + if field is None: + form = m.attributes.get((self.owner.id, 'form'), None) + if form is not None and name: + field = form.get(name, None) + + if field is not None: + ARGS['name'] = field.displayname + ARGS.setdefault('value', field.display) + ARGS['data'] = field.data + ARGS['field'] = field + else: + if name is not None: + ARGS.setdefault('value', m.root_request_args.get(name, None)) + component = m.fetch_component("SELF:%s" % type) + </%init> +% if component.attributes.get('hidden', False) or not label: + <% m.comp(component, **ARGS) %> +% else: + <tr> + <td valign="top"><% label %></td> + <td><%python>m.comp(component, **ARGS)</%python> +% if field is not None: + <& /form:fieldstatus, field=field &> +% + </td> + </tr> +% +</%method> + +<%method row> + <%args> + colspan=None + </%args> + <tr> + <td <% colspan and 'colspan="%s"' % colspan or '' %>> + <% m.content() %> + </td> + </tr> +</%method> + + +<%method text> +<%args> + size=20 + name + value +</%args> +<input type="text" size="<% size %>" name="<% name %>" value="<% value %>"> +</%method> + +<%method password> +<%args> + size=20 + name + value +</%args> +<input type="password" size="<% size %>" name="<% name %>" value="<% value %>"> +</%method> + +<%method textarea> +<%args> + rows=5 + cols=50 + name + value +</%args> +<textarea rows="<% rows %>" cols="<% cols %>" name="<% name %>"><% value %></textarea> +</%method> + +<%method hidden> +<%args> + name=None + value +</%args> +<%attr> + hidden=True +</%attr> +<input type="hidden" name="<% name %>" value="<% value %>"> +</%method> + +<%method dropdown> +<%args> + name + data=() + onselect=None + value=None +</%args> +<select name="<% name %>" <% onselect and 'onChange="%s(this.value)"' % onselect or "" %>> +% for entry in data: +<option value="<% entry[0] %>" <% value == entry[0] and 'selected' %> ><% entry[1] %></option> +% +% if m.has_content(): +<% m.content() %> +% +</select> +</%method> + +<%method submit> + <%args> + value + </%args> + <input type="submit" value="<% value %>"> +</%method> + +<%method button> + <%args> + value + onclick + </%args> + <input type="button" value="<% value %>" onClick=<% repr(str(onclick)) %>> +</%method> + +<%method popup> + <%args> + name + </%args> + <!-- popup <% name %>--> + <tr><td colspan="2"><div id="<% name %>"><% m.content() %></div></td></tr> + <!-- end popup --> +</%method> + +<%method confirm> +<%args> + yes + no +</%args> +<div class="confirm"> +<% m.content() %><br/> +<center> + <&/components:field, type="button", value="Yes", onclick=yes &> + <&/components:field, type="button", value="No", onclick=no &> +</center> +</div> +</%method> </ins><span class="cx">\ No newline at end of file </span></span></pre></div> <a id="zblogtrunkcomponentsdatamyc"></a> <div class="addfile"><h4>Added: zblog/trunk/components/data.myc (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/components/data.myc 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/components/data.myc 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,76 @@ </span><ins>+<%doc> + defines a set of "data listing" methods, which provide a template-oriented + interface to various kinds of list-based information. +</%doc> + +<%global> + from zblog.domain.blog import * +</%global> + +<%method bloglist> +<%args> + loop=True + user=None +</%args> +<%init> + if user is not None: + blogs = Blog.mapper.select_by(owner_id=user.id) + else: + blogs = Blog.mapper.select() + if not m.has_content(): + return blogs +</%init> +% if loop: +% for b in blogs: + <% m.content(blog=b) %> +% +% else: + <% m.content(blogs=blogs) %> +% +</%method> + +<%method blogurl trim="both"> + <%args> + blog + </%args> +/blog/<% repr(blog.id) %>/ +</%method> + +<%method blogposts> +<%args> + loop=True + blog + keyword=False +</%args> +<%init> + posts = Post.mapper.select_by(blog_id=blog.id, keyword=keyword) + if not m.has_content(): + return posts +</%init> +% if loop: +% for p in posts: + <% m.content(post=p) %> +% +% else: + <% m.content(posts=posts) %> +% +</%method> + +<%method postcomments> + <%args> + loop=True + post + </%args> + <%init> + comments = Comment.find_by_post(post) + if not m.has_content(): + return comments + </%init> +% if loop: +% for c in comments: + <% m.content(comment=c) %> +% +% else: + <% m.content(comments=comments) %> +% +</%method> </ins></span></pre></div> <a id="zblogtrunkcomponentsformmyc"></a> <div class="addfile"><h4>Added: zblog/trunk/components/form.myc (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/components/form.myc 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/components/form.myc 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,45 @@ </span><ins>+<%doc> + method: fieldstatus + purpose: displays a blue or red asterisk, given a form.FormField object +</%doc> +<%method fieldstatus trim="both"> + <%args> + field = None + </%args> + <%init>if field is None:return</%init> +% if field.is_valid() is False: + <span style="color:red">*</span> +% elif field.required: + <span style="color:blue">*</span> +% +</%method> + + +<%doc> + method: formstatus + purpose: displays a list of invalid fields, given a form.Form object +</%doc> +<%method formstatus> + <%args> + form + </%args> + <%init>if form is None: return</%init> +<div> +% for success in form.success: + <span style="color:green"><% success %></span><br/> +% +% if form.is_valid(): +% return +% +% for error in form.errors: + <span style="color:red"><% error %></span><br/> +% +% for element in form: +% if element.is_valid() is False: +% for error in element.errors: + <span style="color:red"><% error %></span><br/> +% +% +% +</div> +</%method> </ins><span class="cx">\ No newline at end of file </span></span></pre></div> <a id="zblogtrunkcomponentstoolbarmyc"></a> <div class="addfile"><h4>Added: zblog/trunk/components/toolbar.myc (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/components/toolbar.myc 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/components/toolbar.myc 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,42 @@ </span><ins>+<%init> + s = m.get_session() + user = s.get('user', None) + + quotes = [ + "Welcome User #1!", + 'We Put the "P" in "Plain"', + 'The Best Completely Unfinished Blog App Out There', + 'Voted #1 - On Your Desktop', + 'Its a Great Idea - While it Lasts', + 'Over One Million Downloads - In Backwards Negative UpsideDown Land !', + "Has No Graphics - *And* Doesn't Work in Lynx !", + "Oh We've Got Monkeys. And They're Typin'....Just You Wait.", + ] + import random + quote = quotes[random.randint(0, len(quotes)-1)] +</%init> + +<div class="header"> +<span class="large">Zblog</span> - <span class="slogan">"<% quote %>"</span> + +</div> + +<div class="toolbar"> + +% if user is not None: + <div class="loginstatus"> + you are logged in as <b><% user.name %></b> + </div> +% + +<a href="/">Home</a> +<&|/components:securehref, href="/blog/my/", action=actions.LoggedIn() &>My Blogs</&> +<&|/components:securehref, href="/manage/", action=actions.Manage() &>Manage</&> + +% if user is not None: + <a href="/login/logout/">Logout</a> +% else: + <&|/components:securehref, href="/login/", action=actions.Login() &>Login</&> + <&|/components:securehref, href="/login/register/", action=actions.Register() &>Register</&> +% +</div> </ins></span></pre></div> <a id="zblogtrunkconfigserver_configpy"></a> <div class="addfile"><h4>Added: zblog/trunk/config/server_config.py (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/config/server_config.py 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/config/server_config.py 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,62 @@ </span><ins>+ +"""Myghty server configuration. should be neutral to HTTP environment, i.e. WSGI, standalone, etc. + +This file should be executed in a context that includes "root" as the path to the application root.""" + + +data_dir=os.path.join(root, 'cache') + +resolver_strategy = [ + # request-level resolution: everything goes to the front controller + ConditionalGroup( + context="request", rules=[ + ResolveModule({r'(.*)' : 'zblog.controller.front:index'}), + NotFound(), + ] + ), + + # front-controller resolution: front controller forwards requests into this realm + # after performing security checks + ConditionalGroup( + context="frontcontroller", rules=[ + ResolvePathModule(os.path.join(root, 'lib/zblog/controller'), path_stringtokens=['index'], path_moduletokens=['index']), + NotFound() + ] + ), + + # component/template resolution, for subrequests, component calls, inheritance + + # autohandlers + ResolveUpwards(), + + # for the 'components' directory, we append '.myc' to incoming filenames so templates dont have to + # specify + ResolveFile({'components':os.path.join(root, 'components')}, adjust=lambda u: re.sub(r'$', '.myc', u)), + + # page-level resolution; we convert directory names to index.myt + PathTranslate((r'/$', '/index.myt')), + ResolveFile({'htdocs':os.path.join(root, 'htdocs')}), +] + +#debug_elements=['resolution'] + +attributes = { + 'config_file' : os.path.join(root, 'config/config.py'), +} + +def preproc(source): + """source pre-processor. adds an import to the top of all components.""" + return """ +<%global> +import zblog.domain.actions as actions +</%global> +""" + source +python_pre_processor = preproc + + + +# startup stuff. +import zblog +import zblog.database +zblog.load_config(attributes['config_file']) + </ins></span></pre></div> <a id="zblogtrunkhtdocsadminblogmyt"></a> <div class="addfile"><h4>Added: zblog/trunk/htdocs/admin/blog.myt (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/htdocs/admin/blog.myt 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/htdocs/admin/blog.myt 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,85 @@ </span><ins>+ +<%method ajax> +<%init> + return m.comp('/ajax/myghtyjax.myt:init', + editblog = { + 'handler_uri':'/manage/blog/ajax_editblog/', + 'exectype':'writedom', + 'dom_id':'opwindow' + }, + bloglist = { + 'handler_uri':'/manage/blog/bloglist/', + 'exectype':'writedom', + 'dom_id':'opwindow' + }, + action_editblog = { + 'handler_uri':'/manage/blog/edit_blog/', + 'exectype':'writedom', + 'dom_id':'opwindow' + }, + action_deleteblog = { + 'handler_uri':'/manage/blog/delete_blog/', + 'exectype':'writedom', + 'dom_id':'opwindow' + }, + ) +</%init> +</%method> + +<%method links> +% for link in [('javascript:editblog()', 'Create Blog'), ('javascript:bloglist()', 'Edit Blog')]: +<% m.content(url=link[0], text=link[1]) %> +% +</%method> + +<%method blogedit> + <%args> + form + </%args> + +<& /form:formstatus, form=form &> +<&|/components:entryform, ajaxtarget="action_editblog", form=form, columns=2&> + <&/components:field, type="hidden", name='blog_id'&> + <&|/components:field, type="text", name='name'&> + Blog Name + </&> + <&|/components:field, type="text", name='description'&> + Description + </&> + <&|/components:field, type="text", name='owner_name'&> + Owner + </&> + <&|/components:row, colspan="2"&> +% if form['blog_id'].display: + <&/components:field, type="submit", value="Update Blog"&> + <&/components:field, type="button", value="Delete Blog", onclick=m.comp('/components:ajaxaction', target="action_deleteblog", form=form)&> +% else: + <&/components:field, type="submit", value="Create Blog" &> +% + </&> +</&> +</%method> + +<%method bloglist> + +<&|/components:entryform, name="lookupform", onsubmit="editblog({'blog_id':document.lookupform.blog.value});return false", columns=2&> +<tr> + <td>Select a blog:</td> + <td> + <&/components:dropdown, name="blog", data=[(blog.id, blog.name) for blog in m.comp('/data:bloglist')] &> + <&/components:submit, value="Go"&> + </td> +</tr> +</&> +</%method> + +<%method delete_confirm> + <%args> + blog + </%args> + <&|/components:confirm, yes="action_deleteblog({'blog_id':'%s','confirm':1})" % blog.id, + no="editblog({'blog_id':'%s'})" % blog.id&> + Are you sure you want to delete blog '<% blog.name %>'? + This will delete all posts and comments within this blog. + </&> +</%method> </ins><span class="cx">\ No newline at end of file </span></span></pre></div> <a id="zblogtrunkhtdocsadminindexmyt"></a> <div class="addfile"><h4>Added: zblog/trunk/htdocs/admin/index.myt (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/htdocs/admin/index.myt 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/htdocs/admin/index.myt 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,20 @@ </span><ins>+<%init> + if m.comp('blog.myt:ajax') or m.comp('user.myt:ajax'): return +</%init> + +<& /ajax/myghtyjax.myt:js &> + +<h3>ZBlog Management</h3> + +<ul> + <&|blog.myt:links&> + <li><a href="<% m.content_args['url'] %>"><% m.content_args['text'] %></a></li> + </&> + <&|user.myt:links&> + <li><a href="<% m.content_args['url'] %>"><% m.content_args['text'] %></a></li> + </&> +</ul> + +<div id="opwindow"> +</div> + </ins></span></pre></div> <a id="zblogtrunkhtdocsadminusermyt"></a> <div class="addfile"><h4>Added: zblog/trunk/htdocs/admin/user.myt (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/htdocs/admin/user.myt 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/htdocs/admin/user.myt 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,87 @@ </span><ins>+<%method ajax> +<%init> + return m.comp('/ajax/myghtyjax.myt:init', + useradmin={ + 'handler_uri':'/manage/user/ajax_edituser/', + 'exectype':'writedom', + 'dom_id':'opwindow' + }, + action_edituser={ + 'handler_uri':'/manage/user/edit_user/', + 'exectype':'writedom', + 'dom_id':'opwindow' + }, + action_deluser={ + 'handler_uri':'/manage/user/delete_user/', + 'exectype':'writedom', + 'dom_id':'opwindow' + } + ) +</%init> +</%method> + + +<%method links> +% for link in [('javascript:useradmin()', 'User Administration')]: +<% m.content(url=link[0], text=link[1]) %> +% +</%method> + +<%method userform> + <%args> + form + </%args> + +<& /form:formstatus, form=form &> + +<&|/components:entryform, name="lookupform", onsubmit="useradmin({'username':document.lookupform.username.value});return false;", columns=2&> + <tr> + <td> + Lookup User: + </td> + <td> + <&/components:field, type="text", name="username"&> + <&/components:field, type="submit", value="Find"&> + </td> + </tr> +</&> + +<&|/components:entryform, ajaxtarget="action_edituser", form=form, columns=2&> + <&/components:field, type="hidden", name='user_id'&> + <&|/components:field, type="text", name='name' &> + User Name + </&> + <&|/components:field, type="text", name='fullname'&> + Full Name + </&> + <&|/components:field, type="dropdown", name='group'&> + Group + </&> + <&|/components:field, type="password", name='password_set'&> + Password + </&> + <&|/components:field, type="password", name='password_repeat'&> + Repeat Password + </&> + <&|/components:row, colspan="2" &> +% if form['user_id'].value: + <&/components:field, type="submit", value="Update User"&> + <&/components:field, type="button", value="Delete User", onclick=m.comp('/components:ajaxaction', target="action_deluser", form=form) &> +% else: + <&/components:field, type="submit", value="Create User"&> +% + </&> + +</&> +</%method> + +<%method delete_confirm> + <%args> + user + </%args> + <&|/components:confirm, yes="action_deluser({'user_id':'%s','confirm':1})" % user.id, + no="useradmin({'username':'%s'})" % user.name&> + Are you sure you want to delete user '<% user.fullname %>'? + This also deletes all blogs and posts by that user. + </&> +</%method> </ins><span class="cx">\ No newline at end of file </span></span></pre></div> <a id="zblogtrunkhtdocsajaxmyghtyjaxjs"></a> <div class="addfile"><h4>Added: zblog/trunk/htdocs/ajax/myghtyjax.js (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/htdocs/ajax/myghtyjax.js 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/htdocs/ajax/myghtyjax.js 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,106 @@ </span><ins>+ + function error(message) { + var console = window.open("",'console',"width="+400+",height="+400+",status=yes,dependent=yes,resizable=yes,scrollbars=yes"); + console.document.write(message); + console.document.close(); + } + + + /* + receives javascript from the given url + args and evaluates it + */ + function runRemoteJS(url, args) { + doCall(url, function (x) {eval(x);}, args); + } + + function doCall(url, wrapper, args) { + var req = openConnection(); + if (url.indexOf('?') == -1) { + url = url + "?"; + } + for (var key in args) { + if (key=='_mjax_named') { + for (var k in args[key]) { + url = url + '&' + escape(k) + "=" + escape(args[key][k] || ''); + } + } + else { + url = url + '&' + escape(key) + "=" + escape(args[key] || ''); + } + } + + url = url + "&_rnd=" + new Date().getTime() + + req.open("GET", url); + + req.onreadystatechange = function () { + if (req.readyState != 4) { + return; + } + + //var s = getResponse(); + var s = req.responseText; + if (s) { + try { + wrapper(s); + } + catch (e) { + error(e + "<br/><br/>\n\nArgument:\n<br/>" + s); + } + } + + } + req.send(null); + delete req; + + } + + + function openConnection () { + var conn = null; + try { + conn = new ActiveXObject("Msxml2.XMLHTTP"); + } + catch (e) { + try { + conn = new ActiveXObject("Microsoft.XMLHTTP"); + } + catch (oc) { } + } + if(!conn && typeof XMLHttpRequest != "undefined") { + conn = new XMLHttpRequest(); + } + + if (!conn) { + error("XMLHttp connection object not defined on this browser"); + } + return conn; + } + + + + /* + given the id of a document element, a url, and argument hash, populates the given document + element with the results of the url + args + */ + function populateDOM(id, url, args) { + doCall(url, function (x) { + var elem = document.getElementById(id); + if (elem) { + elem.innerHTML = x; + } + else { + error("No such element '" + id + "'"); + } + }, + args); + } + + function writeDOM(id, string) { + var elem = document.getElementById(id); + if (elem) { + elem.innerHTML = string; + } + } + + </ins></span></pre></div> <a id="zblogtrunkhtdocsajaxmyghtyjaxmyt"></a> <div class="addfile"><h4>Added: zblog/trunk/htdocs/ajax/myghtyjax.myt (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/htdocs/ajax/myghtyjax.myt 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/htdocs/ajax/myghtyjax.myt 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,321 @@ </span><ins>+<%flags> + # myghtyjax.myt needs to inherit from either None, or an autohandler + # that doesnt have any textual output. + inherit=None + trim="both" +</%flags> + +<%doc> + MyghtyJax - a Myghty AJAX Adapter + + The "init" method is called to register the names of javascript functions that will + map to Myghty components and/or python function definitions. supply + key=value arguments, where keys are the names of javascript functions + to be generated and the value is one of: a function object, the string + address of a Myghty component (path or method name), or a Myghty component object. + This method should be called in the %init section of the calling component, usually before + anything else has executed. If it returns true it means the XMLHttpRequest action + is taking effect, and the calling component should return immediately. + If false is returned, the component should continue on normally. + + Note that the "js" method below must be called within the HTML body of a page to + actually write out the javascript functions defined by the init method. + + components that are called can specify options for how they should be handled via their + <%attr> sections. the available options are: + + type='source' + take the HTML source of the component and send it to a supplied javascript + callback function. + + type='writedom' + take the HTML source of the component and send it to the DOM element + with the id given by either the <%attr> 'dom_id', or the first argument + to the javascript function. + + type='exec' + take the output of the component and evaluate it directly as javascript. + + Example: + + if m.comp('myghtyjax.myt:init', + mypage = 'SELF:mymethod' + ): return + + The above example will create a javascript function 'mypage()', which results in an Ajax + call back to the myghtyjax.myt page which will route the request to the <%method mymethod> + inside the current page. The results of the method will be processed per the "type" + attribute on the method. + + A second form of init argument allows the values normally inside <%attr> to be + specified directly to init: + + if m.comp('myghtyjax.myt:init', + mypage = { + 'component' : 'SELF:mymethod', + 'exectype' : 'writedom', + 'dom_id' : 'leftnav' + } + ): return + + The above example will create a javascript function 'mypage()', which results in an Ajax + call back to the myghtyjax.myt page which will route the request to the <%method mymethod> + inside the current page. The resulting output from the method will be written to the + 'leftnav' DOM element on the page. + + There is also a way to circumvent the usual URL of the myghtyjax controller + directly to any URL: + + if m.comp('myghtyjax.myt:init', + mypage = { + 'handler_uri' : '/my_ajax_page/', + 'exectype' : 'writedom', + 'dom_id' : 'leftnav' + } + ): return + + The above example will create a javascript function 'mypage()', which results in an Ajax + call directly to the uri '/my_ajax_page/', and the resulting HTML will be written into the + DOM element 'leftnav'. +</%doc> +<%method init> + <%init> + frame = m.execution_stack.pop() + try: + ret = init(m, **ARGS) + finally: + m.execution_stack.append(frame) + return ret + </%init> +</%method> + +<%doc> + js is called after init has been called, and delivers the javascript "stub" functions + that connect page javascript to server side calls via XMLHttpRequest. + + the scripturi and handleruri arguments are optional arguments that + reference the web-accessible URIs of the myghtyjax.js and myghtyjax.myt + files respectively. + + If these parameters are not supplied, they will be searched for in the interpreter + attributes "myghtyjax.handleruri" and "myghtyjax.scripturi". + if not there either, they will be determined based on the path of the original + calling component. +</%doc> +<%method js> + <%args> + scripturi = None + handleruri = None + </%args> + <%init> + js(m, handleruri = handleruri, scripturi = scripturi) + </%init> +</%method> + +<%args> + _mjax_def + _mjax_component +</%args> + +<%init> + m.comp(_mjax_component, **ARGS) +</%init> + + +<%once> + +import inspect, string, types, posixpath +import myghty.request as request + +# initialization method, receives javascript function names mapped +# to python function pointers, component names, or component objects. +# referenced by the "init" myghty method. +def init(m, **params): + run_func = m.request_args.get('_mjax_def', None) + + if (run_func is not None): + object = params[run_func] + callable = _get_callable(m, run_func, object) + callable.run(m) + return True + else: + defs = m.attributes.setdefault('__mjax_defs', {}) + for (jsname, object) in params.iteritems(): + defs[jsname] = _get_callable(m, jsname, object) + return False + +def _get_callable(m, name, object): + if isinstance(object, types.FunctionType): + return DefCallable(name, object) + elif isinstance(object, dict): + return ComponentCallable(m, name, **object) + else: + return ComponentCallable(m, name, object) + +def js(m, handleruri = None, scripturi = None): + try: + defs = m.attributes['__mjax_defs'] + except KeyError: + raise "myghtyjax.js() method requires components and defs to be init()ialized first" + + path = m.current_component.path + + if handleruri is None: + handleruri = m.interpreter.attributes.get('myghtyjax.handleruri', path) + + + if scripturi is None: + scripturi = m.interpreter.attributes.get('myghtyjax.scripturi', None) + if scripturi is None: + (dir, file) = posixpath.split(path) + scripturi = posixpath.join(dir, "myghtyjax.js") + + m.write("<script src=\"%s\"></script>\n" % scripturi) + m.write("<script>\n"); + for callable in defs.values(): + callable.init_callerpath(m) + if callable.exectype == 'writedom': + m.comp('SELF:write_writedom', callable = callable, handleruri = handleruri) + elif callable.exectype == 'source': + m.comp('SELF:write_docall', callable = callable, handleruri=handleruri) + else: + m.comp('SELF:write_remotejs', callable = callable, handleruri=handleruri) + + m.write("</script>\n") + +class JaxComponent: + def init_callerpath(self, m): + if getattr(self, 'callerpath',None) is None: + self.callerpath = m.caller.path + + def get_function_args(self, leading_comma = False): + args = string.join(self.argnames + ['_mjax_named'], ',') + if leading_comma and args: + return ', ' + args + else: + return args + + def get_handler_uri(self, handler_uri): + return handler_uri + + def get_remote_args(self, m): + return string.join( + [ + "'_mjax_def' : '%s'" % self.jsname, + "'_mjax_component' : '%s'" % self.callerpath, + "'_mjax_named':_mjax_named" + ] + + ["'%s' : %s" % (name, name) for name in self.argnames], + ",\n", + ) + + +class ComponentCallable(JaxComponent): + def __init__(self, m, jsname, component=None, exectype=None, argnames=None, dom_id=None, handler_uri=None): + self.jsname = jsname + self.component = component + self.handler_uri=handler_uri + if type(component) == types.StringType: + componentobj = m.fetch_component(component) + else: + componentobj = component + + if exectype is None: + if componentobj is not None: + self.exectype = componentobj.attributes.get('type', 'exec') + else: + self.exectype = 'exec' + else: + self.exectype = exectype + + if dom_id is None and componentobj is not None: + self.dom_id = componentobj.attributes.get('dom_id', None) + else: + self.dom_id = dom_id + + if argnames is None: + if componentobj is not None: + self.argnames = [arg.name for arg in componentobj.arguments] + else: + self.argnames = [] + else: + self.argnames = argnames + + def get_handler_uri(self, handler_uri): + if self.handler_uri is not None: + return self.handler_uri + else: + return handler_uri + + def run(self, m): + m.comp(self.component, **m.root_request_args) + + +class DefCallable(JaxComponent): + def __init__(self, jsname, defobj): + self.jsname = jsname + + if isinstance(defobj, types.MethodType): + defobj = defobj.im_func + self.defobj = defobj + self.argnames = inspect.getargspec(defobj)[0] + self.has_named = inspect.getargspec(defobj)[2] is not None + self.exectype = 'exec' + + def run(self, m): + if self.has_named: + m.write(self.defobj(**m.root_request_args)) + else: + args = {} + for name in self.argnames: + args[name] = m.root_request_args[name] + + m.write(self.defobj(**args)) + +</%once> + +<%method write_docall> + <%args> + callable + handleruri + </%args> + function <% callable.jsname %>(callback<% callable.get_function_args(leading_comma = True) %>) { + doCall('<% callable.get_handler_uri(handleruri) %>', callback, { +<% callable.get_remote_args(m) %> + }); + } +</%method> + +<%method write_remotejs> + <%args> + callable + handleruri + </%args> + function <% callable.jsname %>(<% callable.get_function_args() %>) { + runRemoteJS('<% callable.get_handler_uri(handleruri) %>', { +<% callable.get_remote_args(m) %> + }); + } +</%method> + +<%method write_writedom> + <%args> + callable + handleruri + </%args> +% if callable.dom_id is None: + function <% callable.jsname %>(domid <% callable.get_function_args(leading_comma = True) %>) { + populateDOM(domid, '<% callable.get_handler_uri(handleruri) %>', { +<% callable.get_remote_args(m) %> + }); + } +% else: + function <% callable.jsname %>(<% callable.get_function_args() %>) { + populateDOM('<% callable.dom_id %>', '<% callable.get_handler_uri(handleruri) %>', { +<% callable.get_remote_args(m) %> + }); + } +% +</%method> + + </ins></span></pre></div> <a id="zblogtrunkhtdocsautohandler"></a> <div class="addfile"><h4>Added: zblog/trunk/htdocs/autohandler (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/htdocs/autohandler 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/htdocs/autohandler 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,28 @@ </span><ins>+<html> +<head> + <title><&SELF:title&></title> + <link href="/style.css" rel="stylesheet" type="text/css"></link> +</head> +<body> +<script src="/global.js"></script> +<&/toolbar&> +<div class="main"> +<&SELF:status&> +% m.call_next() +</div> +</body> +</html> + +<%method title> + ZBlog +</%method> + + +<%method status> + <%args scope="subrequest"> + status=None + </%args> +% if status is not None: + <h3><% status %></h3> +% +</%method> </ins></span></pre></div> <a id="zblogtrunkhtdocsblogformsmyt"></a> <div class="addfile"><h4>Added: zblog/trunk/htdocs/blog/forms.myt (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/htdocs/blog/forms.myt 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/htdocs/blog/forms.myt 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,107 @@ </span><ins>+<%method postform> + <%args> + form + blog + post=None + preview=None + </%args> + +% if preview is not None: + <h3>Post Preview</h3> + <& views.myt:view_post, post=preview &> + <hr/> +% + +% if post is not None: + <h4>Editing '<% post.headline %>'</h4> +% else: + <h4>Posting to <% blog.name %></h4> +% + <&|/components:entryform, form=form, action="/blog/post/" &> + <&/components:field, type="hidden", name="preview", value="0"&> + <&/components:field, type="hidden", name="blog_id"&> + <&/components:field, type="hidden", name="post_id"&> + <&|/components:field, type="text", name="headline", size="50"&> + Headline + </&> + <&|/components:field, type="text", name="topic_keywords", size="50"&> + Topics<br/> + (separated by spaces) + </&> + <&|/components:field, type="textarea", name="summary", rows="3", cols="50"&> + Summary + </&> + <&|/components:field, type="textarea", name="body", rows="10", cols="50"&> + Body + </&> + <&|/components:row, colspan="2"&> +% if post is not None: + <&/components:field, type="submit", value="Update Post"&> + <&/components:field, type="button", value="Delete Post", onclick="delete_post({'post_id':'%s'})" % post.id &> +% else: + <&/components:field, type="submit", value="Post"&> +% + <&/components:field, type="button", value="Preview", onclick="document.%s.preview.value=1;document.%s.submit()" % (form.name, form.name)&> + </&> + </&> +</%method> + + +<%method status> + <%args> + form + </%args> +<& /form:formstatus, form=form &> +</%method> + +<%method delete_confirm> + <%args> + post + </%args> + <&|/components:confirm, yes="delete_post({'post_id':'%s','confirm':1})" % post.id, + no="edit_post({'post_id':'%s'})" % post.id&> + <h3>Delete post '<% post.headline %>'</h3> + Are you sure you want to delete this post ? This will also + delete all comments related to this post. + </&> +</%method> + + +<%method commentform> + <%args> + form + parent=None + post + </%args> + <%init> + user = m.comp('/components:user') + </%init> +<a name="reply"></a> +% if parent is None: + <h3>Post a comment for '<% post.headline %>'</h3> +% else: + <h3>Reply to '<% parent.subject %>'</h3> +% +% if not actions.CreateComment().access(user, **ARGS): + Please <&|/components:securehref, href="/login/", action=actions.Login() &>login</&> + to post a comment ! +% return +% + + <&|/components:entryform, form=form, action="/blog/comments/post/"&> + <&/components:field, type="hidden", name="preview", value="0"&> + <&/components:field, type="hidden", name="post_id"&> + <&/components:field, type="hidden", name="parent_comment_id"&> + <&|/components:field, type="text", name="subject", size="50"&> + Subject + </&> + <&|/components:field, type="textarea", name="body", rows="10", cols="50"&> + Body + </&> + <&|/components:row, colspan="2"&> + <&/components:field, type="submit", value="Post Comment"&> + <&/components:field, type="button", value="Preview", onclick="document.%s.preview.value=1;document.%s.submit()" % (form.name, form.name)&> + </&> + </&> +</%method> + </ins></span></pre></div> <a id="zblogtrunkhtdocsblogindexmyt"></a> <div class="addfile"><h4>Added: zblog/trunk/htdocs/blog/index.myt (1330 => 1331)</h4> <pre class="diff"><span> <span class="info">--- zblog/trunk/htdocs/blog/index.myt 2006-04-24 01:15:49 UTC (rev 1330) +++ zblog/trunk/htdocs/blog/index.myt 2006-04-24 01:17:08 UTC (rev 1331) </span><span class="lines">@@ -0,0 +1,81 @@ </span><ins>+<%args> + blog + loadcomponent = None + form = None + commentform = None + keyword=False +</%args> + +<%init> + if m.comp('/ajax/myghtyjax.myt:init', + post = { + 'handler_uri':'/blog/ajax_post/', + 'exectype':'writedom', + 'dom_id':'opwindow' + }, + edit_post = { + 'handler_uri':'/blog/ajax_edit_post/', + 'exectype':'... [truncated message content] |