[Zope3-checkins] CVS: Products3/NewsSite - index_rss.pt:1.1 browser.py:1.2 configure.zcml:1.10 interfaces.py:1.4 newssite.py:1.4

Tres Seaver tseaver@zope.com
Wed, 26 Mar 2003 12:18:17 -0500


Update of /cvs-repository/Products3/NewsSite
In directory cvs.zope.org:/tmp/cvs-serv9365

Modified Files:
	browser.py configure.zcml interfaces.py newssite.py 
Added Files:
	index_rss.pt 
Log Message:


  - browser.py:

    o Add new view class for editing the syndication policies through
      an adapter.

    o Add another view class for generating RSS (note that this page
      should probably be folded into the "main" view, to allow reusing
      its filtering mechanisms).

  - interfaces.py:

    o Add schema for syndication policies.

  - newssite.py:

    o Normalize some imports.

    o Create adapter for ISyndicationPolicies, which stores the schema
      values in an annontation.

  - configure.zcml:

    o Annotate the configuration, grouping related bits together.

    o Add configuration for syndication views.


=== Added File Products3/NewsSite/index_rss.pt ===
<?xml version="1.0"?>

<rdf:RDF 
  xmlns:tal="http://xml.zope.org/namespaces/tal"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:syn="http://purl.org/rss/1.0/modules/syndication/"
  xmlns="http://purl.org/rss/1.0/"
  tal:define="info_items view/listSyndicationInfo" >

 <channel rdf:about="SITE_URL"
          tal:attributes="rdf:about string:${view/context_url}/index.rss;" >

  <title tal:content="view/dc/title">SITE TITLE</title>

  <link tal:content="view/context_url" />

  <description tal:content="view/dc/description">
    SITE DESCRIPTION
  </description>

  <dc:rights tal:content="view/dc/rights | default">RIGHTS</dc:rights>
  <dc:language tal:content="view/dc/language | default">en/US</dc:language>
  <dc:publisher
    tal:content="view/dc/publisher | default">PUBLISHER</dc:publisher>

  <syn:updatePeriod tal:content="view/syn_policies/period"
  >SYNDICATION UPDATE PERIOD</syn:updatePeriod>

  <syn:updateFrequency tal:content="view/syn_policies/frequency"
  >SYNDICATION UPDATE FREQUENCY</syn:updateFrequency>

  <syn:updateBase tal:content="view/syn_policies/base_update"
  >SYNDICATION BASE DATE</syn:updateBase>

  <items>
   <rdf:Seq>
    <rdf:li rdf:resource="ENTRY_URL"
            tal:repeat="item info_items"
            tal:attributes="rdf:resource item/entry_url"
    />
   </rdf:Seq>
  </items>

 </channel>

 <tal:loop tal:repeat="item info_items"
 ><item rdf:about="ENTRY_URL"
        tal:attributes="rdf:about item/entry_url"
  >
   <title tal:content="item/dc/title">ENTRY TITLE</title>
   <description tal:content="item/dc/description">ENTRY DESC.</description>
   <link tal:content="item/entry_url" />
   <dc:creator tal:content="item/dc/Creator" />
   <dc:date tal:content="item/dc/Date" />
 </item>
 </tal:loop>

</rdf:RDF>



