slinkblog/0000755000175000001440000000000010426163234011410 5ustar pwusersslinkblog/configure.zcml0000644000175000001440000001410310426163052014255 0ustar pwusers slinkblog/browser.py0000644000175000001440000000371410426160167013454 0ustar pwusers# Zope 3 imports from Products.Five import BrowserView # Zope 2 imports from Products.PythonScripts.standard import structured_text # custom imports from interfaces import IPost, IComment """View examples. Views are "multi-adapters". That is, given a context object and a request, it returns something that provides IView. The Five.BrowserView base class takes care of some Zope2 compatibility issues, implements IBrowserView, and provides an appropriate __init__(). Note that in the "classic" style blog_z2style.py, *all* view functionality was embedded in the post class itself. The advantage of doing it in the z3 style with separate view classes is, of course, that extending classes with different views is a lot easier, especially if you don't have control of the source code for the original class! In fact we do this here; BlogView can be used on any ObjectManager. Also note that in zcml we register views for an interface, not a class; that means we can re-use it for anything that implements that interface. """ class PostView(BrowserView): """Adapt an IPost into a view. """ def render_body(self): """ Render the body of an IPost as structured text. """ # This stx implementation can only handle ascii. return structured_text(self.context.body.encode('ascii')) class BlogView(BrowserView): """Adapt an ObjectManager to a view of its contained blog posts. """ def get_posts(self): """ List all objects providing IPost contained in the context. """ maybe_posts = self.context.objectValues() # XXX if we had real metadata we'd use that for sorting. # Currently we assume they are Persistent and we sort on # the zodb mod time. sortable = [(p.bobobase_modification_time(), p) for p in maybe_posts if IPost.providedBy(p)] sortable.sort(reverse=True) posts = [tup[1] for tup in sortable] return posts slinkblog/z2_forms/0000755000175000001440000000000010426136140013145 5ustar pwusersslinkblog/z2_forms/post_view.zpt0000644000175000001440000000057310426136140015730 0ustar pwusers Title goes here

Title

Body content here
Edit this post slinkblog/z2_forms/post_edit.zpt0000644000175000001440000000144410426136140015701 0ustar pwusers

Edit post

Title:
Body:
slinkblog/z2_forms/post_add.zpt0000644000175000001440000000115710426136140015505 0ustar pwusers Add a post

Add a post

