mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1098 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			1098 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| ==============================
 | |
| The syndication feed framework
 | |
| ==============================
 | |
| 
 | |
| .. module:: django.contrib.syndication
 | |
|    :synopsis: A framework for generating syndication feeds, in RSS and Atom,
 | |
|               quite easily.
 | |
| 
 | |
| Django comes with a high-level syndication-feed-generating framework for
 | |
| creating RSS_ and :rfc:`Atom <4287>` feeds.
 | |
| 
 | |
| To create any syndication feed, all you have to do is write a short
 | |
| Python class. You can create as many feeds as you want.
 | |
| 
 | |
| Django also comes with a lower-level feed-generating API. Use this if
 | |
| you want to generate feeds outside of a web context, or in some other
 | |
| lower-level way.
 | |
| 
 | |
| .. _RSS: https://developer.mozilla.org/en-US/docs/Glossary/RSS
 | |
| 
 | |
| The high-level framework
 | |
| ========================
 | |
| 
 | |
| Overview
 | |
| --------
 | |
| 
 | |
| The high-level feed-generating framework is supplied by the
 | |
| :class:`~django.contrib.syndication.views.Feed` class. To create a
 | |
| feed, write a :class:`~django.contrib.syndication.views.Feed` class
 | |
| and point to an instance of it in your :doc:`URLconf
 | |
| </topics/http/urls>`.
 | |
| 
 | |
| ``Feed`` classes
 | |
| ----------------
 | |
| 
 | |
| A :class:`~django.contrib.syndication.views.Feed` class is a Python
 | |
| class that represents a syndication feed. A feed can be simple (e.g.,
 | |
| a "site news" feed, or a basic feed displaying the latest entries of a
 | |
| blog) or more complex (e.g., a feed displaying all the blog entries in
 | |
| a particular category, where the category is variable).
 | |
| 
 | |
| Feed classes subclass :class:`django.contrib.syndication.views.Feed`.
 | |
| They can live anywhere in your codebase.
 | |
| 
 | |
| Instances of :class:`~django.contrib.syndication.views.Feed` classes
 | |
| are views which can be used in your :doc:`URLconf </topics/http/urls>`.
 | |
| 
 | |
| A simple example
 | |
| ----------------
 | |
| 
 | |
| This simple example, taken from a hypothetical police beat news site describes
 | |
| a feed of the latest five news items::
 | |
| 
 | |
|     from django.contrib.syndication.views import Feed
 | |
|     from django.urls import reverse
 | |
|     from policebeat.models import NewsItem
 | |
| 
 | |
| 
 | |
|     class LatestEntriesFeed(Feed):
 | |
|         title = "Police beat site news"
 | |
|         link = "/sitenews/"
 | |
|         description = "Updates on changes and additions to police beat central."
 | |
| 
 | |
|         def items(self):
 | |
|             return NewsItem.objects.order_by("-pub_date")[:5]
 | |
| 
 | |
|         def item_title(self, item):
 | |
|             return item.title
 | |
| 
 | |
|         def item_description(self, item):
 | |
|             return item.description
 | |
| 
 | |
|         # item_link is only needed if NewsItem has no get_absolute_url method.
 | |
|         def item_link(self, item):
 | |
|             return reverse("news-item", args=[item.pk])
 | |
| 
 | |
| To connect a URL to this feed, put an instance of the Feed object in
 | |
| your :doc:`URLconf </topics/http/urls>`. For example::
 | |
| 
 | |
|     from django.urls import path
 | |
|     from myproject.feeds import LatestEntriesFeed
 | |
| 
 | |
|     urlpatterns = [
 | |
|         # ...
 | |
|         path("latest/feed/", LatestEntriesFeed()),
 | |
|         # ...
 | |
|     ]
 | |
| 
 | |
| Note:
 | |
| 
 | |
| * The Feed class subclasses :class:`django.contrib.syndication.views.Feed`.
 | |
| 
 | |
| * ``title``, ``link`` and ``description`` correspond to the
 | |
|   standard RSS ``<title>``, ``<link>`` and ``<description>`` elements,
 | |
|   respectively.
 | |
| 
 | |
| * ``items()`` is, a method that returns a list of objects that should be
 | |
|   included in the feed as ``<item>`` elements. Although this example returns
 | |
|   ``NewsItem`` objects using Django's :doc:`object-relational mapper
 | |
