[Checkins] SVN: Grokstar/trunk/ New viewlet-based Grokstar

Graham Stratton gns24 at beasts.org
Tue Jun 17 18:41:29 EDT 2008


Log message for revision 87483:
  New viewlet-based Grokstar

Changed:
  U   Grokstar/trunk/INSTALL.txt
  D   Grokstar/trunk/bootstrap/
  A   Grokstar/trunk/bootstrap.py
  U   Grokstar/trunk/buildout.cfg
  U   Grokstar/trunk/setup.py
  U   Grokstar/trunk/src/grokstar/__init__.py
  U   Grokstar/trunk/src/grokstar/blog.py
  D   Grokstar/trunk/src/grokstar/blog_templates/blogabout.pt
  U   Grokstar/trunk/src/grokstar/blog_templates/blogindex.pt
  D   Grokstar/trunk/src/grokstar/blog_templates/blogmacros.pt
  A   Grokstar/trunk/src/grokstar/blog_templates/breadcrumbs.pt
  U   Grokstar/trunk/src/grokstar/blog_templates/categories.pt
  A   Grokstar/trunk/src/grokstar/blog_templates/csshead.pt
  U   Grokstar/trunk/src/grokstar/blog_templates/draftsindex.pt
  A   Grokstar/trunk/src/grokstar/blog_templates/layout.pt
  A   Grokstar/trunk/src/grokstar/blog_templates/recententries.pt
  U   Grokstar/trunk/src/grokstar/blog_templates/search.pt
  A   Grokstar/trunk/src/grokstar/blog_templates/titleheader.pt
  U   Grokstar/trunk/src/grokstar/calendar.py
  U   Grokstar/trunk/src/grokstar/calendar_templates/dateindex.pt
  U   Grokstar/trunk/src/grokstar/configure.zcml
  U   Grokstar/trunk/src/grokstar/entry.py
  A   Grokstar/trunk/src/grokstar/entry_templates/entryindex.pt
  D   Grokstar/trunk/src/grokstar/entry_templates/index.pt
  U   Grokstar/trunk/src/grokstar/entry_templates/item.pt
  U   Grokstar/trunk/src/grokstar/form.py
  U   Grokstar/trunk/src/grokstar/form_templates/grokstaraddform.pt
  U   Grokstar/trunk/src/grokstar/form_templates/grokstareditform.pt
  U   Grokstar/trunk/src/grokstar/interfaces.py
  A   Grokstar/trunk/src/grokstar/mail/
  A   Grokstar/trunk/src/grokstar/mail/__init__.py
  A   Grokstar/trunk/src/grokstar/mail/configure.zcml
  A   Grokstar/trunk/src/grokstar/mail/notifications.txt
  A   Grokstar/trunk/src/grokstar/mail/tests.py
  A   Grokstar/trunk/src/grokstar/rss.py
  A   Grokstar/trunk/src/grokstar/rss_templates/
  A   Grokstar/trunk/src/grokstar/rss_templates/rss.pt
  A   Grokstar/trunk/src/grokstar/static/style.css
  A   Grokstar/trunk/src/grokstar/static/syntax.css
  D   Grokstar/trunk/src/grokstar/utils.py
  U   Grokstar/trunk/src/grokstar/workflow.py

-=-
Modified: Grokstar/trunk/INSTALL.txt
===================================================================
--- Grokstar/trunk/INSTALL.txt	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/INSTALL.txt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,6 +1,6 @@
 How to install Grokstar
 
-$ python2.4 bootstrap/bootstrap.py
+$ python2.4 bootstrap.py
 $ bin/buildout
 
 To run:

Added: Grokstar/trunk/bootstrap.py
===================================================================
--- Grokstar/trunk/bootstrap.py	                        (rev 0)
+++ Grokstar/trunk/bootstrap.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 69908 2006-08-31 21:53:00Z jim $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+ez = {}
+exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                     ).read() in ez
+ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)

Modified: Grokstar/trunk/buildout.cfg
===================================================================
--- Grokstar/trunk/buildout.cfg	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/buildout.cfg	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,9 +1,9 @@
 [buildout]
 develop = .
-parts = app data zopectl i18n test
+parts = app data zopectl test
 find-links = http://download.zope.org/distribution/
 newest = false
-extends= http://grok.zope.org/releaseinfo/grok-0.11.1.cfg
+extends= http://grok.zope.org/releaseinfo/grok-0.12.1.cfg
 versions = versions
 
 [data]
@@ -12,10 +12,11 @@
 [app]
 recipe = zc.zope3recipes>=0.5.3:application
 eggs = Grokstar
-site.zcml = <include package="grokstar" />
+site.zcml = <include package="zope.sendmail" file="meta.zcml" />
+            <include package="grokstar" />
+            <include package="zope.sendmail" />
             <include package="zope.app.twisted" />
 
-            <configure i18n_domain="grokstar">
             <unauthenticatedPrincipal id="zope.anybody"
                                       title="Unauthenticated User" />
             <unauthenticatedGroup id="zope.Anybody"
@@ -43,26 +44,14 @@
             <grantAll role="zope.Manager" />
             <grant role="zope.Manager"
                    principal="zope.manager" />
-           </configure>
 
-[data]
-recipe = zc.recipe.filestorage
-
-# this section named so that the start/stop script is called bin/zopectl
 [zopectl]
 recipe = zc.zope3recipes:instance
+address = 8086
 application = app
 zope.conf = ${data:zconfig}
 
 [test]
 recipe = zc.recipe.testrunner
-eggs = Grokstar
+eggs = grokstar
 defaults = ['--tests-pattern', '^f?tests$', '-v']
-
-# this section named so that the i18n scripts are called bin/i18n...
-[i18n]
-recipe = lovely.recipe:i18n
-package = grokstar
-domain = grokstar
-location = src/grokstar
-output = locales

Modified: Grokstar/trunk/setup.py
===================================================================
--- Grokstar/trunk/setup.py	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/setup.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -23,6 +23,10 @@
                         'zc.catalog',
                         'hurry.query',
                         'hurry.workflow',
