slinkblog/ 0000755 0001750 0000144 00000000000 10426163234 011410 5 ustar pw users slinkblog/configure.zcml 0000644 0001750 0000144 00000014103 10426163052 014255 0 ustar pw users
slinkblog/browser.py 0000644 0001750 0000144 00000003714 10426160167 013454 0 ustar pw users # 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/ 0000755 0001750 0000144 00000000000 10426136140 013145 5 ustar pw users slinkblog/z2_forms/post_view.zpt 0000644 0001750 0000144 00000000573 10426136140 015730 0 ustar pw users
Title goes here
Title
Body content here
Edit this post
slinkblog/z2_forms/post_edit.zpt 0000644 0001750 0000144 00000001444 10426136140 015701 0 ustar pw users
Edit post
slinkblog/z2_forms/post_add.zpt 0000644 0001750 0000144 00000001157 10426136140 015505 0 ustar pw users
Add a post
Add a post
slinkblog/interfaces.py 0000644 0001750 0000144 00000001773 10426162751 014120 0 ustar pw users from 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.png 0000644 0001750 0000144 00000002255 10376766705 013127 0 ustar pw users PNG
IHDR Q9 bKGD pHYs tIME3 :IDAT8mKoE[]9cNX !$X"`ǒ{~?!bĂ"l,B"
b;įx<3=ӏ*3;nJ}sS%moڰbLޟk~@+
1QITIҧ 3OͰ,
LbeB`ME!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@G IENDB` slinkblog/post_z2style.py 0000644 0001750 0000144 00000011150 10426153573 014446 0 ustar pw users """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.txt 0000644 0001750 0000144 00000005501 10426163234 013107 0 ustar pw users $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.py 0000644 0001750 0000144 00000004372 10426156740 012717 0 ustar pw users # 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__.py 0000644 0001750 0000144 00000000654 10426140766 013533 0 ustar pw users
# 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.zpt 0000644 0001750 0000144 00000001413 10426160031 014155 0 ustar pw users
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.zpt 0000644 0001750 0000144 00000001177 10426156156 014137 0 ustar pw users
The Folder Title