|   </ref/models/querysets>`, ``items()`` doesn't have to return model instances.
 | |
|   Although you get a few bits of functionality "for free" by using Django
 | |
|   models, ``items()`` can return any type of object you want.
 | |
| 
 | |
| * If you're creating an Atom feed, rather than an RSS feed, set the
 | |
|   ``subtitle`` attribute instead of the ``description`` attribute.
 | |
|   See `Publishing Atom and RSS feeds in tandem`_, later, for an example.
 | |
| 
 | |
| One thing is left to do. In an RSS feed, each ``<item>`` has a ``<title>``,
 | |
| ``<link>`` and ``<description>``. We need to tell the framework what data to put
 | |
| into those elements.
 | |
| 
 | |
| * For the contents of ``<title>`` and ``<description>``, Django tries
 | |
|   calling the methods ``item_title()`` and ``item_description()`` on
 | |
|   the :class:`~django.contrib.syndication.views.Feed` class. They are passed
 | |
|   a single parameter, ``item``, which is the object itself. These are
 | |
|   optional; by default, the string representation of the object is used for
 | |
|   both.
 | |
| 
 | |
|   If you want to do any special formatting for either the title or
 | |
|   description, :doc:`Django templates </ref/templates/language>` can be used
 | |
|   instead. Their paths can be specified with the ``title_template`` and
 | |
|   ``description_template`` attributes on the
 | |
|   :class:`~django.contrib.syndication.views.Feed` class. The templates are
 | |
|   rendered for each item and are passed two template context variables:
 | |
| 
 | |
|   * ``{{ obj }}`` -- The current object (one of whichever objects you
 | |
|     returned in ``items()``).
 | |
| 
 | |
|   * ``{{ site }}`` -- A :class:`django.contrib.sites.models.Site` object
 | |
|     representing the current site. This is useful for ``{{ site.domain
 | |
|     }}`` or ``{{ site.name }}``. If you do *not* have the Django sites
 | |
|     framework installed, this will be set to a
 | |
|     :class:`~django.contrib.sites.requests.RequestSite` object. See the
 | |
|     :ref:`RequestSite section of the sites framework documentation
 | |
|     <requestsite-objects>` for more.
 | |
| 
 | |
|   See `a complex example`_ below that uses a description template.
 | |
| 
 | |
|   .. method:: Feed.get_context_data(**kwargs)
 | |
| 
 | |
|       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().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 <adding-extra-context>` - 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 ``<link>``, you have two options. For each item
 | |
|   in ``items()``, Django first tries calling the
 | |
|   ``item_link()`` method on the
 | |
|   :class:`~django.contrib.syndication.views.Feed` class. In a similar way to
 | |
|   the title and description, it is passed it a single parameter,
 | |
|   ``item``. If that method doesn't exist, Django tries executing a
 | |
|   ``get_absolute_url()`` method on that object. Both
 | |
|   ``get_absolute_url()`` and ``item_link()`` should return the
 | |
|   item's URL as a normal Python string. As with ``get_absolute_url()``, the
 | |
|   result of ``item_link()`` will be included directly in the URL, so you
 | |
|   are responsible for doing all necessary URL quoting and conversion to
 | |
|   ASCII inside the method itself.
 | |
| 
 | |
| A complex example
 | |
| -----------------
 | |
| 
 | |
| The framework also supports more complex feeds, via arguments.
 | |
| 
 | |
| For example, a website could offer an RSS feed of recent crimes for every
 | |
| police beat in a city. It'd be silly to create a separate
 | |
| :class:`~django.contrib.syndication.views.Feed` class for each police beat; that
 | |
| would violate the :ref:`DRY principle <dry>` and would couple data to
 | |
| programming logic. Instead, the syndication framework lets you access the
 | |
| arguments passed from your :doc:`URLconf </topics/http/urls>` so feeds can output
 | |
| items based on information in the feed's URL.
 | |
| 
 | |
| The police beat feeds could be accessible via URLs like this:
 | |
| 
 | |
| * ``/beats/613/rss/`` -- Returns recent crimes for beat 613.
 | |
| * ``/beats/1424/rss/`` -- Returns recent crimes for beat 1424.
 | |
| 
 | |
| These can be matched with a :doc:`URLconf </topics/http/urls>` line such as::
 | |
| 
 | |
|     path("beats/<int:beat_id>/rss/", BeatFeed()),
 | |
| 
 | |
| Like a view, the arguments in the URL are passed to the ``get_object()``
 | |
| method along with the request object.
 | |
| 
 | |
| Here's the code for these beat-specific feeds::
 | |
| 
 | |
|     from django.contrib.syndication.views import Feed
 | |
| 
 | |
| 
 | |
|     class BeatFeed(Feed):
 | |
|         description_template = "feeds/beat_description.html"
 | |
| 
 | |
|         def get_object(self, request, beat_id):
 | |
|             return Beat.objects.get(pk=beat_id)
 | |
| 
 | |
|         def title(self, obj):
 | |
|             return "Police beat central: Crimes for beat %s" % obj.beat
 | |
| 
 | |
|         def link(self, obj):
 | |
|             return obj.get_absolute_url()
 | |
| 
 | |
|         def description(self, obj):
 | |
|             return "Crimes recently reported in police beat %s" % obj.beat
 | |
| 
 | |
|         def items(self, obj):
 | |
|             return Crime.objects.filter(beat=obj).order_by("-crime_date")[:30]
 | |
| 
 | |
| To generate the feed's ``<title>``, ``<link>`` and ``<description>``, Django
 | |
| uses the ``title()``, ``link()`` and ``description()`` methods. In
 | |
| the previous example, they were string class attributes, but this example
 | |
| illustrates that they can be either strings *or* methods. For each of
 | |
| ``title``, ``link`` and ``description``, Django follows this
 | |
| algorithm:
 | |
| 
 | |
| * First, it tries to call a method, passing the ``obj`` argument, where
 | |
|   ``obj`` is the object returned by ``get_object()``.
 | |
| 
 | |
| * Failing that, it tries to call a method with no arguments.
 | |
