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')
388 if self.feeds_enabled:
389 urlpatterns += self.feed_patterns(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)$' % self.tag_permalink_base, 'get_entries_by_tag', 'tag_page', 'get_entries_by_tag')
391 if self.tag_archive_page:
392 urlpatterns += patterns('',
393 url((r'^%s$' % self.tag_permalink_base), self.tag_archive_view, name='tag_archive')
396 if self.entry_archive_page:
397 if self.entry_permalink_style in 'DMY':
398 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_year')
399 if self.entry_permalink_style in 'DM':
400 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})/(?P<month>\d{2})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_month')
401 if self.entry_permalink_style == 'D':
402 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')
404 if self.entry_permalink_style == 'D':
405 urlpatterns += patterns('',
406 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
408 elif self.entry_permalink_style == 'M':
409 urlpatterns += patterns('',
410 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
412 elif self.entry_permalink_style == 'Y':
413 urlpatterns += patterns('',
414 url(r'^(?P<year>\d{4})/(?P<slug>[-\w]+)$', self.entry_view)
416 elif self.entry_permalink_style == 'B':
417 urlpatterns += patterns('',
418 url((r'^%s/(?P<slug>[-\w]+)$' % self.entry_permalink_base), self.entry_view)
421 urlpatterns = patterns('',
422 url(r'^(?P<slug>[-\w]+)$', self.entry_view)
426 def get_context(self):
427 return {'blog': self.blog}
429 def get_entry_queryset(self):
430 return self.blog.entries.all()
432 def get_tag_queryset(self):
433 return self.blog.entry_tags
435 def get_all_entries(self, request, extra_context=None):
436 return self.get_entry_queryset(), extra_context
438 def get_entries_by_ymd(self, request, year=None, month=None, day=None, extra_context=None):
439 if not self.entry_archive_page:
441 entries = self.get_entry_queryset()
443 entries = entries.filter(date__year=year)
445 entries = entries.filter(date__month=month)
447 entries = entries.filter(date__day=day)
449 context = extra_context or {}
450 context.update({'year': year, 'month': month, 'day': day})
451 return entries, context
453 def get_entries_by_tag(self, request, tag_slugs, extra_context=None):
454 tag_slugs = tag_slugs.replace('+', '/').split('/')
455 tags = self.get_tag_queryset().filter(slug__in=tag_slugs)
460 # Raise a 404 on an incorrect slug.
461 found_slugs = [tag.slug for tag in tags]
462 for slug in tag_slugs:
463 if slug and slug not in found_slugs:
466 entries = self.get_entry_queryset()
468 entries = entries.filter(tags=tag)
470 context = extra_context or {}
471 context.update({'tags': tags})
473 return entries, context
475 def entry_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
476 entries = self.get_entry_queryset()
478 entries = entries.filter(date__year=year)
480 entries = entries.filter(date__month=month)
482 entries = entries.filter(date__day=day)
484 entry = entries.get(slug=slug)
487 context = self.get_context()
488 context.update(extra_context or {})
489 context.update({'entry': entry})
490 return self.entry_page.render_to_response(request, extra_context=context)
492 def tag_archive_view(self, request, extra_context=None):
493 if not self.tag_archive_page:
495 context = self.get_context()
496 context.update(extra_context or {})
498 'tags': self.get_tag_queryset()
500 return self.tag_archive_page.render_to_response(request, extra_context=context)
502 def feed_view(self, get_items_attr, reverse_name):
503 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
505 def inner(request, extra_context=None, *args, **kwargs):
506 obj = self.get_object(request, *args, **kwargs)
507 feed = self.get_feed(obj, request, reverse_name)
508 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
509 self.populate_feed(feed, items, request)
511 if 'tags' in extra_context:
512 tags = extra_context['tags']
513 feed.feed['link'] = request.node.construct_url(self.reverse(obj=tags), with_domain=True, request=request, secure=request.is_secure())
515 tags = obj.entry_tags
517 feed.feed['categories'] = [tag.name for tag in tags]
519 response = HttpResponse(mimetype=feed.mime_type)
520 feed.write(response, 'utf-8')
525 def process_page_items(self, request, items):
526 if self.entries_per_page:
527 page_num = request.GET.get('page', 1)
528 paginator, paginated_page, items = paginate(items, self.entries_per_page, page_num)
530 'paginator': paginator,
531 'paginated_page': paginated_page,
532 self.item_context_var: items
536 self.item_context_var: items
538 return items, item_context
540 def title(self, obj):
543 def item_title(self, item):
546 def item_description(self, item):
549 def item_author_name(self, item):
550 return item.author.get_full_name()
552 def item_pubdate(self, item):
555 def item_categories(self, item):
556 return [tag.name for tag in item.tags.all()]
559 class Newsletter(Entity, Titled):
563 register_value_model(Newsletter)
566 class NewsletterArticle(Entity, Titled):
567 newsletter = models.ForeignKey(Newsletter, related_name='articles')
568 authors = models.ManyToManyField(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='newsletterarticles')
569 date = models.DateTimeField(default=None)
570 lede = TemplateField(null=True, blank=True, verbose_name='Summary')
571 full_text = TemplateField(db_index=True)
572 tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True)
574 def save(self, *args, **kwargs):
575 if self.date is None:
576 self.date = datetime.now()
577 super(NewsletterArticle, self).save(*args, **kwargs)
580 get_latest_by = 'date'
582 unique_together = (('newsletter', 'slug'),)
585 register_value_model(NewsletterArticle)
588 class NewsletterIssue(Entity, Titled):
589 newsletter = models.ForeignKey(Newsletter, related_name='issues')
590 numbering = models.CharField(max_length=50, help_text='For example, 04.02 for volume 4, issue 2.')
591 articles = models.ManyToManyField(NewsletterArticle, related_name='issues')
594 ordering = ['-numbering']
595 unique_together = (('newsletter', 'numbering'),)
598 register_value_model(NewsletterIssue)
601 class NewsletterView(FeedView):
602 ARTICLE_PERMALINK_STYLE_CHOICES = (
603 ('D', 'Year, month, and day'),
604 ('M', 'Year and month'),
609 newsletter = models.ForeignKey(Newsletter, related_name='newsletterviews')
611 index_page = models.ForeignKey(Page, related_name='newsletter_index_related')
612 article_page = models.ForeignKey(Page, related_name='newsletter_article_related')
613 article_archive_page = models.ForeignKey(Page, related_name='newsletter_article_archive_related', null=True, blank=True)
614 issue_page = models.ForeignKey(Page, related_name='newsletter_issue_related')
615 issue_archive_page = models.ForeignKey(Page, related_name='newsletter_issue_archive_related', null=True, blank=True)
617 article_permalink_style = models.CharField(max_length=1, choices=ARTICLE_PERMALINK_STYLE_CHOICES)
618 article_permalink_base = models.CharField(max_length=255, blank=False, default='articles')
619 issue_permalink_base = models.CharField(max_length=255, blank=False, default='issues')
621 item_context_var = 'articles'
622 object_attr = 'newsletter'
624 def __unicode__(self):
625 return "NewsletterView for %s" % self.newsletter.__unicode__()
627 def get_reverse_params(self, obj):
628 if isinstance(obj, NewsletterArticle):
629 if obj.newsletter == self.newsletter:
630 kwargs = {'slug': obj.slug}
631 if self.article_permalink_style in 'DMY':
632 kwargs.update({'year': str(obj.date.year).zfill(4)})
633 if self.article_permalink_style in 'DM':
634 kwargs.update({'month': str(obj.date.month).zfill(2)})
635 if self.article_permalink_style == 'D':
636 kwargs.update({'day': str(obj.date.day).zfill(2)})
637 return self.article_view, [], kwargs
638 elif isinstance(obj, NewsletterIssue):
639 if obj.newsletter == self.newsletter:
640 return 'issue', [], {'numbering': obj.numbering}
641 elif isinstance(obj, (date, datetime)):
643 'year': str(obj.year).zfill(4),
644 'month': str(obj.month).zfill(2),
645 'day': str(obj.day).zfill(2)
647 return 'articles_by_day', [], kwargs
648 raise ViewCanNotProvideSubpath
651 def urlpatterns(self):
652 urlpatterns = self.feed_patterns(r'^', 'get_all_articles', 'index_page', 'index') + patterns('',
653 url(r'^%s/(?P<numbering>.+)$' % self.issue_permalink_base, self.page_view('get_articles_by_issue', 'issue_page'), name='issue')
655 if self.issue_archive_page:
656 urlpatterns += patterns('',
657 url(r'^%s$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive')
659 if self.article_archive_page:
660 urlpatterns += patterns('',
661 url(r'^%s' % self.article_permalink_base, include(self.feed_patterns('get_all_articles', 'article_archive_page', 'articles')))
663 if self.article_permalink_style in 'DMY':
664 urlpatterns += self.feed_patterns(r'^%s/(?P<year>\d{4})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_year')
665 if self.article_permalink_style in 'DM':
666 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')
667 if self.article_permalink_style == 'D':
668 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')
670 if self.article_permalink_style == 'Y':
671 urlpatterns += patterns('',
672 url(r'^%s/(?P<year>\d{4})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
674 elif self.article_permalink_style == 'M':
675 urlpatterns += patterns('',
676 url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
678 elif self.article_permalink_style == 'D':
679 urlpatterns += patterns('',
680 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)
683 urlpatterns += patterns('',
684 url(r'^%s/(?P<slug>[-\w]+)$' % self.article_permalink_base, self.article_view)
689 def get_context(self):
690 return {'newsletter': self.newsletter}
692 def get_article_queryset(self):
693 return self.newsletter.articles.all()
695 def get_issue_queryset(self):
696 return self.newsletter.issues.all()
698 def get_all_articles(self, request, extra_context=None):
699 return self.get_article_queryset(), extra_context
701 def get_articles_by_ymd(self, request, year, month=None, day=None, extra_context=None):
702 articles = self.get_article_queryset().filter(date__year=year)
704 articles = articles.filter(date__month=month)
706 articles = articles.filter(date__day=day)
707 return articles, extra_context
709 def get_articles_by_issue(self, request, numbering, extra_context=None):
711 issue = self.get_issue_queryset().get(numbering=numbering)
712 except NewsletterIssue.DoesNotExist:
714 context = extra_context or {}
715 context.update({'issue': issue})
716 return self.get_article_queryset().filter(issues=issue), context
718 def article_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
719 articles = self.get_article_queryset()
721 articles = articles.filter(date__year=year)
723 articles = articles.filter(date__month=month)
725 articles = articles.filter(date__day=day)
727 article = articles.get(slug=slug)
728 except NewsletterArticle.DoesNotExist:
730 context = self.get_context()
731 context.update(extra_context or {})
732 context.update({'article': article})
733 return self.article_page.render_to_response(request, extra_context=context)
735 def issue_archive_view(self, request, extra_context):
736 if not self.issue_archive_page:
738 context = self.get_context()
739 context.update(extra_context or {})
741 'issues': self.get_issue_queryset()
743 return self.issue_archive_page.render_to_response(request, extra_context=context)
745 def title(self, obj):
748 def item_title(self, item):
751 def item_description(self, item):
752 return item.full_text
754 def item_author_name(self, item):
755 authors = list(item.authors.all())
757 return "%s and %s" % (", ".join([author.get_full_name() for author in authors[:-1]]), authors[-1].get_full_name())
759 return authors[0].get_full_name()
763 def item_pubdate(self, item):
766 def item_categories(self, item):
767 return [tag.name for tag in item.tags.all()]