+                        'Pygments',
+                        'zope.sendmail',
+                        'zope.app.session',
+                        'zope.app.securitypolicy',
                         ],
       entry_points="""
       # -*- Entry points: -*-

Modified: Grokstar/trunk/src/grokstar/__init__.py
===================================================================
--- Grokstar/trunk/src/grokstar/__init__.py	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/__init__.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1 +1,35 @@
 # this directory is a package
+from docutils import nodes
+from docutils.parsers.rst import directives
+from pygments import highlight
+from pygments.lexers import get_lexer_by_name
+from pygments.formatters import HtmlFormatter
+
+pygments_formatter = HtmlFormatter()
+
+def pygments_directive(name, arguments, options, content, lineno,
+                       content_offset, block_text, state, state_machine):
+    try:
+        lexer = get_lexer_by_name(arguments[0])
+    except ValueError:
+        # no lexer found - use the text one instead of an exception
+        lexer = get_lexer_by_name('text')
+    parsed = highlight(u'\n'.join(content), lexer, pygments_formatter)
+    return [nodes.raw('', parsed, format='html')]
+pygments_directive.arguments = (1, 0, 1)
+pygments_directive.content = 1
+directives.register_directive('sourcecode', pygments_directive)
+
+from grok import View, Viewlet, Application
+def application(self, name=None):
+    obj = self.context
+    while obj is not None:
+        if isinstance(obj, Application):
+            return obj
+        obj = obj.__parent__
+    raise ValueError("No application found.")
+
+
+View.application = property(application)
+Viewlet.application = property(application)
+#XXX You didn't really see me do that, did you?
\ No newline at end of file

Modified: Grokstar/trunk/src/grokstar/blog.py
===================================================================
--- Grokstar/trunk/src/grokstar/blog.py	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/blog.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -2,34 +2,77 @@
 from datetime import datetime, timedelta
 from itertools import islice
 
-from zc.catalog.catalogindex import SetIndex
 from zope import schema, interface
 from zope.interface import Interface
 from zope.traversing.api import getParents
+from zope.component import getUtility, getMultiAdapter
 from hurry.query.query import Query
 from hurry import query
 from hurry.query.set import AllOf
 from hurry.workflow.interfaces import IWorkflowState
+
 import grok
 from grok import index
-from grokstar.interfaces import IRestructuredTextEntry, IBlog
-from grokstar.interfaces import PUBLISHED, CREATED
-from form import GrokstarEditForm
+from grokstar.interfaces import IEntry, IBlog, PUBLISHED, CREATED
 from zope.app.catalog.interfaces import ICatalog
-from zope.component import getUtility
+grok.context(Interface)
 
+
+class EditArticles(grok.Permission):
+    grok.name('grokstar.Edit') # Single editing permission
+
 class Blog(grok.Container, grok.Application):
     interface.implements(IBlog)
+    title = 'Grokstar'
+    tagline = 'A blogging app written with Grok'
+    footer = ''
+    email = ''
 
     def __init__(self):
         super(Blog, self).__init__()
-        self.title = ''
-        self.tagline = ''
         self['entries'] = Entries()
 
+
+
+
+ at grok.subscribe(Blog, grok.IObjectAddedEvent)
+def registerAsUtility(app, event):
+    app.getSiteManager().registerUtility(app, grok.interfaces.IApplication)
+
+class Index(grok.View):    
+    grok.template('layout')
+
+class Edit(grok.View):
+    grok.require('grokstar.Edit')
+    grok.template('layout')
+
+class AddEntry(grok.View):
+    grok.require('grokstar.Edit')
+    grok.template('layout')
+
+
+class Head(grok.ViewletManager):
+    grok.name('head')
+    
+class Main(grok.ViewletManager):
+    grok.name('main')
+
+class Right(grok.ViewletManager):
+    grok.name('right')
+
+class Top(grok.ViewletManager):
+    grok.name('top')
+
+class CssHead(grok.Viewlet):
+    grok.viewletmanager(Head)
+
+class TitleHeader(grok.Viewlet):
+    grok.viewletmanager(Top)
+
+
 class EntryIndexes(grok.Indexes):
     grok.site(Blog)
-    grok.context(IRestructuredTextEntry)
+    grok.context(IEntry)
     grok.name('entry_catalog')
 
     title = index.Text()
@@ -49,65 +92,86 @@
 class Drafts(grok.Model):
       pass
 
-class DraftsIndex(grok.View):
+
+class Search(grok.Viewlet):
+    grok.viewletmanager(Right)
+    grok.order(-1)
+
+    def update(self):
+        if 'q' not in self.request.form:
+            return
+
+        q = self.request.form['q'].strip()
+        if not q:
+            self.results = lastEntries(10)
+            return
+
+        entries = Query().searchResults(
+            (query.Eq(('entry_catalog', 'workflow_state'), PUBLISHED) &
+             (query.Text(('entry_catalog', 'title'), q) |
+              AllOf(('entry_catalog', 'categories'), [q]) |
+              query.Text(('entry_catalog', 'content'), q))))
+        self.results = list(islice(entries, 10))
+
+
+class DraftsIndex(grok.Viewlet):
     grok.context(Drafts)
-    grok.name('index')
+    grok.require('grokstar.Edit')
+    grok.viewletmanager(Main)
     
     def entries(self): 
         return allEntries(10)
 
+class Breadcrumbs(object):
+    grok.viewletmanager(Top)
+    def parents(self):
+        pl = getParents(self.context)
+        return pl
+        
 class Entries(grok.Container):
     pass
 
-class BlogIndex(grok.View):
+class BlogIndex(grok.Viewlet):
     grok.context(Blog)
-    grok.name('index')
+    grok.viewletmanager(Main)
+    grok.view(Index)
 
     def entries(self):
         return lastEntries(10)
 
-class BlogMacros(grok.View):
-    grok.context(Interface)
+class BlogEdit(grok.Viewlet):
+    grok.context(Blog)
+    grok.viewletmanager(Main)
+    grok.view(Edit)
+    
+    def update(self):
+        self.form = getMultiAdapter((self.context, self.request),
+                                    name='blogeditform')
+        self.form.update_form()
 
-class BlogEdit(GrokstarEditForm):
+    def render(self):
+        return self.form.render()
+
+class BlogEditForm(grok.EditForm):
     grok.context(Blog)
-    grok.name('edit')
-    title = u'Edit Blog'
-
+    
     @grok.action('Save changes')
     def edit(self, **data):
         self.applyData(self.context, **data)
         self.redirect(self.url(self.context))
 
-class BlogAbout(grok.View):
-    grok.context(Blog)
-    grok.name('about')
+class EntriesIndex(grok.Viewlet):
+    grok.context(Entries)
+    grok.viewletmanager(Main)
 
-class Search(grok.View):
-    grok.context(Blog)
+    def render(self):
+        return "Entries: %s" % ' '.join(self.context.keys())
 
-    def update(self, q=None):
-        if q is None:
-            return self.redirect(self.application_url())
-
-        q = q.strip()
-        if not q:
-            self.results = lastEntries(10)
-            return
-
-        entries = Query().searchResults(
-            (query.Eq(('entry_catalog', 'workflow_state'), PUBLISHED) &
-             (query.Text(('entry_catalog', 'title'), q) |
-              AllOf(('entry_catalog', 'categories'), [q]) |
-              query.Text(('entry_catalog', 'content'), q))))
-        self.results = list(islice(entries, 10))
-
-class EntriesIndex(grok.View):
-    grok.context(Entries)
-    grok.name('index')
-
+class RecentEntries(grok.Viewlet):
+    grok.viewletmanager(Right)
+    
     def entries(self):
-        return lastEntries(10)
+        return lastEntries(40)
 
 def lastEntries(amount):
     entries = Query().searchResults(
@@ -126,11 +190,21 @@
         entries, key=lambda entry: entry.updated, reverse=True
         )[:amount]
 
-class Categories(grok.View):
-    grok.context(Blog)
-    grok.name('categories')
+class Categories(grok.Viewlet):
+    grok.viewletmanager(Right)
+    c = None
 
     def categories(self):
         cat = getUtility(ICatalog, 'entry_catalog')
         categories = cat['categories']
         return categories.values()
+
+    def update(self):
+        if 'c' not in self.request.form:
+            return
+
+        self.c = self.request.form['c']
+
+        self.entries = Query().searchResults(
+                (query.Eq(('entry_catalog', 'workflow_state'), PUBLISHED) &
+                  AllOf(('entry_catalog', 'categories'), [self.c])))
\ No newline at end of file

Deleted: Grokstar/trunk/src/grokstar/blog_templates/blogabout.pt
===================================================================
--- Grokstar/trunk/src/grokstar/blog_templates/blogabout.pt	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/blog_templates/blogabout.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,11 +0,0 @@
-<!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" xml:lang="en" metal:use-macro="context/@@blogmacros/macros/blogpage">
-  <head>
-    <title metal:fill-slot="title">About</title>
-  </head>
-  <body>
-    <div metal:fill-slot="main-content">
-      Me explain GrokStar!
-    </div>
-  </body>
-</html>

Modified: Grokstar/trunk/src/grokstar/blog_templates/blogindex.pt
===================================================================
--- Grokstar/trunk/src/grokstar/blog_templates/blogindex.pt	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/blog_templates/blogindex.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,21 +1,9 @@
-<!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" xml:lang="en" metal:use-macro="context/@@blogmacros/macros/blogpage">
-  <head>
-    <title metal:fill-slot="title" tal:content="context/title"/>
-  </head>
-  <body>
-    <div metal:fill-slot="main-content">
-      <div id="blogtitle"
-           tal:define="title context/title;
-                       tagline context/tagline">
-        <h1 tal:content="python:title and title or default">Untitled</h1>
-        <h2 tal:content="python:tagline and tagline or default">No tagline</h2>
-        <a href="edit" tal:attributes="href python:view.url('edit')">Edit</a>
-      </div>
-      <ol>
-        <li tal:repeat="entry view/entries"
-            tal:content="structure entry/@@item" />
-      </ol>
-    </div>
-  </body>
-</html>
+<div tal:condition="not: view/entries">
+  No published entries. <a tal:attributes="href string:${view/application/@@absolute_url}/edit">Set up Grokstar</a>, then <a tal:attributes="href string:${view/application/@@absolute_url}/addentry">add an entry</a>
+</div>
+<div class="entries">
+  <tal:block repeat="entry view/entries">
+    <tal:block content="structure entry/@@item"/>
+	  	<hr/>
+  </tal:block>
+</div>

Deleted: Grokstar/trunk/src/grokstar/blog_templates/blogmacros.pt
===================================================================
--- Grokstar/trunk/src/grokstar/blog_templates/blogmacros.pt	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/blog_templates/blogmacros.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,50 +0,0 @@
-<html metal:define-macro="blogpage">
-  <head>
-    <title metal:define-slot="title" tal:content="python:view.__name__" />
-    <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.4.1/build/reset-fonts-grids/reset-fonts-grids.css" />
-    <link rel="stylesheet" type="text/css" href="static/grokstar.css"
-          tal:attributes="href static/grokstar.css" />
-    <div metal:define-slot="headers" />
-  </head>
-  <body id="doc">
-    <div id="hd">
-      <div id="logo">
-        <div id="appname">GrokStar!</div>
-        Me need logo!
-      </div>
-      <div id="menu">
-	<ul>
-	  <li><a href="#" tal:attributes="href view/application_url">Home</a></li>
-	  <li><a href="#" tal:attributes="href python:view.application_url('add')">Post</a></li>
-	  <li><a href="#" tal:attributes="href python:view.application_url('drafts')">Drafts</a></li>
-	  <li><a href="#" tal:attributes="href python:view.application_url('entries')">Published</a></li>
-	  <li><a href="#" tal:attributes="href python:view.application_url('categories')">Categories</a></li>
-	  <li><a href="#" tal:attributes="href python:view.application_url('about')">About</a></li>
-	</ul>
-      </div>
-      <div id="search">
-        <form action="/search" method="get"
-              tal:attributes="action python:view.application_url('search')">
-          <input type="text" name="q" value="" size="15"
-                 tal:attributes="value request/q | default" />
-        </form>
-      </div>
-    </div>
-    <div id="bd">
-      <div metal:define-slot="main-content" />
-    </div>
-    <div id="ft">
-      <span id="num-posts"
-            tal:define="utils nocall:context/@@utils">
-        Me grok <tal:posts replace="utils/numPosts" /> posts!
-      </span>
-      <div id="external-links">
-        <ul>
-          <li><a href="http://grok.zope.org">GROK</a></li>
-          <li><a href="http://www.zope.org">Zope</a></li>
-          <li><a href="http://www.python.org">Python</a></li>
-        </ul>
-      </div>
-    </div>
-  </body>
-</html>

Added: Grokstar/trunk/src/grokstar/blog_templates/breadcrumbs.pt
===================================================================
--- Grokstar/trunk/src/grokstar/blog_templates/breadcrumbs.pt	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/blog_templates/breadcrumbs.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,3 @@
+<span tal:repeat="obj view/parents" tal:omit-tag="">
+  <a href="" tal:attributes="href python: view.url(obj)" tal:content="obj/__name__" />
+</span>

Modified: Grokstar/trunk/src/grokstar/blog_templates/categories.pt
===================================================================
--- Grokstar/trunk/src/grokstar/blog_templates/categories.pt	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/blog_templates/categories.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,16 +1,11 @@
-<!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" xml:lang="en" metal:use-macro="context/@@blogmacros/macros/blogpage">
-  <head>
-    <title metal:fill-slot="title">Categories</title>
-  </head>
-  <body>
-    <div metal:fill-slot="main-content">
-      <ul>
-        <li tal:repeat="cat view/categories">
-          <a tal:attributes="href string:${view/application_url}/search?q=${cat}"
-             tal:content="cat" />
-        </li>
-      </ul>
-    </div>
-  </body>
-</html>
+<h4 tal:condition="view/categories">Categories</h4>
+<ul>
+  <li tal:repeat="cat view/categories">
+    <a tal:attributes="href string:${context/@@absolute_url}/?c=${cat}"
+       tal:content="cat" />
+    <ul tal:condition="python:cat==view.c">
+      <li tal:repeat="entry view/entries"><a tal:attributes="href string:${entry/@@absolute_url}?c=${view/c}"
+              tal:content="structure entry/title">An entry</a></li>
+    </ul>
+  </li>
+</ul>
\ No newline at end of file

Added: Grokstar/trunk/src/grokstar/blog_templates/csshead.pt
===================================================================
--- Grokstar/trunk/src/grokstar/blog_templates/csshead.pt	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/blog_templates/csshead.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,3 @@
+<link rel="stylesheet" tal:attributes="href static/syntax.css">
+<link rel="stylesheet" tal:attributes="href static/style.css">
+<link rel="alternate" type="application/rss+xml"  tal:attributes="title view/application/title; href string:${view/application/@@absolute_url}/feed.rss">
\ No newline at end of file

Modified: Grokstar/trunk/src/grokstar/blog_templates/draftsindex.pt
===================================================================
--- Grokstar/trunk/src/grokstar/blog_templates/draftsindex.pt	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/blog_templates/draftsindex.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,20 +1,6 @@
-<!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" xml:lang="en" metal:use-macro="context/@@blogmacros/macros/blogpage">
-  <body>
-    <div metal:fill-slot="main-content">
-      <div class="entries"
-           tal:define="entries view/entries">
-        <tal:block condition="entries" repeat="entry view/entries">
-          <tal:block content="structure entry/@@item"/>
-        </tal:block>
-        <tal:block condition="not:entries">
-          No drafts.
-          <a href="#"
-             tal:attributes="href python:view.application_url('add')">
-            Post?
-          </a>
-        </tal:block>
-      </div>
-    </div>
-  </body>
-</html>
+<a href="../add">Add Blog Entry</a><br />
+<div class="entries">
+  <tal:block repeat="entry view/entries">
+    <tal:block content="structure entry/@@item"/>
+  </tal:block>
+</div>
\ No newline at end of file

Added: Grokstar/trunk/src/grokstar/blog_templates/layout.pt
===================================================================
--- Grokstar/trunk/src/grokstar/blog_templates/layout.pt	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/blog_templates/layout.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,22 @@
+<!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" xml:lang="en">
+<head>
+    <tal:head content="structure provider:head" />
+</head>
+<body>
+
+<div id="head">
+    <tal:header content="structure provider:top" />
+</div>
+<div id="right" style="float:right; width:24%;">
+    <tal:main content="structure provider:right" />
+</div>
+<div id="main" style="float:right; width:74%; padding-right:2%;">
+    <tal:left content="structure provider:main" />
+</div>
+<div style="clear:both" />
+
+<p class="footer" tal:content="view/application/footer" />
+
+</body>
+</html>
\ No newline at end of file

Added: Grokstar/trunk/src/grokstar/blog_templates/recententries.pt
===================================================================
--- Grokstar/trunk/src/grokstar/blog_templates/recententries.pt	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/blog_templates/recententries.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,4 @@
+<h4>Recent entries</h4>
+<ul>
+<li tal:repeat="entry view/entries"><a tal:content="entry/title" tal:attributes="href entry/@@absolute_url"></a></li>
+</ul>
\ No newline at end of file

Modified: Grokstar/trunk/src/grokstar/blog_templates/search.pt
===================================================================
--- Grokstar/trunk/src/grokstar/blog_templates/search.pt	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/blog_templates/search.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,20 +1,17 @@
-<!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" xml:lang="en" metal:use-macro="context/@@blogmacros/macros/blogpage">
-  <head>
-    <title metal:fill-slot="title">Search</title>
-  </head>
-  <body>
-    <div metal:fill-slot="main-content"
-         tal:define="results view/results">
-      <tal:no_results condition="not:results">
-        Sorry, no results for '<span tal:replace="request/q" />'
-      </tal:no_results>
-      <div class="search-results" tal:condition="results">
-        <ol>
-          <li tal:repeat="entry results"
-              tal:content="structure entry/@@item" />
-        </ol>
-      </div>
-    </div>
-  </body>
-</html>
+<form action="." method="get">
+  <p><input type="text" name="q" value="" size="15"
+         tal:attributes="value request/q | default" />
+    <input type="submit" value="Search" />
+  </p>
+</form>
+<tal:was_search condition="request/q | nothing">
+  <tal:no_results condition="not:view/results">
+    Sorry, no results for '<span tal:replace="request/q" />'
+  </tal:no_results>
+  <div class="search-results" tal:condition="view/results">
+    <ol>
+      <li tal:repeat="entry view/results"><a tal:attributes="href string:${entry/@@absolute_url}?q=${request/q}"
+          tal:content="structure entry/title">An entry</a></li>
+    </ol>
+  </div>
+</tal:was_search>
\ No newline at end of file

Added: Grokstar/trunk/src/grokstar/blog_templates/titleheader.pt
===================================================================
--- Grokstar/trunk/src/grokstar/blog_templates/titleheader.pt	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/blog_templates/titleheader.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,4 @@
+<a tal:attributes="href view/application/@@absolute_url">
+  <h1 tal:content="view/application/title"/>
+  <h3 tal:content="view/application/tagline"/>
+</a>
\ No newline at end of file

Modified: Grokstar/trunk/src/grokstar/calendar.py
===================================================================
--- Grokstar/trunk/src/grokstar/calendar.py	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/calendar.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -4,6 +4,7 @@
 import grok
 from hurry.query.query import Query
 from hurry import query
+from grokstar.blog import Main, Right
 
 from grokstar.interfaces import PUBLISHED
 
@@ -20,10 +21,10 @@
             return None
         return Month(self.year, month)
 
-class YearIndex(grok.View):
-    grok.name('index')
+class YearIndex(grok.Viewlet):
     grok.context(Year)
     grok.template('dateindex')
+    grok.viewletmanager(Main)
 
     def entries(self):
         from_ = datetime(self.context.year, 1, 1)
@@ -43,10 +44,10 @@
         # XXX should check whether day is acceptable
         return Day(self.year, self.month, day)
 
-class MonthIndex(grok.View):
-    grok.name('index')
+class MonthIndex(grok.Viewlet):
     grok.context(Month)
     grok.template('dateindex')
+    grok.viewletmanager(Main)
 
     def entries(self):
         from_ = datetime(self.context.year,
@@ -66,10 +67,10 @@
         self.month = month
         self.day = day
 
-class DayIndex(grok.View):
-    grok.name('index')
+class DayIndex(grok.Viewlet):
     grok.context(Day)
     grok.template('dateindex')
+    grok.viewletmanager(Main)
 
     def entries(self):
         from_ = datetime(self.context.year,

Modified: Grokstar/trunk/src/grokstar/calendar_templates/dateindex.pt
===================================================================
--- Grokstar/trunk/src/grokstar/calendar_templates/dateindex.pt	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/calendar_templates/dateindex.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,15 +1,6 @@
-<!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" xml:lang="en" metal:use-macro="context/@@blogmacros/macros/blogpage">
-  <head>
-    <title metal:fill-slot="title">blog title</title>
-  </head>
-  <body>
-    <div metal:fill-slot="main-content">
-      <h1>blog index</h1>
-
-      <tal:block repeat="entry view/entries">
+<div class="entries">
+  <tal:block repeat="entry view/entries">
 	<tal:block content="structure entry/@@item"/>
-      </tal:block>
-    </div>
-  </body>
-</html>
+	<hr/>
+  </tal:block>
+</div>
\ No newline at end of file

Modified: Grokstar/trunk/src/grokstar/configure.zcml
===================================================================
--- Grokstar/trunk/src/grokstar/configure.zcml	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/configure.zcml	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,5 +1,6 @@
 <configure xmlns="http://namespaces.zope.org/zope"
            xmlns:grok="http://namespaces.zope.org/grok">
   <include package="grok" />
+  <include package=".mail" />
   <grok:grok package="." />
 </configure>

Modified: Grokstar/trunk/src/grokstar/entry.py
===================================================================
--- Grokstar/trunk/src/grokstar/entry.py	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/entry.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -8,60 +8,119 @@
 
 import grok
 
-from grokstar.blog import Blog
+from grokstar.blog import Blog, Index, Edit, Main, Right, AddEntry
 from grokstar import interfaces
-from form import GrokstarAddForm, GrokstarEditForm
+import grokstar.form
 
-class Entry(grok.Model):
+from zope.component import getUtility, getMultiAdapter
+
+class Entry(grok.Container):
     interface.implements(interfaces.IEntry, IAttributeAnnotatable)
 
-    def __init__(self, title, summary, rightsinfo, categories=None):
+    def __init__(self, title, summary, categories=[]):
+        grok.Container.__init__(self)
         self.title = title
-        self.updated = datetime.now()
-        self.published = None
+        self.updated = self.published = datetime.now()
         self.summary = summary
-        self.rightsinfo = rightsinfo
-        if categories is None:
-            self.categories = []
-        else:
-            self.categories = categories
+        self.categories = categories
 
 class RestructuredTextEntry(Entry):
     interface.implements(interfaces.IRestructuredTextEntry)
 
-    def __init__(self, title, summary, rightsinfo, content, categories=None):
-        super(RestructuredTextEntry, self).__init__(title, summary, rightsinfo, categories)
+    def __init__(self, title='', summary='', content='', categories=[]):
+        Entry.__init__(self, title, summary, categories)
         self.content = content
 
+class Comment(grok.Model):
+    interface.implements(interfaces.IComment, IAttributeAnnotatable)
+    comment = u""
+    date = datetime.now() 
+    author = u""
+    
+    def __init__(self, comment, author):
+        self.comment = comment
+        self.author = author
+        self.date = datetime.now()
+
 grok.context(RestructuredTextEntry)
 
-
-class Index(grok.View):
+class EntryIndex(grok.Viewlet):
+    grok.viewletmanager(Main)
+    grok.view(Index)
+    
+    def update(self):
+        self.comments=sorted(self.context.values(), key=lambda c:c.date)
+    
+class Item(grok.View):
     pass
 
+class AddComment(grok.Viewlet):
+    grok.context(Entry)
+    grok.viewletmanager(Main)
+    grok.view(Index)
+    grok.order(8)
 
-class Item(grok.View):
-    def format_published(self, published_date):
-        return published_date.strftime('%Y-%m-%d')
+    def update(self):
+        self.form = getMultiAdapter((self.context, self.request),
+                                    name='addcommentform')
+        self.form.update_form()
 
+    def render(self):
+        return self.form.renderedPreview + self.form.render()
 
-class Add(GrokstarAddForm):
+class AddCommentForm(grokstar.form.GrokstarAddForm):
+    form_fields = grok.AutoFields(Comment).omit('date')
+    renderedPreview = ''    
+
+    @grok.action('Preview')
+    def preview(self, comment='', **data):
+        self.renderedPreview = '<h2>Preview</h2><div class="comment">' + renderRest(comment) + '</div>'
+        self.form_reset = False
+    
+    @grok.action('Add comment')
+    def add(self, **data):
+        new_comment = Comment(**data)
+        cid = 1
+        while str(cid) in self.context:
+            cid += 1 #Not very clever, but fine for < 10000 comments!
+        self.context[str(cid)] = new_comment
+        self.redirect(self.url(self.context))
+
+
+class AddEntryViewlet(grok.Viewlet):
+    grok.viewletmanager(Main)
+    grok.view(AddEntry)
     grok.context(Blog)
-    title = u'Add Entry'
-    # add the url that the user wants
+
+    def update(self):
+        self.form = getMultiAdapter((self.context, self.request),
+                                    name='addentryform')
+        self.form.update_form()
+
+    def render(self):
+        return self.form.renderedPreview + self.form.render()
+
+class AddEntryForm(grokstar.form.GrokstarAddForm):
+    grok.context(Blog)
+    
     form_fields = grok.Fields(
-        id=schema.TextLine(title=u"Post slug"))
-    # don't show them these timestamps
+        id=schema.TextLine(title=u"id"))
     form_fields += grok.AutoFields(RestructuredTextEntry).omit(
         'published', 'updated')
+    renderedPreview = ''
 
-    @grok.action('Add entry')
+    @grok.action('Preview')
+    def preview(self, content='', **data):
+        self.renderedPreview = '<h2>Preview</h2><div class="comment">' + renderRest(content) + '</div>'
+        self.form_reset = False
+
+    @grok.action('Add draft entry')
     def add(self, id, **data):
         new_entry = RestructuredTextEntry(**data)
         self.context['entries'][id] = new_entry
         IWorkflowInfo(new_entry).fireTransition('create')
         self.redirect(self.url(self.context))
-
+    
     @grok.action('Add published entry')
     def add_published(self, id, **data):
         new_entry = RestructuredTextEntry(**data)
@@ -70,25 +129,51 @@
         IWorkflowInfo(new_entry).fireTransitionToward(interfaces.PUBLISHED)        
         self.redirect(self.url(self.context))
 
+class EntryEdit(grok.Viewlet):
+    grok.context(Entry)
+    grok.viewletmanager(Main)
+    grok.view(Edit)
 
-class Edit(GrokstarEditForm):
-    grok.context(RestructuredTextEntry)
-    title = u'Edit Entry'
+    def update(self):
+        self.form = getMultiAdapter((self.context, self.request),
+                                    name='entryeditform')
+        self.form.update_form()
+
+    def render(self):
+        return self.form.renderedPreview + self.form.render()
+
+class EntryEditForm(grok.EditForm):
     form_fields = grok.AutoFields(RestructuredTextEntry).omit(
         'published', 'updated')
+    renderedPreview = ''
 
     @grok.action('Save changes')
     def edit(self, **data):
         self.applyData(self.context, **data)
         self.redirect(self.url(self.context))
 
+    @grok.action('Preview')
+    def preview(self, content, **data):
+        self.renderedPreview = renderRest(content)
+        self.form_reset = False
+
     @grok.action('Publish')
     def publish(self, **data):
         self.applyData(self.context, **data)
         IWorkflowInfo(self.context).fireTransitionToward(interfaces.PUBLISHED)
         self.redirect(self.url(self.context))
 
+    @grok.action('Retract')
+    def retract(self, **data):
+        self.applyData(self.context, **data)
+        IWorkflowInfo(self.context).fireTransitionToward(interfaces.CREATED)
+        self.redirect(self.url(self.context))
 
+class RenderedComment(grok.View):
+    grok.context(Comment)
+    def render(self):
+        return renderRest(self.context.comment)
+
 class RenderedContent(grok.View):
     def render(self):
         return renderRest(self.context.content)

Added: Grokstar/trunk/src/grokstar/entry_templates/entryindex.pt
===================================================================
--- Grokstar/trunk/src/grokstar/entry_templates/entryindex.pt	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/entry_templates/entryindex.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,12 @@
+<h1 tal:content="context/title">Entry</h1>
+<span class="published" tal:content="python:context.published.strftime('%e %B %Y %H:%M')">29 February 1999</span>
+<tal:block content="structure context/@@renderedcontent" />
+
+<a name="comments"></a>
+<p tal:condition="context/values" class="commenthead">Comments</p>
+<div class="comment" tal:repeat="comment view/comments">
+  <p class="commenttitle"><span tal:replace="comment/author">Fred</span> wrote on <span tal:replace="python:comment.date.strftime('%e %B %Y')">29 February 1999</span>:</p>
+  <tal:block content="structure comment/@@renderedcomment" />
+</div>
+<a name="newcomment"></a>
+<p class="commenthead">Leave a comment</p>

Deleted: Grokstar/trunk/src/grokstar/entry_templates/index.pt
===================================================================
--- Grokstar/trunk/src/grokstar/entry_templates/index.pt	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/entry_templates/index.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,12 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html metal:use-macro="context/@@blogmacros/macros/blogpage" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
-  <head>
-    <title metal:fill-slot="title" tal:content="context/title"/>
-  </head>
-  <body>
-    <div metal:fill-slot="main-content">
-      <tal:entry replace="structure context/@@item" />
-      <a tal:attributes="href python:view.url('edit')">edit</a>
-    </div>
-  </body>
-</html>

Modified: Grokstar/trunk/src/grokstar/entry_templates/item.pt
===================================================================
--- Grokstar/trunk/src/grokstar/entry_templates/item.pt	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/entry_templates/item.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,12 +1,10 @@
 <div class="entry">
-  <div class="title">
-    <a tal:attributes="href python:view.url(context)"
-       tal:content="context/title" />
-    <tal:published define="published context/published"
-                   condition="published">
-      (<span class="published"
-             tal:content="python:view.format_published(published)"/>)
-    </tal:published>
-  </div>
+  <h2 class="title">
+    <a tal:attributes="href python:view.url(context)">
+      <tal:block content="context/title"/>
+    </a>
+  </h2>
+  <span class="published" tal:content="python:context.published.strftime('%e %B %Y %H:%M')">29 February 1999</span>
   <tal:block content="structure context/@@renderedcontent"/>
-</div>
+  <a tal:attributes="href string:${context/@@absolute_url}#comments">Comments: <span tal:replace="python:len(context.values())">0</span></a>
+</div>
\ No newline at end of file

Modified: Grokstar/trunk/src/grokstar/form.py
===================================================================
--- Grokstar/trunk/src/grokstar/form.py	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/form.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -7,4 +7,4 @@
 class GrokstarEditForm(grok.EditForm):
     pass
 
-grok.context(Interface)
+grok.context(Interface)
\ No newline at end of file

Modified: Grokstar/trunk/src/grokstar/form_templates/grokstaraddform.pt
===================================================================
--- Grokstar/trunk/src/grokstar/form_templates/grokstaraddform.pt	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/form_templates/grokstaraddform.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,10 +1,3 @@
-<html metal:use-macro="context/@@blogmacros/macros/blogpage" >
-<head>
-  <title metal:fill-slot="title" tal:content="view/title | default">Add</title>
-</head>
-
-<body>
-<div metal:fill-slot="main-content">
 <form action="." tal:attributes="action request/URL" method="post"
       class="edit-form" enctype="multipart/form-data">
 
@@ -65,7 +58,4 @@
              />
     </span>
   </div>
-</form>
-</div>
-</body>
-</html>
+</form>
\ No newline at end of file

Modified: Grokstar/trunk/src/grokstar/form_templates/grokstareditform.pt
===================================================================
--- Grokstar/trunk/src/grokstar/form_templates/grokstareditform.pt	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/form_templates/grokstareditform.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,72 +1,61 @@
-<html metal:use-macro="context/@@blogmacros/macros/blogpage">
-<head>
-  <title metal:fill-slot="title" tal:content="view/title | default">Edit</title>
-</head>
+<form action="." tal:attributes="action request/URL" method="post"
+      class="edit-form" enctype="multipart/form-data">
 
-<body>
-  <div metal:fill-slot="main-content">
-    <form action="." tal:attributes="action request/URL" method="post"
-          class="edit-form" enctype="multipart/form-data">
+  <h1 i18n:translate=""
+      tal:condition="view/label"
+      tal:content="view/label">Label</h1>
 
-      <h1 i18n:translate=""
-          tal:condition="view/label"
-          tal:content="view/label">Label</h1>
+  <div class="form-status"
+       tal:define="status view/status"
+       tal:condition="status">
 
-      <div class="form-status"
-           tal:define="status view/status"
-           tal:condition="status">
+    <div i18n:translate="" tal:content="view/status">
+      Form status summary
+    </div>
 
-        <div i18n:translate="" tal:content="view/status">
-          Form status summary
-        </div>
+    <ul class="errors" tal:condition="view/errors">
+      <li tal:repeat="error view/error_views">
+        <span tal:replace="structure error">Error Type</span>
+      </li>
+    </ul>
+  </div>
 
-        <ul class="errors" tal:condition="view/errors">
-          <li tal:repeat="error view/error_views">
-            <span tal:replace="structure error">Error Type</span>
-          </li>
-        </ul>
-      </div>
+  <table class="form-fields">
+    <tbody>
+      <tal:block repeat="widget view/widgets">
+        <tr>
+          <td class="label" tal:define="hint widget/hint">
+            <label tal:condition="python:hint"
+                   tal:attributes="for widget/name">
+              <span class="required" tal:condition="widget/required"
+                    >*</span><span i18n:translate=""
+                                   tal:content="widget/label">label</span>
+            </label>
+            <label tal:condition="python:not hint"
+                   tal:attributes="for widget/name">
+              <span class="required" tal:condition="widget/required"
+                    >*</span><span i18n:translate=""
+                                   tal:content="widget/label">label</span>
+            </label>
+          </td>
+          <td class="field">
+            <div class="widget" tal:content="structure widget">
+              <input type="text" />
+            </div>
+            <div class="error" tal:condition="widget/error">
+              <span tal:replace="structure widget/error">error</span>
+            </div>
+          </td>
+        </tr>
+      </tal:block>
+    </tbody>
+  </table>
 
-      <table class="form-fields">
-        <tbody>
-          <tal:block repeat="widget view/widgets">
-            <tr>
-              <td class="label" tal:define="hint widget/hint">
-                <label tal:condition="python:hint"
-                       tal:attributes="for widget/name">
-                  <span class="required" tal:condition="widget/required"
-                        >*</span><span i18n:translate=""
-                                       tal:content="widget/label">label</span>
-                </label>
-                <label tal:condition="python:not hint"
-                       tal:attributes="for widget/name">
-                  <span class="required" tal:condition="widget/required"
-                        >*</span><span i18n:translate=""
-                                       tal:content="widget/label">label</span>
-                </label>
-              </td>
-              <td class="field">
-                <div class="widget" tal:content="structure widget">
-                  <input type="text" />
-                </div>
-                <div class="error" tal:condition="widget/error">
-                  <span tal:replace="structure widget/error">error</span>
-                </div>
-              </td>
-            </tr>
-          </tal:block>
-        </tbody>
-      </table>
-
-      <div id="actionsView">
-        <span class="actionButtons" tal:condition="view/availableActions">
-          <input tal:repeat="action view/actions"
-                 tal:replace="structure action/render"
-                 />
-        </span>
-      </div>
-    </form>
-
+  <div id="actionsView">
+    <span class="actionButtons" tal:condition="view/availableActions">
+      <input tal:repeat="action view/actions"
+             tal:replace="structure action/render"
+             />
+    </span>
   </div>
-</body>
-</html>
+</form>
\ No newline at end of file

Modified: Grokstar/trunk/src/grokstar/interfaces.py
===================================================================
--- Grokstar/trunk/src/grokstar/interfaces.py	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/interfaces.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,13 +1,16 @@
 from zope.interface import Interface
 from zope import schema, interface
+from zope.app.container.interfaces import IContainer
 
 CREATED = 0
 PUBLISHED = 1
 class IBlog(Interface):
-    title = schema.TextLine(title=u'Title', default=u'')
-    tagline = schema.TextLine(title=u'Tagline', default=u'')
+    title = schema.TextLine(title=u'Title')
+    tagline = schema.TextLine(title=u'Tagline')
+    footer = schema.TextLine(title=u'Footer', required=False)
+    email = schema.TextLine(title=u'Email')
 
-class IEntry(Interface):
+class IEntry(IContainer):
     """
     This interface is based on the Atom entry definition, from the Atom RFC.
     