| 
 | |
| * Failing that, it uses the class attribute.
 | |
| 
 | |
| Also note that ``items()`` also follows the same algorithm -- first, it
 | |
| tries ``items(obj)``, then ``items()``, then finally an ``items``
 | |
| class attribute (which should be a list).
 | |
| 
 | |
| We are using a template for the item descriptions. It can be as minimal as
 | |
| this:
 | |
| 
 | |
| .. code-block:: html+django
 | |
| 
 | |
|     {{ obj.description }}
 | |
| 
 | |
| However, you are free to add formatting as desired.
 | |
| 
 | |
| The ``ExampleFeed`` class below gives full documentation on methods and
 | |
| attributes of :class:`~django.contrib.syndication.views.Feed` classes.
 | |
| 
 | |
| Specifying the type of feed
 | |
| ---------------------------
 | |
| 
 | |
| By default, feeds produced in this framework use RSS 2.0.
 | |
| 
 | |
| To change that, add a ``feed_type`` attribute to your
 | |
| :class:`~django.contrib.syndication.views.Feed` class, like so::
 | |
| 
 | |
|     from django.utils.feedgenerator import Atom1Feed
 | |
| 
 | |
| 
 | |
|     class MyFeed(Feed):
 | |
|         feed_type = Atom1Feed
 | |
| 
 | |
| Note that you set ``feed_type`` to a class object, not an instance.
 | |
| 
 | |
| Currently available feed types are:
 | |
| 
 | |
| * :class:`django.utils.feedgenerator.Rss201rev2Feed` (RSS 2.01. Default.)
 | |
| * :class:`django.utils.feedgenerator.RssUserland091Feed` (RSS 0.91.)
 | |
| * :class:`django.utils.feedgenerator.Atom1Feed` (Atom 1.0.)
 | |
| 
 | |
| Enclosures
 | |
| ----------
 | |
| 
 | |
| To specify enclosures, such as those used in creating podcast feeds, use the
 | |
| ``item_enclosures`` hook or, alternatively and if you only have a single
 | |
| enclosure per item, the ``item_enclosure_url``, ``item_enclosure_length``, and
 | |
| ``item_enclosure_mime_type`` hooks. See the ``ExampleFeed`` class below for
 | |
| usage examples.
 | |
| 
 | |
| Language
 | |
| --------
 | |
| 
 | |
| Feeds created by the syndication framework automatically include the
 | |
| appropriate ``<language>`` tag (RSS 2.0) or ``xml:lang`` attribute (Atom). By
 | |
| default, this is :func:`django.utils.translation.get_language()`. You can change it
 | |
| by setting the ``language`` class attribute.
 | |
| 
 | |
| URLs
 | |
| ----
 | |
| 
 | |
| The ``link`` method/attribute can return either an absolute path (e.g.
 | |
| ``"/blog/"``) or a URL with the fully-qualified domain and protocol (e.g.
 | |
| ``"https://www.example.com/blog/"``). If ``link`` doesn't return the domain,
 | |
| the syndication framework will insert the domain of the current site, according
 | |
| to your :setting:`SITE_ID setting <SITE_ID>`.
 | |
| 
 | |
| Atom feeds require a ``<link rel="self">`` that defines the feed's current
 | |
| location. The syndication framework populates this automatically, using the
 | |
| domain of the current site according to the :setting:`SITE_ID` setting.
 | |
| 
 | |
| Publishing Atom and RSS feeds in tandem
 | |
| ---------------------------------------
 | |
| 
 | |
| Some developers like to make available both Atom *and* RSS versions of their
 | |
| feeds. To do that, you can create a subclass of your
 | |
| :class:`~django.contrib.syndication.views.Feed` class and set the ``feed_type``
 | |
| to something different. Then update your URLconf to add the extra versions.
 | |
| 
 | |
| Here's a full example::
 | |
| 
 | |
|     from django.contrib.syndication.views import Feed
 | |
|     from policebeat.models import NewsItem
 | |
|     from django.utils.feedgenerator import Atom1Feed
 | |
| 
 | |
| 
 | |
|     class RssSiteNewsFeed(Feed):
 | |
|         title = "Police beat site news"
 | |
|         link = "/sitenews/"
 | |
|         description = "Updates on changes and additions to police beat central."
 | |
| 
 | |
|         def items(self):
 | |
|             return NewsItem.objects.order_by("-pub_date")[:5]
 | |
| 
 | |
| 
 | |
|     class AtomSiteNewsFeed(RssSiteNewsFeed):
 | |
|         feed_type = Atom1Feed
 | |
|         subtitle = RssSiteNewsFeed.description
 | |
| 
 | |
| .. Note::
 | |
|     In this example, the RSS feed uses a ``description`` while the Atom
 | |
|     feed uses a ``subtitle``. That's because Atom feeds don't provide for
 | |
|     a feed-level "description," but they *do* provide for a "subtitle."
 | |
| 
 | |
|     If you provide a ``description`` in your
 | |
|     :class:`~django.contrib.syndication.views.Feed` class, Django will *not*
 | |
|     automatically put that into the ``subtitle`` element, because a
 | |
|     subtitle and description are not necessarily the same thing. Instead, you
 | |
