1 from django.conf import settings
2 from django.conf.urls.defaults import url, patterns, include
3 from django.contrib.sites.models import Site, RequestSite
4 from django.contrib.syndication.views import add_domain
5 from django.db import models
6 from django.http import Http404, HttpResponse
7 from django.template import RequestContext, Template as DjangoTemplate
8 from django.utils import feedgenerator, tzinfo
9 from django.utils.datastructures import SortedDict
10 from django.utils.encoding import smart_unicode, force_unicode
11 from django.utils.html import escape
12 from datetime import date, datetime
13 from philo.contrib.penfield.validators import validate_pagination_count
14 from philo.exceptions import ViewCanNotProvideSubpath
15 from philo.models import Tag, Titled, Entity, MultiView, Page, register_value_model, TemplateField, Template
16 from philo.utils import paginate
23 ATOM = feedgenerator.Atom1Feed.mime_type
24 RSS = feedgenerator.Rss201rev2Feed.mime_type
26 (ATOM, feedgenerator.Atom1Feed),
27 (RSS, feedgenerator.Rss201rev2Feed),
35 class FeedView(MultiView):
37 The FeedView expects to handle a number of different feeds for the
38 same object - i.e. patterns for a blog to handle all entries or
39 just entries for a certain year/month/day.
41 This class would subclass django.contrib.syndication.views.Feed, but
42 that would make it callable, which causes problems.
44 feed_type = models.CharField(max_length=50, choices=FEED_CHOICES, default=ATOM)
45 feed_suffix = models.CharField(max_length=255, blank=False, default="feed")
46 feeds_enabled = models.BooleanField(default=True)
48 item_title_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_title_related")
49 item_description_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_description_related")
51 item_context_var = 'items'
52 object_attr = 'object'
56 def feed_patterns(self, base, get_items_attr, page_attr, reverse_name):
58 Given the name to be used to reverse this view and the names of
59 the attributes for the function that fetches the objects, returns
60 patterns suitable for inclusion in urlpatterns.
62 urlpatterns = patterns('')
63 if self.feeds_enabled:
64 feed_reverse_name = "%s_feed" % reverse_name
65 feed_view = self.feed_view(get_items_attr, feed_reverse_name)
66 feed_pattern = r'%s%s%s$' % (base, (base and base[-1] != "^") and "/" or "", self.feed_suffix)
67 urlpatterns += patterns('',
68 url(feed_pattern, feed_view, name=feed_reverse_name),
70 urlpatterns += patterns('',
71 url(r"%s$" % base, self.page_view(get_items_attr, page_attr), name=reverse_name)
75 def get_object(self, request, **kwargs):
76 return getattr(self, self.object_attr)
78 def feed_view(self, get_items_attr, reverse_name):
80 Returns a view function that renders a list of items as a feed.
82 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
84 def inner(request, extra_context=None, *args, **kwargs):
85 obj = self.get_object(request, *args, **kwargs)
86 feed = self.get_feed(obj, request, reverse_name)
87 items, xxx = get_items(request, extra_context=extra_context, *args, **kwargs)
88 self.populate_feed(feed, items, request)
90 response = HttpResponse(mimetype=feed.mime_type)
91 feed.write(response, 'utf-8')
96 def page_view(self, get_items_attr, page_attr):
98 Returns a view function that renders a list of items as a page.
100 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
101 page = isinstance(page_attr, Page) and page_attr or getattr(self, page_attr)
103 def inner(request, extra_context=None, *args, **kwargs):
104 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
105 items, item_context = self.process_page_items(request, items)
107 context = self.get_context()
108 context.update(extra_context or {})
109 context.update(item_context or {})
111 return page.render_to_response(request, extra_context=context)
114 def process_page_items(self, request, items):
116 Hook for handling any extra processing of items based on a
117 request, such as pagination or searching. This method is
118 expected to return a list of items and a dictionary to be
119 added to the page context.
122 self.item_context_var: items
124 return items, item_context
126 def get_feed_type(self, request):
127 feed_type = self.feed_type
128 if feed_type not in FEEDS:
129 feed_type = FEEDS.keys()[0]
130 accept = request.META.get('HTTP_ACCEPT')
131 if accept and feed_type not in accept and "*/*" not in accept and "%s/*" % feed_type.split("/")[0] not in accept:
132 # Wups! They aren't accepting the chosen format. Is there another format we can use?
134 feed_type = mimeparse.best_match(FEEDS.keys(), accept)
136 for feed_type in FEEDS.keys():
137 if feed_type in accept or "%s/*" % feed_type.split("/")[0] in accept:
143 return HttpResponse(status=406)
144 return FEEDS[feed_type]
146 def get_feed(self, obj, request, reverse_name):
148 Returns an unpopulated feedgenerator.DefaultFeed object for this object.
151 current_site = Site.objects.get_current()
152 except Site.DoesNotExist:
153 current_site = RequestSite(request)
155 feed_type = self.get_feed_type(request)
157 link = node.get_absolute_url(with_domain=True, request=request, secure=request.is_secure())
160 title = self.__get_dynamic_attr('title', obj),
161 subtitle = self.__get_dynamic_attr('subtitle', obj),
163 description = self.__get_dynamic_attr('description', obj),
164 language = settings.LANGUAGE_CODE.decode(),
165 feed_url = add_domain(
167 self.__get_dynamic_attr('feed_url', obj) or node.construct_url(node.subpath, with_domain=True, request=request, secure=request.is_secure()),
170 author_name = self.__get_dynamic_attr('author_name', obj),
171 author_link = self.__get_dynamic_attr('author_link', obj),
172 author_email = self.__get_dynamic_attr('author_email', obj),
173 categories = self.__get_dynamic_attr('categories', obj),
174 feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
175 feed_guid = self.__get_dynamic_attr('feed_guid', obj),
176 ttl = self.__get_dynamic_attr('ttl', obj),
177 **self.feed_extra_kwargs(obj)
181 def populate_feed(self, feed, items, request):
182 if self.item_title_template:
183 title_template = DjangoTemplate(self.item_title_template.code)
185 title_template = None
186 if self.item_description_template:
187 description_template = DjangoTemplate(self.item_description_template.code)
189 description_template = None
193 current_site = Site.objects.get_current()
194 except Site.DoesNotExist:
195 current_site = RequestSite(request)
198 if title_template is not None:
199 title = title_template.render(RequestContext(request, {'obj': item}))
201 title = self.__get_dynamic_attr('item_title', item)
202 if description_template is not None:
203 description = description_template.render(RequestContext(request, {'obj': item}))
205 description = self.__get_dynamic_attr('item_description', item)
207 link = node.construct_url(self.reverse(obj=item), with_domain=True, request=request, secure=request.is_secure())
210 enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
212 enc = feedgenerator.Enclosure(
213 url = smart_unicode(add_domain(
218 length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
219 mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
221 author_name = self.__get_dynamic_attr('item_author_name', item)
222 if author_name is not None:
223 author_email = self.__get_dynamic_attr('item_author_email', item)
224 author_link = self.__get_dynamic_attr('item_author_link', item)
226 author_email = author_link = None
228 pubdate = self.__get_dynamic_attr('item_pubdate', item)
229 if pubdate and not pubdate.tzinfo:
230 ltz = tzinfo.LocalTimezone(pubdate)
231 pubdate = pubdate.replace(tzinfo=ltz)
236 description = description,
237 unique_id = self.__get_dynamic_attr('item_guid', item, link),
240 author_name = author_name,
241 author_email = author_email,
242 author_link = author_link,
243 categories = self.__get_dynamic_attr('item_categories', item),
244 item_copyright = self.__get_dynamic_attr('item_copyright', item),
245 **self.item_extra_kwargs(item)
248 def __get_dynamic_attr(self, attname, obj, default=None):
250 attr = getattr(self, attname)
251 except AttributeError:
254 # Check func_code.co_argcount rather than try/excepting the
255 # function and catching the TypeError, because something inside
256 # the function may raise the TypeError. This technique is more
258 if hasattr(attr, 'func_code'):
259 argcount = attr.func_code.co_argcount
261 argcount = attr.__call__.func_code.co_argcount
262 if argcount == 2: # one argument is 'self'
268 def feed_extra_kwargs(self, obj):
270 Returns an extra keyword arguments dictionary that is used when
271 initializing the feed generator.
275 def item_extra_kwargs(self, item):
277 Returns an extra keyword arguments dictionary that is used with
278 the `add_item` call of the feed generator.
282 def item_title(self, item):
283 return escape(force_unicode(item))
285 def item_description(self, item):
286 return force_unicode(item)
292 class Blog(Entity, Titled):
294 def entry_tags(self):
295 """ Returns a QuerySet of Tags that are used on any entries in this blog. """
296 return Tag.objects.filter(blogentries__blog=self).distinct()
299 def entry_dates(self):
300 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')}
304 register_value_model(Blog)
307 class BlogEntry(Entity, Titled):
308 blog = models.ForeignKey(Blog, related_name='entries', blank=True, null=True)
309 author = models.ForeignKey(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='blogentries')
310 date = models.DateTimeField(default=None)
311 content = models.TextField()
312 excerpt = models.TextField(blank=True, null=True)
313 tags = models.ManyToManyField(Tag, related_name='blogentries', blank=True, null=True)
315 def save(self, *args, **kwargs):
316 if self.date is None:
317 self.date = datetime.now()
318 super(BlogEntry, self).save(*args, **kwargs)
322 verbose_name_plural = "blog entries"
323 get_latest_by = "date"
326 register_value_model(BlogEntry)
329 class BlogView(FeedView):
330 ENTRY_PERMALINK_STYLE_CHOICES = (
331 ('D', 'Year, month, and day'),
332 ('M', 'Year and month'),
334 ('B', 'Custom base'),
338 blog = models.ForeignKey(Blog, related_name='blogviews')
340 index_page = models.ForeignKey(Page, related_name='blog_index_related')
341 entry_page = models.ForeignKey(Page, related_name='blog_entry_related')
342 # TODO: entry_archive is misleading. Rename to ymd_page or timespan_page.
343 entry_archive_page = models.ForeignKey(Page, related_name='blog_entry_archive_related', null=True, blank=True)
344 tag_page = models.ForeignKey(Page, related_name='blog_tag_related')
345 tag_archive_page = models.ForeignKey(Page, related_name='blog_tag_archive_related', null=True, blank=True)
346 entries_per_page = models.IntegerField(blank=True, validators=[validate_pagination_count], null=True)
348 entry_permalink_style = models.CharField(max_length=1, choices=ENTRY_PERMALINK_STYLE_CHOICES)
349 entry_permalink_base = models.CharField(max_length=255, blank=False, default='entries')
350 tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags')
352 item_context_var = 'entries'
355 def __unicode__(self):
356 return u'BlogView for %s' % self.blog.title
358 def get_reverse_params(self, obj):
359 if isinstance(obj, BlogEntry):
360 if obj.blog == self.blog:
361 kwargs = {'slug': obj.slug}
362 if self.entry_permalink_style in 'DMY':
363 kwargs.update({'year': str(obj.date.year).zfill(4)})
364 if self.entry_permalink_style in 'DM':
365 kwargs.update({'month': str(obj.date.month).zfill(2)})
366 if self.entry_permalink_style == 'D':
367 kwargs.update({'day': str(obj.date.day).zfill(2)})
368 return self.entry_view, [], kwargs
369 elif isinstance(obj, Tag) or (isinstance(obj, models.query.QuerySet) and obj.model == Tag and obj):
370 if isinstance(obj, Tag):
372 slugs = [tag.slug for tag in obj if tag in self.get_tag_queryset()]
374 return 'entries_by_tag', [], {'tag_slugs': "/".join(slugs)}
375 elif isinstance(obj, (date, datetime)):
377 'year': str(obj.year).zfill(4),
378 'month': str(obj.month).zfill(2),
379 'day': str(obj.day).zfill(2)
381 return 'entries_by_day', [], kwargs
382 raise ViewCanNotProvideSubpath
385 def urlpatterns(self):
386 urlpatterns = self.feed_patterns(r'^', 'get_all_entries', 'index_page', 'index') +\
387 self.feed_patterns(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)$' % self.tag_permalink_base, 'get_entries_by_tag', 'tag_page', 'entries_by_tag')
389 if self.tag_archive_page:
390 urlpatterns += patterns('',
391 url((r'^%s$' % self.tag_permalink_base), self.tag_archive_view, name='tag_archive')
394 if self.entry_archive_page:
395 if self.entry_permalink_style in 'DMY':
396 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_year')
397 if self.entry_permalink_style in 'DM':
398 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})/(?P<month>\d{2})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_month')
399 if self.entry_permalink_style == 'D':
400 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')
402 if self.entry_permalink_style == 'D':
403 urlpatterns += patterns('',
404 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
406 elif self.entry_permalink_style == 'M':
407 urlpatterns += patterns('',
408 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
410 elif self.entry_permalink_style == 'Y':
411 urlpatterns += patterns('',
412 url(r'^(?P<year>\d{4})/(?P<slug>[-\w]+)$', self.entry_view)
414 elif self.entry_permalink_style == 'B':
415 urlpatterns += patterns('',
416 url((r'^%s/(?P<slug>[-\w]+)$' % self.entry_permalink_base), self.entry_view)
419 urlpatterns = patterns('',
420 url(r'^(?P<slug>[-\w]+)$', self.entry_view)
424 def get_context(self):
425 return {'blog': self.blog}
427 def get_entry_queryset(self):
428 return self.blog.entries.all()
430 def get_tag_queryset(self):
431 return self.blog.entry_tags
433 def get_all_entries(self, request, extra_context=None):
434 return self.get_entry_queryset(), extra_context
436 def get_entries_by_ymd(self, request, year=None, month=None, day=None, extra_context=None):
437 if not self.entry_archive_page:
439 entries = self.get_entry_queryset()
441 entries = entries.filter(date__year=year)
443 entries = entries.filter(date__month=month)
445 entries = entries.filter(date__day=day)
447 context = extra_context or {}
448 context.update({'year': year, 'month': month, 'day': day})
449 return entries, context
451 def get_entries_by_tag(self, request, tag_slugs, extra_context=None):
452 tag_slugs = tag_slugs.replace('+', '/').split('/')
453 tags = self.get_tag_queryset().filter(slug__in=tag_slugs)
458 # Raise a 404 on an incorrect slug.
459 found_slugs = [tag.slug for tag in tags]
460 for slug in tag_slugs:
461 if slug and slug not in found_slugs:
464 entries = self.get_entry_queryset()
466 entries = entries.filter(tags=tag)
468 context = extra_context or {}
469 context.update({'tags': tags})
471 return entries, context
473 def entry_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
474 entries = self.get_entry_queryset()
476 entries = entries.filter(date__year=year)
478 entries = entries.filter(date__month=month)
480 entries = entries.filter(date__day=day)
482 entry = entries.get(slug=slug)
485 context = self.get_context()
486 context.update(extra_context or {})
487 context.update({'entry': entry})
488 return self.entry_page.render_to_response(request, extra_context=context)
490 def tag_archive_view(self, request, extra_context=None):
491 if not self.tag_archive_page:
493 context = self.get_context()
494 context.update(extra_context or {})
496 'tags': self.get_tag_queryset()
498 return self.tag_archive_page.render_to_response(request, extra_context=context)
500 def feed_view(self, get_items_attr, reverse_name):
501 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
503 def inner(request, extra_context=None, *args, **kwargs):
504 obj = self.get_object(request, *args, **kwargs)
505 feed = self.get_feed(obj, request, reverse_name)
506 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
507 self.populate_feed(feed, items, request)
509 if 'tags' in extra_context:
510 tags = extra_context['tags']
511 feed.feed['link'] = request.node.construct_url(self.reverse(obj=tags), with_domain=True, request=request, secure=request.is_secure())
513 tags = obj.entry_tags
515 feed.feed['categories'] = [tag.name for tag in tags]
517 response = HttpResponse(mimetype=feed.mime_type)
518 feed.write(response, 'utf-8')
523 def process_page_items(self, request, items):
524 if self.entries_per_page:
525 page_num = request.GET.get('page', 1)
526 paginator, paginated_page, items = paginate(items, self.entries_per_page, page_num)
528 'paginator': paginator,
529 'paginated_page': paginated_page,
530 self.item_context_var: items
534 self.item_context_var: items
536 return items, item_context
538 def title(self, obj):
541 def item_title(self, item):
544 def item_description(self, item):
547 def item_author_name(self, item):
548 return item.author.get_full_name()
550 def item_pubdate(self, item):
553 def item_categories(self, item):
554 return [tag.name for tag in item.tags.all()]
557 class Newsletter(Entity, Titled):
561 register_value_model(Newsletter)
564 class NewsletterArticle(Entity, Titled):
565 newsletter = models.ForeignKey(Newsletter, related_name='articles')
566 authors = models.ManyToManyField(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='newsletterarticles')
567 date = models.DateTimeField(default=None)
568 lede = TemplateField(null=True, blank=True, verbose_name='Summary')
569 full_text = TemplateField(db_index=True)
570 tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True)
572 def save(self, *args, **kwargs):
573 if self.date is None:
574 self.date = datetime.now()
575 super(NewsletterArticle, self).save(*args, **kwargs)
578 get_latest_by = 'date'
580 unique_together = (('newsletter', 'slug'),)
583 register_value_model(NewsletterArticle)
586 class NewsletterIssue(Entity, Titled):
587 newsletter = models.ForeignKey(Newsletter, related_name='issues')
588 numbering = models.CharField(max_length=50, help_text='For example, 04.02 for volume 4, issue 2.')
589 articles = models.ManyToManyField(NewsletterArticle, related_name='issues')
592 ordering = ['-numbering']
593 unique_together = (('newsletter', 'numbering'),)
596 register_value_model(NewsletterIssue)
599 class NewsletterView(FeedView):
600 ARTICLE_PERMALINK_STYLE_CHOICES = (
601 ('D', 'Year, month, and day'),
602 ('M', 'Year and month'),
607 newsletter = models.ForeignKey(Newsletter, related_name='newsletterviews')
609 index_page = models.ForeignKey(Page, related_name='newsletter_index_related')
610 article_page = models.ForeignKey(Page, related_name='newsletter_article_related')
611 article_archive_page = models.ForeignKey(Page, related_name='newsletter_article_archive_related', null=True, blank=True)
612 issue_page = models.ForeignKey(Page, related_name='newsletter_issue_related')
613 issue_archive_page = models.ForeignKey(Page, related_name='newsletter_issue_archive_related', null=True, blank=True)
615 article_permalink_style = models.CharField(max_length=1, choices=ARTICLE_PERMALINK_STYLE_CHOICES)
616 article_permalink_base = models.CharField(max_length=255, blank=False, default='articles')
617 issue_permalink_base = models.CharField(max_length=255, blank=False, default='issues')
619 item_context_var = 'articles'
620 object_attr = 'newsletter'
622 def __unicode__(self):
623 return "NewsletterView for %s" % self.newsletter.__unicode__()
625 def get_reverse_params(self, obj):
626 if isinstance(obj, NewsletterArticle):
627 if obj.newsletter == self.newsletter:
628 kwargs = {'slug': obj.slug}
629 if self.article_permalink_style in 'DMY':
630 kwargs.update({'year': str(obj.date.year).zfill(4)})
631 if self.article_permalink_style in 'DM':
632 kwargs.update({'month': str(obj.date.month).zfill(2)})
633 if self.article_permalink_style == 'D':
634 kwargs.update({'day': str(obj.date.day).zfill(2)})
635 return self.article_view, [], kwargs
636 elif isinstance(obj, NewsletterIssue):
637 if obj.newsletter == self.newsletter:
638 return 'issue', [], {'numbering': obj.numbering}
639 elif isinstance(obj, (date, datetime)):
641 'year': str(obj.year).zfill(4),
642 'month': str(obj.month).zfill(2),
643 'day': str(obj.day).zfill(2)
645 return 'articles_by_day', [], kwargs
646 raise ViewCanNotProvideSubpath
649 def urlpatterns(self):
650 urlpatterns = self.feed_patterns(r'^', 'get_all_articles', 'index_page', 'index') + patterns('',
651 url(r'^%s/(?P<numbering>.+)$' % self.issue_permalink_base, self.page_view('get_articles_by_issue', 'issue_page'), name='issue')
653 if self.issue_archive_page:
654 urlpatterns += patterns('',
655 url(r'^%s$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive')
657 if self.article_archive_page:
658 urlpatterns += patterns('',
659 url(r'^%s' % self.article_permalink_base, include(self.feed_patterns('get_all_articles', 'article_archive_page', 'articles')))
661 if self.article_permalink_style in 'DMY':
662 urlpatterns += self.feed_patterns(r'^%s/(?P<year>\d{4})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_year')
663 if self.article_permalink_style in 'DM':
664 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')
665 if self.article_permalink_style == 'D':
666 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')
668 if self.article_permalink_style == 'Y':
669 urlpatterns += patterns('',
670 url(r'^%s/(?P<year>\d{4})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
672 elif self.article_permalink_style == 'M':
673 urlpatterns += patterns('',
674 url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
676 elif self.article_permalink_style == 'D':
677 urlpatterns += patterns('',
678 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)
681 urlpatterns += patterns('',
682 url(r'^%s/(?P<slug>[-\w]+)$' % self.article_permalink_base, self.article_view)
687 def get_context(self):
688 return {'newsletter': self.newsletter}
690 def get_article_queryset(self):
691 return self.newsletter.articles.all()
693 def get_issue_queryset(self):
694 return self.newsletter.issues.all()
696 def get_all_articles(self, request, extra_context=None):
697 return self.get_article_queryset(), extra_context
699 def get_articles_by_ymd(self, request, year, month=None, day=None, extra_context=None):
700 articles = self.get_article_queryset().filter(date__year=year)
702 articles = articles.filter(date__month=month)
704 articles = articles.filter(date__day=day)
705 return articles, extra_context
707 def get_articles_by_issue(self, request, numbering, extra_context=None):
709 issue = self.get_issue_queryset().get(numbering=numbering)
710 except NewsletterIssue.DoesNotExist:
712 context = extra_context or {}
713 context.update({'issue': issue})
714 return self.get_article_queryset().filter(issues=issue), context
716 def article_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
717 articles = self.get_article_queryset()
719 articles = articles.filter(date__year=year)
721 articles = articles.filter(date__month=month)
723 articles = articles.filter(date__day=day)
725 article = articles.get(slug=slug)
726 except NewsletterArticle.DoesNotExist:
728 context = self.get_context()
729 context.update(extra_context or {})
730 context.update({'article': article})
731 return self.article_page.render_to_response(request, extra_context=context)
733 def issue_archive_view(self, request, extra_context):
734 if not self.issue_archive_page:
736 context = self.get_context()
737 context.update(extra_context or {})
739 'issues': self.get_issue_queryset()
741 return self.issue_archive_page.render_to_response(request, extra_context=context)
743 def title(self, obj):
746 def item_title(self, item):
749 def item_description(self, item):
750 return item.full_text
752 def item_author_name(self, item):
753 authors = list(item.authors.all())
755 return "%s and %s" % (", ".join([author.get_full_name() for author in authors[:-1]]), authors[-1].get_full_name())
757 return authors[0].get_full_name()
761 def item_pubdate(self, item):
764 def item_categories(self, item):
765 return [tag.name for tag in item.tags.all()]