@@ -37,14 +40,21 @@
 
     summary = schema.SourceText(title=u"Summary", required=False)
 
-    # content = schema.SourceText(title=u"Content")
+    content = schema.SourceText(title=u"Content")
 
-    rightsinfo = schema.SourceText(title=u"Rights", required=False)
+    # rightsinfo = schema.SourceText(title=u"Rights", required=False)
 
     # source is too complicated to support for us right now
 
 class IRestructuredTextEntry(IEntry):
     content = schema.SourceText(title=u"Content")
+    
+class IComment(Interface):
+    date = schema.Datetime(title=u"Date", required=True)
+    
+    comment = schema.SourceText(title=u"Comment", required=True)
+    
+    author = schema.TextLine(title=u"Author", required=True)
 
 class IAtomEntry(Interface):
     


Property changes on: Grokstar/trunk/src/grokstar/mail
___________________________________________________________________
Name: svn:ignore
   + mail-queue


Added: Grokstar/trunk/src/grokstar/mail/__init__.py
===================================================================
--- Grokstar/trunk/src/grokstar/mail/__init__.py	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/mail/__init__.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,29 @@
+import email.Charset #See Phillip's book, p318 2nd Ed
+email.Charset.add_charset('utf-8', email.Charset.SHORTEST, None, None)
+from datetime import datetime
+from email.MIMEText import MIMEText
+
+from zope.component import getUtility, adapter
+from zope.sendmail.interfaces import IMailDelivery
+
+from zope.app.container.interfaces import IObjectAddedEvent
+
+from grok.interfaces import IApplication
+
+from grokstar.interfaces import IComment
+
+
+ at adapter(IComment, IObjectAddedEvent)
+def notifyCommentAdded(comment, event):
+        return commentAdded(comment)
+
+def commentAdded(comment):
+    message = MIMEText(comment.comment.encode('utf-8'), 'plain', 'utf-8')
+    message['Subject'] = "Comment from " + comment.author
+    recipient = getUtility(IApplication).email
+    message['From'] = recipient
+    message['To'] = recipient
+    message['Date'] = datetime.now().strftime('%a, %e %b %Y %H:%M:%S %z')
+    
+    mailer = getUtility(IMailDelivery, 'grokstar')
+    mailer.send(recipient, [recipient], message.as_string())