|     should define a ``subtitle`` attribute.
 | |
| 
 | |
|     In the above example, we set the Atom feed's ``subtitle`` to the RSS feed's
 | |
|     ``description``, because it's quite short already.
 | |
| 
 | |
| And the accompanying URLconf::
 | |
| 
 | |
|     from django.urls import path
 | |
|     from myproject.feeds import AtomSiteNewsFeed, RssSiteNewsFeed
 | |
| 
 | |
|     urlpatterns = [
 | |
|         # ...
 | |
|         path("sitenews/rss/", RssSiteNewsFeed()),
 | |
|         path("sitenews/atom/", AtomSiteNewsFeed()),
 | |
|         # ...
 | |
|     ]
 | |
| 
 | |
| ``Feed`` class reference
 | |
| ------------------------
 | |
| 
 | |
| .. class:: views.Feed
 | |
| 
 | |
| This example illustrates all possible attributes and methods for a
 | |
| :class:`~django.contrib.syndication.views.Feed` class::
 | |
| 
 | |
|     from django.contrib.syndication.views import Feed
 | |
|     from django.utils import feedgenerator
 | |
| 
 | |
| 
 | |
|     class ExampleFeed(Feed):
 | |
|         # FEED TYPE -- Optional. This should be a class that subclasses
 | |
|         # django.utils.feedgenerator.SyndicationFeed. This designates
 | |
|         # which type of feed this should be: RSS 2.0, Atom 1.0, etc. If
 | |
|         # you don't specify feed_type, your feed will be RSS 2.0. This
 | |
|         # should be a class, not an instance of the class.
 | |
| 
 | |
|         feed_type = feedgenerator.Rss201rev2Feed
 | |
| 
 | |
|         # TEMPLATE NAMES -- Optional. These should be strings
 | |
|         # representing names of Django templates that the system should
 | |
|         # use in rendering the title and description of your feed items.
 | |
|         # Both are optional. If a template is not specified, the
 | |
|         # item_title() or item_description() methods are used instead.
 | |
| 
 | |
|         title_template = None
 | |
|         description_template = None
 | |
| 
 | |
|         # LANGUAGE -- Optional. This should be a string specifying a language
 | |
|         # code. Defaults to django.utils.translation.get_language().
 | |
|         language = "de"
 | |
| 
 | |
|         # TITLE -- One of the following three is required. The framework
 | |
|         # looks for them in this order.
 | |
| 
 | |
|         def title(self, obj):
 | |
|             """
 | |
|             Takes the object returned by get_object() and returns the
 | |
|             feed's title as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def title(self):
 | |
|             """
 | |
|             Returns the feed's title as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         title = "foo"  # Hard-coded title.
 | |
| 
 | |
|         # LINK -- One of the following three is required. The framework
 | |
|         # looks for them in this order.
 | |
| 
 | |
|         def link(self, obj):
 | |
|             """
 | |
|             # Takes the object returned by get_object() and returns the URL
 | |
|             # of the HTML version of the feed as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def link(self):
 | |
|             """
 | |
|             Returns the URL of the HTML version of the feed as a normal Python
 | |
|             string.
 | |
|             """
 | |
| 
 | |
|         link = "/blog/"  # Hard-coded URL.
 | |
| 
 | |
|         # FEED_URL -- One of the following three is optional. The framework
 | |
|         # looks for them in this order.
 | |
| 
 | |
|         def feed_url(self, obj):
 | |
|             """
 | |
|             # Takes the object returned by get_object() and returns the feed's
 | |
|             # own URL as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def feed_url(self):
 | |
|             """
 | |
|             Returns the feed's own URL as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         feed_url = "/blog/rss/"  # Hard-coded URL.
 | |
| 
 | |
|         # GUID -- One of the following three is optional. The framework looks
 | |
|         # for them in this order. This property is only used for Atom feeds
 | |
|         # (where it is the feed-level ID element). If not provided, the feed
 | |
|         # link is used as the ID.
 | |
| 
 | |
|         def feed_guid(self, obj):
 | |
|             """
 | |
|             Takes the object returned by get_object() and returns the globally
 | |
|             unique ID for the feed as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def feed_guid(self):
 | |
|             """
 | |
|             Returns the feed's globally unique ID as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         feed_guid = "/foo/bar/1234"  # Hard-coded guid.
 | |
| 
 | |
|         # DESCRIPTION -- One of the following three is required. The framework
 | |
|         # looks for them in this order.
 | |
| 
 | |
|         def description(self, obj):
 | |
|             """
 | |
|             Takes the object returned by get_object() and returns the feed's
 | |
|             description as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def description(self):
 | |
|             """
 | |
|             Returns the feed's description as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         description = "Foo bar baz."  # Hard-coded description.
 | |
| 
 | |
|         # AUTHOR NAME --One of the following three is optional. The framework
 | |
|         # looks for them in this order.
 | |
| 
 | |
|         def author_name(self, obj):
 | |
|             """
 | |
|             Takes the object returned by get_object() and returns the feed's
 | |
|             author's name as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def author_name(self):
 | |