Id:
Title:
Body:
slinkblog/interfaces.py0000644000175000001440000000177310426162751014120 0ustar pwusersfrom zope.interface import Interface from zope.schema import Text, TextLine #, Datetime class IPost(Interface): """A minimal blog post. """ title = TextLine( title=u"Title", description=u"Displayed title of the post.", required=True, ) body = Text( title=u"Body", description=u"Main body of the post.", required=True, ) class IComment(IPost): """A comment to a blog post or another comment. Not required to have a title. """ title = TextLine( title=u"Title", description=u"Displayed title of the comment.", required=True, missing_value=u"" ) class IBlog(Interface): """ A marker interface that declares an ObjectManager is a Blog. Marker interfaces are useful for things like this: you can select one in the ZMI for an object and thus change the behavior of that object. See configure.zcml to see what we provide for the IBlog interface. """ slinkblog/post.png0000644000175000001440000000225510376766705013127 0ustar pwusersPNG  IHDRQ9bKGD pHYs  tIME3:IDAT8mKoE[]9cNX !$X"`ǒ{~?!bĂ "l,B" b;įx<3=ӏ*3;nJ}sS%moڰb Lޟk~@+ 1QITIҧ 3OͰ, LbeB`MŽE!Z#XCȓ`&bHP7 j'| D φh1SݾԢkua}.\n_V v84#R~[:G{mF\9Ϗɨ$Gi(pyauߍxؼ-z&yq[(M_{YM?xQso/ub쐄aĥgю[xB hLYCL3[< <pF8JK1O='O L9kD. 0 `-8^@-\Xlaa#b(G39=v"8(EY2liʅA9{`$/H5M@GIENDB`slinkblog/post_z2style.py0000644000175000001440000001115010426153573014446 0ustar pwusers"""Example of a simple blog post in Zope 2 classic style. This is just here as an example of how we used to do things before Five made it possible to use zope 3 idioms in zope 2... """ from OFS.SimpleItem import Item, SimpleItem from AccessControl import ClassSecurityInfo from AccessControl import Permissions from Globals import InitializeClass from Products.PythonScripts.standard import structured_text from Products.PageTemplates.PageTemplateFile import PageTemplateFile EDIT_PERMISSION='Edit Blog Posts' class Z2Post(SimpleItem): """ A Post implementation that can be added to Zope 2 containers, e.g. Folders. """ meta_type = 'Zope 2 Blog Post' # meta_type is used for several things, including # what shows up in the ZMI add menus. title = '' body = '' # Now let's give it some tabs in the ZMI. # Create a tuple of dicts and bind it to 'manage_options'. manage_options=( {'label':'Edit', 'action':'manage_main', }, {'label':'View', 'action':'index_html', }, ) + SimpleItem.manage_options def __init__(self, id, title, body): self.id = id self.edit(title, body) security = ClassSecurityInfo() # Needed for declarative security. # In "classic" Zope 2 development, security is sprinkled throughout # your class body as seen below. security.declareProtected(Permissions.view_management_screens, 'manage_main') # Both permissions and methods to protect are specified as # strings. Spell it wrong? No security for you! # Now let's add an edit page in the ZMI. manage_main = PageTemplateFile('z2_forms/post_edit', globals()) # That's how we add views to a class in Zope 2. # The corresponding page template should have the name given # plus ".zpt", i.e. "post_edit.zpt". # The page template will in turn call methods of this class. security.declareProtected(EDIT_PERMISSION, 'edit') def edit(self, title, body, REQUEST=None): """ Edit a post. This method needs a docstring since we're going to post a request directly to it, and zpublisher won't publish anything with no docstring. """ self.title = title self.body = body if REQUEST is not None: # This method was the target of an http post. Lots of # ways to handle this; we'll do the redirect-after-post # thing. msg = '%s updated' % self.getId() url = '%s/%s?manage_tabs_message=%s' % (self.absolute_url(), 'manage_main', msg) REQUEST.RESPONSE.redirect(url) security.declareProtected(Permissions.view, 'getTitle') def getTitle(self, title): return self.title security.declareProtected(Permissions.view, 'getRenderedBody') def getRenderedBody(self): """ Render the body of a Post as structured text. """ return structured_text(self.body) security.declareProtected(EDIT_PERMISSION, 'getRawBody') def getRawBody(self, body): return self.body security.declareProtected(Permissions.view, 'index_html') index_html = PageTemplateFile('z2_forms/post_view', globals()) InitializeClass(Z2Post) # Without this, security doesn't work. # In order to create instances through the web, we still need to # create and register a factory. The registration is done in # __init__.py. Contrast this with blog.py and configure.zcml, # where we didn't write a factory, we just did some configuration # with the "content" and "browser:addForm" directives in configure.zcml. def manage_addZ2Post(self, id, title='', body='', REQUEST=None, submit=None): """A factory for adding posts to an ObjectManager (a container). 'self' is the ObjectManager. This factory will be the target of an add form that will live in z2forms/. """ post = Z2Post(id, title, body) self._setObject(id, post) #If called through the web, we want to redirect to # an appropriate page. if REQUEST is not None: destURL = self.absolute_url() if submit == " Add and Edit ": # Go directly to the edit form after adding. destURL = "%s/%s/manage_main" % (destURL, id) else: # Go back to the container's main management view. destURL += '/manage_main?manage_tabs_message=' + \ 'Post %s added.' % id REQUEST.RESPONSE.redirect(destURL) manage_addZ2PostForm = PageTemplateFile('z2_forms/post_add', globals()) slinkblog/README.txt0000644000175000001440000000550110426163234013107 0ustar pwusers$Id$ Author ====== Paul Winkler stuff@slinkp.com Description =========== This is an example proto-blog developed for my "Introduction to Zope 2 Application Development" presentation at PyCon Dallas 2006, see: http://slinkp.com/~paul/pycon_2006/z2/notes.html The main thing of interest is the demonstration of using some Zope 3 idioms in Zope 2. Features ======== So it's not much of a blog :) We have: * posts * add and edit forms for posts * a time-sorted view of all posts in a folder No comments. (so far there's just an interface for them.) The UI is not consistently or well integrated with the ZMI; the edit form in particular is a dead end. See the notes in configure.zcml. Oh well, at least validation works (try saving a post with a blank title or body). We get this "free" by basing posts on a schema. Requirements ============ Tested with Zope 2.9.0 / 2.9.1. Installation ============ Drop this directory in your zope instance's Products and restart. To add a post, go into any folder in the ZMI and select "Slinkblog Post" from the "Add Product" menu. Try making a folder into a Blog. To do this, go into the ZMI, click the Interfaces tab, and check Products.slinkblog.interfaces.IBlog in the list of available marker interfaces. Then submit. Now the default view of the folder will show a list of posts. Contents ======== README.txt This file. __init__.py Makes it a package, and does some crap only needed for the "classic" zope 2 development demo. blog.py Source code for the Post class, written in a Zope 3 / Five style. There's very little to it, just a bare-bones content class. browser.py This is where we write python code for views of the blog and posts. configure.zcml Configuration for the Post class and its views. Good example of working with Five, with lots of comments. This is actually where most of the interesting stuff in this demo happens. interfaces.py Interfaces used in blog.py and configure.zcml. This contains the schema used for Posts, which is among other things how we get validation for free. view_post.zpt A simple page template for viewing Post instances. Uses the blog.PostView class. post.png An ugly icon. It's supposed to look like a fence post, har har. post_z2style.py The Post class rewritten in the "Classic" zope 2 style. Pro: it doesn't need configure.zcml. Con: it needs an awful lot of ugly boilerplate. You can try these out in the ZMI by selecting "Zope 2 Blog Post" from the add menu. z2_forms/ Contains the page templates needed for post_z2style.py. The z3 version doesn't use any of this stuff. TO DO ====== Unit tests! Better UI. Probably want to skip the poor attempt at ZMI integration. Demo of formlib. A marker interface that could be set on an object manager that would give it view_blog as its default view. slinkblog/blog.py0000644000175000001440000000437210426156740012717 0ustar pwusers# Zope 3 imports from persistent import Persistent from zope.interface import implements #from zope.app.container.interfaces import IOrderedContainer from zope.schema.fieldproperty import FieldProperty # Zope 2 imports from Globals import InitializeClass from OFS.SimpleItem import Item, SimpleItem # custom imports from interfaces import IPost, IComment class PersistentPost(Persistent): """ A base implementation of a blog post. This would work as-is in Zope 3. """ implements(IPost) #, IOrderedContainer) # Using FieldProperty() for attributes allows auto-validation # whenever they are set. title = FieldProperty(IPost['title']) body = FieldProperty(IPost['body']) class Post(PersistentPost, SimpleItem): """ A Post implementation that can be added to Zope 2 containers, e.g. Folders. It would be nice if we could derive from something simpler than SimpleItem, but for now that's the easiest way to make something that can be added to Zope 2 containers. Hopefully in the future we can break that down to something smaller. """ # If this was a "traditional" zope 2 persistent class, there'd be # a ton of boilerplate crap in the class body to deal with # security, methods for adding and editing and such. Now that's # all in configure.zcml. For comparison, see post_z2style.py. # A concession to Zope 2: If you want to add some tabs in the ZMI, # you have to do it this way in the class: Create a tuple of dicts # and bind it to 'manage_options'. manage_options=( # The first item in this tuple is the default zmi view and is # browsable at 'manage_workspace'. {'label': 'View', 'action': 'view_post.html', }, {'label': 'Edit', 'action': 'edit_post.html', }, ) + SimpleItem.manage_options # A concession to zope 2: in order to use security and add instances # via the ZMI, we need to call InitializeClass. I would prefer a Five # zcml directive for doing this... InitializeClass(Post) # Note that unlike post_z2style.py, we don't need to write and # register a manage_addFoo() factory. Adding instances is instead # configured in configure.zcml by the "content" and "browser:addForm" # directives. slinkblog/__init__.py0000644000175000001440000000065410426140766013533 0ustar pwusers # I'll make the z2-style posts addable, just for fun # and for comparison with the z3-style posts. from post_z2style import Z2Post, manage_addZ2Post, manage_addZ2PostForm #Method to register the class in the ZODB. def initialize(context): context.registerClass( Z2Post, constructors=(manage_addZ2PostForm, manage_addZ2Post, ), icon='post.png', ) slinkblog/view_post.zpt0000644000175000001440000000141310426160031014155 0ustar pwusers The Post Title

The Post Title

modified date... XXX should use dublin core instead of zodb timestamp

Body Text Goes Here

List of all posts slinkblog/view_blog.zpt0000644000175000001440000000117710426156156014137 0ustar pwusers The Folder Title

The Folder Title

A very minimal demo blog.