Added: Grokstar/trunk/src/grokstar/mail/configure.zcml
===================================================================
--- Grokstar/trunk/src/grokstar/mail/configure.zcml	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/mail/configure.zcml	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,22 @@
+<configure
+	xmlns="http://namespaces.zope.org/zope"
+	xmlns:mail="http://namespaces.zope.org/mail"
+	>
+
+  <mail:smtpMailer
+	  name="grokstar"
+	  hostname="localhost"
+	  port="25"
+	  />
+	
+  <mail:queuedDelivery
+	  name="grokstar"
+	  permission="zope.SendMail"
+	  queuePath="mail-queue"
+	  mailer="grokstar"
+	  />
+	
+  <subscriber handler=".notifyCommentAdded" />
+
+</configure>
+	  
\ No newline at end of file

Added: Grokstar/trunk/src/grokstar/mail/notifications.txt
===================================================================
--- Grokstar/trunk/src/grokstar/mail/notifications.txt	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/mail/notifications.txt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,23 @@
+This should be really simple.
+
+We want to create comments: 
+
+>>> from grokstar.entry import Comment
+>>> comment = Comment("Good stuff", "fred")
+
+When we notify zope of the creation, a mail should be sent:
+
+>>> from zope.app.container.interfaces import IObjectAddedEvent
+>>> from zope.lifecycleevent import ObjectCreatedEvent
+>>> from zope.event import notify
+>>> notify(ObjectCreatedEvent(comment))
+ 
+I'm not whether we should be subscribing to ObjectCreatedEvent or ObjectAddedEvent.
+
+We need to register a dummy mailer for the test:
+
+>>> from zope.sendmail.interfaces import IMailDelivery
+>>> from zope.component import getUtility
+>>> mailer = getUtility(IMailDelivery, 'grokstar')
+
+>> mailer.send(recipient, [recipient], message.as_string())
\ No newline at end of file

