forked from collective/Products.feedfeeder
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
svn path=/feedfeeder/trunk/; revision=65293
- Loading branch information
0 parents
commit 3714d0f
Showing
77 changed files
with
6,525 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages | ||
try: | ||
__import__('pkg_resources').declare_namespace(__name__) | ||
except ImportError: | ||
from pkgutil import extend_path | ||
__path__ = extend_path(__path__, __name__) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from StringIO import StringIO | ||
from Products.CMFCore.utils import getToolByName | ||
|
||
|
||
def install(site): | ||
out = StringIO() | ||
applyGenericSetupProfile(site, out) | ||
|
||
|
||
def applyGenericSetupProfile(site, out): | ||
"""Just apply our own extension profile. | ||
""" | ||
|
||
setup_tool = getToolByName(site, 'portal_setup') | ||
setup_tool.setImportContext('profile-feedfeeder:default') | ||
print >> out, "Applying the generic setup profile for feedfeeder..." | ||
setup_tool.runAllImportSteps(purge_old=False) | ||
try: | ||
setup_tool.setImportContext('profile-CMFPlone:plone') | ||
except KeyError: | ||
# Plone 3.0 has a different profile name | ||
setup_tool.setImportContext('profile-Products.CMFPlone:plone') | ||
print >> out, "Applied the generic setup profile for feedfeeder" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import os.path | ||
import sys | ||
from StringIO import StringIO | ||
from sets import Set | ||
from App.Common import package_home | ||
from Products.CMFCore.utils import getToolByName | ||
from Products.CMFCore.utils import manage_addTool | ||
from Products.ExternalMethod.ExternalMethod import ExternalMethod | ||
from zExceptions import NotFound, BadRequest | ||
|
||
from Products.Archetypes.Extensions.utils import installTypes | ||
from Products.Archetypes.Extensions.utils import install_subskin | ||
from Products.Archetypes.config import TOOL_NAME as ARCHETYPETOOLNAME | ||
from Products.Archetypes.atapi import listTypes | ||
from Products.feedfeeder.config import PROJECTNAME | ||
from Products.feedfeeder.config import product_globals as GLOBALS | ||
|
||
def install(self, reinstall=False): | ||
""" External Method to install feedfeeder """ | ||
out = StringIO() | ||
print >> out, "Installation log of %s:" % PROJECTNAME | ||
|
||
# If the config contains a list of dependencies, try to install | ||
# them. Add a list called DEPENDENCIES to your custom | ||
# AppConfig.py (imported by config.py) to use it. | ||
try: | ||
from Products.feedfeeder.config import DEPENDENCIES | ||
except: | ||
DEPENDENCIES = [] | ||
portal = getToolByName(self,'portal_url').getPortalObject() | ||
quickinstaller = portal.portal_quickinstaller | ||
for dependency in DEPENDENCIES: | ||
print >> out, "Installing dependency %s:" % dependency | ||
quickinstaller.installProduct(dependency) | ||
import transaction | ||
transaction.savepoint(optimistic=True) | ||
|
||
classes = listTypes(PROJECTNAME) | ||
installTypes(self, out, | ||
classes, | ||
PROJECTNAME) | ||
install_subskin(self, out, GLOBALS) | ||
|
||
|
||
# try to call a workflow install method | ||
# in 'InstallWorkflows.py' method 'installWorkflows' | ||
try: | ||
installWorkflows = ExternalMethod('temp', 'temp', | ||
PROJECTNAME+'.InstallWorkflows', | ||
'installWorkflows').__of__(self) | ||
except NotFound: | ||
installWorkflows = None | ||
|
||
if installWorkflows: | ||
print >>out,'Workflow Install:' | ||
res = installWorkflows(self,out) | ||
print >>out,res or 'no output' | ||
else: | ||
print >>out,'no workflow install' | ||
|
||
|
||
# enable portal_factory for given types | ||
factory_tool = getToolByName(self,'portal_factory') | ||
factory_types=[ | ||
"FeedConsumer", | ||
"StandardContentHandler", | ||
"FeedfeederFolder", | ||
"FeedFeederItem", | ||
] + factory_tool.getFactoryTypes().keys() | ||
factory_tool.manage_setPortalFactoryTypes(listOfTypeIds=factory_types) | ||
|
||
from Products.feedfeeder.config import STYLESHEETS | ||
try: | ||
portal_css = getToolByName(portal, 'portal_css') | ||
for stylesheet in STYLESHEETS: | ||
try: | ||
portal_css.unregisterResource(stylesheet['id']) | ||
except: | ||
pass | ||
defaults = {'id': '', | ||
'media': 'all', | ||
'enabled': True} | ||
defaults.update(stylesheet) | ||
portal_css.manage_addStylesheet(**defaults) | ||
except: | ||
# No portal_css registry | ||
pass | ||
from Products.feedfeeder.config import JAVASCRIPTS | ||
try: | ||
portal_javascripts = getToolByName(portal, 'portal_javascripts') | ||
for javascript in JAVASCRIPTS: | ||
try: | ||
portal_javascripts.unregisterResource(javascript['id']) | ||
except: | ||
pass | ||
defaults = {'id': ''} | ||
defaults.update(javascript) | ||
portal_javascripts.registerScript(**defaults) | ||
except: | ||
# No portal_javascripts registry | ||
pass | ||
|
||
# try to call a custom install method | ||
# in 'AppInstall.py' method 'install' | ||
try: | ||
install = ExternalMethod('temp', 'temp', | ||
PROJECTNAME+'.AppInstall', 'install') | ||
except NotFound: | ||
install = None | ||
|
||
if install: | ||
print >>out,'Custom Install:' | ||
try: | ||
res = install(self, reinstall) | ||
except TypeError: | ||
res = install(self) | ||
if res: | ||
print >>out,res | ||
else: | ||
print >>out,'no output' | ||
else: | ||
print >>out,'no custom install' | ||
return out.getvalue() | ||
|
||
def uninstall(self, reinstall=False): | ||
out = StringIO() | ||
|
||
# try to call a workflow uninstall method | ||
# in 'InstallWorkflows.py' method 'uninstallWorkflows' | ||
try: | ||
uninstallWorkflows = ExternalMethod('temp', 'temp', | ||
PROJECTNAME+'.InstallWorkflows', | ||
'uninstallWorkflows').__of__(self) | ||
except NotFound: | ||
uninstallWorkflows = None | ||
|
||
if uninstallWorkflows: | ||
print >>out, 'Workflow Uninstall:' | ||
res = uninstallWorkflows(self, out) | ||
print >>out, res or 'no output' | ||
else: | ||
print >>out,'no workflow uninstall' | ||
|
||
# try to call a custom uninstall method | ||
# in 'AppInstall.py' method 'uninstall' | ||
try: | ||
uninstall = ExternalMethod('temp', 'temp', | ||
PROJECTNAME+'.AppInstall', 'uninstall') | ||
except: | ||
uninstall = None | ||
|
||
if uninstall: | ||
print >>out,'Custom Uninstall:' | ||
try: | ||
res = uninstall(self, reinstall) | ||
except TypeError: | ||
res = uninstall(self) | ||
if res: | ||
print >>out,res | ||
else: | ||
print >>out,'no output' | ||
else: | ||
print >>out,'no custom uninstall' | ||
|
||
return out.getvalue() | ||
|
||
def beforeUninstall(self, reinstall, product, cascade): | ||
""" try to call a custom beforeUninstall method in 'AppInstall.py' | ||
method 'beforeUninstall' | ||
""" | ||
out = StringIO() | ||
try: | ||
beforeuninstall = ExternalMethod('temp', 'temp', | ||
PROJECTNAME+'.AppInstall', 'beforeUninstall') | ||
except: | ||
beforeuninstall = [] | ||
|
||
if beforeuninstall: | ||
print >>out, 'Custom beforeUninstall:' | ||
res = beforeuninstall(self, reinstall=reinstall | ||
, product=product | ||
, cascade=cascade) | ||
if res: | ||
print >>out, res | ||
else: | ||
print >>out, 'no output' | ||
else: | ||
print >>out, 'no custom beforeUninstall' | ||
return (out,cascade) | ||
|
||
def afterInstall(self, reinstall, product): | ||
""" try to call a custom afterInstall method in 'AppInstall.py' method | ||
'afterInstall' | ||
""" | ||
out = StringIO() | ||
try: | ||
afterinstall = ExternalMethod('temp', 'temp', | ||
PROJECTNAME+'.AppInstall', 'afterInstall') | ||
except: | ||
afterinstall = None | ||
|
||
if afterinstall: | ||
print >>out, 'Custom afterInstall:' | ||
res = afterinstall(self, product=None | ||
, reinstall=None) | ||
if res: | ||
print >>out, res | ||
else: | ||
print >>out, 'no output' | ||
else: | ||
print >>out, 'no custom afterInstall' | ||
return out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# make me a python module |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
1.0 beta 4 (unreleased) | ||
----------------------- | ||
|
||
- Eggification: you can now install it as the Products.feedfeeder | ||
egg. [maurits] | ||
|
||
|
||
1.0 beta 3 (13 May 2008) | ||
------------------------ | ||
|
||
- In the tests, use plone_workflow explicitly, so it is easier to test | ||
on both Plone 2.5 and 3.0. [maurits] | ||
|
||
- Make update_feed_items available in the object_buttons for Plone 3, | ||
using new small @@is_feedcontainer as condition. [maurits] | ||
|
||
- Avoid deprecation warnings for events and interfaces. [maurits] | ||
|
||
- Remove semicolon in page template that broke in Plone 3. [maurits] | ||
|
||
- Fix imports so they work in Plone 3 as well, without deprecation | ||
warnings. [derstappenit] | ||
|
||
|
||
1.0 beta 2 (2 January 2008) | ||
--------------------------- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
Feedfeeder | ||
========== | ||
|
||
Feedfeeder has just a few things it needs to do: | ||
|
||
- Read in a few ATOM feeds (not too many). | ||
|
||
- Create FeedfeederItems out of the entries pulled from the ATOM feeds. | ||
Any feed items that contain enclosures will have the enclosures | ||
pulled down and added as File items to the feed item. | ||
|
||
- This means figuring out which items are new, which also means having | ||
a good ID generating mechanism. | ||
|
||
|
||
Wait, no existing product? | ||
-------------------------- | ||
|
||
There's a whole slew of RSS/ATOM reading products for zope and | ||
plone. None of them seemed to be a good fit. There was only one | ||
product that actually stored the entries in the zope database, but | ||
that was aimed at a lot of users individually adding a lot of feeds, | ||
so it needed either a separate ZEO process (old version) or a | ||
standalone mysql database (new version). | ||
|
||
All the other products didn't store the entries in the database, were | ||
old/unmaintained/etc. | ||
|
||
In a sense, we're using an existing product as we use Mark Pilgrim's | ||
excellent feedparser (http://feedparser.org) that'll do the actual | ||
ATOM reading for us. | ||
|
||
|
||
Product name | ||
------------ | ||
|
||
The product feeds the content of ATOM feeds to plone as document/file | ||
content types. So "feedfeeder" sort of suggested itself as a funny | ||
name. Fun is important :-) | ||
|
||
|
||
Product structure | ||
----------------- | ||
|
||
I'm using archgenxml to generate the boiler plate stuff. There's a | ||
'generate.sh' shell script that'll call archgenxml for you. Nothing | ||
fancy. | ||
|
||
The feedfeeder's content types are: | ||
- folder.FeedfeederFolder | ||
- item.FeedfeederItem | ||
|
||
|
||
How it works | ||
------------ | ||
|
||
A feedfeeder is a folder which contains all the previously-added feed | ||
entries as documents or files. It has a 'feeds' attribute that | ||
contains a list of feeds to read. | ||
|
||
Feedparser is called periodically (through a cron job?) to parse the | ||
feeds. The UID of the items in the feed are converted to a suitable | ||
filename (md5 hex hash of the atom id of the entry), that way you can | ||
detect whether there are new items. | ||
|
||
New items are turned into feed items. | ||
|
||
Scheduled updates for feed folders | ||
|
||
Zope can be configured to periodically trigger a url call. | ||
In zope.conf you can use the <clock-server> directive to define a schedule and url | ||
with the following data. | ||
<clock-server> | ||
method /path_to_feedfolder/update_feed_items | ||
period 3600 # seconds | ||
user admin | ||
password 123 | ||
host localhost:8080 | ||
</clock-server> | ||
|
||
|
||
Tests | ||
----- | ||
|
||
The look-here-first test is the doctest at 'doc/testDocIntegrationTests.txt'. | ||
|
||
Testing is best done with zope's zopectl. 'instancemanager | ||
<projectname> --test feedfeeder' will do that for you if you've set | ||
up instancemanager. Otherwise 'bin/zopectl test -s | ||
Products.feedfeeder'. | ||
|
Oops, something went wrong.