Fixed #6547, added support for GeoRSS feeds in `django.contrib.gis.feeds`; added the `feed_extra_kwargs` and `item_extra_kwargs` to the `Feed` baseclass so that it's possible for subclasses to add dynamic attributes.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@8414 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
c127f0117d
commit
a2be52fd2a
|
@ -0,0 +1,135 @@
|
||||||
|
from django.contrib.syndication.feeds import Feed as BaseFeed, FeedDoesNotExist
|
||||||
|
from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
|
||||||
|
|
||||||
|
class GeoFeedMixin(object):
|
||||||
|
"""
|
||||||
|
This mixin provides the necessary routines for SyndicationFeed subclasses
|
||||||
|
to produce simple GeoRSS or W3C Geo elements.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def georss_coords(self, coords):
|
||||||
|
"""
|
||||||
|
In GeoRSS coordinate pairs are ordered by lat/lon and separated by
|
||||||
|
a single white space. Given a tuple of coordinates, this will return
|
||||||
|
a unicode GeoRSS representation.
|
||||||
|
"""
|
||||||
|
return u' '.join([u'%f %f' % (coord[1], coord[0]) for coord in coords])
|
||||||
|
|
||||||
|
def add_georss_point(self, handler, coords, w3c_geo=False):
|
||||||
|
"""
|
||||||
|
Adds a GeoRSS point with the given coords using the given handler.
|
||||||
|
Handles the differences between simple GeoRSS and the more pouplar
|
||||||
|
W3C Geo specification.
|
||||||
|
"""
|
||||||
|
if w3c_geo:
|
||||||
|
lon, lat = coords[:2]
|
||||||
|
handler.addQuickElement(u'geo:lat', u'%f' % lat)
|
||||||
|
handler.addQuickElement(u'geo:lon', u'%f' % lon)
|
||||||
|
else:
|
||||||
|
handler.addQuickElement(u'georss:point', self.georss_coords((coords,)))
|
||||||
|
|
||||||
|
def add_georss_element(self, handler, item, w3c_geo=False):
|
||||||
|
"""
|
||||||
|
This routine adds a GeoRSS XML element using the given item and handler.
|
||||||
|
"""
|
||||||
|
# Getting the Geometry object.
|
||||||
|
geom = item.get('geometry', None)
|
||||||
|
if not geom is None:
|
||||||
|
if isinstance(geom, (list, tuple)):
|
||||||
|
# Special case if a tuple/list was passed in. The tuple may be
|
||||||
|
# a point or a box
|
||||||
|
box_coords = None
|
||||||
|
if isinstance(geom[0], (list, tuple)):
|
||||||
|
# Box: ( (X0, Y0), (X1, Y1) )
|
||||||
|
if len(geom) == 2:
|
||||||
|
box_coords = geom
|
||||||
|
else:
|
||||||
|
raise ValueError('Only should be two sets of coordinates.')
|
||||||
|
else:
|
||||||
|
if len(geom) == 2:
|
||||||
|
# Point: (X, Y)
|
||||||
|
self.add_georss_point(handler, geom, w3c_geo=w3c_geo)
|
||||||
|
elif len(geom) == 4:
|
||||||
|
# Box: (X0, Y0, X1, Y1)
|
||||||
|
box_coords = (geom[:2], geom[2:])
|
||||||
|
else:
|
||||||
|
raise ValueError('Only should be 2 or 4 numeric elements.')
|
||||||
|
# If a GeoRSS box was given via tuple.
|
||||||
|
if not box_coords is None:
|
||||||
|
if w3c_geo: raise ValueError('Cannot use simple GeoRSS box in W3C Geo feeds.')
|
||||||
|
handler.addQuickElement(u'georss:box', self.georss_coords(box_coords))
|
||||||
|
else:
|
||||||
|
# Getting the lower-case geometry type.
|
||||||
|
gtype = str(geom.geom_type).lower()
|
||||||
|
if gtype == 'point':
|
||||||
|
self.add_georss_point(handler, geom.coords, w3c_geo=w3c_geo)
|
||||||
|
else:
|
||||||
|
if w3c_geo: raise ValueError('W3C Geo only supports Point geometries.')
|
||||||
|
# For formatting consistent w/the GeoRSS simple standard:
|
||||||
|
# http://georss.org/1.0#simple
|
||||||
|
if gtype in ('linestring', 'linearring'):
|
||||||
|
handler.addQuickElement(u'georss:line', self.georss_coords(geom.coords))
|
||||||
|
elif gtype in ('polygon',):
|
||||||
|
# Only support the exterior ring.
|
||||||
|
handler.addQuickElement(u'georss:polygon', self.georss_coords(geom[0].coords))
|
||||||
|
else:
|
||||||
|
raise ValueError('Geometry type "%s" not supported.' % geom.geom_type)
|
||||||
|
|
||||||
|
### SyndicationFeed subclasses ###
|
||||||
|
class GeoRSSFeed(Rss201rev2Feed, GeoFeedMixin):
|
||||||
|
def rss_attributes(self):
|
||||||
|
attrs = super(GeoRSSFeed, self).rss_attributes()
|
||||||
|
attrs[u'xmlns:georss'] = u'http://www.georss.org/georss'
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def add_item_elements(self, handler, item):
|
||||||
|
super(GeoRSSFeed, self).add_item_elements(handler, item)
|
||||||
|
self.add_georss_element(handler, item)
|
||||||
|
|
||||||
|
def add_root_elements(self, handler):
|
||||||
|
super(GeoRSSFeed, self).add_root_elements(handler)
|
||||||
|
self.add_georss_element(handler, self.feed)
|
||||||
|
|
||||||
|
class GeoAtom1Feed(Atom1Feed, GeoFeedMixin):
|
||||||
|
def root_attributes(self):
|
||||||
|
attrs = super(GeoAtom1Feed, self).root_attributes()
|
||||||
|
attrs[u'xmlns:georss'] = u'http://www.georss.org/georss'
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def add_item_elements(self, handler, item):
|
||||||
|
super(GeoAtom1Feed, self).add_item_elements(handler, item)
|
||||||
|
self.add_georss_element(handler, item)
|
||||||
|
|
||||||
|
def add_root_elements(self, handler):
|
||||||
|
super(GeoAtom1Feed, self).add_root_elements(handler)
|
||||||
|
self.add_georss_element(handler, self.feed)
|
||||||
|
|
||||||
|
class W3CGeoFeed(Rss201rev2Feed, GeoFeedMixin):
|
||||||
|
def rss_attributes(self):
|
||||||
|
attrs = super(W3CGeoFeed, self).rss_attributes()
|
||||||
|
attrs[u'xmlns:geo'] = u'http://www.w3.org/2003/01/geo/wgs84_pos#'
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def add_item_elements(self, handler, item):
|
||||||
|
super(W3CGeoFeed, self).add_item_elements(handler, item)
|
||||||
|
self.add_georss_element(handler, item, w3c_geo=True)
|
||||||
|
|
||||||
|
def add_root_elements(self, handler):
|
||||||
|
super(W3CGeoFeed, self).add_root_elements(handler)
|
||||||
|
self.add_georss_element(handler, self.feed, w3c_geo=True)
|
||||||
|
|
||||||
|
### Feed subclass ###
|
||||||
|
class Feed(BaseFeed):
|
||||||
|
"""
|
||||||
|
This is a subclass of the `Feed` from `django.contrib.syndication`.
|
||||||
|
This allows users to define a `geometry(obj)` and/or `item_geometry(item)`
|
||||||
|
methods on their own subclasses so that geo-referenced information may
|
||||||
|
placed in the feed.
|
||||||
|
"""
|
||||||
|
feed_type = GeoRSSFeed
|
||||||
|
|
||||||
|
def feed_extra_kwargs(self, obj):
|
||||||
|
return {'geometry' : self.__get_dynamic_attr('geometry', obj)}
|
||||||
|
|
||||||
|
def item_extra_kwargs(self, item):
|
||||||
|
return {'geometry' : self.__get_dynamic_attr('item_geometry', item)}
|
|
@ -1,5 +1,4 @@
|
||||||
import sys
|
import sys
|
||||||
from copy import copy
|
|
||||||
from unittest import TestSuite, TextTestRunner
|
from unittest import TestSuite, TextTestRunner
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -94,18 +93,34 @@ def run_tests(module_list, verbosity=1, interactive=True):
|
||||||
from django.contrib.gis.db.backend import create_spatial_db
|
from django.contrib.gis.db.backend import create_spatial_db
|
||||||
from django.contrib.gis.tests.utils import mysql
|
from django.contrib.gis.tests.utils import mysql
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
from django.db.models import loading
|
||||||
|
|
||||||
# Getting initial values.
|
# Getting initial values.
|
||||||
old_debug = settings.DEBUG
|
old_debug = settings.DEBUG
|
||||||
old_name = copy(settings.DATABASE_NAME)
|
old_name = settings.DATABASE_NAME
|
||||||
old_installed = copy(settings.INSTALLED_APPS)
|
old_installed = settings.INSTALLED_APPS
|
||||||
new_installed = copy(settings.INSTALLED_APPS)
|
old_root_urlconf = settings.ROOT_URLCONF
|
||||||
|
|
||||||
|
# Based on ALWAYS_INSTALLED_APPS from django test suite --
|
||||||
|
# this prevents us from creating tables in our test database
|
||||||
|
# from locally installed apps.
|
||||||
|
new_installed = ['django.contrib.contenttypes',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.sites',
|
||||||
|
'django.contrib.flatpages',
|
||||||
|
'django.contrib.gis',
|
||||||
|
'django.contrib.redirects',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.comments',
|
||||||
|
'django.contrib.admin',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Setting the URLs.
|
||||||
|
settings.ROOT_URLCONF = 'django.contrib.gis.tests.urls'
|
||||||
|
|
||||||
# Want DEBUG to be set to False.
|
# Want DEBUG to be set to False.
|
||||||
settings.DEBUG = False
|
settings.DEBUG = False
|
||||||
|
|
||||||
from django.db.models import loading
|
|
||||||
|
|
||||||
# Creating the test suite, adding the test models to INSTALLED_APPS, and
|
# Creating the test suite, adding the test models to INSTALLED_APPS, and
|
||||||
# adding the model test suites to our suite package.
|
# adding the model test suites to our suite package.
|
||||||
test_suite, test_models = geo_suite()
|
test_suite, test_models = geo_suite()
|
||||||
|
@ -117,8 +132,9 @@ def run_tests(module_list, verbosity=1, interactive=True):
|
||||||
test_module_name = 'tests'
|
test_module_name = 'tests'
|
||||||
new_installed.append(module_name)
|
new_installed.append(module_name)
|
||||||
|
|
||||||
# Getting the test suite
|
# Getting the model test suite
|
||||||
tsuite = getattr(__import__('django.contrib.gis.tests.%s' % test_model, globals(), locals(), [test_module_name]), test_module_name)
|
tsuite = getattr(__import__('django.contrib.gis.tests.%s' % test_model, globals(), locals(), [test_module_name]),
|
||||||
|
test_module_name)
|
||||||
test_suite.addTest(tsuite.suite())
|
test_suite.addTest(tsuite.suite())
|
||||||
|
|
||||||
# Resetting the loaded flag to take into account what we appended to
|
# Resetting the loaded flag to take into account what we appended to
|
||||||
|
@ -138,6 +154,7 @@ def run_tests(module_list, verbosity=1, interactive=True):
|
||||||
connection.creation.destroy_test_db(old_name, verbosity)
|
connection.creation.destroy_test_db(old_name, verbosity)
|
||||||
settings.DEBUG = old_debug
|
settings.DEBUG = old_debug
|
||||||
settings.INSTALLED_APPS = old_installed
|
settings.INSTALLED_APPS = old_installed
|
||||||
|
settings.ROOT_URLCONF = old_root_urlconf
|
||||||
|
|
||||||
# Returning the total failures and errors
|
# Returning the total failures and errors
|
||||||
return len(result.failures) + len(result.errors)
|
return len(result.failures) + len(result.errors)
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
from django.contrib.gis import feeds
|
||||||
|
from django.contrib.gis.tests.utils import mysql
|
||||||
|
from models import City, Country
|
||||||
|
|
||||||
|
class TestGeoRSS1(feeds.Feed):
|
||||||
|
link = '/city/'
|
||||||
|
title = 'Test GeoDjango Cities'
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return City.objects.all()
|
||||||
|
|
||||||
|
def item_link(self, item):
|
||||||
|
return '/city/%s/' % item.pk
|
||||||
|
|
||||||
|
def item_geometry(self, item):
|
||||||
|
return item.point
|
||||||
|
|
||||||
|
class TestGeoRSS2(TestGeoRSS1):
|
||||||
|
def geometry(self, obj):
|
||||||
|
# This should attach a <georss:box> element for the extent of
|
||||||
|
# of the cities in the database. This tuple came from
|
||||||
|
# calling `City.objects.extent()` -- we can't do that call here
|
||||||
|
# because `extent` is not implemented for MySQL/Oracle.
|
||||||
|
return (-123.30, -41.32, 174.78, 48.46)
|
||||||
|
|
||||||
|
def item_geometry(self, item):
|
||||||
|
# Returning a simple tuple for the geometry.
|
||||||
|
return item.point.x, item.point.y
|
||||||
|
|
||||||
|
class TestGeoAtom1(TestGeoRSS1):
|
||||||
|
feed_type = feeds.GeoAtom1Feed
|
||||||
|
|
||||||
|
class TestGeoAtom2(TestGeoRSS2):
|
||||||
|
feed_type = feeds.GeoAtom1Feed
|
||||||
|
|
||||||
|
def geometry(self, obj):
|
||||||
|
# This time we'll use a 2-tuple of coordinates for the box.
|
||||||
|
return ((-123.30, -41.32), (174.78, 48.46))
|
||||||
|
|
||||||
|
class TestW3CGeo1(TestGeoRSS1):
|
||||||
|
feed_type = feeds.W3CGeoFeed
|
||||||
|
|
||||||
|
# The following feeds are invalid, and will raise exceptions.
|
||||||
|
class TestW3CGeo2(TestGeoRSS2):
|
||||||
|
feed_type = feeds.W3CGeoFeed
|
||||||
|
|
||||||
|
class TestW3CGeo3(TestGeoRSS1):
|
||||||
|
feed_type = feeds.W3CGeoFeed
|
||||||
|
|
||||||
|
def item_geometry(self, item):
|
||||||
|
from django.contrib.gis.geos import Polygon
|
||||||
|
return Polygon(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
|
|
@ -0,0 +1,76 @@
|
||||||
|
import unittest
|
||||||
|
from xml.dom import minidom
|
||||||
|
|
||||||
|
from django.test import Client
|
||||||
|
from models import City
|
||||||
|
|
||||||
|
class GeoFeedTest(unittest.TestCase):
|
||||||
|
client = Client()
|
||||||
|
|
||||||
|
def assertChildNodes(self, elem, expected):
|
||||||
|
"Taken from regressiontests/syndication/tests.py."
|
||||||
|
actual = set([n.nodeName for n in elem.childNodes])
|
||||||
|
expected = set(expected)
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
def test_geofeed_rss(self):
|
||||||
|
"Tests geographic feeds using GeoRSS over RSSv2."
|
||||||
|
# Uses `GEOSGeometry` in `item_geometry`
|
||||||
|
doc1 = minidom.parseString(self.client.get('/geoapp/feeds/rss1/').content)
|
||||||
|
# Uses a 2-tuple in `item_geometry`
|
||||||
|
doc2 = minidom.parseString(self.client.get('/geoapp/feeds/rss2/').content)
|
||||||
|
feed1, feed2 = doc1.firstChild, doc2.firstChild
|
||||||
|
|
||||||
|
# Making sure the box got added to the second GeoRSS feed.
|
||||||
|
self.assertChildNodes(feed2.getElementsByTagName('channel')[0],
|
||||||
|
['title', 'link', 'description', 'language', 'lastBuildDate', 'item', 'georss:box']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Incrementing through the feeds.
|
||||||
|
for feed in [feed1, feed2]:
|
||||||
|
# Ensuring the georss namespace was added to the <rss> element.
|
||||||
|
self.assertEqual(feed.getAttribute(u'xmlns:georss'), u'http://www.georss.org/georss')
|
||||||
|
chan = feed.getElementsByTagName('channel')[0]
|
||||||
|
items = chan.getElementsByTagName('item')
|
||||||
|
self.assertEqual(len(items), City.objects.count())
|
||||||
|
|
||||||
|
# Ensuring the georss element was added to each item in the feed.
|
||||||
|
for item in items:
|
||||||
|
self.assertChildNodes(item, ['title', 'link', 'description', 'guid', 'georss:point'])
|
||||||
|
|
||||||
|
def test_geofeed_atom(self):
|
||||||
|
"Testing geographic feeds using GeoRSS over Atom."
|
||||||
|
doc1 = minidom.parseString(self.client.get('/geoapp/feeds/atom1/').content)
|
||||||
|
doc2 = minidom.parseString(self.client.get('/geoapp/feeds/atom2/').content)
|
||||||
|
feed1, feed2 = doc1.firstChild, doc2.firstChild
|
||||||
|
|
||||||
|
# Making sure the box got added to the second GeoRSS feed.
|
||||||
|
self.assertChildNodes(feed2, ['title', 'link', 'id', 'updated', 'entry', 'georss:box'])
|
||||||
|
|
||||||
|
for feed in [feed1, feed2]:
|
||||||
|
# Ensuring the georsss namespace was added to the <feed> element.
|
||||||
|
self.assertEqual(feed.getAttribute(u'xmlns:georss'), u'http://www.georss.org/georss')
|
||||||
|
entries = feed.getElementsByTagName('entry')
|
||||||
|
self.assertEqual(len(entries), City.objects.count())
|
||||||
|
|
||||||
|
# Ensuring the georss element was added to each entry in the feed.
|
||||||
|
for entry in entries:
|
||||||
|
self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'georss:point'])
|
||||||
|
|
||||||
|
def test_geofeed_w3c(self):
|
||||||
|
"Testing geographic feeds using W3C Geo."
|
||||||
|
doc = minidom.parseString(self.client.get('/geoapp/feeds/w3cgeo1/').content)
|
||||||
|
feed = doc.firstChild
|
||||||
|
# Ensuring the geo namespace was added to the <feed> element.
|
||||||
|
self.assertEqual(feed.getAttribute(u'xmlns:geo'), u'http://www.w3.org/2003/01/geo/wgs84_pos#')
|
||||||
|
chan = feed.getElementsByTagName('channel')[0]
|
||||||
|
items = chan.getElementsByTagName('item')
|
||||||
|
self.assertEqual(len(items), City.objects.count())
|
||||||
|
|
||||||
|
# Ensuring the geo:lat and geo:lon element was added to each item in the feed.
|
||||||
|
for item in items:
|
||||||
|
self.assertChildNodes(item, ['title', 'link', 'description', 'guid', 'geo:lat', 'geo:lon'])
|
||||||
|
|
||||||
|
# Boxes and Polygons aren't allowed in W3C Geo feeds.
|
||||||
|
self.assertRaises(ValueError, self.client.get, '/geoapp/feeds/w3cgeo2/') # Box in <channel>
|
||||||
|
self.assertRaises(ValueError, self.client.get, '/geoapp/feeds/w3cgeo3/') # Polygons in <entry>
|
|
@ -372,8 +372,8 @@ class GeoModelTest(unittest.TestCase):
|
||||||
for c in qs: self.assertEqual(True, c.name in cities)
|
for c in qs: self.assertEqual(True, c.name in cities)
|
||||||
|
|
||||||
def test14_equals(self):
|
def test14_equals(self):
|
||||||
if DISABLE: return
|
|
||||||
"Testing the 'same_as' and 'equals' lookup types."
|
"Testing the 'same_as' and 'equals' lookup types."
|
||||||
|
if DISABLE: return
|
||||||
pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326)
|
pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326)
|
||||||
c1 = City.objects.get(point=pnt)
|
c1 = City.objects.get(point=pnt)
|
||||||
c2 = City.objects.get(point__same_as=pnt)
|
c2 = City.objects.get(point__same_as=pnt)
|
||||||
|
@ -558,7 +558,9 @@ class GeoModelTest(unittest.TestCase):
|
||||||
self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
|
self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
|
||||||
self.assertEqual(c.mpoly.union(geom), c.union)
|
self.assertEqual(c.mpoly.union(geom), c.union)
|
||||||
|
|
||||||
|
from test_feeds import GeoFeedTest
|
||||||
def suite():
|
def suite():
|
||||||
s = unittest.TestSuite()
|
s = unittest.TestSuite()
|
||||||
s.addTest(unittest.makeSuite(GeoModelTest))
|
s.addTest(unittest.makeSuite(GeoModelTest))
|
||||||
|
s.addTest(unittest.makeSuite(GeoFeedTest))
|
||||||
return s
|
return s
|
||||||
|
|
|
@ -173,7 +173,9 @@ class GeoModelTest(unittest.TestCase):
|
||||||
self.assertRaises(ImproperlyConfigured, State.objects.all().kml, field_name='poly')
|
self.assertRaises(ImproperlyConfigured, State.objects.all().kml, field_name='poly')
|
||||||
self.assertRaises(ImproperlyConfigured, Country.objects.all().gml, field_name='mpoly')
|
self.assertRaises(ImproperlyConfigured, Country.objects.all().gml, field_name='mpoly')
|
||||||
|
|
||||||
|
from test_feeds import GeoFeedTest
|
||||||
def suite():
|
def suite():
|
||||||
s = unittest.TestSuite()
|
s = unittest.TestSuite()
|
||||||
s.addTest(unittest.makeSuite(GeoModelTest))
|
s.addTest(unittest.makeSuite(GeoModelTest))
|
||||||
|
s.addTest(unittest.makeSuite(GeoFeedTest))
|
||||||
return s
|
return s
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
from django.conf.urls.defaults import *
|
||||||
|
from feeds import TestGeoRSS1, TestGeoRSS2, TestGeoAtom1, TestGeoAtom2, TestW3CGeo1, TestW3CGeo2, TestW3CGeo3
|
||||||
|
|
||||||
|
feed_dict = {
|
||||||
|
'rss1' : TestGeoRSS1,
|
||||||
|
'rss2' : TestGeoRSS2,
|
||||||
|
'atom1' : TestGeoAtom1,
|
||||||
|
'atom2' : TestGeoAtom2,
|
||||||
|
'w3cgeo1' : TestW3CGeo1,
|
||||||
|
'w3cgeo2' : TestW3CGeo2,
|
||||||
|
'w3cgeo3' : TestW3CGeo3,
|
||||||
|
}
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feed_dict})
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.conf.urls.defaults import *
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
(r'^geoapp/', include('django.contrib.gis.tests.geoapp.urls')),
|
||||||
|
)
|
||||||
|
|
|
@ -59,6 +59,20 @@ class Feed(object):
|
||||||
return attr()
|
return attr()
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
|
def feed_extra_kwargs(self, obj):
|
||||||
|
"""
|
||||||
|
Returns an extra keyword arguments dictionary that is used when
|
||||||
|
initializing the feed generator.
|
||||||
|
"""
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def item_extra_kwargs(self, item):
|
||||||
|
"""
|
||||||
|
Returns an extra keyword arguments dictionary that is used with
|
||||||
|
the `add_item` call of the feed generator.
|
||||||
|
"""
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_object(self, bits):
|
def get_object(self, bits):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -100,6 +114,7 @@ class Feed(object):
|
||||||
feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
|
feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
|
||||||
feed_guid = self.__get_dynamic_attr('feed_guid', obj),
|
feed_guid = self.__get_dynamic_attr('feed_guid', obj),
|
||||||
ttl = self.__get_dynamic_attr('ttl', obj),
|
ttl = self.__get_dynamic_attr('ttl', obj),
|
||||||
|
**self.feed_extra_kwargs(obj)
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -158,5 +173,6 @@ class Feed(object):
|
||||||
author_link = author_link,
|
author_link = author_link,
|
||||||
categories = self.__get_dynamic_attr('item_categories', item),
|
categories = self.__get_dynamic_attr('item_categories', item),
|
||||||
item_copyright = self.__get_dynamic_attr('item_copyright', item),
|
item_copyright = self.__get_dynamic_attr('item_copyright', item),
|
||||||
|
**self.item_extra_kwargs(item)
|
||||||
)
|
)
|
||||||
return feed
|
return feed
|
||||||
|
|
Loading…
Reference in New Issue