Added: Grokstar/trunk/src/grokstar/mail/tests.py
===================================================================
--- Grokstar/trunk/src/grokstar/mail/tests.py	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/mail/tests.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,31 @@
+import unittest
+import doctest
+
+from zope.sendmail.interfaces import IMailDelivery
+import zope.component.testing
+import zope.component.eventtesting
+from zope.interface import implements
+
+from grokstar.mail import notifyCommentAdded
+
+class DummyMailDelivery(object):
+    implements(IMailDelivery)
+    def send(self, fromaddr, toaddr, msg):
+        print msg
+        
+def setUp(test):
+    zope.component.testing.setUp(test)
+    zope.component.eventtesting.setUp(test)
+    zope.component.provideUtility(DummyMailDelivery(), name='grokstar')
+    zope.component.provideHandler(notifyCommentAdded)
+    
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite('notifications.txt',
+                             setUp=setUp,
+                             tearDown=zope.component.testing.tearDown,
+                             optionflags=doctest.ELLIPSIS),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
\ No newline at end of file

Added: Grokstar/trunk/src/grokstar/rss.py
===================================================================
--- Grokstar/trunk/src/grokstar/rss.py	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/rss.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,10 @@
+import grok
+
+from grokstar.blog import Blog, lastEntries
+
+class RSS(grok.View):
+    grok.context(Blog)
+    grok.name('feed.rss')
+                
+    def items(self, max=10):
+        return lastEntries(max)
\ No newline at end of file