|             """
 | |
|             Returns the feed's author's name as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         author_name = "Sally Smith"  # Hard-coded author name.
 | |
| 
 | |
|         # AUTHOR EMAIL --One of the following three is optional. The framework
 | |
|         # looks for them in this order.
 | |
| 
 | |
|         def author_email(self, obj):
 | |
|             """
 | |
|             Takes the object returned by get_object() and returns the feed's
 | |
|             author's email as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def author_email(self):
 | |
|             """
 | |
|             Returns the feed's author's email as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         author_email = "test@example.com"  # Hard-coded author email.
 | |
| 
 | |
|         # AUTHOR LINK --One of the following three is optional. The framework
 | |
|         # looks for them in this order. In each case, the URL should include
 | |
|         # the "http://" and domain name.
 | |
| 
 | |
|         def author_link(self, obj):
 | |
|             """
 | |
|             Takes the object returned by get_object() and returns the feed's
 | |
|             author's URL as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def author_link(self):
 | |
|             """
 | |
|             Returns the feed's author's URL as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         author_link = "https://www.example.com/"  # Hard-coded author URL.
 | |
| 
 | |
|         # CATEGORIES -- One of the following three is optional. The framework
 | |
|         # looks for them in this order. In each case, the method/attribute
 | |
|         # should return an iterable object that returns strings.
 | |
| 
 | |
|         def categories(self, obj):
 | |
|             """
 | |
|             Takes the object returned by get_object() and returns the feed's
 | |
|             categories as iterable over strings.
 | |
|             """
 | |
| 
 | |
|         def categories(self):
 | |
|             """
 | |
|             Returns the feed's categories as iterable over strings.
 | |
|             """
 | |
| 
 | |
|         categories = ["python", "django"]  # Hard-coded list of categories.
 | |
| 
 | |
|         # COPYRIGHT NOTICE -- One of the following three is optional. The
 | |
|         # framework looks for them in this order.
 | |
| 
 | |
|         def feed_copyright(self, obj):
 | |
|             """
 | |
|             Takes the object returned by get_object() and returns the feed's
 | |
|             copyright notice as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def feed_copyright(self):
 | |
|             """
 | |
|             Returns the feed's copyright notice as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         feed_copyright = "Copyright (c) 2007, Sally Smith"  # Hard-coded copyright notice.
 | |
| 
 | |
|         # TTL -- One of the following three is optional. The framework looks
 | |
|         # for them in this order. Ignored for Atom feeds.
 | |
| 
 | |
|         def ttl(self, obj):
 | |
|             """
 | |
|             Takes the object returned by get_object() and returns the feed's
 | |
|             TTL (Time To Live) as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def ttl(self):
 | |
|             """
 | |
|             Returns the feed's TTL as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         ttl = 600  # Hard-coded Time To Live.
 | |
| 
 | |
|         # ITEMS -- One of the following three is required. The framework looks
 | |
|         # for them in this order.
 | |
| 
 | |
|         def items(self, obj):
 | |
|             """
 | |
|             Takes the object returned by get_object() and returns a list of
 | |
|             items to publish in this feed.
 | |
|             """
 | |
| 
 | |
|         def items(self):
 | |
|             """
 | |
|             Returns a list of items to publish in this feed.
 | |
|             """
 | |
| 
 | |
|         items = ["Item 1", "Item 2"]  # Hard-coded items.
 | |
| 
 | |
|         # GET_OBJECT -- This is required for feeds that publish different data
 | |
|         # for different URL parameters. (See "A complex example" above.)
 | |
| 
 | |
|         def get_object(self, request, *args, **kwargs):
 | |
|             """
 | |
|             Takes the current request and the arguments from the URL, and
 | |
|             returns an object represented by this feed. Raises
 | |
|             django.core.exceptions.ObjectDoesNotExist on error.
 | |
|             """
 | |
| 
 | |
|         # ITEM TITLE AND DESCRIPTION -- If title_template or
 | |
|         # description_template are not defined, these are used instead. Both are
 | |
|         # optional, by default they will use the string representation of the
 | |
|         # item.
 | |
| 
 | |
|         def item_title(self, item):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             title as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def item_title(self):
 | |
|             """
 | |
|             Returns the title for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_title = "Breaking News: Nothing Happening"  # Hard-coded title.
 | |
| 
 | |
|         def item_description(self, item):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             description as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def item_description(self):
 | |
|             """
 | |
|             Returns the description for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         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.
 | |
| 
 | |
|         # First, the framework tries the two methods below, in
 | |
|         # order. Failing that, it falls back to the get_absolute_url()
 | |
|         # method on each item returned by items().
 | |
| 
 | |
|         def item_link(self, item):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's URL.
 | |
|             """
 | |
| 
 | |
|         def item_link(self):
 | |
|             """
 | |
|             Returns the URL for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         # ITEM_GUID -- The following method is optional. If not provided, the
 | |
|         # item's link is used by default.
 | |
| 
 | |
|         def item_guid(self, obj):
 | |
|             """
 | |
|             Takes an item, as return by items(), and returns the item's ID.
 | |
|             """
 | |
| 
 | |
|         # ITEM_GUID_IS_PERMALINK -- The following method is optional. If
 | |
|         # provided, it sets the 'isPermaLink' attribute of an item's
 | |
|         # GUID element. This method is used only when 'item_guid' is
 | |
|         # specified.
 | |
| 
 | |
|         def item_guid_is_permalink(self, obj):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns a boolean.
 | |
|             """
 | |
