From 0a8402eb052a5c35085baa5408aaf4ee36ebc0a6 Mon Sep 17 00:00:00 2001 From: Zbigniew Siciarz Date: Sun, 24 Feb 2013 15:00:34 +0100 Subject: [PATCH] Test case and docs for custom context data in feeds Thanks Paul Winkler for the initial patch. (Ref #18112). --- django/contrib/syndication/views.py | 16 +++++- docs/ref/contrib/syndication.txt | 54 +++++++++++++++++++ .../class-based-views/generic-display.txt | 2 + tests/regressiontests/syndication/feeds.py | 13 +++++ .../syndication/description_context.html | 1 + .../templates/syndication/title_context.html | 1 + tests/regressiontests/syndication/tests.py | 16 ++++++ tests/regressiontests/syndication/urls.py | 1 + 8 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 tests/regressiontests/syndication/templates/syndication/description_context.html create mode 100644 tests/regressiontests/syndication/templates/syndication/title_context.html diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py index a80b9d1fae..4abf1e53a9 100644 --- a/django/contrib/syndication/views.py +++ b/django/contrib/syndication/views.py @@ -100,6 +100,16 @@ class Feed(object): def get_object(self, request, *args, **kwargs): return None + def get_context_data(self, **kwargs): + """ + Returns a dictionary to use as extra context if either + ``self.description_template`` or ``self.item_template`` are used. + + Default implementation preserves the old behavior + of using {'obj': item, 'site': current_site} as the context. + """ + return {'obj': kwargs.get('item'), 'site': kwargs.get('site')} + def get_feed(self, obj, request): """ Returns a feedgenerator.DefaultFeed object, fully populated, for @@ -146,12 +156,14 @@ class Feed(object): pass for item in self.__get_dynamic_attr('items', obj): + context = self.get_context_data(item=item, site=current_site, + obj=obj, request=request) if title_tmp is not None: - title = title_tmp.render(RequestContext(request, {'obj': item, 'site': current_site})) + title = title_tmp.render(RequestContext(request, context)) else: title = self.__get_dynamic_attr('item_title', item) if description_tmp is not None: - description = description_tmp.render(RequestContext(request, {'obj': item, 'site': current_site})) + description = description_tmp.render(RequestContext(request, context)) else: description = self.__get_dynamic_attr('item_description', item) link = add_domain( diff --git a/docs/ref/contrib/syndication.txt b/docs/ref/contrib/syndication.txt index 65aa7b57b4..02159c415b 100644 --- a/docs/ref/contrib/syndication.txt +++ b/docs/ref/contrib/syndication.txt @@ -137,6 +137,51 @@ into those elements. See `a complex example`_ below that uses a description template. + There is also a way to pass additional information to title and description + templates, if you need to supply more than the two variables mentioned + before. You can provide your implementation of ``get_context_data`` method + in your Feed subclass. For example:: + + from mysite.models import Article + from django.contrib.syndication.views import Feed + + class ArticlesFeed(Feed): + title = "My articles" + description_template = "feeds/articles.html" + + def items(self): + return Article.objects.order_by('-pub_date')[:5] + + def get_context_data(self, **kwargs): + context = super(ArticlesFeed, self).get_context_data(**kwargs) + context['foo'] = 'bar' + return context + + And the template: + + .. code-block:: html+django + + Something about {{ foo }}: {{ obj.description }} + + This method will be called once per each item in the list returned by + ``items()`` with the following keyword arguments: + + * ``item``: the current item. For backward compatibility reasons, the name + of this context variable is ``{{ obj }}``. + + * ``obj``: the object returned by ``get_object()``. By default this is not + exposed to the templates to avoid confusion with ``{{ obj }}`` (see above), + but you can use it in your implementation of ``get_context_data()``. + + * ``site``: current site as described above. + + * ``request``: current request. + + The behavior of ``get_context_data()`` mimics that of + :ref:`generic views ` - you're supposed to call + ``super()`` to retrieve context data from parent class, add your data + and return the modified dictionary. + * To specify the contents of ````, you have two options. For each item in ``items()``, Django first tries calling the ``item_link()`` method on the @@ -599,6 +644,15 @@ This example illustrates all possible attributes and methods for a item_description = 'A description of the item.' # Hard-coded description. + def get_context_data(self, **kwargs): + """ + Returns a dictionary to use as extra context if either + description_template or item_template are used. + + Default implementation preserves the old behavior + of using {'obj': item, 'site': current_site} as the context. + """ + # ITEM LINK -- One of these three is required. The framework looks for # them in this order. diff --git a/docs/topics/class-based-views/generic-display.txt b/docs/topics/class-based-views/generic-display.txt index 8fe6cd0d65..8695af7fe6 100644 --- a/docs/topics/class-based-views/generic-display.txt +++ b/docs/topics/class-based-views/generic-display.txt @@ -188,6 +188,8 @@ Providing a useful ``context_object_name`` is always a good idea. Your coworkers who design templates will thank you. +.. _adding-extra-context: + Adding extra context -------------------- diff --git a/tests/regressiontests/syndication/feeds.py b/tests/regressiontests/syndication/feeds.py index 25757057b9..0956820bf0 100644 --- a/tests/regressiontests/syndication/feeds.py +++ b/tests/regressiontests/syndication/feeds.py @@ -97,6 +97,19 @@ class TemplateFeed(TestRss2Feed): return "Not in a template" +class TemplateContextFeed(TestRss2Feed): + """ + A feed to test custom context data in templates for title or description. + """ + title_template = 'syndication/title_context.html' + description_template = 'syndication/description_context.html' + + def get_context_data(self, **kwargs): + context = super(TemplateContextFeed, self).get_context_data(**kwargs) + context['foo'] = 'bar' + return context + + class NaiveDatesFeed(TestAtomFeed): """ A feed with naive (non-timezone-aware) dates. diff --git a/tests/regressiontests/syndication/templates/syndication/description_context.html b/tests/regressiontests/syndication/templates/syndication/description_context.html new file mode 100644 index 0000000000..319d84b1b0 --- /dev/null +++ b/tests/regressiontests/syndication/templates/syndication/description_context.html @@ -0,0 +1 @@ +{{ obj }} (foo is {{ foo }}) \ No newline at end of file diff --git a/tests/regressiontests/syndication/templates/syndication/title_context.html b/tests/regressiontests/syndication/templates/syndication/title_context.html new file mode 100644 index 0000000000..319d84b1b0 --- /dev/null +++ b/tests/regressiontests/syndication/templates/syndication/title_context.html @@ -0,0 +1 @@ +{{ obj }} (foo is {{ foo }}) \ No newline at end of file diff --git a/tests/regressiontests/syndication/tests.py b/tests/regressiontests/syndication/tests.py index 8885dc28c0..e8fc6be420 100644 --- a/tests/regressiontests/syndication/tests.py +++ b/tests/regressiontests/syndication/tests.py @@ -323,6 +323,22 @@ class SyndicationFeedTest(FeedTestCase): 'link': 'http://example.com/blog/1/', }) + def test_template_context_feed(self): + """ + Test that custom context data can be passed to templates for title + and description. + """ + response = self.client.get('/syndication/template_context/') + doc = minidom.parseString(response.content) + feed = doc.getElementsByTagName('rss')[0] + chan = feed.getElementsByTagName('channel')[0] + items = chan.getElementsByTagName('item') + + self.assertChildNodeContent(items[0], { + 'title': 'My first entry (foo is bar)', + 'description': 'My first entry (foo is bar)', + }) + def test_add_domain(self): """ Test add_domain() prefixes domains onto the correct URLs. diff --git a/tests/regressiontests/syndication/urls.py b/tests/regressiontests/syndication/urls.py index ec3c8cc596..1dd7e92332 100644 --- a/tests/regressiontests/syndication/urls.py +++ b/tests/regressiontests/syndication/urls.py @@ -21,4 +21,5 @@ urlpatterns = patterns('django.contrib.syndication.views', (r'^syndication/feedurl/$', feeds.TestFeedUrlFeed()), (r'^syndication/articles/$', feeds.ArticlesFeed()), (r'^syndication/template/$', feeds.TemplateFeed()), + (r'^syndication/template_context/$', feeds.TemplateContextFeed()), )