Added: Grokstar/trunk/src/grokstar/rss_templates/rss.pt
===================================================================
--- Grokstar/trunk/src/grokstar/rss_templates/rss.pt	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/rss_templates/rss.pt	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<rss version="2.0" xmlns:tal="http://xml.zope.org/namespaces/tal">
+  <channel>
+    <title tal:content="context/title"/>
+    <link tal:content="view/application_url"/>
+    <description tal:content="context/tagline"/>
+    <item tal:repeat="item view/items">
+      <title tal:content="item/title"/>
+      <link tal:content="item/@@absolute_url"/>
+      <description tal:content="item/summary"/>
+      <pubDate tal:content="item/updated"/>
+    </item>
+  </channel>
+</rss>
\ No newline at end of file

Added: Grokstar/trunk/src/grokstar/static/style.css
===================================================================
--- Grokstar/trunk/src/grokstar/static/style.css	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/static/style.css	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,105 @@
+body {
+    width:980px;
+    font-family:sans-serif;
+    color:#222;
+    font-size:80%;
+    line-height:140%;
+}
+
+a {
+    color:#e90;
+}
+
+#head {
+    background:#fdb;
+    padding:1em;
+    margin-bottom:0.5em;
+    text-align:center;
+}
+
+#head a {
+    text-decoration:none;
+    color:#000;
+}
+
+#search {
+    margin-left:0.5em;
+    padding:1em;
+}
+
+h1, h2, h3, h4 {
+    margin-bottom:0.2em;
+}
+
+ul {
+    margin-top:0.2em;
+    list-style-type:none;
+    padding-left:1em;
+}
+
+hr {
+    border-color:#080;
+    background-color:#080;
+    border-width:0px;
+    height:1px;
+}
+
+.document h1 {
+    font-size:130%;
+    font-weight:normal;
+}
+
+.published {
+    font-weight:bold;
+    font-size:60%;
+    color:#ccc;
+    padding-left:1em;
+    margin-top:0;
+}
+
+.commenthead {
+    background-color:#7d7;
+    border:1px solid #080;
+    font-weight:bold;
+    padding:0.5em;
+}
+
+.comment {
+    background-color:#beb;
+    padding:0.5em;
+    margin-top:0.5em;
+}
+
+.commenttitle {
+    margin:0.5em;
+    font-weight:bold;
+}
+
+.doctest-block, .highlight, .literal-block  {
+    margin:1em;
+    border: 1px dashed #0b0;
+    background-color:#efe;
+    overflow:auto;
+    padding:0.5em 1em 0.5em 1em;
+    line-height:140%;
+    font-size:110%;
+}
+
+.literal-block {
+    border-color:#333;
+    background-color:#eee;
+}
+
+.footer {
+    font-size:65%;
+    font-weight:bold;
+    text-align:center;
+    color:#060;
+}
+
+cite {
+    font-family:monospace;
+    font-style:normal;
+}
+
+pre { margin:0;} /* Not 0 by default in Firefox */
\ No newline at end of file