| 
 | |
|         item_guid_is_permalink = False  # Hard coded value
 | |
| 
 | |
|         # ITEM AUTHOR NAME -- One of the following three is optional. The
 | |
|         # framework looks for them in this order.
 | |
| 
 | |
|         def item_author_name(self, item):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             author's name as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def item_author_name(self):
 | |
|             """
 | |
|             Returns the author name for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_author_name = "Sally Smith"  # Hard-coded author name.
 | |
| 
 | |
|         # ITEM AUTHOR EMAIL --One of the following three is optional. The
 | |
|         # framework looks for them in this order.
 | |
|         #
 | |
|         # If you specify this, you must specify item_author_name.
 | |
| 
 | |
|         def item_author_email(self, obj):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             author's email as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def item_author_email(self):
 | |
|             """
 | |
|             Returns the author email for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_author_email = "test@example.com"  # Hard-coded author email.
 | |
| 
 | |
|         # ITEM AUTHOR LINK -- One of the following three is optional. The
 | |
|         # framework looks for them in this order. In each case, the URL should
 | |
|         # include the "http://" and domain name.
 | |
|         #
 | |
|         # If you specify this, you must specify item_author_name.
 | |
| 
 | |
|         def item_author_link(self, obj):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             author's URL as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def item_author_link(self):
 | |
|             """
 | |
|             Returns the author URL for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_author_link = "https://www.example.com/"  # Hard-coded author URL.
 | |
| 
 | |
|         # ITEM ENCLOSURES -- One of the following three is optional. The
 | |
|         # framework looks for them in this order. If one of them is defined,
 | |
|         # ``item_enclosure_url``, ``item_enclosure_length``, and
 | |
|         # ``item_enclosure_mime_type`` will have no effect.
 | |
| 
 | |
|         def item_enclosures(self, item):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns a list of
 | |
|             ``django.utils.feedgenerator.Enclosure`` objects.
 | |
|             """
 | |
| 
 | |
|         def item_enclosures(self):
 | |
|             """
 | |
|             Returns the ``django.utils.feedgenerator.Enclosure`` list for every
 | |
|             item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_enclosures = []  # Hard-coded enclosure list
 | |
| 
 | |
|         # ITEM ENCLOSURE URL -- One of these three is required if you're
 | |
|         # publishing enclosures and you're not using ``item_enclosures``. The
 | |
|         # framework looks for them in this order.
 | |
| 
 | |
|         def item_enclosure_url(self, item):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             enclosure URL.
 | |
|             """
 | |
| 
 | |
|         def item_enclosure_url(self):
 | |
|             """
 | |
|             Returns the enclosure URL for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_enclosure_url = "/foo/bar.mp3"  # Hard-coded enclosure link.
 | |
| 
 | |
|         # ITEM ENCLOSURE LENGTH -- One of these three is required if you're
 | |
|         # publishing enclosures and you're not using ``item_enclosures``. The
 | |
|         # framework looks for them in this order. In each case, the returned
 | |
|         # value should be either an integer, or a string representation of the
 | |
|         # integer, in bytes.
 | |
| 
 | |
|         def item_enclosure_length(self, item):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             enclosure length.
 | |
|             """
 | |
| 
 | |
|         def item_enclosure_length(self):
 | |
|             """
 | |
|             Returns the enclosure length for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_enclosure_length = 32000  # Hard-coded enclosure length.
 | |
| 
 | |
|         # ITEM ENCLOSURE MIME TYPE -- One of these three is required if you're
 | |
|         # publishing enclosures and you're not using ``item_enclosures``. The
 | |
|         # framework looks for them in this order.
 | |
| 
 | |
|         def item_enclosure_mime_type(self, item):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             enclosure MIME type.
 | |
|             """
 | |
| 
 | |
|         def item_enclosure_mime_type(self):
 | |
|             """
 | |
|             Returns the enclosure MIME type for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_enclosure_mime_type = "audio/mpeg"  # Hard-coded enclosure MIME type.
 | |
| 
 | |
|         # ITEM PUBDATE -- It's optional to use one of these three. This is a
 | |
|         # hook that specifies how to get the pubdate for a given item.
 | |
|         # In each case, the method/attribute should return a Python
 | |
|         # datetime.datetime object.
 | |
| 
 | |
|         def item_pubdate(self, item):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             pubdate.
 | |
|             """
 | |
| 
 | |
|         def item_pubdate(self):
 | |
|             """
 | |
|             Returns the pubdate for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_pubdate = datetime.datetime(2005, 5, 3)  # Hard-coded pubdate.
 | |
| 
 | |
|         # ITEM UPDATED -- It's optional to use one of these three. This is a
 | |
|         # hook that specifies how to get the updateddate for a given item.
 | |
|         # In each case, the method/attribute should return a Python
 | |
|         # datetime.datetime object.
 | |
| 
 | |
|         def item_updateddate(self, item):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             updateddate.
 | |
|             """
 | |
| 
 | |
|         def item_updateddate(self):
 | |
