1 from datetime import date, datetime
3 from django.conf import settings
4 from django.conf.urls.defaults import url, patterns, include
5 from django.contrib.sites.models import Site, RequestSite
6 from django.contrib.syndication.views import add_domain
7 from django.db import models
8 from django.http import Http404, HttpResponse
9 from django.template import RequestContext, Template as DjangoTemplate
10 from django.utils import feedgenerator, tzinfo
11 from django.utils.datastructures import SortedDict
12 from django.utils.encoding import smart_unicode, force_unicode
13 from django.utils.html import escape
15 from philo.contrib.penfield.exceptions import HttpNotAcceptable
16 from philo.contrib.penfield.middleware import http_not_acceptable
17 from philo.contrib.penfield.validators import validate_pagination_count
18 from philo.exceptions import ViewCanNotProvideSubpath
19 from philo.models import Tag, Entity, MultiView, Page, register_value_model, Template
20 from philo.models.fields import TemplateField
21 from philo.utils import paginate
29 ATOM = feedgenerator.Atom1Feed.mime_type
30 RSS = feedgenerator.Rss201rev2Feed.mime_type
32 (ATOM, feedgenerator.Atom1Feed),
33 (RSS, feedgenerator.Rss201rev2Feed),
41 class FeedView(MultiView):
43 The FeedView expects to handle a number of different feeds for the
44 same object - i.e. patterns for a blog to handle all entries or
45 just entries for a certain year/month/day.
47 This class would subclass django.contrib.syndication.views.Feed, but
48 that would make it callable, which causes problems.
50 feed_type = models.CharField(max_length=50, choices=FEED_CHOICES, default=ATOM)
51 feed_suffix = models.CharField(max_length=255, blank=False, default="feed")
52 feeds_enabled = models.BooleanField(default=True)
53 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.")
55 item_title_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_title_related")
56 item_description_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_description_related")
58 item_context_var = 'items'
59 object_attr = 'object'
63 def feed_patterns(self, base, get_items_attr, page_attr, reverse_name):
65 Given the name to be used to reverse this view and the names of
66 the attributes for the function that fetches the objects, returns
67 patterns suitable for inclusion in urlpatterns.
69 urlpatterns = patterns('')
70 if self.feeds_enabled:
71 feed_reverse_name = "%s_feed" % reverse_name
72 feed_view = http_not_acceptable(self.feed_view(get_items_attr, feed_reverse_name))
73 feed_pattern = r'%s%s%s$' % (base, (base and base[-1] != "^") and "/" or "", self.feed_suffix)
74 urlpatterns += patterns('',
75 url(feed_pattern, feed_view, name=feed_reverse_name),
77 urlpatterns += patterns('',
78 url(r"%s$" % base, self.page_view(get_items_attr, page_attr), name=reverse_name)
82 def get_object(self, request, **kwargs):
83 return getattr(self, self.object_attr)
85 def feed_view(self, get_items_attr, reverse_name):
87 Returns a view function that renders a list of items as a feed.
89 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
91 def inner(request, extra_context=None, *args, **kwargs):
92 obj = self.get_object(request, *args, **kwargs)
93 feed = self.get_feed(obj, request, reverse_name)
94 items, xxx = get_items(request, extra_context=extra_context, *args, **kwargs)
95 self.populate_feed(feed, items, request)
97 response = HttpResponse(mimetype=feed.mime_type)
98 feed.write(response, 'utf-8')
103 def page_view(self, get_items_attr, page_attr):
105 Returns a view function that renders a list of items as a page.
107 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
108 page = isinstance(page_attr, Page) and page_attr or getattr(self, page_attr)
110 def inner(request, extra_context=None, *args, **kwargs):
111 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
112 items, item_context = self.process_page_items(request, items)
114 context = self.get_context()
115 context.update(extra_context or {})
116 context.update(item_context or {})
118 return page.render_to_response(request, extra_context=context)
121 def process_page_items(self, request, items):
123 Hook for handling any extra processing of items based on a
124 request, such as pagination or searching. This method is
125 expected to return a list of items and a dictionary to be
126 added to the page context.
129 self.item_context_var: items
131 return items, item_context
133 def get_feed_type(self, request):
134 feed_type = self.feed_type
135 if feed_type not in FEEDS:
136 feed_type = FEEDS.keys()[0]
137 accept = request.META.get('HTTP_ACCEPT')
138 if accept and feed_type not in accept and "*/*" not in accept and "%s/*" % feed_type.split("/")[0] not in accept:
139 # Wups! They aren't accepting the chosen format. Is there another format we can use?
141 feed_type = mimeparse.best_match(FEEDS.keys(), accept)
143 for feed_type in FEEDS.keys():
144 if feed_type in accept or "%s/*" % feed_type.split("/")[0] in accept:
149 raise HttpNotAcceptable
150 return FEEDS[feed_type]
152 def get_feed(self, obj, request, reverse_name):
154 Returns an unpopulated feedgenerator.DefaultFeed object for this object.
157 current_site = Site.objects.get_current()
158 except Site.DoesNotExist:
159 current_site = RequestSite(request)
161 feed_type = self.get_feed_type(request)
163 link = node.get_absolute_url(with_domain=True, request=request, secure=request.is_secure())
166 title = self.__get_dynamic_attr('title', obj),
167 subtitle = self.__get_dynamic_attr('subtitle', obj),
169 description = self.__get_dynamic_attr('description', obj),
170 language = settings.LANGUAGE_CODE.decode(),
171 feed_url = add_domain(
173 self.__get_dynamic_attr('feed_url', obj) or node.construct_url(node.subpath, with_domain=True, request=request, secure=request.is_secure()),
176 author_name = self.__get_dynamic_attr('author_name', obj),
177 author_link = self.__get_dynamic_attr('author_link', obj),
178 author_email = self.__get_dynamic_attr('author_email', obj),
179 categories = self.__get_dynamic_attr('categories', obj),
180 feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
181 feed_guid = self.__get_dynamic_attr('feed_guid', obj),
182 ttl = self.__get_dynamic_attr('ttl', obj),
183 **self.feed_extra_kwargs(obj)
187 def populate_feed(self, feed, items, request):
188 if self.item_title_template:
189 title_template = DjangoTemplate(self.item_title_template.code)
191 title_template = None
192 if self.item_description_template:
193 description_template = DjangoTemplate(self.item_description_template.code)
195 description_template = None
199 current_site = Site.objects.get_current()
200 except Site.DoesNotExist:
201 current_site = RequestSite(request)
203 if self.feed_length is not None:
204 items = items[:self.feed_length]
207 if title_template is not None:
208 title = title_template.render(RequestContext(request, {'obj': item}))
210 title = self.__get_dynamic_attr('item_title', item)
211 if description_template is not None:
212 description = description_template.render(RequestContext(request, {'obj': item}))
214 description = self.__get_dynamic_attr('item_description', item)
216 link = node.construct_url(self.reverse(obj=item), with_domain=True, request=request, secure=request.is_secure())
219 enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
221 enc = feedgenerator.Enclosure(
222 url = smart_unicode(add_domain(
227 length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
228 mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
230 author_name = self.__get_dynamic_attr('item_author_name', item)
231 if author_name is not None:
232 author_email = self.__get_dynamic_attr('item_author_email', item)
233 author_link = self.__get_dynamic_attr('item_author_link', item)
235 author_email = author_link = None
237 pubdate = self.__get_dynamic_attr('item_pubdate', item)
238 if pubdate and not pubdate.tzinfo:
239 ltz = tzinfo.LocalTimezone(pubdate)
240 pubdate = pubdate.replace(tzinfo=ltz)
245 description = description,
246 unique_id = self.__get_dynamic_attr('item_guid', item, link),
249 author_name = author_name,
250 author_email = author_email,
251 author_link = author_link,
252 categories = self.__get_dynamic_attr('item_categories', item),
253 item_copyright = self.__get_dynamic_attr('item_copyright', item),
254 **self.item_extra_kwargs(item)
257 def __get_dynamic_attr(self, attname, obj, default=None):
259 attr = getattr(self, attname)
260 except AttributeError:
263 # Check func_code.co_argcount rather than try/excepting the
264 # function and catching the TypeError, because something inside
265 # the function may raise the TypeError. This technique is more
267 if hasattr(attr, 'func_code'):
268 argcount = attr.func_code.co_argcount
270 argcount = attr.__call__.func_code.co_argcount
271 if argcount == 2: # one argument is 'self'
277 def feed_extra_kwargs(self, obj):
279 Returns an extra keyword arguments dictionary that is used when
280 initializing the feed generator.
284 def item_extra_kwargs(self, item):
286 Returns an extra keyword arguments dictionary that is used with
287 the `add_item` call of the feed generator.
291 def item_title(self, item):
292 return escape(force_unicode(item))
294 def item_description(self, item):
295 return force_unicode(item)
302 title = models.CharField(max_length=255)
303 slug = models.SlugField(max_length=255)
305 def __unicode__(self):
309 def entry_tags(self):
310 """ Returns a QuerySet of Tags that are used on any entries in this blog. """
311 return Tag.objects.filter(blogentries__blog=self).distinct()
314 def entry_dates(self):
315 dates = {'year': self.entries.dates('date', 'year', order='DESC'), 'month': self.entries.dates('date', 'month', order='DESC'), 'day': self.entries.dates('date', 'day', order='DESC')}
319 register_value_model(Blog)
322 class BlogEntry(Entity):
323 title = models.CharField(max_length=255)
324 slug = models.SlugField(max_length=255)
325 blog = models.ForeignKey(Blog, related_name='entries', blank=True, null=True)
326 author = models.ForeignKey(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='blogentries')
327 date = models.DateTimeField(default=None)
328 content = models.TextField()
329 excerpt = models.TextField(blank=True, null=True)
330 tags = models.ManyToManyField(Tag, related_name='blogentries', blank=True, null=True)
332 def save(self, *args, **kwargs):
333 if self.date is None:
334 self.date = datetime.now()
335 super(BlogEntry, self).save(*args, **kwargs)
337 def __unicode__(self):
342 verbose_name_plural = "blog entries"
343 get_latest_by = "date"
346 register_value_model(BlogEntry)
349 class BlogView(FeedView):
350 ENTRY_PERMALINK_STYLE_CHOICES = (
351 ('D', 'Year, month, and day'),
352 ('M', 'Year and month'),
354 ('B', 'Custom base'),
358 blog = models.ForeignKey(Blog, related_name='blogviews')
360 index_page = models.ForeignKey(Page, related_name='blog_index_related')
361 entry_page = models.ForeignKey(Page, related_name='blog_entry_related')
362 # TODO: entry_archive is misleading. Rename to ymd_page or timespan_page.
363 entry_archive_page = models.ForeignKey(Page, related_name='blog_entry_archive_related', null=True, blank=True)
364 tag_page = models.ForeignKey(Page, related_name='blog_tag_related')
365 tag_archive_page = models.ForeignKey(Page, related_name='blog_tag_archive_related', null=True, blank=True)
366 entries_per_page = models.IntegerField(blank=True, validators=[validate_pagination_count], null=True)
368 entry_permalink_style = models.CharField(max_length=1, choices=ENTRY_PERMALINK_STYLE_CHOICES)
369 entry_permalink_base = models.CharField(max_length=255, blank=False, default='entries')
370 tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags')
372 item_context_var = 'entries'
375 def __unicode__(self):
376 return u'BlogView for %s' % self.blog.title
378 def get_reverse_params(self, obj):
379 if isinstance(obj, BlogEntry):
380 if obj.blog == self.blog:
381 kwargs = {'slug': obj.slug}
382 if self.entry_permalink_style in 'DMY':
383 kwargs.update({'year': str(obj.date.year).zfill(4)})
384 if self.entry_permalink_style in 'DM':
385 kwargs.update({'month': str(obj.date.month).zfill(2)})
386 if self.entry_permalink_style == 'D':
387 kwargs.update({'day': str(obj.date.day).zfill(2)})
388 return self.entry_view, [], kwargs
389 elif isinstance(obj, Tag) or (isinstance(obj, models.query.QuerySet) and obj.model == Tag and obj):
390 if isinstance(obj, Tag):
392 slugs = [tag.slug for tag in obj if tag in self.get_tag_queryset()]
394 return 'entries_by_tag', [], {'tag_slugs': "/".join(slugs)}
395 elif isinstance(obj, (date, datetime)):
397 'year': str(obj.year).zfill(4),
398 'month': str(obj.month).zfill(2),
399 'day': str(obj.day).zfill(2)
401 return 'entries_by_day', [], kwargs
402 raise ViewCanNotProvideSubpath
405 def urlpatterns(self):
406 urlpatterns = self.feed_patterns(r'^', 'get_all_entries', 'index_page', 'index') +\
407 self.feed_patterns(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)$' % self.tag_permalink_base, 'get_entries_by_tag', 'tag_page', 'entries_by_tag')
409 if self.tag_archive_page:
410 urlpatterns += patterns('',
411 url((r'^%s$' % self.tag_permalink_base), self.tag_archive_view, name='tag_archive')
414 if self.entry_archive_page:
415 if self.entry_permalink_style in 'DMY':
416 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_year')
417 if self.entry_permalink_style in 'DM':
418 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})/(?P<month>\d{2})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_month')
419 if self.entry_permalink_style == 'D':
420 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_day')
422 if self.entry_permalink_style == 'D':
423 urlpatterns += patterns('',
424 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
426 elif self.entry_permalink_style == 'M':
427 urlpatterns += patterns('',
428 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
430 elif self.entry_permalink_style == 'Y':
431 urlpatterns += patterns('',
432 url(r'^(?P<year>\d{4})/(?P<slug>[-\w]+)$', self.entry_view)
434 elif self.entry_permalink_style == 'B':
435 urlpatterns += patterns('',
436 url((r'^%s/(?P<slug>[-\w]+)$' % self.entry_permalink_base), self.entry_view)
439 urlpatterns = patterns('',
440 url(r'^(?P<slug>[-\w]+)$', self.entry_view)
444 def get_context(self):
445 return {'blog': self.blog}
447 def get_entry_queryset(self):
448 return self.blog.entries.all()
450 def get_tag_queryset(self):
451 return self.blog.entry_tags
453 def get_all_entries(self, request, extra_context=None):
454 return self.get_entry_queryset(), extra_context
456 def get_entries_by_ymd(self, request, year=None, month=None, day=None, extra_context=None):
457 if not self.entry_archive_page:
459 entries = self.get_entry_queryset()
461 entries = entries.filter(date__year=year)
463 entries = entries.filter(date__month=month)
465 entries = entries.filter(date__day=day)
467 context = extra_context or {}
468 context.update({'year': year, 'month': month, 'day': day})
469 return entries, context
471 def get_entries_by_tag(self, request, tag_slugs, extra_context=None):
472 tag_slugs = tag_slugs.replace('+', '/').split('/')
473 tags = self.get_tag_queryset().filter(slug__in=tag_slugs)
478 # Raise a 404 on an incorrect slug.
479 found_slugs = [tag.slug for tag in tags]
480 for slug in tag_slugs:
481 if slug and slug not in found_slugs:
484 entries = self.get_entry_queryset()
486 entries = entries.filter(tags=tag)
488 context = extra_context or {}
489 context.update({'tags': tags})
491 return entries, context
493 def entry_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
494 entries = self.get_entry_queryset()
496 entries = entries.filter(date__year=year)
498 entries = entries.filter(date__month=month)
500 entries = entries.filter(date__day=day)
502 entry = entries.get(slug=slug)
505 context = self.get_context()
506 context.update(extra_context or {})
507 context.update({'entry': entry})
508 return self.entry_page.render_to_response(request, extra_context=context)
510 def tag_archive_view(self, request, extra_context=None):
511 if not self.tag_archive_page:
513 context = self.get_context()
514 context.update(extra_context or {})
516 'tags': self.get_tag_queryset()
518 return self.tag_archive_page.render_to_response(request, extra_context=context)
520 def feed_view(self, get_items_attr, reverse_name):
521 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
523 def inner(request, extra_context=None, *args, **kwargs):
524 obj = self.get_object(request, *args, **kwargs)
525 feed = self.get_feed(obj, request, reverse_name)
526 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
527 self.populate_feed(feed, items, request)
529 if 'tags' in extra_context:
530 tags = extra_context['tags']
531 feed.feed['link'] = request.node.construct_url(self.reverse(obj=tags), with_domain=True, request=request, secure=request.is_secure())
533 tags = obj.entry_tags
535 feed.feed['categories'] = [tag.name for tag in tags]
537 response = HttpResponse(mimetype=feed.mime_type)
538 feed.write(response, 'utf-8')
543 def process_page_items(self, request, items):
544 if self.entries_per_page:
545 page_num = request.GET.get('page', 1)
546 paginator, paginated_page, items = paginate(items, self.entries_per_page, page_num)
548 'paginator': paginator,
549 'paginated_page': paginated_page,
550 self.item_context_var: items
554 self.item_context_var: items
556 return items, item_context
558 def title(self, obj):
561 def item_title(self, item):
564 def item_description(self, item):
567 def item_author_name(self, item):
568 return item.author.get_full_name()
570 def item_pubdate(self, item):
573 def item_categories(self, item):
574 return [tag.name for tag in item.tags.all()]
577 class Newsletter(Entity):
578 title = models.CharField(max_length=255)
579 slug = models.SlugField(max_length=255)
581 def __unicode__(self):
585 register_value_model(Newsletter)
588 class NewsletterArticle(Entity):
589 title = models.CharField(max_length=255)
590 slug = models.SlugField(max_length=255)
591 newsletter = models.ForeignKey(Newsletter, related_name='articles')
592 authors = models.ManyToManyField(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='newsletterarticles')
593 date = models.DateTimeField(default=None)
594 lede = TemplateField(null=True, blank=True, verbose_name='Summary')
595 full_text = TemplateField(db_index=True)
596 tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True)
598 def save(self, *args, **kwargs):
599 if self.date is None:
600 self.date = datetime.now()
601 super(NewsletterArticle, self).save(*args, **kwargs)
603 def __unicode__(self):
607 get_latest_by = 'date'
609 unique_together = (('newsletter', 'slug'),)
612 register_value_model(NewsletterArticle)
615 class NewsletterIssue(Entity):
616 title = models.CharField(max_length=255)
617 slug = models.SlugField(max_length=255)
618 newsletter = models.ForeignKey(Newsletter, related_name='issues')
619 numbering = models.CharField(max_length=50, help_text='For example, 04.02 for volume 4, issue 2.')
620 articles = models.ManyToManyField(NewsletterArticle, related_name='issues')
622 def __unicode__(self):
626 ordering = ['-numbering']
627 unique_together = (('newsletter', 'numbering'),)
630 register_value_model(NewsletterIssue)
633 class NewsletterView(FeedView):
634 ARTICLE_PERMALINK_STYLE_CHOICES = (
635 ('D', 'Year, month, and day'),
636 ('M', 'Year and month'),
641 newsletter = models.ForeignKey(Newsletter, related_name='newsletterviews')
643 index_page = models.ForeignKey(Page, related_name='newsletter_index_related')
644 article_page = models.ForeignKey(Page, related_name='newsletter_article_related')
645 article_archive_page = models.ForeignKey(Page, related_name='newsletter_article_archive_related', null=True, blank=True)
646 issue_page = models.ForeignKey(Page, related_name='newsletter_issue_related')
647 issue_archive_page = models.ForeignKey(Page, related_name='newsletter_issue_archive_related', null=True, blank=True)
649 article_permalink_style = models.CharField(max_length=1, choices=ARTICLE_PERMALINK_STYLE_CHOICES)
650 article_permalink_base = models.CharField(max_length=255, blank=False, default='articles')
651 issue_permalink_base = models.CharField(max_length=255, blank=False, default='issues')
653 item_context_var = 'articles'
654 object_attr = 'newsletter'
656 def __unicode__(self):
657 return "NewsletterView for %s" % self.newsletter.__unicode__()
659 def get_reverse_params(self, obj):
660 if isinstance(obj, NewsletterArticle):
661 if obj.newsletter == self.newsletter:
662 kwargs = {'slug': obj.slug}
663 if self.article_permalink_style in 'DMY':
664 kwargs.update({'year': str(obj.date.year).zfill(4)})
665 if self.article_permalink_style in 'DM':
666 kwargs.update({'month': str(obj.date.month).zfill(2)})
667 if self.article_permalink_style == 'D':
668 kwargs.update({'day': str(obj.date.day).zfill(2)})
669 return self.article_view, [], kwargs
670 elif isinstance(obj, NewsletterIssue):
671 if obj.newsletter == self.newsletter:
672 return 'issue', [], {'numbering': obj.numbering}
673 elif isinstance(obj, (date, datetime)):
675 'year': str(obj.year).zfill(4),
676 'month': str(obj.month).zfill(2),
677 'day': str(obj.day).zfill(2)
679 return 'articles_by_day', [], kwargs
680 raise ViewCanNotProvideSubpath
683 def urlpatterns(self):
684 urlpatterns = self.feed_patterns(r'^', 'get_all_articles', 'index_page', 'index') + patterns('',
685 url(r'^%s/(?P<numbering>.+)$' % self.issue_permalink_base, self.page_view('get_articles_by_issue', 'issue_page'), name='issue')
687 if self.issue_archive_page:
688 urlpatterns += patterns('',
689 url(r'^%s$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive')
691 if self.article_archive_page:
692 urlpatterns += patterns('',
693 url(r'^%s' % self.article_permalink_base, include(self.feed_patterns('get_all_articles', 'article_archive_page', 'articles')))
695 if self.article_permalink_style in 'DMY':
696 urlpatterns += self.feed_patterns(r'^%s/(?P<year>\d{4})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_year')
697 if self.article_permalink_style in 'DM':
698 urlpatterns += self.feed_patterns(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_month')
699 if self.article_permalink_style == 'D':
700 urlpatterns += self.feed_patterns(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_day')
702 if self.article_permalink_style == 'Y':
703 urlpatterns += patterns('',
704 url(r'^%s/(?P<year>\d{4})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
706 elif self.article_permalink_style == 'M':
707 urlpatterns += patterns('',
708 url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
710 elif self.article_permalink_style == 'D':
711 urlpatterns += patterns('',
712 url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
715 urlpatterns += patterns('',
716 url(r'^%s/(?P<slug>[-\w]+)$' % self.article_permalink_base, self.article_view)
721 def get_context(self):
722 return {'newsletter': self.newsletter}
724 def get_article_queryset(self):
725 return self.newsletter.articles.all()
727 def get_issue_queryset(self):
728 return self.newsletter.issues.all()
730 def get_all_articles(self, request, extra_context=None):
731 return self.get_article_queryset(), extra_context
733 def get_articles_by_ymd(self, request, year, month=None, day=None, extra_context=None):
734 articles = self.get_article_queryset().filter(date__year=year)
736 articles = articles.filter(date__month=month)
738 articles = articles.filter(date__day=day)
739 return articles, extra_context
741 def get_articles_by_issue(self, request, numbering, extra_context=None):
743 issue = self.get_issue_queryset().get(numbering=numbering)
744 except NewsletterIssue.DoesNotExist:
746 context = extra_context or {}
747 context.update({'issue': issue})
748 return self.get_article_queryset().filter(issues=issue), context
750 def article_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
751 articles = self.get_article_queryset()
753 articles = articles.filter(date__year=year)
755 articles = articles.filter(date__month=month)
757 articles = articles.filter(date__day=day)
759 article = articles.get(slug=slug)
760 except NewsletterArticle.DoesNotExist:
762 context = self.get_context()
763 context.update(extra_context or {})
764 context.update({'article': article})
765 return self.article_page.render_to_response(request, extra_context=context)
767 def issue_archive_view(self, request, extra_context):
768 if not self.issue_archive_page:
770 context = self.get_context()
771 context.update(extra_context or {})
773 'issues': self.get_issue_queryset()
775 return self.issue_archive_page.render_to_response(request, extra_context=context)
777 def title(self, obj):
780 def item_title(self, item):
783 def item_description(self, item):
784 return item.full_text
786 def item_author_name(self, item):
787 authors = list(item.authors.all())
789 return "%s and %s" % (", ".join([author.get_full_name() for author in authors[:-1]]), authors[-1].get_full_name())
791 return authors[0].get_full_name()
795 def item_pubdate(self, item):
798 def item_categories(self, item):
799 return [tag.name for tag in item.tags.all()]