Added: Grokstar/trunk/src/grokstar/static/syntax.css
===================================================================
--- Grokstar/trunk/src/grokstar/static/syntax.css	                        (rev 0)
+++ Grokstar/trunk/src/grokstar/static/syntax.css	2008-06-17 22:41:28 UTC (rev 87483)
@@ -0,0 +1,59 @@
+.highlight .c { color: #408080; font-style: italic } /* Comment */
+.highlight .err { border: 1px solid #FF0000 } /* Error */
+.highlight .k { color: #008000; font-weight: bold } /* Keyword */
+.highlight .o { color: #666666 } /* Operator */
+.highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #BC7A00 } /* Comment.Preproc */
+.highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #408080; font-style: italic } /* Comment.Special */
+.highlight .gd { color: #A00000 } /* Generic.Deleted */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .gr { color: #FF0000 } /* Generic.Error */
+.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.highlight .gi { color: #00A000 } /* Generic.Inserted */
+.highlight .go { color: #808080 } /* Generic.Output */
+.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.highlight .gt { color: #0040D0 } /* Generic.Traceback */
+.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.highlight .kp { color: #008000 } /* Keyword.Pseudo */
+.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #B00040 } /* Keyword.Type */
+.highlight .m { color: #666666 } /* Literal.Number */
+.highlight .s { color: #BA2121 } /* Literal.String */
+.highlight .na { color: #7D9029 } /* Name.Attribute */
+.highlight .nb { color: #008000 } /* Name.Builtin */
+.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */
+.highlight .no { color: #880000 } /* Name.Constant */
+.highlight .nd { color: #AA22FF } /* Name.Decorator */
+.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
+.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #0000FF } /* Name.Function */
+.highlight .nl { color: #A0A000 } /* Name.Label */
+.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
+.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
+.highlight .nv { color: #19177C } /* Name.Variable */
+.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
+.highlight .w { color: #bbbbbb } /* Text.Whitespace */
+.highlight .mf { color: #666666 } /* Literal.Number.Float */
+.highlight .mh { color: #666666 } /* Literal.Number.Hex */
+.highlight .mi { color: #666666 } /* Literal.Number.Integer */
+.highlight .mo { color: #666666 } /* Literal.Number.Oct */
+.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
+.highlight .sc { color: #BA2121 } /* Literal.String.Char */
+.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
+.highlight .s2 { color: #BA2121 } /* Literal.String.Double */
+.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
+.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
+.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
+.highlight .sx { color: #008000 } /* Literal.String.Other */
+.highlight .sr { color: #BB6688 } /* Literal.String.Regex */
+.highlight .s1 { color: #BA2121 } /* Literal.String.Single */
+.highlight .ss { color: #19177C } /* Literal.String.Symbol */
+.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
+.highlight .vc { color: #19177C } /* Name.Variable.Class */
+.highlight .vg { color: #19177C } /* Name.Variable.Global */
+.highlight .vi { color: #19177C } /* Name.Variable.Instance */
+.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
\ No newline at end of file

Deleted: Grokstar/trunk/src/grokstar/utils.py
===================================================================
--- Grokstar/trunk/src/grokstar/utils.py	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/utils.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -1,21 +0,0 @@
-import grok
-from hurry.query.query import Query
-from hurry import query
-from zope.interface import Interface
-from grokstar.interfaces import IBlog
-
-class Utils(grok.View):
-    """contain common view methods"""
-    grok.context(Interface)
-
-    def numPosts(self):
-        # @@ is there a better way to get all entries through the catalog?
-        obj = self.context
-        while obj is not None:
-            if IBlog.providedBy(obj):
-                return len(obj['entries'])
-            obj = obj.__parent__
-        return 0
-
-    def render(self):
-        return ''

Modified: Grokstar/trunk/src/grokstar/workflow.py
===================================================================
--- Grokstar/trunk/src/grokstar/workflow.py	2008-06-17 22:29:27 UTC (rev 87482)
+++ Grokstar/trunk/src/grokstar/workflow.py	2008-06-17 22:41:28 UTC (rev 87483)
@@ -2,10 +2,8 @@
 
 import grok
 from grokstar.entry import Entry
-from hurry.workflow import workflow
+from hurry.workflow import workflow, interfaces
 from hurry.workflow.interfaces import IWorkflow
-from hurry.workflow.interfaces import IWorkflowState
-from hurry.workflow.interfaces import IWorkflowInfo
 from hurry.query.query import Query
 from hurry.query import Eq
 
@@ -35,10 +33,16 @@
         destination=PUBLISHED,
         action=publish_action)
     
-    return [create_transition, publish_transition, update_transition]
+    retract_transition = workflow.Transition(
+        transition_id='retract',
+        title='retract',
+        source=PUBLISHED,
+        destination=CREATED)
+    
+    return [create_transition, publish_transition, update_transition,
+            retract_transition]
 
 class Workflow(grok.GlobalUtility, workflow.Workflow):
-    # grok.name('grokstar_workflow')
     grok.provides(IWorkflow)
     
     def __init__(self):
@@ -68,9 +72,9 @@
     
 class WorkflowState(grok.Adapter, workflow.WorkflowState):
     grok.context(Entry)
-    grok.provides(IWorkflowState)
+    grok.provides(interfaces.IWorkflowState)
     
 class WorkflowInfo(grok.Adapter, workflow.WorkflowInfo):
     grok.context(Entry)
-    grok.provides(IWorkflowInfo)
+    grok.provides(interfaces.IWorkflowInfo)
 



More information about the Checkins mailing list