|             """
 | |
|             Returns the updateddate for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_updateddate = datetime.datetime(2005, 5, 3)  # Hard-coded updateddate.
 | |
| 
 | |
|         # ITEM CATEGORIES -- It's optional to use one of these three. This is
 | |
|         # a hook that specifies how to get the list of categories for a given
 | |
|         # item. In each case, the method/attribute should return an iterable
 | |
|         # object that returns strings.
 | |
| 
 | |
|         def item_categories(self, item):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             categories.
 | |
|             """
 | |
| 
 | |
|         def item_categories(self):
 | |
|             """
 | |
|             Returns the categories for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_categories = ["python", "django"]  # Hard-coded categories.
 | |
| 
 | |
|         # ITEM COPYRIGHT NOTICE (only applicable to Atom feeds) -- One of the
 | |
|         # following three is optional. The framework looks for them in this
 | |
|         # order.
 | |
| 
 | |
|         def item_copyright(self, obj):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             copyright notice as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def item_copyright(self):
 | |
|             """
 | |
|             Returns the copyright notice for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_copyright = "Copyright (c) 2007, Sally Smith"  # Hard-coded copyright notice.
 | |
| 
 | |
|         # ITEM COMMENTS URL -- It's optional to use one of these three. This is
 | |
|         # a hook that specifies how to get the URL of a page for comments for a
 | |
|         # given item.
 | |
| 
 | |
|         def item_comments(self, obj):
 | |
|             """
 | |
|             Takes an item, as returned by items(), and returns the item's
 | |
|             comments URL as a normal Python string.
 | |
|             """
 | |
| 
 | |
|         def item_comments(self):
 | |
|             """
 | |
|             Returns the comments URL for every item in the feed.
 | |
|             """
 | |
| 
 | |
|         item_comments = "https://www.example.com/comments"  # Hard-coded comments URL
 | |
| 
 | |
| The low-level framework
 | |
| =======================
 | |
| 
 | |
| Behind the scenes, the high-level RSS framework uses a lower-level framework
 | |
| for generating feeds' XML. This framework lives in a single module:
 | |
| :source:`django/utils/feedgenerator.py`.
 | |
| 
 | |
| You use this framework on your own, for lower-level feed generation. You can
 | |
| also create custom feed generator subclasses for use with the ``feed_type``
 | |
| ``Feed`` option.
 | |
| 
 | |
| .. currentmodule:: django.utils.feedgenerator
 | |
| 
 | |
| ``SyndicationFeed`` classes
 | |
| ---------------------------
 | |
| 
 | |
| The :mod:`~django.utils.feedgenerator` module contains a base class:
 | |
| 
 | |
| * :class:`django.utils.feedgenerator.SyndicationFeed`
 | |
| 
 | |
| and several subclasses:
 | |
| 
 | |
| * :class:`django.utils.feedgenerator.RssUserland091Feed`
 | |
| * :class:`django.utils.feedgenerator.Rss201rev2Feed`
 | |
| * :class:`django.utils.feedgenerator.Atom1Feed`
 | |
| 
 | |
| Each of these three classes knows how to render a certain type of feed as XML.
 | |
| They share this interface:
 | |
| 
 | |
| :meth:`.SyndicationFeed.__init__`
 | |
|     Initialize the feed with the given dictionary of metadata, which applies to
 | |
|     the entire feed. Required keyword arguments are:
 | |
| 
 | |
|     * ``title``
 | |
|     * ``link``
 | |
|     * ``description``
 | |
| 
 | |
|     There's also a bunch of other optional keywords:
 | |
| 
 | |
|     * ``language``
 | |
|     * ``author_email``
 | |
|     * ``author_name``
 | |
|     * ``author_link``
 | |
|     * ``subtitle``
 | |
|     * ``categories``
 | |
|     * ``feed_url``
 | |
|     * ``feed_copyright``
 | |
|     * ``feed_guid``
 | |
|     * ``ttl``
 | |
| 
 | |
|     Any extra keyword arguments you pass to ``__init__`` will be stored in
 | |
|     ``self.feed`` for use with `custom feed generators`_.
 | |
| 
 | |
|     All parameters should be strings, except ``categories``, which should be a
 | |
|     sequence of strings. Beware that some control characters
 | |
|     are `not allowed <https://www.w3.org/International/questions/qa-controls>`_
 | |
|     in XML documents. If your content has some of them, you might encounter a
 | |
|     :exc:`ValueError` when producing the feed.
 | |
| 
 | |
| :meth:`.SyndicationFeed.add_item`
 | |
|     Add an item to the feed with the given parameters.
 | |
| 
 | |
|     Required keyword arguments are:
 | |
| 
 | |
|     * ``title``
 | |
|     * ``link``
 | |
|     * ``description``
 | |
| 
 | |
|     Optional keyword arguments are:
 | |
| 
 | |
|     * ``author_email``
 | |
|     * ``author_name``
 | |
|     * ``author_link``
 | |
|     * ``pubdate``
 | |
|     * ``comments``
 | |
|     * ``unique_id``
 | |
|     * ``enclosures``
 | |
|     * ``categories``
 | |
|     * ``item_copyright``
 | |
|     * ``ttl``
 | |
|     * ``updateddate``
 | |
| 
 | |