=== Products3/NewsSite/browser.py 1.1 => 1.2 ===
--- Products3/NewsSite/browser.py:1.1	Wed Mar 26 07:14:49 2003
+++ Products3/NewsSite/browser.py	Wed Mar 26 12:18:16 2003
@@ -16,9 +16,78 @@
 $Id$
 """
 
+from zope.component import getAdapter
+from zope.component import getView
+from zope.component import queryUtility
+
+from zope.proxy.context import ContextWrapper
+
+from zope.app.interfaces.dublincore import IZopeDublinCore
 from zope.app.browser.container.adding import Adding
 
+from interfaces import INewsSite
+from interfaces import ISyndicationPolicies
+
 class NewsSiteAdding(Adding):
     """Custom adding view for NewsSite objects.
     """
     menu_id = "add_news"
+
+class NewsSiteSyndicationPoliciesView:
+
+    """ Simple class for managing the RSS policies for the site.
+    """
+    __used_for__ = INewsSite
+
+class NewsSiteSyndicationView:
+
+    """ Provide an interface for syndicating a NewsSite over RSS.
+    """
+    __used_for__ = INewsSite
+
+    def __init__(self, context, request):
+
+        self.context = context
+        self.request = request
+        self.site = getAdapter(context, INewsSite)
+        self.dc = getAdapter(context, IZopeDublinCore)
+        self.context_url = getView(context, 'absolute_url', request)()
+
+        # XXX
+        self.logo_url = 'newssite_logo.png'
+
+        self.syn_policies = getAdapter(context, ISyndicationPolicies)
+
+    def listSortedEntries(self):
+
+        result = []
+        for k, v in self.site.items():
+            dc = getAdapter(v, IZopeDublinCore)
+            result.append((dc.effective or dc.created, k))
+        return [x[1] for x in result]
+
+
+    def listSyndicationInfo(self):
+
+        """ Return a sequence of mappings describing the syndicatable entries.
+        """
+        result = []
+
+        keys = self.listSortedEntries()
+
+        for key in keys[:self.syn_policies.max_items]:
+
+            raw_entry = self.site[key]
+            wrapped_entry = ContextWrapper(raw_entry, self.context,
+                                           name=key)
+
+            entry_url = getView(wrapped_entry, 'absolute_url', self.request)
+            entry_dc = getAdapter(wrapped_entry, IZopeDublinCore)
+
+            result.append( { 'title'        : entry_dc.title
+                           , 'entry'        : wrapped_entry
+                           , 'entry_url'    : entry_url()
+                           , 'dc'           : entry_dc
+                           } )
+
+        return result


=== Products3/NewsSite/configure.zcml 1.9 => 1.10 ===
--- Products3/NewsSite/configure.zcml:1.9	Wed Mar 26 10:14:14 2003
+++ Products3/NewsSite/configure.zcml	Wed Mar 26 12:18:16 2003
@@ -3,58 +3,68 @@
     xmlns:browser='http://namespaces.zope.org/browser'
 >
 
-<browser:menu id="add_news" title="News Site Items" />
+<!-- Declare a special menu for the content to be added within
+  :: a NewsSite.  The custom "adding" view for the site uses this
+  :: menu, and the content objects will insert their add forms into
+  :: it.
+  -->
+<browser:menu
+    id="add_news"
+    title="News Site Items"
+    />
 
 <include package=".NewsItem" />
-<!-- Configuration for News Site Object -->
+
+<!-- Configuration for News Site Object
+  -->
 <content 
-	class=".newssite.NewsSite">
+    class=".newssite.NewsSite">
+
+    <implements
+        interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
+        />
+
+    <factory
+        id="NewsSite"
+        title="News site"
+        description="boring news site"
+        permission="zope.ManageContent"
+        />
+
+    <allow
+        interface="zope.app.interfaces.services.service.Read"
+        />
+
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.services.service.Write"
+        />
+
+    <require
+        permission="zope.View"
+        interface="zope.app.interfaces.container.IReadContainer"
+        />
+
+    <require
+        permission="zope.ManageContent"
+        interface="zope.app.interfaces.container.IWriteContainer"
+        />
 
-	<factory
-		id="NewsSite"
-		permission="zope.ManageContent"
-		title="News site"
-		description="boring news site"/>
-
-  <allow
-      interface="zope.app.interfaces.services.service.Read"
-      />
-
-  <require
-      permission="zope.ManageServices"
-      interface="zope.app.interfaces.services.service.Write"
-      />
-
-  <require
-      permission="zope.View"
-      interface="zope.app.interfaces.container.IReadContainer"
-      />
-
-  <require
-      permission="zope.ManageContent"
-      interface="zope.app.interfaces.container.IWriteContainer"
-      />
- 
 </content>
 
+<!-- Register us with the "global" add list.
+  -->
 <browser:menuItem
-	menu="add_content"
-	for="zope.app.interfaces.container.IAdding"
-	title="News site"
-	action="NewsSite"
+    menu="add_content"
+    for="zope.app.interfaces.container.IAdding"
+    title="News site"
+    action="NewsSite"
     description="A boring news site."
 	permission="zope.View"
-/>
-	
-<browser:page
-	template="newsindex.pt"
-	name="list.html"
-	for=".interfaces.INewsSite"
-	permission="zope.View"
-	menu="zmi_views"
-	title="View news index"
-	class=".newssite.NewsSiteView"/>
+    />
 
+<!-- Custom adding view.
+  -->
 <browser:view
     for=".interfaces.INewsSite"
     name="+" 
@@ -63,10 +73,63 @@
     permission="zope.ManageContent"
     allowed_attributes="addingInfo"
     >
-
     <browser:page name="index.html"  template="add.pt" />
     <browser:page name="action.html" attribute="action" />
 </browser:view>
+
+<!-- Default view.
+  ::
+  :: XXX:  This should be named 'index.html', obviating the need for the
+  ::       'browser:defaultView' below;  however, some other directive is
+  ::       stomping on our registration if we do that! :(
+  -->
+<browser:page
+    template="newsindex.pt"
+    name="list.html"
+    for=".interfaces.INewsSite"
+    permission="zope.View"
+    menu="zmi_views"
+    title="View news index"
+    class=".newssite.NewsSiteView"
+    />
+
+<browser:defaultView
+    name="list.html"
+    />
+
+<!-- Render RSS for our items.
+  -->
+<browser:page
+    for=".interfaces.INewsSite"
+    name="index.rss"
+    template="index_rss.pt"
+    menu="zmi_views"
+    title="RSS"
+    class=".browser.NewsSiteSyndicationView"
+    permission="zope.View"
+    />
+
+<!-- Adapt the NewsSite to ISyndicationPolicies.
+  -->
+<adapter
+    factory=".newssite.NewsSiteSyndicationPoliciesAdapter"
+    provides=".interfaces.ISyndicationPolicies"
+    for=".interfaces.INewsSite" />
+
+<!-- Use the adapter to edit the syndication policies.
+  -->
+<browser:editform
+    name="syndication.html"
+    schema=".interfaces.ISyndicationPolicies"
+    label="Syndication Policies"
+    permission="zope.ManageContent"
+    for=".interfaces.INewsSite"
+    class=".browser.NewsSiteSyndicationPoliciesView"
+    menu="zmi_views"
+    title="Syndication"
+    />
+
+
 <!--
 <browser:addform
     name="register.html"


=== Products3/NewsSite/interfaces.py 1.3 => 1.4 ===
--- Products3/NewsSite/interfaces.py:1.3	Wed Mar 26 06:54:09 2003
+++ Products3/NewsSite/interfaces.py	Wed Mar 26 12:18:16 2003
@@ -17,12 +17,21 @@
 """
 
 from zope.interface import Interface
