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
22 ATOM = feedgenerator.Atom1Feed.mime_type
23 RSS = feedgenerator.Rss201rev2Feed.mime_type
25 (ATOM, feedgenerator.Atom1Feed),
26 (RSS, feedgenerator.Rss201rev2Feed),
34 class FeedView(MultiView):
36 The FeedView expects to handle a number of different feeds for the
37 same object - i.e. patterns for a blog to handle all entries or
38 just entries for a certain year/month/day.
40 This class would subclass django.contrib.syndication.views.Feed, but
41 that would make it callable, which causes problems.
43 feed_type = models.CharField(max_length=50, choices=FEED_CHOICES, default=ATOM)
44 feed_suffix = models.CharField(max_length=255, blank=False, default="feed")
45 feeds_enabled = models.BooleanField(default=True)
47 item_title_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_title_related")
48 item_description_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_description_related")
50 item_context_var = 'items'
51 object_attr = 'object'
55 def feed_patterns(self, get_items_attr, page_attr, reverse_name):
57 Given the name to be used to reverse this view and the names of
58 the attributes for the function that fetches the objects, returns
59 patterns suitable for inclusion in urlpatterns.
61 urlpatterns = patterns('',
62 url(r'^$', self.page_view(get_items_attr, page_attr), name=reverse_name)
64 if self.feeds_enabled:
65 feed_reverse_name = "%s_feed" % reverse_name
66 urlpatterns += patterns('',
67 # include an optional slash at the beginning in case someone uses an include
68 # with a base that doesn't include a slash.
69 url(r'^/?%s$' % self.feed_suffix, self.feed_view(get_items_attr, feed_reverse_name), name=feed_reverse_name),
73 def get_object(self, request, **kwargs):
74 return getattr(self, self.object_attr)
76 def feed_view(self, get_items_attr, reverse_name):
78 Returns a view function that renders a list of items as a feed.
80 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
82 def inner(request, extra_context=None, *args, **kwargs):
83 obj = self.get_object(request, *args, **kwargs)
84 feed = self.get_feed(obj, request, reverse_name)
85 items, xxx = get_items(request, extra_context=extra_context, *args, **kwargs)
86 self.populate_feed(feed, items, request)
88 response = HttpResponse(mimetype=feed.mime_type)
89 feed.write(response, 'utf-8')
94 def page_view(self, get_items_attr, page_attr):
96 Returns a view function that renders a list of items as a page.
98 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
99 page = isinstance(page_attr, Page) and page_attr or getattr(self, page_attr)
101 def inner(request, extra_context=None, *args, **kwargs):
102 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
103 items, item_context = self.process_page_items(request, items)
105 context = self.get_context()
106 context.update(extra_context or {})
107 context.update(item_context or {})
109 return page.render_to_response(request, extra_context=context)
112 def process_page_items(self, request, items):
114 Hook for handling any extra processing of items based on a
115 request, such as pagination or searching. This method is
116 expected to return a list of items and a dictionary to be
117 added to the page context.
120 self.item_context_var: items
122 return items, item_context
124 def get_feed_type(self, request):
125 feed_type = self.feed_type
126 accept = request.META.get('HTTP_ACCEPT')
127 if accept and feed_type not in accept and "*/*" not in accept and "%s/*" % feed_type.split("/")[0] not in accept:
128 # Wups! They aren't accepting the chosen format. Is there another format we can use?
130 feed_type = mimeparse.best_match(FEEDS.keys(), accept)
132 for feed_type in FEEDS.keys():
133 if feed_type in accept or "%s/*" % feed_type.split("/")[0] in accept:
139 return HttpResponse(status=406)
140 return FEEDS[feed_type]
142 def get_feed(self, obj, request, reverse_name):
144 Returns an unpopulated feedgenerator.DefaultFeed object for this object.
147 current_site = Site.objects.get_current()
148 except Site.DoesNotExist:
149 current_site = RequestSite(request)
151 feed_type = self.get_feed_type(request)
153 link = node.get_absolute_url(with_domain=True, request=request, secure=request.is_secure())
156 title = self.__get_dynamic_attr('title', obj),
157 subtitle = self.__get_dynamic_attr('subtitle', obj),
159 description = self.__get_dynamic_attr('description', obj),
160 language = settings.LANGUAGE_CODE.decode(),
161 feed_url = add_domain(
163 self.__get_dynamic_attr('feed_url', obj) or node.construct_url(node.subpath, with_domain=True, request=request, secure=request.is_secure()),
166 author_name = self.__get_dynamic_attr('author_name', obj),
167 author_link = self.__get_dynamic_attr('author_link', obj),
168 author_email = self.__get_dynamic_attr('author_email', obj),
169 categories = self.__get_dynamic_attr('categories', obj),
170 feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
171 feed_guid = self.__get_dynamic_attr('feed_guid', obj),
172 ttl = self.__get_dynamic_attr('ttl', obj),
173 **self.feed_extra_kwargs(obj)
177 def populate_feed(self, feed, items, request):
178 if self.item_title_template:
179 title_template = DjangoTemplate(self.item_title_template.code)
181 title_template = None
182 if self.item_description_template:
183 description_template = DjangoTemplate(self.item_description_template.code)
185 description_template = None
189 current_site = Site.objects.get_current()
190 except Site.DoesNotExist:
191 current_site = RequestSite(request)
194 if title_template is not None:
195 title = title_template.render(RequestContext(request, {'obj': item}))
197 title = self.__get_dynamic_attr('item_title', item)
198 if description_template is not None:
199 description = description_template.render(RequestContext(request, {'obj': item}))
201 description = self.__get_dynamic_attr('item_description', item)
203 link = node.construct_url(self.reverse(obj=item), with_domain=True, request=request, secure=request.is_secure())
206 enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
208 enc = feedgenerator.Enclosure(
209 url = smart_unicode(add_domain(
214 length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
215 mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
217 author_name = self.__get_dynamic_attr('item_author_name', item)
218 if author_name is not None:
219 author_email = self.__get_dynamic_attr('item_author_email', item)
220 author_link = self.__get_dynamic_attr('item_author_link', item)
222 author_email = author_link = None
224 pubdate = self.__get_dynamic_attr('item_pubdate', item)
225 if pubdate and not pubdate.tzinfo:
226 ltz = tzinfo.LocalTimezone(pubdate)
227 pubdate = pubdate.replace(tzinfo=ltz)
232 description = description,
233 unique_id = self.__get_dynamic_attr('item_guid', item, link),
236 author_name = author_name,
237 author_email = author_email,
238 author_link = author_link,
239 categories = self.__get_dynamic_attr('item_categories', item),
240 item_copyright = self.__get_dynamic_attr('item_copyright', item),
241 **self.item_extra_kwargs(item)
244 def __get_dynamic_attr(self, attname, obj, default=None):
246 attr = getattr(self, attname)
247 except AttributeError:
250 # Check func_code.co_argcount rather than try/excepting the
251 # function and catching the TypeError, because something inside
252 # the function may raise the TypeError. This technique is more
254 if hasattr(attr, 'func_code'):
255 argcount = attr.func_code.co_argcount
257 argcount = attr.__call__.func_code.co_argcount
258 if argcount == 2: # one argument is 'self'
264 def feed_extra_kwargs(self, obj):
266 Returns an extra keyword arguments dictionary that is used when
267 initializing the feed generator.
271 def item_extra_kwargs(self, item):
273 Returns an extra keyword arguments dictionary that is used with
274 the `add_item` call of the feed generator.
278 def item_title(self, item):
279 return escape(force_unicode(item))
281 def item_description(self, item):
282 return force_unicode(item)
288 class Blog(Entity, Titled):
290 def entry_tags(self):
291 """ Returns a QuerySet of Tags that are used on any entries in this blog. """
292 return Tag.objects.filter(blogentries__blog=self).distinct()
295 def entry_dates(self):
296 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')}
300 register_value_model(Blog)
303 class BlogEntry(Entity, Titled):
304 blog = models.ForeignKey(Blog, related_name='entries', blank=True, null=True)
305 author = models.ForeignKey(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='blogentries')
306 date = models.DateTimeField(default=None)
307 content = models.TextField()
308 excerpt = models.TextField(blank=True, null=True)
309 tags = models.ManyToManyField(Tag, related_name='blogentries', blank=True, null=True)
311 def save(self, *args, **kwargs):
312 if self.date is None:
313 self.date = datetime.now()
314 super(BlogEntry, self).save(*args, **kwargs)
318 verbose_name_plural = "blog entries"
319 get_latest_by = "date"
322 register_value_model(BlogEntry)
325 class BlogView(FeedView):
326 ENTRY_PERMALINK_STYLE_CHOICES = (
327 ('D', 'Year, month, and day'),
328 ('M', 'Year and month'),
330 ('B', 'Custom base'),
334 blog = models.ForeignKey(Blog, related_name='blogviews')
336 index_page = models.ForeignKey(Page, related_name='blog_index_related')
337 entry_page = models.ForeignKey(Page, related_name='blog_entry_related')
338 # TODO: entry_archive is misleading. Rename to ymd_page or timespan_page.
339 entry_archive_page = models.ForeignKey(Page, related_name='blog_entry_archive_related', null=True, blank=True)
340 tag_page = models.ForeignKey(Page, related_name='blog_tag_related')
341 tag_archive_page = models.ForeignKey(Page, related_name='blog_tag_archive_related', null=True, blank=True)
342 entries_per_page = models.IntegerField(blank=True, validators=[validate_pagination_count], null=True)
344 entry_permalink_style = models.CharField(max_length=1, choices=ENTRY_PERMALINK_STYLE_CHOICES)
345 entry_permalink_base = models.CharField(max_length=255, blank=False, default='entries')
346 tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags')
348 item_context_var = 'entries'
351 def __unicode__(self):
352 return u'BlogView for %s' % self.blog.title
354 def get_reverse_params(self, obj):
355 if isinstance(obj, BlogEntry):
356 if obj.blog == self.blog:
357 kwargs = {'slug': obj.slug}
358 if self.entry_permalink_style in 'DMY':
359 kwargs.update({'year': str(obj.date.year).zfill(4)})
360 if self.entry_permalink_style in 'DM':
361 kwargs.update({'month': str(obj.date.month).zfill(2)})
362 if self.entry_permalink_style == 'D':
363 kwargs.update({'day': str(obj.date.day).zfill(2)})
364 return self.entry_view, [], kwargs
365 elif isinstance(obj, Tag) or (isinstance(obj, models.query.QuerySet) and obj.model == Tag and obj):
366 if isinstance(obj, Tag):
368 slugs = [tag.slug for tag in obj if tag in self.get_tag_queryset()]
370 return 'entries_by_tag', [], {'tag_slugs': "/".join(slugs)}
371 elif isinstance(obj, (date, datetime)):
373 'year': str(obj.year).zfill(4),
374 'month': str(obj.month).zfill(2),
375 'day': str(obj.day).zfill(2)
377 return 'entries_by_day', [], kwargs
378 raise ViewCanNotProvideSubpath
381 def urlpatterns(self):
382 urlpatterns = patterns('',
383 url(r'^', include(self.feed_patterns('get_all_entries', 'index_page', 'index'))),
385 if self.feeds_enabled:
386 urlpatterns += patterns('',
387 url(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)/%s$' % (self.tag_permalink_base, self.feed_suffix), self.feed_view('get_entries_by_tag', 'entries_by_tag_feed'), name='entries_by_tag_feed'),
389 urlpatterns += patterns('',
390 url(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)$' % self.tag_permalink_base, self.page_view('get_entries_by_tag', 'tag_page'), name='entries_by_tag')
392 if self.tag_archive_page:
393 urlpatterns += patterns('',
394 url((r'^%s$' % self.tag_permalink_base), self.tag_archive_view, name='tag_archive')
397 if self.entry_archive_page:
398 if self.entry_permalink_style in 'DMY':
399 urlpatterns += patterns('',
400 url(r'^(?P<year>\d{4})', include(self.feed_patterns('get_entries_by_ymd', 'entry_archive_page', 'entries_by_year')))
402 if self.entry_permalink_style in 'DM':
403 urlpatterns += patterns('',
404 url(r'^(?P<year>\d{4})/(?P<month>\d{2})$', include(self.feed_patterns('get_entries_by_ymd', 'entry_archive_page', 'entries_by_month'))),
406 if self.entry_permalink_style == 'D':
407 urlpatterns += patterns('',
408 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})$', include(self.feed_patterns('get_entries_by_ymd', 'entry_archive_page', 'entries_by_day')))
411 if self.entry_permalink_style == 'D':
412 urlpatterns += patterns('',
413 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
415 elif self.entry_permalink_style == 'M':
416 urlpatterns += patterns('',
417 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
419 elif self.entry_permalink_style == 'Y':
420 urlpatterns += patterns('',
421 url(r'^(?P<year>\d{4})/(?P<slug>[-\w]+)$', self.entry_view)
423 elif self.entry_permalink_style == 'B':
424 urlpatterns += patterns('',
425 url((r'^%s/(?P<slug>[-\w]+)$' % self.entry_permalink_base), self.entry_view)
428 urlpatterns = patterns('',
429 url(r'^(?P<slug>[-\w]+)$', self.entry_view)
433 def get_context(self):
434 return {'blog': self.blog}
436 def get_entry_queryset(self):
437 return self.blog.entries.all()
439 def get_tag_queryset(self):
440 return self.blog.entry_tags
442 def get_all_entries(self, request, extra_context=None):
443 return self.get_entry_queryset(), extra_context
445 def get_entries_by_ymd(self, request, year=None, month=None, day=None, extra_context=None):
446 if not self.entry_archive_page:
448 entries = self.get_entry_queryset()
450 entries = entries.filter(date__year=year)
452 entries = entries.filter(date__month=month)
454 entries = entries.filter(date__day=day)
456 context = extra_context or {}
457 context.update({'year': year, 'month': month, 'day': day})
458 return entries, context
460 def get_entries_by_tag(self, request, tag_slugs, extra_context=None):
461 tag_slugs = tag_slugs.replace('+', '/').split('/')
462 tags = self.get_tag_queryset().filter(slug__in=tag_slugs)
467 # Raise a 404 on an incorrect slug.
468 found_slugs = [tag.slug for tag in tags]
469 for slug in tag_slugs:
470 if slug and slug not in found_slugs:
473 entries = self.get_entry_queryset()
475 entries = entries.filter(tags=tag)
477 context = extra_context or {}
478 context.update({'tags': tags})
480 return entries, context
482 def entry_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
483 entries = self.get_entry_queryset()
485 entries = entries.filter(date__year=year)
487 entries = entries.filter(date__month=month)
489 entries = entries.filter(date__day=day)
491 entry = entries.get(slug=slug)
494 context = self.get_context()
495 context.update(extra_context or {})
496 context.update({'entry': entry})
497 return self.entry_page.render_to_response(request, extra_context=context)
499 def tag_archive_view(self, request, extra_context=None):
500 if not self.tag_archive_page:
502 context = self.get_context()
503 context.update(extra_context or {})
505 'tags': self.get_tag_queryset()
507 return self.tag_archive_page.render_to_response(request, extra_context=context)
509 def feed_view(self, get_items_attr, reverse_name):
510 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
512 def inner(request, extra_context=None, *args, **kwargs):
513 obj = self.get_object(request, *args, **kwargs)
514 feed = self.get_feed(obj, request, reverse_name)
515 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
516 self.populate_feed(feed, items, request)
518 if 'tags' in extra_context:
519 tags = extra_context['tags']
520 feed.feed['link'] = request.node.construct_url(self.reverse(obj=tags), with_domain=True, request=request, secure=request.is_secure())
522 tags = obj.entry_tags
524 feed.feed['categories'] = [tag.name for tag in tags]
526 response = HttpResponse(mimetype=feed.mime_type)
527 feed.write(response, 'utf-8')
532 def process_page_items(self, request, items):
533 if self.entries_per_page:
534 page_num = request.GET.get('page', 1)
535 paginator, paginated_page, items = paginate(items, self.entries_per_page, page_num)
537 'paginator': paginator,
538 'paginated_page': paginated_page,
539 self.item_context_var: items
543 self.item_context_var: items
545 return items, item_context
547 def title(self, obj):
550 def item_title(self, item):
553 def item_description(self, item):
556 def item_author_name(self, item):
557 return item.author.get_full_name()
559 def item_pubdate(self, item):
562 def item_categories(self, item):
563 return [tag.name for tag in item.tags.all()]
566 class Newsletter(Entity, Titled):
570 register_value_model(Newsletter)
573 class NewsletterArticle(Entity, Titled):
574 newsletter = models.ForeignKey(Newsletter, related_name='articles')
575 authors = models.ManyToManyField(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='newsletterarticles')
576 date = models.DateTimeField(default=None)
577 lede = TemplateField(null=True, blank=True, verbose_name='Summary')
578 full_text = TemplateField(db_index=True)
579 tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True)
581 def save(self, *args, **kwargs):
582 if self.date is None:
583 self.date = datetime.now()
584 super(NewsletterArticle, self).save(*args, **kwargs)
587 get_latest_by = 'date'
589 unique_together = (('newsletter', 'slug'),)
592 register_value_model(NewsletterArticle)
595 class NewsletterIssue(Entity, Titled):
596 newsletter = models.ForeignKey(Newsletter, related_name='issues')
597 numbering = models.CharField(max_length=50, help_text='For example, 04.02 for volume 4, issue 2.')
598 articles = models.ManyToManyField(NewsletterArticle, related_name='issues')
601 ordering = ['-numbering']
602 unique_together = (('newsletter', 'numbering'),)
605 register_value_model(NewsletterIssue)
608 class NewsletterView(FeedView):
609 ARTICLE_PERMALINK_STYLE_CHOICES = (
610 ('D', 'Year, month, and day'),
611 ('M', 'Year and month'),
616 newsletter = models.ForeignKey(Newsletter, related_name='newsletterviews')
618 index_page = models.ForeignKey(Page, related_name='newsletter_index_related')
619 article_page = models.ForeignKey(Page, related_name='newsletter_article_related')
620 article_archive_page = models.ForeignKey(Page, related_name='newsletter_article_archive_related', null=True, blank=True)
621 issue_page = models.ForeignKey(Page, related_name='newsletter_issue_related')
622 issue_archive_page = models.ForeignKey(Page, related_name='newsletter_issue_archive_related', null=True, blank=True)
624 article_permalink_style = models.CharField(max_length=1, choices=ARTICLE_PERMALINK_STYLE_CHOICES)
625 article_permalink_base = models.CharField(max_length=255, blank=False, default='articles')
626 issue_permalink_base = models.CharField(max_length=255, blank=False, default='issues')
628 item_context_var = 'articles'
629 object_attr = 'newsletter'
631 def __unicode__(self):
632 return "NewsletterView for %s" % self.newsletter.__unicode__()
634 def get_reverse_params(self, obj):
635 if isinstance(obj, NewsletterArticle):
636 if obj.newsletter == self.newsletter:
637 kwargs = {'slug': obj.slug}
638 if self.article_permalink_style in 'DMY':
639 kwargs.update({'year': str(obj.date.year).zfill(4)})
640 if self.article_permalink_style in 'DM':
641 kwargs.update({'month': str(obj.date.month).zfill(2)})
642 if self.article_permalink_style == 'D':
643 kwargs.update({'day': str(obj.date.day).zfill(2)})
644 return self.article_view, [], kwargs
645 elif isinstance(obj, NewsletterIssue):
646 if obj.newsletter == self.newsletter:
647 return 'issue', [], {'numbering': obj.numbering}
648 elif isinstance(obj, (date, datetime)):
650 'year': str(obj.year).zfill(4),
651 'month': str(obj.month).zfill(2),
652 'day': str(obj.day).zfill(2)
654 return 'articles_by_day', [], kwargs
655 raise ViewCanNotProvideSubpath
658 def urlpatterns(self):
659 urlpatterns = patterns('',
660 url(r'^', include(self.feed_patterns('get_all_articles', 'index_page', 'index'))),
661 url(r'^%s/(?P<numbering>.+)' % self.issue_permalink_base, include(self.feed_patterns('get_articles_by_issue', 'issue_page', 'issue')))
663 if self.issue_archive_page:
664 urlpatterns += patterns('',
665 url(r'^%s$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive')
667 if self.article_archive_page:
668 urlpatterns += patterns('',
669 url(r'^%s' % self.article_permalink_base, include(self.feed_patterns('get_all_articles', 'article_archive_page', 'articles')))
671 if self.article_permalink_style in 'DMY':
672 urlpatterns += patterns('',
673 url(r'^%s/(?P<year>\d{4})' % self.article_permalink_base, include(self.feed_patterns('get_articles_by_ymd', 'article_archive_page', 'articles_by_year')))
675 if self.article_permalink_style in 'DM':
676 urlpatterns += patterns('',
677 url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})' % self.article_permalink_base, include(self.feed_patterns('get_articles_by_ymd', 'article_archive_page', 'articles_by_month')))
679 if self.article_permalink_style == 'D':
680 urlpatterns += patterns('',
681 url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})' % self.article_permalink_base, include(self.feed_patterns('get_articles_by_ymd', 'article_archive_page', 'articles_by_day')))
684 if self.article_permalink_style == 'Y':
685 urlpatterns += patterns('',
686 url(r'^%s/(?P<year>\d{4})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
688 elif self.article_permalink_style == 'M':
689 urlpatterns += patterns('',
690 url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
692 elif self.article_permalink_style == 'D':
693 urlpatterns += patterns('',
694 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)
697 urlpatterns += patterns('',
698 url(r'^%s/(?P<slug>[-\w]+)$' % self.article_permalink_base, self.article_view)
703 def get_context(self):
704 return {'newsletter': self.newsletter}
706 def get_article_queryset(self):
707 return self.newsletter.articles.all()
709 def get_issue_queryset(self):
710 return self.newsletter.issues.all()
712 def get_all_articles(self, request, extra_context=None):
713 return self.get_article_queryset(), extra_context
715 def get_articles_by_ymd(self, request, year, month=None, day=None, extra_context=None):
716 articles = self.get_article_queryset().filter(date__year=year)
718 articles = articles.filter(date__month=month)
720 articles = articles.filter(date__day=day)
721 return articles, extra_context
723 def get_articles_by_issue(self, request, numbering, extra_context=None):
725 issue = self.get_issue_queryset().get(numbering=numbering)
726 except NewsletterIssue.DoesNotExist:
728 context = extra_context or {}
729 context.update({'issue': issue})
730 return self.get_article_queryset().filter(issues=issue), context
732 def article_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
733 articles = self.get_article_queryset()
735 articles = articles.filter(date__year=year)
737 articles = articles.filter(date__month=month)
739 articles = articles.filter(date__day=day)
741 article = articles.get(slug=slug)
742 except NewsletterArticle.DoesNotExist:
744 context = self.get_context()
745 context.update(extra_context or {})
746 context.update({'article': article})
747 return self.article_page.render_to_response(request, extra_context=context)
749 def issue_archive_view(self, request, extra_context):
750 if not self.issue_archive_page:
752 context = self.get_context()
753 context.update(extra_context or {})
755 'issues': self.get_issue_queryset()
757 return self.issue_archive_page.render_to_response(request, extra_context=context)
759 def title(self, obj):
762 def item_title(self, item):
765 def item_description(self, item):
766 return item.full_text
768 def item_author_name(self, item):
769 authors = list(item.authors.all())
771 return "%s and %s" % (", ".join([author.get_full_name() for author in authors[:-1]]), authors[-1].get_full_name())
773 return authors[0].get_full_name()
777 def item_pubdate(self, item):
780 def item_categories(self, item):
781 return [tag.name for tag in item.tags.all()]