From 1ff9f3bdbf09e3a7d2a478472ddd5ad73af1d45e Mon Sep 17 00:00:00 2001 From: Stephen Burrows Date: Fri, 3 Jun 2011 10:44:05 -0400 Subject: [PATCH 1/1] Improved penfield docstrings. Added urlpatterns and support for all registered feed types. Resolves issue #141. --- philo/contrib/penfield/models.py | 24 +++++------ philo/contrib/syndication/models.py | 65 ++++++++++++++++++----------- 2 files changed, 53 insertions(+), 36 deletions(-) diff --git a/philo/contrib/penfield/models.py b/philo/contrib/penfield/models.py index e31de9f..c22dcfc 100644 --- a/philo/contrib/penfield/models.py +++ b/philo/contrib/penfield/models.py @@ -83,7 +83,7 @@ register_value_model(BlogEntry) class BlogView(FeedView): """ - A subclass of :class:`FeedView` which handles patterns and feeds for a :class:`Blog` and its related :class:`entries `. + A subclass of :class:`.FeedView` which handles patterns and feeds for a :class:`Blog` and its related :class:`entries `. """ ENTRY_PERMALINK_STYLE_CHOICES = ( @@ -208,11 +208,11 @@ class BlogView(FeedView): return self.blog.entry_tags def get_all_entries(self, request, extra_context=None): - """Used to generate :meth:`~FeedView.feed_patterns` for all entries.""" + """Used to generate :meth:`~.FeedView.feed_patterns` for all entries.""" return self.get_entry_queryset(), extra_context def get_entries_by_ymd(self, request, year=None, month=None, day=None, extra_context=None): - """Used to generate :meth:`~FeedView.feed_patterns` for entries with a specific year, month, and day.""" + """Used to generate :meth:`~.FeedView.feed_patterns` for entries with a specific year, month, and day.""" if not self.entry_archive_page: raise Http404 entries = self.get_entry_queryset() @@ -228,7 +228,7 @@ class BlogView(FeedView): return entries, context def get_entries_by_tag(self, request, tag_slugs, extra_context=None): - """Used to generate :meth:`~FeedView.feed_patterns` for entries with all of the given tags.""" + """Used to generate :meth:`~.FeedView.feed_patterns` for entries with all of the given tags.""" tag_slugs = tag_slugs.replace('+', '/').split('/') tags = self.get_tag_queryset().filter(slug__in=tag_slugs) @@ -279,13 +279,13 @@ class BlogView(FeedView): }) return self.tag_archive_page.render_to_response(request, extra_context=context) - def feed_view(self, get_items_attr, reverse_name): - """Overrides :meth:`FeedView.feed_view` to add :class:`.Tag`\ s to the feed as categories.""" + def feed_view(self, get_items_attr, reverse_name, feed_type=None): + """Overrides :meth:`.FeedView.feed_view` to add :class:`.Tag`\ s to the feed as categories.""" 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) + feed = self.get_feed(obj, request, reverse_name, feed_type) items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs) self.populate_feed(feed, items, request) @@ -304,7 +304,7 @@ class BlogView(FeedView): return inner def process_page_items(self, request, items): - """Overrides :meth:`FeedView.process_page_items` to add pagination.""" + """Overrides :meth:`.FeedView.process_page_items` to add pagination.""" if self.entries_per_page: page_num = request.GET.get('page', 1) paginator, paginated_page, items = paginate(items, self.entries_per_page, page_num) @@ -413,7 +413,7 @@ register_value_model(NewsletterIssue) class NewsletterView(FeedView): - """A subclass of :class:`FeedView` which handles patterns and feeds for a :class:`Newsletter` and its related :class:`articles `.""" + """A subclass of :class:`.FeedView` which handles patterns and feeds for a :class:`Newsletter` and its related :class:`articles `.""" ARTICLE_PERMALINK_STYLE_CHOICES = ( ('D', 'Year, month, and day'), ('M', 'Year and month'), @@ -527,11 +527,11 @@ class NewsletterView(FeedView): return self.newsletter.issues.all() def get_all_articles(self, request, extra_context=None): - """Used to generate :meth:`FeedView.feed_patterns` for all entries.""" + """Used to generate :meth:`~.FeedView.feed_patterns` for all entries.""" return self.get_article_queryset(), extra_context def get_articles_by_ymd(self, request, year, month=None, day=None, extra_context=None): - """Used to generate :meth:`FeedView.feed_patterns` for a specific year, month, and day.""" + """Used to generate :meth:`~.FeedView.feed_patterns` for a specific year, month, and day.""" articles = self.get_article_queryset().filter(date__year=year) if month: articles = articles.filter(date__month=month) @@ -540,7 +540,7 @@ class NewsletterView(FeedView): return articles, extra_context def get_articles_by_issue(self, request, numbering, extra_context=None): - """Used to generate :meth:`FeedView.feed_patterns` for articles from a certain issue.""" + """Used to generate :meth:`~.FeedView.feed_patterns` for articles from a certain issue.""" try: issue = self.get_issue_queryset().get(numbering=numbering) except NewsletterIssue.DoesNotExist: diff --git a/philo/contrib/syndication/models.py b/philo/contrib/syndication/models.py index aea2077..d08a715 100644 --- a/philo/contrib/syndication/models.py +++ b/philo/contrib/syndication/models.py @@ -49,7 +49,7 @@ class FeedView(MultiView): 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. + 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. In addition to ``base`` and ``base`` + :attr:`feed_suffix`, patterns will be provided for each registered feed type as ``base`` + ``slug``. :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`. @@ -68,12 +68,14 @@ class FeedView(MultiView): """ 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), - ) + suffixes = [(self.feed_suffix, None)] + [(slug, slug) for slug in registry] + for suffix, feed_type in suffixes: + feed_reverse_name = "%s_%s" % (reverse_name, suffix) + feed_view = http_not_acceptable(self.feed_view(get_items_attr, feed_reverse_name, feed_type)) + feed_pattern = r'%s%s%s$' % (base, "/" if base and base[-1] != "^" else "", 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) ) @@ -83,12 +85,13 @@ class FeedView(MultiView): """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): + def feed_view(self, get_items_attr, reverse_name, feed_type=None): """ 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. + :param feed_type: The slug used to render the feed class which will be used by the returned view function. :returns: A view function that renders a list of items as a feed. @@ -97,7 +100,7 @@ class FeedView(MultiView): def inner(request, extra_context=None, *args, **kwargs): obj = self.get_object(request, *args, **kwargs) - feed = self.get_feed(obj, request, reverse_name) + feed = self.get_feed(obj, request, reverse_name, feed_type) items, xxx = get_items(request, extra_context=extra_context, *args, **kwargs) self.populate_feed(feed, items, request) @@ -139,42 +142,56 @@ class FeedView(MultiView): } return items, item_context - def get_feed_type(self, request): + def get_feed_type(self, request, feed_type=None): """ - 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`. + If ``feed_type`` is not ``None``, returns the corresponding class from the registry or reises :exc:`.HttpNotAcceptable`. Otherwise, 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`, raises :exc:`.HttpNotAcceptable`. """ - feed_type = registry.get(self.feed_type, DEFAULT_FEED) + if feed_type is not None: + feed_type = registry[feed_type] + loose = False + else: + feed_type = registry.get(self.feed_type, DEFAULT_FEED) + loose = True 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? + # Wups! They aren't accepting the chosen format. 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: + if loose: + # Is there another format we can use? + 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] - break + 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): + def get_feed(self, obj, request, reverse_name, feed_type=None): """ Returns an unpopulated :class:`django.utils.feedgenerator.DefaultFeed` object for this object. + :param obj: The object for which the feed should be generated. + :param request: The current request. + :param reverse_name: The name which can be used to reverse the feed's URL. + :param feed_type: The slug used to register the feed class that will be instantiated and returned. + + :returns: An instance of the feed class registered as ``feed_type``, falling back to :attr:`feed_type` if ``feed_type`` is ``None``. + """ try: current_site = Site.objects.get_current() except Site.DoesNotExist: current_site = RequestSite(request) - feed_type = self.get_feed_type(request) + feed_type = self.get_feed_type(request, feed_type) node = request.node link = node.get_absolute_url(with_domain=True, request=request, secure=request.is_secure()) -- 2.20.1