-from zope.schema import TextLine, Password
+from zope.proxy.context import ContextProperty
+from zope.schema import TextLine, Password, Int, Datetime
+
 from zope.app.interfaces.container import IContentContainer
+from zope.app.datetimeutils import parseDatetimetz
 
+#
+#   Content interfacse
+#
 class INewsSite(IContentContainer):
 	"""Provides a marker interface for news site"""
 
+#
+#   Registration interfaces
+#
 class IMember(Interface):
     """Provide information about site members.
 
@@ -42,6 +51,46 @@
                 required=True)
 
                         
+
+#
+#   Syndication policy interfaces
+#
+ALLOWED_PERIODS = (u'hourly', u'daily', u'weekly', u'monthly', u'yearly')
+
+class SyndicationPeriodField(TextLine):
+
+    def _allowed(self):
+        return ALLOWED_PERIODS
+
+    allowed_values = ContextProperty( _allowed )
+
+class ISyndicationPolicies(Interface):
+    """ Track a set of policy settings for syndicating our news over RSS.
+    """
+    max_items = Int(title=u'Max. RSS Items',
+                    description=u'The number of entries available to RSS.',
+                    required=True,
+                    default=15
+                   )
+
+    period = SyndicationPeriodField(
+                      title=u'Update Period',
+                      description=u'Periodicity of update to the corpus.',
+                      required=True,
+                      default=u'daily'
+                     )
+
+    frequency = Int(title=u'Frequency',
+                    description=u'How often per period is the corpus updated?',
+                    required=True,
+                    default=1
+                   )
+
+    base_update = Datetime(title=u'Base Update Time',
+                           description=u'Starting point for computing updates.',
+                           required=True,
+                           default=parseDatetimetz( '2003/01/01 00:00 UTC' )
+                          )
     
     
 


=== Products3/NewsSite/newssite.py 1.3 => 1.4 ===
--- Products3/NewsSite/newssite.py:1.3	Wed Mar 26 11:53:39 2003
+++ Products3/NewsSite/newssite.py	Wed Mar 26 12:18:16 2003
@@ -15,12 +15,18 @@
 
 $Id$
 """
