X-Git-Url: http://git.ithinksw.org/philo.git/blobdiff_plain/12949bcd8bca1a6c9b0171774e447b63ead6edf0..201a2fe9f559dab1ff5fb3c97786471eb0bb75b9:/contrib/penfield/models.py diff --git a/contrib/penfield/models.py b/contrib/penfield/models.py index 3791e55..a03bed8 100644 --- a/contrib/penfield/models.py +++ b/contrib/penfield/models.py @@ -1,15 +1,297 @@ -from django.db import models from django.conf import settings -from philo.models import Tag, Titled, Entity, MultiView, Page, register_value_model -from philo.exceptions import ViewCanNotProvideSubpath from django.conf.urls.defaults import url, patterns, include -from django.core.urlresolvers import reverse -from django.http import Http404 -from datetime import datetime -from philo.utils import paginate +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 datetime import date, datetime +from philo.contrib.penfield.exceptions import HttpNotAcceptable +from philo.contrib.penfield.middleware import http_not_acceptable from philo.contrib.penfield.validators import validate_pagination_count -from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed -from philo.contrib.penfield.utils import FeedMultiViewMixin +from philo.exceptions import ViewCanNotProvideSubpath +from philo.models import Tag, Titled, Entity, MultiView, Page, register_value_model, TemplateField, Template +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): + """ + The FeedView expects to handle a number of different feeds for the + same object - i.e. patterns for a blog to handle all entries or + just entries for a certain year/month/day. + + This class would subclass django.contrib.syndication.views.Feed, but + that would make it callable, which causes problems. + """ + feed_type = models.CharField(max_length=50, choices=FEED_CHOICES, default=ATOM) + feed_suffix = models.CharField(max_length=255, blank=False, default="feed") + feeds_enabled = models.BooleanField(default=True) + 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.") + + item_title_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_title_related") + item_description_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_description_related") + + item_context_var = 'items' + object_attr = 'object' + + 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. + """ + 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): + 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. + """ + 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): + """ + Returns a view function that renders a list of items as a page. + """ + 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 a + request, 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): + 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 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): + 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, Titled): @@ -30,20 +312,26 @@ register_value_model(Blog) class BlogEntry(Entity, Titled): blog = models.ForeignKey(Blog, related_name='entries', blank=True, null=True) author = models.ForeignKey(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='blogentries') - date = models.DateTimeField(default=datetime.now) + date = models.DateTimeField(default=None) content = models.TextField() excerpt = models.TextField(blank=True, null=True) tags = models.ManyToManyField(Tag, related_name='blogentries', blank=True, null=True) + def save(self, *args, **kwargs): + if self.date is None: + self.date = datetime.now() + super(BlogEntry, self).save(*args, **kwargs) + class Meta: ordering = ['-date'] verbose_name_plural = "blog entries" + get_latest_by = "date" register_value_model(BlogEntry) -class BlogView(MultiView, FeedMultiViewMixin): +class BlogView(FeedView): ENTRY_PERMALINK_STYLE_CHOICES = ( ('D', 'Year, month, and day'), ('M', 'Year and month'), @@ -56,6 +344,7 @@ class BlogView(MultiView, FeedMultiViewMixin): index_page = models.ForeignKey(Page, related_name='blog_index_related') entry_page = models.ForeignKey(Page, related_name='blog_entry_related') + # TODO: entry_archive is misleading. Rename to ymd_page or timespan_page. entry_archive_page = models.ForeignKey(Page, related_name='blog_entry_archive_related', null=True, blank=True) tag_page = models.ForeignKey(Page, related_name='blog_tag_related') tag_archive_page = models.ForeignKey(Page, related_name='blog_tag_archive_related', null=True, blank=True) @@ -64,22 +353,14 @@ class BlogView(MultiView, FeedMultiViewMixin): entry_permalink_style = models.CharField(max_length=1, choices=ENTRY_PERMALINK_STYLE_CHOICES) entry_permalink_base = models.CharField(max_length=255, blank=False, default='entries') tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags') - feed_suffix = models.CharField(max_length=255, blank=False, default=FeedMultiViewMixin.feed_suffix) - feeds_enabled = models.BooleanField() - list_var = 'entries' + + item_context_var = 'entries' + object_attr = 'blog' def __unicode__(self): return u'BlogView for %s' % self.blog.title - @property - def per_page(self): - return self.entries_per_page - - @property - def feed_title(self): - return self.blog.title - - def get_subpath(self, obj): + def get_reverse_params(self, obj): if isinstance(obj, BlogEntry): if obj.blog == self.blog: kwargs = {'slug': obj.slug} @@ -89,84 +370,78 @@ class BlogView(MultiView, FeedMultiViewMixin): kwargs.update({'month': str(obj.date.month).zfill(2)}) if self.entry_permalink_style == 'D': kwargs.update({'day': str(obj.date.day).zfill(2)}) - return reverse(self.entry_view, urlconf=self, kwargs=kwargs) - elif isinstance(obj, Tag): - if obj in self.blog.entry_tags: - return reverse('entries_by_tag', urlconf=self, kwargs={'tag_slugs': obj.slug}) - elif isinstance(obj, (str, unicode)): - split_obj = obj.split(':') - if len(split_obj) > 1: - kwargs = {} - try: - kwargs.update({'year': str(int(split_obj[1])).zfill(4)}) - if len(split_obj) > 2: - kwargs.update({'month': str(int(split_obj[2])).zfill(2)}) - if len(split_obj) > 3: - kwargs.update({'day': str(int(split_obj[3])).zfill(2)}) - return reverse('entries_by_day', urlconf=self, kwargs=kwargs) - return reverse('entries_by_month', urlconf=self, kwargs=kwargs) - return reverse('entries_by_year', urlconf=self, kwargs=kwargs) - except: - pass + return self.entry_view, [], kwargs + elif isinstance(obj, Tag) or (isinstance(obj, models.query.QuerySet) and obj.model == Tag and obj): + if isinstance(obj, Tag): + obj = [obj] + slugs = [tag.slug for tag in obj if tag in self.get_tag_queryset()] + if slugs: + return 'entries_by_tag', [], {'tag_slugs': "/".join(slugs)} + elif isinstance(obj, (date, datetime)): + kwargs = { + 'year': str(obj.year).zfill(4), + 'month': str(obj.month).zfill(2), + 'day': str(obj.day).zfill(2) + } + return 'entries_by_day', [], kwargs raise ViewCanNotProvideSubpath - def get_context(self): - return {'blog': self.blog} - @property def urlpatterns(self): - urlpatterns = patterns('', - url(r'^', include(self.feed_patterns(self.get_all_entries, self.index_page, 'index'))), - url((r'^(?:%s)/(?P[-\w]+[-+/\w]*)/' % self.tag_permalink_base), include(self.feed_patterns(self.get_entries_by_tag, self.tag_page, 'entries_by_tag'))) - ) + urlpatterns = self.feed_patterns(r'^', 'get_all_entries', 'index_page', 'index') +\ + self.feed_patterns(r'^%s/(?P[-\w]+[-+/\w]*)$' % self.tag_permalink_base, 'get_entries_by_tag', 'tag_page', 'entries_by_tag') + if self.tag_archive_page: urlpatterns += patterns('', - url((r'^(?:%s)/?$' % self.tag_permalink_base), self.tag_archive_view) + url((r'^%s$' % self.tag_permalink_base), self.tag_archive_view, name='tag_archive') ) if self.entry_archive_page: if self.entry_permalink_style in 'DMY': - urlpatterns += patterns('', - url(r'^(?P\d{4})/', include(self.feed_patterns(self.get_entries_by_ymd, self.entry_archive_page, 'entries_by_year'))) - ) + urlpatterns += self.feed_patterns(r'^(?P\d{4})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_year') if self.entry_permalink_style in 'DM': - urlpatterns += patterns('', - url(r'^(?P\d{4})/(?P\d{2})/?$', include(self.feed_patterns(self.get_entries_by_ymd, self.entry_archive_page, 'entries_by_month'))), - ) + urlpatterns += self.feed_patterns(r'^(?P\d{4})/(?P\d{2})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_month') if self.entry_permalink_style == 'D': - urlpatterns += patterns('', - url(r'^(?P\d{4})/(?P\d{2})/(?P\d{2})/?$', include(self.feed_patterns(self.get_entries_by_ymd, self.entry_archive_page, 'entries_by_day'))) - ) + urlpatterns += self.feed_patterns(r'^(?P\d{4})/(?P\d{2})/(?P\d{2})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_day') if self.entry_permalink_style == 'D': urlpatterns += patterns('', - url(r'^(?P\d{4})/(?P\d{2})/(?P\d{2})/(?P[-\w]+)/?$', self.entry_view) + url(r'^(?P\d{4})/(?P\d{2})/(?P\d{2})/(?P[-\w]+)$', self.entry_view) ) elif self.entry_permalink_style == 'M': urlpatterns += patterns('', - url(r'^(?P\d{4})/(?P\d{2})/(?P[-\w]+)/?$', self.entry_view) + url(r'^(?P\d{4})/(?P\d{2})/(?P[-\w]+)$', self.entry_view) ) elif self.entry_permalink_style == 'Y': urlpatterns += patterns('', - url(r'^(?P\d{4})/(?P[-\w]+)/?$', self.entry_view) + url(r'^(?P\d{4})/(?P[-\w]+)$', self.entry_view) ) elif self.entry_permalink_style == 'B': urlpatterns += patterns('', - url((r'^(?:%s)/(?P[-\w]+)/?$' % self.entry_permalink_base), self.entry_view) + url((r'^%s/(?P[-\w]+)$' % self.entry_permalink_base), self.entry_view) ) else: urlpatterns = patterns('', - url(r'^(?P[-\w]+)/?$', self.entry_view) + url(r'^(?P[-\w]+)$', self.entry_view) ) return urlpatterns - def get_all_entries(self, request, node=None, extra_context=None): - return self.blog.entries.all(), extra_context + def get_context(self): + return {'blog': self.blog} + + def get_entry_queryset(self): + return self.blog.entries.all() + + def get_tag_queryset(self): + return self.blog.entry_tags - def get_entries_by_ymd(self, request, year=None, month=None, day=None, node=None, extra_context=None): + def get_all_entries(self, request, extra_context=None): + return self.get_entry_queryset(), extra_context + + def get_entries_by_ymd(self, request, year=None, month=None, day=None, extra_context=None): if not self.entry_archive_page: raise Http404 - entries = self.blog.entries.all() + entries = self.get_entry_queryset() if year: entries = entries.filter(date__year=year) if month: @@ -178,31 +453,30 @@ class BlogView(MultiView, FeedMultiViewMixin): context.update({'year': year, 'month': month, 'day': day}) return entries, context - def get_entries_by_tag(self, request, node=None, extra_context=None): - tags = [] - for tag_slug in tag_slugs.replace('+', '/').split('/'): - if tag_slug: # ignore blank slugs, handles for multiple consecutive separators (+ or /) - try: - tag = self.blog.entry_tags.get(slug=tag_slug) - except: - raise Http404 - tags.append(tag) - if len(tags) <= 0: + def get_entries_by_tag(self, request, tag_slugs, extra_context=None): + tag_slugs = tag_slugs.replace('+', '/').split('/') + tags = self.get_tag_queryset().filter(slug__in=tag_slugs) + + if not tags: raise Http404 + + # Raise a 404 on an incorrect slug. + found_slugs = [tag.slug for tag in tags] + for slug in tag_slugs: + if slug and slug not in found_slugs: + raise Http404 - entries = self.blog.entries.all() + entries = self.get_entry_queryset() for tag in tags: entries = entries.filter(tags=tag) - if entries.count() <= 0: - raise Http404 - return entries, extra_context - - def get_obj_description(self, obj): - return obj.excerpt + context = extra_context or {} + context.update({'tags': tags}) + + return entries, context - def entry_view(self, request, slug, year=None, month=None, day=None, node=None, extra_context=None): - entries = self.blog.entries.all() + def entry_view(self, request, slug, year=None, month=None, day=None, extra_context=None): + entries = self.get_entry_queryset() if year: entries = entries.filter(date__year=year) if month: @@ -216,15 +490,73 @@ class BlogView(MultiView, FeedMultiViewMixin): context = self.get_context() context.update(extra_context or {}) context.update({'entry': entry}) - return self.entry_page.render_to_response(node, request, extra_context=context) + return self.entry_page.render_to_response(request, extra_context=context) - def tag_archive_view(self, request, node=None, extra_context=None): + def tag_archive_view(self, request, extra_context=None): if not self.tag_archive_page: raise Http404 - context = {} + context = self.get_context() context.update(extra_context or {}) - context.update({'blog': self.blog}) - return self.tag_archive_page.render_to_response(node, request, extra_context=context) + context.update({ + 'tags': self.get_tag_queryset() + }) + return self.tag_archive_page.render_to_response(request, extra_context=context) + + def feed_view(self, get_items_attr, reverse_name): + 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, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs) + self.populate_feed(feed, items, request) + + if 'tags' in extra_context: + tags = extra_context['tags'] + feed.feed['link'] = request.node.construct_url(self.reverse(obj=tags), with_domain=True, request=request, secure=request.is_secure()) + else: + tags = obj.entry_tags + + feed.feed['categories'] = [tag.name for tag in tags] + + response = HttpResponse(mimetype=feed.mime_type) + feed.write(response, 'utf-8') + return response + + return inner + + def process_page_items(self, request, items): + if self.entries_per_page: + page_num = request.GET.get('page', 1) + paginator, paginated_page, items = paginate(items, self.entries_per_page, page_num) + item_context = { + 'paginator': paginator, + 'paginated_page': paginated_page, + self.item_context_var: items + } + else: + item_context = { + self.item_context_var: items + } + return items, item_context + + def title(self, obj): + return obj.title + + def item_title(self, item): + return item.title + + def item_description(self, item): + return item.content + + def item_author_name(self, item): + return item.author.get_full_name() + + def item_pubdate(self, item): + return item.date + + def item_categories(self, item): + return [tag.name for tag in item.tags.all()] class Newsletter(Entity, Titled): @@ -237,9 +569,15 @@ register_value_model(Newsletter) class NewsletterArticle(Entity, Titled): newsletter = models.ForeignKey(Newsletter, related_name='articles') authors = models.ManyToManyField(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='newsletterarticles') - date = models.DateTimeField(default=datetime.now) - lede = models.TextField(null=True, blank=True) - full_text = models.TextField() + date = models.DateTimeField(default=None) + lede = TemplateField(null=True, blank=True, verbose_name='Summary') + full_text = TemplateField(db_index=True) + tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True) + + def save(self, *args, **kwargs): + if self.date is None: + self.date = datetime.now() + super(NewsletterArticle, self).save(*args, **kwargs) class Meta: get_latest_by = 'date' @@ -252,18 +590,18 @@ register_value_model(NewsletterArticle) class NewsletterIssue(Entity, Titled): newsletter = models.ForeignKey(Newsletter, related_name='issues') - number = models.PositiveIntegerField() + numbering = models.CharField(max_length=50, help_text='For example, 04.02 for volume 4, issue 2.') articles = models.ManyToManyField(NewsletterArticle, related_name='issues') class Meta: - ordering = ['-number'] - unique_together = (('newsletter', 'number'),) + ordering = ['-numbering'] + unique_together = (('newsletter', 'numbering'),) register_value_model(NewsletterIssue) -class NewsletterView(MultiView, FeedMultiViewMixin): +class NewsletterView(FeedView): ARTICLE_PERMALINK_STYLE_CHOICES = ( ('D', 'Year, month, and day'), ('M', 'Year and month'), @@ -283,15 +621,13 @@ class NewsletterView(MultiView, FeedMultiViewMixin): article_permalink_base = models.CharField(max_length=255, blank=False, default='articles') issue_permalink_base = models.CharField(max_length=255, blank=False, default='issues') - feed_suffix = models.CharField(max_length=255, blank=False, default=FeedMultiViewMixin.feed_suffix) - feeds_enabled = models.BooleanField() - list_var = 'articles' + item_context_var = 'articles' + object_attr = 'newsletter' - @property - def feed_title(self): - return self.newsletter.title + def __unicode__(self): + return "NewsletterView for %s" % self.newsletter.__unicode__() - def get_subpath(self, obj): + def get_reverse_params(self, obj): if isinstance(obj, NewsletterArticle): if obj.newsletter == self.newsletter: kwargs = {'slug': obj.slug} @@ -301,54 +637,54 @@ class NewsletterView(MultiView, FeedMultiViewMixin): kwargs.update({'month': str(obj.date.month).zfill(2)}) if self.article_permalink_style == 'D': kwargs.update({'day': str(obj.date.day).zfill(2)}) - return reverse(self.article_view, urlconf=self, kwargs=kwargs) + return self.article_view, [], kwargs elif isinstance(obj, NewsletterIssue): if obj.newsletter == self.newsletter: - return reverse('issue', urlconf=self, kwargs={'number': str(obj.number)}) + return 'issue', [], {'numbering': obj.numbering} + elif isinstance(obj, (date, datetime)): + kwargs = { + 'year': str(obj.year).zfill(4), + 'month': str(obj.month).zfill(2), + 'day': str(obj.day).zfill(2) + } + return 'articles_by_day', [], kwargs raise ViewCanNotProvideSubpath @property def urlpatterns(self): - urlpatterns = patterns('', - url(r'^', include(self.feed_patterns(self.get_all_articles, self.index_page, 'index'))), - url(r'^(?:%s)/(?P\d+)/' % self.issue_permalink_base, include(self.feed_patterns(self.get_articles_by_issue, self.issue_page, 'issue'))) + urlpatterns = self.feed_patterns(r'^', 'get_all_articles', 'index_page', 'index') + patterns('', + url(r'^%s/(?P.+)$' % self.issue_permalink_base, self.page_view('get_articles_by_issue', 'issue_page'), name='issue') ) if self.issue_archive_page: urlpatterns += patterns('', - url(r'^(?:%s)/$' % self.issue_permalink_base, self.issue_archive_view) + url(r'^%s$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive') ) if self.article_archive_page: urlpatterns += patterns('', - url(r'^(?:%s)/' % self.article_permalink_base, include(self.feed_patterns(self.get_all_articles, self.article_archive_page, 'articles'))) + url(r'^%s' % self.article_permalink_base, include(self.feed_patterns('get_all_articles', 'article_archive_page', 'articles'))) ) if self.article_permalink_style in 'DMY': - urlpatterns += patterns('', - url(r'^(?:%s)/(?P\d{4})/' % self.article_permalink_base, include(self.feed_patterns(self.get_articles_by_ymd, self.article_archive_page, 'articles_by_year'))) - ) + urlpatterns += self.feed_patterns(r'^%s/(?P\d{4})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_year') if self.article_permalink_style in 'DM': - urlpatterns += patterns('', - url(r'^(?:%s)/(?P\d{4})/(?P\d{2})/' % self.article_permalink_base, include(self.feed_patterns(self.get_articles_by_ymd, self.article_archive_page, 'articles_by_month'))) - ) + urlpatterns += self.feed_patterns(r'^%s/(?P\d{4})/(?P\d{2})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_month') if self.article_permalink_style == 'D': - urlpatterns += patterns('', - url(r'^(?:%s)/(?P\d{4})/(?P\d{2})/(?P\d{2})/' % self.article_permalink_base, include(self.feed_patterns(self.get_articles_by_ymd, self.article_archive_page, 'articles_by_day'))) - ) + urlpatterns += self.feed_patterns(r'^%s/(?P\d{4})/(?P\d{2})/(?P\d{2})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_day') if self.article_permalink_style == 'Y': urlpatterns += patterns('', - url(r'^(?:%s)/(?P\d{4})/(?P[\w-]+)/$' % self.article_permalink_base, self.article_view) + url(r'^%s/(?P\d{4})/(?P[\w-]+)$' % self.article_permalink_base, self.article_view) ) elif self.article_permalink_style == 'M': urlpatterns += patterns('', - url(r'^(?:%s)/(?P\d{4})/(?P\d{2})/(?P[\w-]+)/$' % self.article_permalink_base, self.article_view) + url(r'^%s/(?P\d{4})/(?P\d{2})/(?P[\w-]+)$' % self.article_permalink_base, self.article_view) ) elif self.article_permalink_style == 'D': urlpatterns += patterns('', - url(r'^(?:%s)/(?P\d{4})/(?P\d{2})/(?P\d{2})/(?P[\w-]+)/$' % self.article_permalink_base, self.article_view) + url(r'^%s/(?P\d{4})/(?P\d{2})/(?P\d{2})/(?P[\w-]+)$' % self.article_permalink_base, self.article_view) ) else: urlpatterns += patterns('', - url(r'^(?:%s)/(?P[-\w]+)/?$' % self.article_permalink_base, self.article_view) + url(r'^%s/(?P[-\w]+)$' % self.article_permalink_base, self.article_view) ) return urlpatterns @@ -356,28 +692,34 @@ class NewsletterView(MultiView, FeedMultiViewMixin): def get_context(self): return {'newsletter': self.newsletter} - def get_all_articles(self, request, node, extra_context=None): - return self.newsletter.articles.all(), extra_context + def get_article_queryset(self): + return self.newsletter.articles.all() + + def get_issue_queryset(self): + return self.newsletter.issues.all() + + def get_all_articles(self, request, extra_context=None): + return self.get_article_queryset(), extra_context - def get_articles_by_ymd(self, request, year, month=None, day=None, node=None, extra_context=None): - articles = self.newsletter.articles.filter(dat__year=year) + def get_articles_by_ymd(self, request, year, month=None, day=None, extra_context=None): + articles = self.get_article_queryset().filter(date__year=year) if month: articles = articles.filter(date__month=month) if day: articles = articles.filter(date__day=day) - return articles + return articles, extra_context - def get_articles_by_issue(self, request, number, node=None, extra_context=None): + def get_articles_by_issue(self, request, numbering, extra_context=None): try: - issue = self.newsletter.issues.get(number=number) - except: + issue = self.get_issue_queryset().get(numbering=numbering) + except NewsletterIssue.DoesNotExist: raise Http404 context = extra_context or {} context.update({'issue': issue}) - return issue.articles.all(), context + return self.get_article_queryset().filter(issues=issue), context - def article_view(self, request, slug, year=None, month=None, day=None, node=None, extra_context=None): - articles = self.newsletter.articles.all() + def article_view(self, request, slug, year=None, month=None, day=None, extra_context=None): + articles = self.get_article_queryset() if year: articles = articles.filter(date__year=year) if month: @@ -386,20 +728,43 @@ class NewsletterView(MultiView, FeedMultiViewMixin): articles = articles.filter(date__day=day) try: article = articles.get(slug=slug) - except: + except NewsletterArticle.DoesNotExist: raise Http404 context = self.get_context() context.update(extra_context or {}) context.update({'article': article}) - return self.article_page.render_to_response(node, request, extra_context=context) + return self.article_page.render_to_response(request, extra_context=context) - def issue_archive_view(self, request, node=None, extra_context=None): + def issue_archive_view(self, request, extra_context): if not self.issue_archive_page: raise Http404 - context = {} + context = self.get_context() context.update(extra_context or {}) - context.update({'newsletter': self.newsletter}) - return self.issue_archive_page.render_to_response(node, request, extra_context=context) + context.update({ + 'issues': self.get_issue_queryset() + }) + return self.issue_archive_page.render_to_response(request, extra_context=context) + + def title(self, obj): + return obj.title + + def item_title(self, item): + return item.title + + def item_description(self, item): + return item.full_text + + def item_author_name(self, item): + authors = list(item.authors.all()) + if len(authors) > 1: + return "%s and %s" % (", ".join([author.get_full_name() for author in authors[:-1]]), authors[-1].get_full_name()) + elif authors: + return authors[0].get_full_name() + else: + return '' + + def item_pubdate(self, item): + return item.date - def get_obj_description(self, obj): - return obj.lede or obj.full_text \ No newline at end of file + def item_categories(self, item): + return [tag.name for tag in item.tags.all()] \ No newline at end of file