From e3ad0b4b28aca417a136f5ed1fa9a90ee9d78a54 Mon Sep 17 00:00:00 2001 From: Stephen Burrows Date: Thu, 2 Jun 2011 17:35:59 -0400 Subject: [PATCH] Initial implementation of a separate syndication contrib app based on penfield's FeedView, updated to use a registry instead of a dict. --- philo/contrib/julian/models.py | 9 +- philo/contrib/penfield/models.py | 316 +----------------- philo/contrib/syndication/__init__.py | 0 .../{penfield => syndication}/exceptions.py | 0 philo/contrib/syndication/feeds.py | 13 + .../{penfield => syndication}/middleware.py | 0 philo/contrib/syndication/models.py | 309 +++++++++++++++++ 7 files changed, 328 insertions(+), 319 deletions(-) create mode 100644 philo/contrib/syndication/__init__.py rename philo/contrib/{penfield => syndication}/exceptions.py (100%) create mode 100644 philo/contrib/syndication/feeds.py rename philo/contrib/{penfield => syndication}/middleware.py (100%) create mode 100644 philo/contrib/syndication/models.py diff --git a/philo/contrib/julian/models.py b/philo/contrib/julian/models.py index 62b938a..33c6e2a 100644 --- a/philo/contrib/julian/models.py +++ b/philo/contrib/julian/models.py @@ -15,7 +15,8 @@ from django.http import HttpResponse, Http404 from django.utils.encoding import force_unicode from philo.contrib.julian.feedgenerator import ICalendarFeed -from philo.contrib.penfield.models import FeedView, FEEDS +from philo.contrib.syndication.models import FeedView +from philo.contrib.syndication.feeds import registry from philo.exceptions import ViewCanNotProvideSubpath from philo.models import Tag, Entity, Page from philo.models.fields import TemplateField @@ -25,8 +26,7 @@ from philo.utils import ContentTypeRegistryLimiter __all__ = ('register_location_model', 'unregister_location_model', 'Location', 'TimedModel', 'Event', 'Calendar', 'CalendarView',) -ICALENDAR = ICalendarFeed.mime_type -FEEDS[ICALENDAR] = ICalendarFeed +registry.register(ICalendarFeed, verbose_name="iCalendar") try: DEFAULT_SITE = Site.objects.get_current() except: @@ -461,5 +461,4 @@ class CalendarView(FeedView): return u"%s for %s" % (self.__class__.__name__, self.calendar) field = CalendarView._meta.get_field('feed_type') -field._choices += ((ICALENDAR, 'iCalendar'),) -field.default = ICALENDAR \ No newline at end of file +field.default = registry.get_slug(ICalendarFeed, field.default) \ No newline at end of file diff --git a/philo/contrib/penfield/models.py b/philo/contrib/penfield/models.py index 2a145fb..e31de9f 100644 --- a/philo/contrib/penfield/models.py +++ b/philo/contrib/penfield/models.py @@ -2,327 +2,15 @@ from datetime import date, datetime from django.conf import settings from django.conf.urls.defaults import url, patterns, include -from django.contrib.sites.models import Site, RequestSite -from django.contrib.syndication.views import add_domain from django.db import models from django.http import Http404, HttpResponse -from django.template import RequestContext, Template as DjangoTemplate -from django.utils import feedgenerator, tzinfo -from django.utils.datastructures import SortedDict -from django.utils.encoding import smart_unicode, force_unicode -from django.utils.html import escape -from philo.contrib.penfield.exceptions import HttpNotAcceptable -from philo.contrib.penfield.middleware import http_not_acceptable +from philo.contrib.syndication.models import FeedView from philo.exceptions import ViewCanNotProvideSubpath -from philo.models import Tag, Entity, MultiView, Page, register_value_model, Template +from philo.models import Tag, Entity, Page, register_value_model from philo.models.fields import TemplateField from philo.utils import paginate -try: - import mimeparse -except: - mimeparse = None - - -ATOM = feedgenerator.Atom1Feed.mime_type -RSS = feedgenerator.Rss201rev2Feed.mime_type -FEEDS = SortedDict([ - (ATOM, feedgenerator.Atom1Feed), - (RSS, feedgenerator.Rss201rev2Feed), -]) -FEED_CHOICES = ( - (ATOM, "Atom"), - (RSS, "RSS"), -) - - -class FeedView(MultiView): - """ - :class:`FeedView` handles a number of pages and related feeds for a single object such as a blog or newsletter. In addition to all other methods and attributes, :class:`FeedView` supports the same generic API as `django.contrib.syndication.views.Feed `_. - - """ - #: The type of feed which should be served by the :class:`FeedView`. - feed_type = models.CharField(max_length=50, choices=FEED_CHOICES, default=ATOM) - #: The suffix which will be appended to a page URL for a feed of its items. Default: "feed" - feed_suffix = models.CharField(max_length=255, blank=False, default="feed") - #: A :class:`BooleanField` - whether or not feeds are enabled. - feeds_enabled = models.BooleanField(default=True) - #: A :class:`PositiveIntegerField` - the maximum number of items to return for this feed. All items will be returned if this field is blank. Default: 15. - feed_length = models.PositiveIntegerField(blank=True, null=True, default=15, help_text="The maximum number of items to return for this feed. All items will be returned if this field is blank.") - - #: A :class:`ForeignKey` to a :class:`.Template` which will be used to render the title of each item in the feed if provided. - item_title_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_title_related") - #: A :class:`ForeignKey` to a :class:`.Template` which will be used to render the description of each item in the feed if provided. - item_description_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_description_related") - - #: The name of the context variable to be populated with the items managed by the :class:`FeedView`. - item_context_var = 'items' - #: The attribute on a subclass of :class:`FeedView` which will contain the main object of a feed (such as a :class:`Blog`.) - object_attr = 'object' - - #: A description of the feeds served by the :class:`FeedView`. This is a required part of the :class:`django.contrib.syndication.view.Feed` API. - description = "" - - def feed_patterns(self, base, get_items_attr, page_attr, reverse_name): - """ - Given the name to be used to reverse this view and the names of the attributes for the function that fetches the objects, returns patterns suitable for inclusion in urlpatterns. - - :param base: The base of the returned patterns - that is, the subpath pattern which will reference the page for the items. The :attr:`feed_suffix` will be appended to this subpath. - :param get_items_attr: A callable or the name of a callable on the :class:`FeedView` which will return an (``items``, ``extra_context``) tuple. This will be passed directly to :meth:`feed_view` and :meth:`page_view`. - :param page_attr: A :class:`.Page` instance or the name of an attribute on the :class:`FeedView` which contains a :class:`.Page` instance. This will be passed directly to :meth:`page_view` and will be rendered with the items from ``get_items_attr``. - :param reverse_name: The string which is considered the "name" of the view function returned by :meth:`page_view` for the given parameters. - :returns: Patterns suitable for use in urlpatterns. - - Example:: - - @property - def urlpatterns(self): - urlpatterns = self.feed_patterns(r'^', 'get_all_entries', 'index_page', 'index') - urlpatterns += self.feed_patterns(r'^(?P\d{4})/(?P\d{2})/(?P\d{2})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_day') - return urlpatterns - - """ - urlpatterns = patterns('') - if self.feeds_enabled: - feed_reverse_name = "%s_feed" % reverse_name - feed_view = http_not_acceptable(self.feed_view(get_items_attr, feed_reverse_name)) - feed_pattern = r'%s%s%s$' % (base, (base and base[-1] != "^") and "/" or "", self.feed_suffix) - urlpatterns += patterns('', - url(feed_pattern, feed_view, name=feed_reverse_name), - ) - urlpatterns += patterns('', - url(r"%s$" % base, self.page_view(get_items_attr, page_attr), name=reverse_name) - ) - return urlpatterns - - def get_object(self, request, **kwargs): - """By default, returns the object stored in the attribute named by :attr:`object_attr`. This can be overridden for subclasses that publish different data for different URL parameters. It is part of the :class:`django.contrib.syndication.views.Feed` API.""" - return getattr(self, self.object_attr) - - def feed_view(self, get_items_attr, reverse_name): - """ - Returns a view function that renders a list of items as a feed. - - :param get_items_attr: A callable or the name of a callable on the :class:`FeedView` that will return a (items, extra_context) tuple when called with view arguments. - :param reverse_name: The name which can be used reverse this feed using the :class:`FeedView` as the urlconf. - - :returns: A view function that renders a list of items as a feed. - - """ - get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr) - - def inner(request, extra_context=None, *args, **kwargs): - obj = self.get_object(request, *args, **kwargs) - feed = self.get_feed(obj, request, reverse_name) - items, xxx = get_items(request, extra_context=extra_context, *args, **kwargs) - self.populate_feed(feed, items, request) - - response = HttpResponse(mimetype=feed.mime_type) - feed.write(response, 'utf-8') - return response - - return inner - - def page_view(self, get_items_attr, page_attr): - """ - :param get_items_attr: A callable or the name of a callable on the :class:`FeedView` that will return a (items, extra_context) tuple when called with view arguments. - :param page_attr: A :class:`.Page` instance or the name of an attribute on the :class:`FeedView` which contains a :class:`.Page` instance. This will be rendered with the items from ``get_items_attr``. - - :returns: A view function that renders a list of items as an :class:`HttpResponse`. - - """ - get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr) - page = isinstance(page_attr, Page) and page_attr or getattr(self, page_attr) - - def inner(request, extra_context=None, *args, **kwargs): - items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs) - items, item_context = self.process_page_items(request, items) - - context = self.get_context() - context.update(extra_context or {}) - context.update(item_context or {}) - - return page.render_to_response(request, extra_context=context) - return inner - - def process_page_items(self, request, items): - """ - Hook for handling any extra processing of ``items`` based on an :class:`HttpRequest`, such as pagination or searching. This method is expected to return a list of items and a dictionary to be added to the page context. - - """ - item_context = { - self.item_context_var: items - } - return items, item_context - - def get_feed_type(self, request): - """ - Intelligently chooses a feed type for a given request. Tries to return :attr:`feed_type`, but if the Accept header does not include that mimetype, tries to return the best match from the feed types that are offered by the :class:`FeedView`. If none of the offered feed types are accepted by the :class:`HttpRequest`, then this method will raise :exc:`philo.contrib.penfield.exceptions.HttpNotAcceptable`. - - """ - feed_type = self.feed_type - if feed_type not in FEEDS: - feed_type = FEEDS.keys()[0] - accept = request.META.get('HTTP_ACCEPT') - if accept and feed_type not in accept and "*/*" not in accept and "%s/*" % feed_type.split("/")[0] not in accept: - # Wups! They aren't accepting the chosen format. Is there another format we can use? - if mimeparse: - feed_type = mimeparse.best_match(FEEDS.keys(), accept) - else: - for feed_type in FEEDS.keys(): - if feed_type in accept or "%s/*" % feed_type.split("/")[0] in accept: - break - else: - feed_type = None - if not feed_type: - raise HttpNotAcceptable - return FEEDS[feed_type] - - def get_feed(self, obj, request, reverse_name): - """ - Returns an unpopulated :class:`django.utils.feedgenerator.DefaultFeed` object for this object. - - """ - try: - current_site = Site.objects.get_current() - except Site.DoesNotExist: - current_site = RequestSite(request) - - feed_type = self.get_feed_type(request) - node = request.node - link = node.get_absolute_url(with_domain=True, request=request, secure=request.is_secure()) - - feed = feed_type( - title = self.__get_dynamic_attr('title', obj), - subtitle = self.__get_dynamic_attr('subtitle', obj), - link = link, - description = self.__get_dynamic_attr('description', obj), - language = settings.LANGUAGE_CODE.decode(), - feed_url = add_domain( - current_site.domain, - self.__get_dynamic_attr('feed_url', obj) or node.construct_url(node._subpath, with_domain=True, request=request, secure=request.is_secure()), - request.is_secure() - ), - author_name = self.__get_dynamic_attr('author_name', obj), - author_link = self.__get_dynamic_attr('author_link', obj), - author_email = self.__get_dynamic_attr('author_email', obj), - categories = self.__get_dynamic_attr('categories', obj), - feed_copyright = self.__get_dynamic_attr('feed_copyright', obj), - feed_guid = self.__get_dynamic_attr('feed_guid', obj), - ttl = self.__get_dynamic_attr('ttl', obj), - **self.feed_extra_kwargs(obj) - ) - return feed - - def populate_feed(self, feed, items, request): - """Populates a :class:`django.utils.feedgenerator.DefaultFeed` instance as is returned by :meth:`get_feed` with the passed-in ``items``.""" - if self.item_title_template: - title_template = DjangoTemplate(self.item_title_template.code) - else: - title_template = None - if self.item_description_template: - description_template = DjangoTemplate(self.item_description_template.code) - else: - description_template = None - - node = request.node - try: - current_site = Site.objects.get_current() - except Site.DoesNotExist: - current_site = RequestSite(request) - - if self.feed_length is not None: - items = items[:self.feed_length] - - for item in items: - if title_template is not None: - title = title_template.render(RequestContext(request, {'obj': item})) - else: - title = self.__get_dynamic_attr('item_title', item) - if description_template is not None: - description = description_template.render(RequestContext(request, {'obj': item})) - else: - description = self.__get_dynamic_attr('item_description', item) - - link = node.construct_url(self.reverse(obj=item), with_domain=True, request=request, secure=request.is_secure()) - - enc = None - enc_url = self.__get_dynamic_attr('item_enclosure_url', item) - if enc_url: - enc = feedgenerator.Enclosure( - url = smart_unicode(add_domain( - current_site.domain, - enc_url, - request.is_secure() - )), - length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)), - mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item)) - ) - author_name = self.__get_dynamic_attr('item_author_name', item) - if author_name is not None: - author_email = self.__get_dynamic_attr('item_author_email', item) - author_link = self.__get_dynamic_attr('item_author_link', item) - else: - author_email = author_link = None - - pubdate = self.__get_dynamic_attr('item_pubdate', item) - if pubdate and not pubdate.tzinfo: - ltz = tzinfo.LocalTimezone(pubdate) - pubdate = pubdate.replace(tzinfo=ltz) - - feed.add_item( - title = title, - link = link, - description = description, - unique_id = self.__get_dynamic_attr('item_guid', item, link), - enclosure = enc, - pubdate = pubdate, - author_name = author_name, - author_email = author_email, - author_link = author_link, - categories = self.__get_dynamic_attr('item_categories', item), - item_copyright = self.__get_dynamic_attr('item_copyright', item), - **self.item_extra_kwargs(item) - ) - - def __get_dynamic_attr(self, attname, obj, default=None): - try: - attr = getattr(self, attname) - except AttributeError: - return default - if callable(attr): - # Check func_code.co_argcount rather than try/excepting the - # function and catching the TypeError, because something inside - # the function may raise the TypeError. This technique is more - # accurate. - if hasattr(attr, 'func_code'): - argcount = attr.func_code.co_argcount - else: - argcount = attr.__call__.func_code.co_argcount - if argcount == 2: # one argument is 'self' - return attr(obj) - else: - 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 item_title(self, item): - return escape(force_unicode(item)) - - def item_description(self, item): - return force_unicode(item) - - class Meta: - abstract=True - class Blog(Entity): """Represents a blog which can be posted to.""" diff --git a/philo/contrib/syndication/__init__.py b/philo/contrib/syndication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/philo/contrib/penfield/exceptions.py b/philo/contrib/syndication/exceptions.py similarity index 100% rename from philo/contrib/penfield/exceptions.py rename to philo/contrib/syndication/exceptions.py diff --git a/philo/contrib/syndication/feeds.py b/philo/contrib/syndication/feeds.py new file mode 100644 index 0000000..0554591 --- /dev/null +++ b/philo/contrib/syndication/feeds.py @@ -0,0 +1,13 @@ +from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed + +from philo.utils.registry import Registry + + +DEFAULT_FEED = Atom1Feed + + +registry = Registry() + + +registry.register(Atom1Feed, verbose_name='Atom') +registry.register(Rss201rev2Feed, verbose_name='RSS') \ No newline at end of file diff --git a/philo/contrib/penfield/middleware.py b/philo/contrib/syndication/middleware.py similarity index 100% rename from philo/contrib/penfield/middleware.py rename to philo/contrib/syndication/middleware.py diff --git a/philo/contrib/syndication/models.py b/philo/contrib/syndication/models.py new file mode 100644 index 0000000..aea2077 --- /dev/null +++ b/philo/contrib/syndication/models.py @@ -0,0 +1,309 @@ +from django.conf import settings +from django.conf.urls.defaults import url, patterns, include +from django.contrib.sites.models import Site, RequestSite +from django.contrib.syndication.views import add_domain +from django.db import models +from django.http import HttpResponse +from django.template import RequestContext, Template as DjangoTemplate +from django.utils import feedgenerator, tzinfo +from django.utils.encoding import smart_unicode, force_unicode +from django.utils.html import escape + +from philo.contrib.syndication.exceptions import HttpNotAcceptable +from philo.contrib.syndication.feeds import registry, DEFAULT_FEED +from philo.contrib.syndication.middleware import http_not_acceptable +from philo.models import Page, Template, MultiView + +try: + import mimeparse +except: + mimeparse = None + + +class FeedView(MultiView): + """ + :class:`FeedView` handles a number of pages and related feeds for a single object such as a blog or newsletter. In addition to all other methods and attributes, :class:`FeedView` supports the same generic API as `django.contrib.syndication.views.Feed `_. + + """ + #: The type of feed which should be served by the :class:`FeedView`. + feed_type = models.CharField(max_length=50, choices=registry.choices, default=registry.get_slug(DEFAULT_FEED)) + #: The suffix which will be appended to a page URL for a feed of its items. Default: "feed" + feed_suffix = models.CharField(max_length=255, blank=False, default="feed") + #: A :class:`BooleanField` - whether or not feeds are enabled. + feeds_enabled = models.BooleanField(default=True) + #: A :class:`PositiveIntegerField` - the maximum number of items to return for this feed. All items will be returned if this field is blank. Default: 15. + feed_length = models.PositiveIntegerField(blank=True, null=True, default=15, help_text="The maximum number of items to return for this feed. All items will be returned if this field is blank.") + + #: A :class:`ForeignKey` to a :class:`.Template` which will be used to render the title of each item in the feed if provided. + item_title_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_title_related") + #: A :class:`ForeignKey` to a :class:`.Template` which will be used to render the description of each item in the feed if provided. + item_description_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_description_related") + + #: The name of the context variable to be populated with the items managed by the :class:`FeedView`. + item_context_var = 'items' + #: The attribute on a subclass of :class:`FeedView` which will contain the main object of a feed (such as a :class:`Blog`.) + object_attr = 'object' + + #: A description of the feeds served by the :class:`FeedView`. This is a required part of the :class:`django.contrib.syndication.view.Feed` API. + description = "" + + def feed_patterns(self, base, get_items_attr, page_attr, reverse_name): + """ + Given the name to be used to reverse this view and the names of the attributes for the function that fetches the objects, returns patterns suitable for inclusion in urlpatterns. + + :param base: The base of the returned patterns - that is, the subpath pattern which will reference the page for the items. The :attr:`feed_suffix` will be appended to this subpath. + :param get_items_attr: A callable or the name of a callable on the :class:`FeedView` which will return an (``items``, ``extra_context``) tuple. This will be passed directly to :meth:`feed_view` and :meth:`page_view`. + :param page_attr: A :class:`.Page` instance or the name of an attribute on the :class:`FeedView` which contains a :class:`.Page` instance. This will be passed directly to :meth:`page_view` and will be rendered with the items from ``get_items_attr``. + :param reverse_name: The string which is considered the "name" of the view function returned by :meth:`page_view` for the given parameters. + :returns: Patterns suitable for use in urlpatterns. + + Example:: + + @property + def urlpatterns(self): + urlpatterns = self.feed_patterns(r'^', 'get_all_entries', 'index_page', 'index') + urlpatterns += self.feed_patterns(r'^(?P\d{4})/(?P\d{2})/(?P\d{2})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_day') + return urlpatterns + + """ + urlpatterns = patterns('') + if self.feeds_enabled: + feed_reverse_name = "%s_feed" % reverse_name + feed_view = http_not_acceptable(self.feed_view(get_items_attr, feed_reverse_name)) + feed_pattern = r'%s%s%s$' % (base, (base and base[-1] != "^") and "/" or "", self.feed_suffix) + urlpatterns += patterns('', + url(feed_pattern, feed_view, name=feed_reverse_name), + ) + urlpatterns += patterns('', + url(r"%s$" % base, self.page_view(get_items_attr, page_attr), name=reverse_name) + ) + return urlpatterns + + def get_object(self, request, **kwargs): + """By default, returns the object stored in the attribute named by :attr:`object_attr`. This can be overridden for subclasses that publish different data for different URL parameters. It is part of the :class:`django.contrib.syndication.views.Feed` API.""" + return getattr(self, self.object_attr) + + def feed_view(self, get_items_attr, reverse_name): + """ + Returns a view function that renders a list of items as a feed. + + :param get_items_attr: A callable or the name of a callable on the :class:`FeedView` that will return a (items, extra_context) tuple when called with view arguments. + :param reverse_name: The name which can be used reverse this feed using the :class:`FeedView` as the urlconf. + + :returns: A view function that renders a list of items as a feed. + + """ + get_items = get_items_attr if callable(get_items_attr) else getattr(self, get_items_attr) + + def inner(request, extra_context=None, *args, **kwargs): + obj = self.get_object(request, *args, **kwargs) + feed = self.get_feed(obj, request, reverse_name) + items, xxx = get_items(request, extra_context=extra_context, *args, **kwargs) + self.populate_feed(feed, items, request) + + response = HttpResponse(mimetype=feed.mime_type) + feed.write(response, 'utf-8') + return response + + return inner + + def page_view(self, get_items_attr, page_attr): + """ + :param get_items_attr: A callable or the name of a callable on the :class:`FeedView` that will return a (items, extra_context) tuple when called with view arguments. + :param page_attr: A :class:`.Page` instance or the name of an attribute on the :class:`FeedView` which contains a :class:`.Page` instance. This will be rendered with the items from ``get_items_attr``. + + :returns: A view function that renders a list of items as an :class:`HttpResponse`. + + """ + get_items = get_items_attr if callable(get_items_attr) else getattr(self, get_items_attr) + page = page_attr if isinstance(page_attr, Page) else getattr(self, page_attr) + + def inner(request, extra_context=None, *args, **kwargs): + items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs) + items, item_context = self.process_page_items(request, items) + + context = self.get_context() + context.update(extra_context or {}) + context.update(item_context or {}) + + return page.render_to_response(request, extra_context=context) + return inner + + def process_page_items(self, request, items): + """ + Hook for handling any extra processing of ``items`` based on an :class:`HttpRequest`, such as pagination or searching. This method is expected to return a list of items and a dictionary to be added to the page context. + + """ + item_context = { + self.item_context_var: items + } + return items, item_context + + def get_feed_type(self, request): + """ + Intelligently chooses a feed type for a given request. Tries to return :attr:`feed_type`, but if the Accept header does not include that mimetype, tries to return the best match from the feed types that are offered by the :class:`FeedView`. If none of the offered feed types are accepted by the :class:`HttpRequest`, then this method will raise :exc:`philo.contrib.penfield.exceptions.HttpNotAcceptable`. + + """ + feed_type = registry.get(self.feed_type, DEFAULT_FEED) + mt = feed_type.mime_type + accept = request.META.get('HTTP_ACCEPT') + if accept and mt not in accept and "*/*" not in accept and "%s/*" % mt.split("/")[0] not in accept: + # Wups! They aren't accepting the chosen format. Is there another format we can use? + feed_type = None + accepted_mts = dict([(obj.mime_type, obj) for obj in registry.values()]) + if mimeparse: + mt = mimeparse.best_match(accepted_mts.keys(), accept) + if mt: + feed_type = accepted_mts[mt] + else: + for mt in accepted_mts: + if mt in accept or "%s/*" % mt.split("/")[0] in accept: + feed_type = accepted_mts[mt] + break + if not feed_type: + raise HttpNotAcceptable + return feed_type + + def get_feed(self, obj, request, reverse_name): + """ + Returns an unpopulated :class:`django.utils.feedgenerator.DefaultFeed` object for this object. + + """ + try: + current_site = Site.objects.get_current() + except Site.DoesNotExist: + current_site = RequestSite(request) + + feed_type = self.get_feed_type(request) + node = request.node + link = node.get_absolute_url(with_domain=True, request=request, secure=request.is_secure()) + + feed = feed_type( + title = self.__get_dynamic_attr('title', obj), + subtitle = self.__get_dynamic_attr('subtitle', obj), + link = link, + description = self.__get_dynamic_attr('description', obj), + language = settings.LANGUAGE_CODE.decode(), + feed_url = add_domain( + current_site.domain, + self.__get_dynamic_attr('feed_url', obj) or node.construct_url(node._subpath, with_domain=True, request=request, secure=request.is_secure()), + request.is_secure() + ), + author_name = self.__get_dynamic_attr('author_name', obj), + author_link = self.__get_dynamic_attr('author_link', obj), + author_email = self.__get_dynamic_attr('author_email', obj), + categories = self.__get_dynamic_attr('categories', obj), + feed_copyright = self.__get_dynamic_attr('feed_copyright', obj), + feed_guid = self.__get_dynamic_attr('feed_guid', obj), + ttl = self.__get_dynamic_attr('ttl', obj), + **self.feed_extra_kwargs(obj) + ) + return feed + + def populate_feed(self, feed, items, request): + """Populates a :class:`django.utils.feedgenerator.DefaultFeed` instance as is returned by :meth:`get_feed` with the passed-in ``items``.""" + if self.item_title_template: + title_template = DjangoTemplate(self.item_title_template.code) + else: + title_template = None + if self.item_description_template: + description_template = DjangoTemplate(self.item_description_template.code) + else: + description_template = None + + node = request.node + try: + current_site = Site.objects.get_current() + except Site.DoesNotExist: + current_site = RequestSite(request) + + if self.feed_length is not None: + items = items[:self.feed_length] + + for item in items: + if title_template is not None: + title = title_template.render(RequestContext(request, {'obj': item})) + else: + title = self.__get_dynamic_attr('item_title', item) + if description_template is not None: + description = description_template.render(RequestContext(request, {'obj': item})) + else: + description = self.__get_dynamic_attr('item_description', item) + + link = node.construct_url(self.reverse(obj=item), with_domain=True, request=request, secure=request.is_secure()) + + enc = None + enc_url = self.__get_dynamic_attr('item_enclosure_url', item) + if enc_url: + enc = feedgenerator.Enclosure( + url = smart_unicode(add_domain( + current_site.domain, + enc_url, + request.is_secure() + )), + length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)), + mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item)) + ) + author_name = self.__get_dynamic_attr('item_author_name', item) + if author_name is not None: + author_email = self.__get_dynamic_attr('item_author_email', item) + author_link = self.__get_dynamic_attr('item_author_link', item) + else: + author_email = author_link = None + + pubdate = self.__get_dynamic_attr('item_pubdate', item) + if pubdate and not pubdate.tzinfo: + ltz = tzinfo.LocalTimezone(pubdate) + pubdate = pubdate.replace(tzinfo=ltz) + + feed.add_item( + title = title, + link = link, + description = description, + unique_id = self.__get_dynamic_attr('item_guid', item, link), + enclosure = enc, + pubdate = pubdate, + author_name = author_name, + author_email = author_email, + author_link = author_link, + categories = self.__get_dynamic_attr('item_categories', item), + item_copyright = self.__get_dynamic_attr('item_copyright', item), + **self.item_extra_kwargs(item) + ) + + def __get_dynamic_attr(self, attname, obj, default=None): + try: + attr = getattr(self, attname) + except AttributeError: + return default + if callable(attr): + # Check func_code.co_argcount rather than try/excepting the + # function and catching the TypeError, because something inside + # the function may raise the TypeError. This technique is more + # accurate. + if hasattr(attr, 'func_code'): + argcount = attr.func_code.co_argcount + else: + argcount = attr.__call__.func_code.co_argcount + if argcount == 2: # one argument is 'self' + return attr(obj) + else: + 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 item_title(self, item): + return escape(force_unicode(item)) + + def item_description(self, item): + return force_unicode(item) + + class Meta: + abstract=True \ No newline at end of file -- 2.20.1