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 url(r'^%s$' % self.feed_suffix, self.feed_view(get_items_attr, feed_reverse_name), name=feed_reverse_name),
71 def get_object(self, request, **kwargs):
72 return getattr(self, self.object_attr)
74 def feed_view(self, get_items_attr, reverse_name):
76 Returns a view function that renders a list of items as a feed.
78 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
80 def inner(request, extra_context=None, *args, **kwargs):
81 obj = self.get_object(request, *args, **kwargs)
82 feed = self.get_feed(obj, request, reverse_name)
83 items, xxx = get_items(request, extra_context=extra_context, *args, **kwargs)
84 self.populate_feed(feed, items, request)
86 response = HttpResponse(mimetype=feed.mime_type)
87 feed.write(response, 'utf-8')
92 def page_view(self, get_items_attr, page_attr):
94 Returns a view function that renders a list of items as a page.
96 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
97 page = isinstance(page_attr, Page) and page_attr or getattr(self, page_attr)
99 def inner(request, extra_context=None, *args, **kwargs):
100 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
101 items, item_context = self.process_page_items(request, items)
103 context = self.get_context()
104 context.update(extra_context or {})
105 context.update(item_context or {})
107 return page.render_to_response(request, extra_context=context)
110 def process_page_items(self, request, items):
112 Hook for handling any extra processing of items based on a
113 request, such as pagination or searching. This method is
114 expected to return a list of items and a dictionary to be
115 added to the page context.
118 self.item_context_var: items
120 return items, item_context
122 def get_feed_type(self, request):
123 feed_type = self.feed_type
124 accept = request.META.get('HTTP_ACCEPT')
125 if accept and feed_type not in accept and "*/*" not in accept and "%s/*" % feed_type.split("/")[0] not in accept:
126 # Wups! They aren't accepting the chosen format. Is there another format we can use?
128 feed_type = mimeparse.best_match(FEEDS.keys(), accept)
130 for feed_type in FEEDS.keys():
131 if feed_type in accept or "%s/*" % feed_type.split("/")[0] in accept:
137 return HttpResponse(status=406)
138 return FEEDS[feed_type]
140 def get_feed(self, obj, request, reverse_name):
142 Returns an unpopulated feedgenerator.DefaultFeed object for this object.
145 current_site = Site.objects.get_current()
146 except Site.DoesNotExist:
147 current_site = RequestSite(request)
149 feed_type = self.get_feed_type(request)
151 link = node.get_absolute_url(with_domain=True, request=request, secure=request.is_secure())
154 title = self.__get_dynamic_attr('title', obj),
155 subtitle = self.__get_dynamic_attr('subtitle', obj),
157 description = self.__get_dynamic_attr('description', obj),
158 language = settings.LANGUAGE_CODE.decode(),
159 feed_url = add_domain(
161 self.__get_dynamic_attr('feed_url', obj) or node.construct_url(node.subpath, with_domain=True, request=request, secure=request.is_secure()),
164 author_name = self.__get_dynamic_attr('author_name', obj),
165 author_link = self.__get_dynamic_attr('author_link', obj),
166 author_email = self.__get_dynamic_attr('author_email', obj),
167 categories = self.__get_dynamic_attr('categories', obj),
168 feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
169 feed_guid = self.__get_dynamic_attr('feed_guid', obj),
170 ttl = self.__get_dynamic_attr('ttl', obj),
171 **self.feed_extra_kwargs(obj)
175 def populate_feed(self, feed, items, request):
176 if self.item_title_template:
177 title_template = DjangoTemplate(self.item_title_template.code)
179 title_template = None
180 if self.item_description_template:
181 description_template = DjangoTemplate(self.item_description_template.code)
183 description_template = None
187 current_site = Site.objects.get_current()
188 except Site.DoesNotExist:
189 current_site = RequestSite(request)
192 if title_template is not None:
193 title = title_template.render(RequestContext(request, {'obj': item}))
195 title = self.__get_dynamic_attr('item_title', item)
196 if description_template is not None:
197 description = description_template.render(RequestContext(request, {'obj': item}))
199 description = self.__get_dynamic_attr('item_description', item)
201 link = node.construct_url(self.reverse(obj=item), with_domain=True, request=request, secure=request.is_secure())
204 enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
206 enc = feedgenerator.Enclosure(
207 url = smart_unicode(add_domain(
212 length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
213 mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
215 author_name = self.__get_dynamic_attr('item_author_name', item)
216 if author_name is not None:
217 author_email = self.__get_dynamic_attr('item_author_email', item)
218 author_link = self.__get_dynamic_attr('item_author_link', item)
220 author_email = author_link = None
222 pubdate = self.__get_dynamic_attr('item_pubdate', item)
223 if pubdate and not pubdate.tzinfo:
224 ltz = tzinfo.LocalTimezone(pubdate)
225 pubdate = pubdate.replace(tzinfo=ltz)
230 description = description,
231 unique_id = self.__get_dynamic_attr('item_guid', item, link),
234 author_name = author_name,
235 author_email = author_email,
236 author_link = author_link,
237 categories = self.__get_dynamic_attr('item_categories', item),
238 item_copyright = self.__get_dynamic_attr('item_copyright', item),
239 **self.item_extra_kwargs(item)
242 def __get_dynamic_attr(self, attname, obj, default=None):
244 attr = getattr(self, attname)
245 except AttributeError:
248 # Check func_code.co_argcount rather than try/excepting the
249 # function and catching the TypeError, because something inside
250 # the function may raise the TypeError. This technique is more
252 if hasattr(attr, 'func_code'):
253 argcount = attr.func_code.co_argcount
255 argcount = attr.__call__.func_code.co_argcount
256 if argcount == 2: # one argument is 'self'
262 def feed_extra_kwargs(self, obj):
264 Returns an extra keyword arguments dictionary that is used when
265 initializing the feed generator.
269 def item_extra_kwargs(self, item):
271 Returns an extra keyword arguments dictionary that is used with
272 the `add_item` call of the feed generator.
276 def item_title(self, item):
277 return escape(force_unicode(item))
279 def item_description(self, item):
280 return force_unicode(item)
286 class Blog(Entity, Titled):
288 def entry_tags(self):
289 """ Returns a QuerySet of Tags that are used on any entries in this blog. """
290 return Tag.objects.filter(blogentries__blog=self).distinct()
293 def entry_dates(self):
294 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')}
298 register_value_model(Blog)
301 class BlogEntry(Entity, Titled):
302 blog = models.ForeignKey(Blog, related_name='entries', blank=True, null=True)
303 author = models.ForeignKey(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='blogentries')
304 date = models.DateTimeField(default=None)
305 content = models.TextField()
306 excerpt = models.TextField(blank=True, null=True)
307 tags = models.ManyToManyField(Tag, related_name='blogentries', blank=True, null=True)
309 def save(self, *args, **kwargs):
310 if self.date is None:
311 self.date = datetime.now()
312 super(BlogEntry, self).save(*args, **kwargs)
316 verbose_name_plural = "blog entries"
317 get_latest_by = "date"
320 register_value_model(BlogEntry)
323 class BlogView(FeedView):
324 ENTRY_PERMALINK_STYLE_CHOICES = (
325 ('D', 'Year, month, and day'),
326 ('M', 'Year and month'),
328 ('B', 'Custom base'),
332 blog = models.ForeignKey(Blog, related_name='blogviews')
334 index_page = models.ForeignKey(Page, related_name='blog_index_related')
335 entry_page = models.ForeignKey(Page, related_name='blog_entry_related')
336 entry_archive_page = models.ForeignKey(Page, related_name='blog_entry_archive_related', null=True, blank=True)
337 tag_page = models.ForeignKey(Page, related_name='blog_tag_related')
338 tag_archive_page = models.ForeignKey(Page, related_name='blog_tag_archive_related', null=True, blank=True)
339 entries_per_page = models.IntegerField(blank=True, validators=[validate_pagination_count], null=True)
341 entry_permalink_style = models.CharField(max_length=1, choices=ENTRY_PERMALINK_STYLE_CHOICES)
342 entry_permalink_base = models.CharField(max_length=255, blank=False, default='entries')
343 tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags')
345 item_context_var = 'entries'
348 def __unicode__(self):
349 return u'BlogView for %s' % self.blog.title
351 def get_reverse_params(self, obj):
352 if isinstance(obj, BlogEntry):
353 if obj.blog == self.blog:
354 kwargs = {'slug': obj.slug}
355 if self.entry_permalink_style in 'DMY':
356 kwargs.update({'year': str(obj.date.year).zfill(4)})
357 if self.entry_permalink_style in 'DM':
358 kwargs.update({'month': str(obj.date.month).zfill(2)})
359 if self.entry_permalink_style == 'D':
360 kwargs.update({'day': str(obj.date.day).zfill(2)})
361 return self.entry_view, [], kwargs
362 elif isinstance(obj, Tag) or (isinstance(obj, models.QuerySet) and obj.model == Tag and obj):
363 if isinstance(obj, Tag):
365 slugs = [tag.slug for tag in obj if tag in self.get_tag_queryset()]
367 return 'entries_by_tag', [], {'tag_slugs': "/".join(slugs)}
368 elif isinstance(obj, (date, datetime)):
370 'year': str(obj.year).zfill(4),
371 'month': str(obj.month).zfill(2),
372 'day': str(obj.day).zfill(2)
374 return 'entries_by_day', [], kwargs
375 raise ViewCanNotProvideSubpath
378 def urlpatterns(self):
379 urlpatterns = patterns('',
380 url(r'^', include(self.feed_patterns('get_all_entries', 'index_page', 'index'))),
382 if self.feeds_enabled:
383 urlpatterns += patterns('',
384 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'),
386 urlpatterns += patterns('',
387 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')
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 += patterns('',
397 url(r'^(?P<year>\d{4})', include(self.feed_patterns('get_entries_by_ymd', 'entry_archive_page', 'entries_by_year')))
399 if self.entry_permalink_style in 'DM':
400 urlpatterns += patterns('',
401 url(r'^(?P<year>\d{4})/(?P<month>\d{2})$', include(self.feed_patterns('get_entries_by_ymd', 'entry_archive_page', 'entries_by_month'))),
403 if self.entry_permalink_style == 'D':
404 urlpatterns += patterns('',
405 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')))
408 if self.entry_permalink_style == 'D':
409 urlpatterns += patterns('',
410 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
412 elif self.entry_permalink_style == 'M':
413 urlpatterns += patterns('',
414 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
416 elif self.entry_permalink_style == 'Y':
417 urlpatterns += patterns('',
418 url(r'^(?P<year>\d{4})/(?P<slug>[-\w]+)$', self.entry_view)
420 elif self.entry_permalink_style == 'B':
421 urlpatterns += patterns('',
422 url((r'^%s/(?P<slug>[-\w]+)$' % self.entry_permalink_base), self.entry_view)
425 urlpatterns = patterns('',
426 url(r'^(?P<slug>[-\w]+)$', self.entry_view)
430 def get_context(self):
431 return {'blog': self.blog}
433 def get_entry_queryset(self):
434 return self.blog.entries.all()
436 def get_tag_queryset(self):
437 return self.blog.entry_tags
439 def get_all_entries(self, request, extra_context=None):
440 return self.get_entry_queryset(), extra_context
442 def get_entries_by_ymd(self, request, year=None, month=None, day=None, extra_context=None):
443 if not self.entry_archive_page:
445 entries = self.get_entry_queryset()
447 entries = entries.filter(date__year=year)
449 entries = entries.filter(date__month=month)
451 entries = entries.filter(date__day=day)
453 context = extra_context or {}
454 context.update({'year': year, 'month': month, 'day': day})
455 return entries, context
457 def get_entries_by_tag(self, request, tag_slugs, extra_context=None):
458 tag_slugs = tag_slugs.replace('+', '/').split('/')
459 tags = self.get_tag_queryset().filter(slug__in=tag_slugs)
464 # Raise a 404 on an incorrect slug.
465 found_slugs = [tag.slug for tag in tags]
466 for slug in tag_slugs:
467 if slug and slug not in found_slugs:
470 entries = self.get_entry_queryset()
472 entries = entries.filter(tags=tag)
474 context = extra_context or {}
475 context.update({'tags': tags})
477 return entries, context
479 def entry_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
480 entries = self.get_entry_queryset()
482 entries = entries.filter(date__year=year)
484 entries = entries.filter(date__month=month)
486 entries = entries.filter(date__day=day)
488 entry = entries.get(slug=slug)
491 context = self.get_context()
492 context.update(extra_context or {})
493 context.update({'entry': entry})
494 return self.entry_page.render_to_response(request, extra_context=context)
496 def tag_archive_view(self, request, extra_context=None):
497 if not self.tag_archive_page:
499 context = self.get_context()
500 context.update(extra_context or {})
502 'tags': self.get_tag_queryset()
504 return self.tag_archive_page.render_to_response(request, extra_context=context)
506 def feed_view(self, get_items_attr, reverse_name):
507 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
509 def inner(request, extra_context=None, *args, **kwargs):
510 obj = self.get_object(request, *args, **kwargs)
511 feed = self.get_feed(obj, request, reverse_name)
512 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
513 self.populate_feed(feed, items, request)
515 if 'tags' in extra_context:
516 tags = extra_context['tags']
517 feed.feed['link'] = request.node.construct_url(self.reverse(tags), with_domain=True, request=request, secure=request.is_secure())
519 tags = obj.entry_tags
521 feed.feed['categories'] = [tag.name for tag in tags]
523 response = HttpResponse(mimetype=feed.mime_type)
524 feed.write(response, 'utf-8')
529 def process_page_items(self, request, items):
530 if self.entries_per_page:
531 page_num = request.GET.get('page', 1)
532 paginator, paginated_page, items = paginate(items, self.entries_per_page, page_num)
534 'paginator': paginator,
535 'paginated_page': paginated_page,
536 self.item_context_var: items
540 self.item_context_var: items
542 return items, item_context
544 def title(self, obj):
547 def item_title(self, item):
550 def item_description(self, item):
553 def item_author_name(self, item):
554 return item.author.get_full_name()
556 def item_pubdate(self, item):
559 def item_categories(self, item):
560 return [tag.name for tag in item.tags.all()]
563 class Newsletter(Entity, Titled):
567 register_value_model(Newsletter)
570 class NewsletterArticle(Entity, Titled):
571 newsletter = models.ForeignKey(Newsletter, related_name='articles')
572 authors = models.ManyToManyField(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='newsletterarticles')
573 date = models.DateTimeField(default=None)
574 lede = TemplateField(null=True, blank=True, verbose_name='Summary')
575 full_text = TemplateField(db_index=True)
576 tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True)
578 def save(self, *args, **kwargs):
579 if self.date is None:
580 self.date = datetime.now()
581 super(NewsletterArticle, self).save(*args, **kwargs)
584 get_latest_by = 'date'
586 unique_together = (('newsletter', 'slug'),)
589 register_value_model(NewsletterArticle)
592 class NewsletterIssue(Entity, Titled):
593 newsletter = models.ForeignKey(Newsletter, related_name='issues')
594 numbering = models.CharField(max_length=50, help_text='For example, 04.02 for volume 4, issue 2.')
595 articles = models.ManyToManyField(NewsletterArticle, related_name='issues')
598 ordering = ['-numbering']
599 unique_together = (('newsletter', 'numbering'),)
602 register_value_model(NewsletterIssue)
605 class NewsletterView(FeedView):
606 ARTICLE_PERMALINK_STYLE_CHOICES = (
607 ('D', 'Year, month, and day'),
608 ('M', 'Year and month'),
613 newsletter = models.ForeignKey(Newsletter, related_name='newsletterviews')
615 index_page = models.ForeignKey(Page, related_name='newsletter_index_related')
616 article_page = models.ForeignKey(Page, related_name='newsletter_article_related')
617 article_archive_page = models.ForeignKey(Page, related_name='newsletter_article_archive_related', null=True, blank=True)
618 issue_page = models.ForeignKey(Page, related_name='newsletter_issue_related')
619 issue_archive_page = models.ForeignKey(Page, related_name='newsletter_issue_archive_related', null=True, blank=True)
621 article_permalink_style = models.CharField(max_length=1, choices=ARTICLE_PERMALINK_STYLE_CHOICES)
622 article_permalink_base = models.CharField(max_length=255, blank=False, default='articles')
623 issue_permalink_base = models.CharField(max_length=255, blank=False, default='issues')
625 item_context_var = 'articles'
626 object_attr = 'newsletter'
628 def __unicode__(self):
629 return "NewsletterView for %s" % self.newsletter.__unicode__()
631 def get_reverse_params(self, obj):
632 if isinstance(obj, NewsletterArticle):
633 if obj.newsletter == self.newsletter:
634 kwargs = {'slug': obj.slug}
635 if self.article_permalink_style in 'DMY':
636 kwargs.update({'year': str(obj.date.year).zfill(4)})
637 if self.article_permalink_style in 'DM':
638 kwargs.update({'month': str(obj.date.month).zfill(2)})
639 if self.article_permalink_style == 'D':
640 kwargs.update({'day': str(obj.date.day).zfill(2)})
641 return self.article_view, [], kwargs
642 elif isinstance(obj, NewsletterIssue):
643 if obj.newsletter == self.newsletter:
644 return 'issue', [], {'numbering': obj.numbering}
645 elif isinstance(obj, (date, datetime)):
647 'year': str(obj.year).zfill(4),
648 'month': str(obj.month).zfill(2),
649 'day': str(obj.day).zfill(2)
651 return 'articles_by_day', [], kwargs
652 raise ViewCanNotProvideSubpath
655 def urlpatterns(self):
656 urlpatterns = patterns('',
657 url(r'^', include(self.feed_patterns('get_all_articles', 'index_page', 'index'))),
658 url(r'^%s/(?P<numbering>.+)' % self.issue_permalink_base, include(self.feed_patterns('get_articles_by_issue', 'issue_page', 'issue')))
660 if self.issue_archive_page:
661 urlpatterns += patterns('',
662 url(r'^%s$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive')
664 if self.article_archive_page:
665 urlpatterns += patterns('',
666 url(r'^%s' % self.article_permalink_base, include(self.feed_patterns('get_all_articles', 'article_archive_page', 'articles')))
668 if self.article_permalink_style in 'DMY':
669 urlpatterns += patterns('',
670 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')))
672 if self.article_permalink_style in 'DM':
673 urlpatterns += patterns('',
674 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')))
676 if self.article_permalink_style == 'D':
677 urlpatterns += patterns('',
678 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')))
681 if self.article_permalink_style == 'Y':
682 urlpatterns += patterns('',
683 url(r'^%s/(?P<year>\d{4})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
685 elif self.article_permalink_style == 'M':
686 urlpatterns += patterns('',
687 url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
689 elif self.article_permalink_style == 'D':
690 urlpatterns += patterns('',
691 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)
694 urlpatterns += patterns('',
695 url(r'^%s/(?P<slug>[-\w]+)$' % self.article_permalink_base, self.article_view)
700 def get_context(self):
701 return {'newsletter': self.newsletter}
703 def get_article_queryset(self):
704 return self.newsletter.articles.all()
706 def get_issue_queryset(self):
707 return self.newsletter.issues.all()
709 def get_all_articles(self, request, extra_context=None):
710 return self.get_article_queryset(), extra_context
712 def get_articles_by_ymd(self, request, year, month=None, day=None, extra_context=None):
713 articles = self.get_article_queryset().filter(date__year=year)
715 articles = articles.filter(date__month=month)
717 articles = articles.filter(date__day=day)
718 return articles, extra_context
720 def get_articles_by_issue(self, request, numbering, extra_context=None):
722 issue = self.get_issue_queryset().get(numbering=numbering)
723 except NewsletterIssue.DoesNotExist:
725 context = extra_context or {}
726 context.update({'issue': issue})
727 return self.get_article_queryset().filter(issues=issue), context
729 def article_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
730 articles = self.get_article_queryset()
732 articles = articles.filter(date__year=year)
734 articles = articles.filter(date__month=month)
736 articles = articles.filter(date__day=day)
738 article = articles.get(slug=slug)
739 except NewsletterArticle.DoesNotExist:
741 context = self.get_context()
742 context.update(extra_context or {})
743 context.update({'article': article})
744 return self.article_page.render_to_response(request, extra_context=context)
746 def issue_archive_view(self, request, extra_context):
747 if not self.issue_archive_page:
749 context = self.get_context()
750 context.update(extra_context or {})
752 'issues': self.get_issue_queryset()
754 return self.issue_archive_page.render_to_response(request, extra_context=context)
756 def title(self, obj):
759 def item_title(self, item):
762 def item_description(self, item):
763 return item.full_text
765 def item_author_name(self, item):
766 authors = list(item.authors.all())
768 return "%s and %s" % (", ".join([author.get_full_name() for author in authors[:-1]]), authors[-1].get_full_name())
770 return authors[0].get_full_name()
774 def item_pubdate(self, item):
777 def item_categories(self, item):
778 return [tag.name for tag in item.tags.all()]