|     Extra keyword arguments will be stored for `custom feed generators`_.
 | |
| 
 | |
|     All parameters, if given, should be strings, except:
 | |
| 
 | |
|     * ``pubdate`` should be a Python  :class:`~datetime.datetime` object.
 | |
|     * ``updateddate`` should be a Python  :class:`~datetime.datetime` object.
 | |
|     * ``enclosures`` should be a list of
 | |
|       :class:`django.utils.feedgenerator.Enclosure` instances.
 | |
|     * ``categories`` should be a sequence of strings.
 | |
| 
 | |
| :meth:`.SyndicationFeed.write`
 | |
|     Outputs the feed in the given encoding to outfile, which is a file-like object.
 | |
| 
 | |
| :meth:`.SyndicationFeed.writeString`
 | |
|     Returns the feed as a string in the given encoding.
 | |
| 
 | |
| For example, to create an Atom 1.0 feed and print it to standard output:
 | |
| 
 | |
| .. code-block:: pycon
 | |
| 
 | |
|     >>> from django.utils import feedgenerator
 | |
|     >>> from datetime import datetime
 | |
|     >>> f = feedgenerator.Atom1Feed(
 | |
|     ...     title="My Blog",
 | |
|     ...     link="https://www.example.com/",
 | |
|     ...     description="In which I write about what I ate today.",
 | |
|     ...     language="en",
 | |
|     ...     author_name="Myself",
 | |
|     ...     feed_url="https://example.com/atom.xml",
 | |
|     ... )
 | |
|     >>> f.add_item(
 | |
|     ...     title="Hot dog today",
 | |
|     ...     link="https://www.example.com/entries/1/",
 | |
|     ...     pubdate=datetime.now(),
 | |
|     ...     description="<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>",
 | |
|     ... )
 | |
|     >>> print(f.writeString("UTF-8"))
 | |
|     <?xml version="1.0" encoding="UTF-8"?>
 | |
|     <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
 | |
|     ...
 | |
|     </feed>
 | |
| 
 | |
| .. currentmodule:: django.contrib.syndication
 | |
| 
 | |
| Custom feed generators
 | |
| ----------------------
 | |
| 
 | |
| If you need to produce a custom feed format, you've got a couple of options.
 | |
| 
 | |
| If the feed format is totally custom, you'll want to subclass
 | |
| ``SyndicationFeed`` and completely replace the ``write()`` and
 | |
| ``writeString()`` methods.
 | |
| 
 | |
| However, if the feed format is a spin-off of RSS or Atom (i.e. GeoRSS_, Apple's
 | |
| `iTunes podcast format`_, etc.), you've got a better choice. These types of
 | |
| feeds typically add extra elements and/or attributes to the underlying format,
 | |
| and there are a set of methods that ``SyndicationFeed`` calls to get these extra
 | |
| attributes. Thus, you can subclass the appropriate feed generator class
 | |
| (``Atom1Feed`` or ``Rss201rev2Feed``) and extend these callbacks. They are:
 | |
| 
 | |
| .. _georss: https://georss.org
 | |
| .. _itunes podcast format: https://help.apple.com/itc/podcasts_connect/#/itcb54353390
 | |
| 
 | |
| ``SyndicationFeed.root_attributes(self)``
 | |
|     Return a ``dict`` of attributes to add to the root feed element
 | |
|     (``feed``/``channel``).
 | |
| 
 | |
| ``SyndicationFeed.add_root_elements(self, handler)``
 | |
|     Callback to add elements inside the root feed element
 | |
|     (``feed``/``channel``). ``handler`` is an
 | |
|     :class:`~xml.sax.saxutils.XMLGenerator` from Python's built-in SAX library;
 | |
|     you'll call methods on it to add to the XML document in process.
 | |
| 
 | |
| ``SyndicationFeed.item_attributes(self, item)``
 | |
|     Return a ``dict`` of attributes to add to each item (``item``/``entry``)
 | |
|     element. The argument, ``item``, is a dictionary of all the data passed to
 | |
|     ``SyndicationFeed.add_item()``.
 | |
| 
 | |
| ``SyndicationFeed.add_item_elements(self, handler, item)``
 | |
|     Callback to add elements to each item (``item``/``entry``) element.
 | |
|     ``handler`` and ``item`` are as above.
 | |
| 
 | |
| .. warning::
 | |
| 
 | |
|     If you override any of these methods, be sure to call the superclass methods
 | |
|     since they add the required elements for each feed format.
 | |
| 
 | |
| For example, you might start implementing an iTunes RSS feed generator like so::
 | |
| 
 | |
|     class iTunesFeed(Rss201rev2Feed):
 | |
|         def root_attributes(self):
 | |
|             attrs = super().root_attributes()
 | |
|             attrs["xmlns:itunes"] = "http://www.itunes.com/dtds/podcast-1.0.dtd"
 | |
|             return attrs
 | |
| 
 | |
|         def add_root_elements(self, handler):
 | |
|             super().add_root_elements(handler)
 | |
|             handler.addQuickElement("itunes:explicit", "clean")
 | |
| 
 | |
| There's a lot more work to be done for a complete custom feed class, but the
 | |
| above example should demonstrate the basic idea.
 |