-from zope.app.content.folder import Folder
-from zopeproducts.NewsSite.interfaces import INewsSite
-from zopeproducts.NewsSite.NewsItem.interfaces import INewsItem
+from zope.app.datetimeutils import parseDatetimetz
+from persistence.dict import PersistentDict
+
 from zope.publisher.browser import BrowserView 
-from zope.app.interfaces.dublincore import ICMFDublinCore
 from zope.component import getAdapter
+from zope.app.interfaces.annotation import IAnnotations
+from zope.app.interfaces.dublincore import ICMFDublinCore
+from zope.app.content.folder import Folder
+
+from interfaces import INewsSite
+from interfaces import ISyndicationPolicies
+from NewsItem.interfaces import INewsItem
 from zope.proxy.context import ContextWrapper
 
 class NewsSite(Folder):
@@ -28,7 +34,7 @@
 
     __implements__ = (Folder.__implements__, INewsSite)
 
-    
+
 class NewsSiteView(BrowserView):
 
     __used_for__ = INewsSite
@@ -46,3 +52,93 @@
         list = [ x[0] for x in list]
         return list
 
+#
+#   Adapter for storing ISyndicationPolicies as annotations.
+#
+SPkey = "zopeproducts.NewsSite.SyndicationPolicies"
+
+class NewsSiteSyndicationPoliciesAdapter( object ):
+
+    """ Adapt news site to ISP interface.
+    """
+
+    __implements__ = ( ISyndicationPolicies, )
+    __used_for__ = ( INewsSite, )
+
+    _annotations = None
+
+    _DEFAULT_MAX_ITEMS = 15
+    _DEFAULT_PERIOD = 'daily'
+    _DEFAULT_FREQUENCY = 1
+    _DEFAULT_BASE_UPDATE = parseDatetimetz( '2003/01/01' )
+
+    def __init__( self, context ):
+
+        annotations = getAdapter( context, IAnnotations )
+
+        spdata = annotations.get( SPkey )
+
+        if not spdata:
+            self._annotations = annotations
+            spdata = PersistentDict()
+            spdata[ 'max_items' ] = self._DEFAULT_MAX_ITEMS
+            spdata[ 'period' ] = self._DEFAULT_PERIOD
+            spdata[ 'frequency' ] = self._DEFAULT_FREQUENCY
+            spdata[ 'base_update' ] = self._DEFAULT_BASE_UPDATE
+
+        self._mapping = spdata
+
+    def _changed( self ):
+
+        # Is this a new annotation?
+        if self._annotations is not None:
+            self._annotations[ SPkey ] = self._mapping
+            self._annotations = None
+
+    #
+    #   'max_items' field
+    #
+    def _get_max_items( self ):
+        return self._mapping[ 'max_items' ]
+
+    def _set_max_items( self, value ):
+        self._mapping[ 'max_items' ] = value
+        self._changed()
+
+    max_items = property( _get_max_items, _set_max_items )
+
+    #
+    #   'period' field
+    #
+    def _get_period( self ):
+        return self._mapping[ 'period' ]
+
+    def _set_period( self, value ):
+        self._mapping[ 'period' ] = value
+        self._changed()
+
+    period = property( _get_period, _set_period )
+
+    #
+    #   'freqeuency' field
+    #
+    def _get_frequency( self ):
+        return self._mapping[ 'frequency' ]
+
+    def _set_frequency( self, value ):
+        self._mapping[ 'frequency' ] = value
+        self._changed()
+
+    frequency = property( _get_frequency, _set_frequency )
+
+    #
+    #   'base_update' field
+    #
+    def _get_base_update( self ):
+        return self._mapping[ 'base_update' ]
+
+    def _set_base_update( self, value ):
+        self._mapping[ 'base_update' ] = value
+        self._changed()
+
+    base_update = property( _get_base_update, _set_base_update )