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.exceptions import HttpNotAcceptable
14 from philo.contrib.penfield.middleware import http_not_acceptable
15 from philo.contrib.penfield.validators import validate_pagination_count
16 from philo.exceptions import ViewCanNotProvideSubpath
17 from philo.models import Tag, Titled, Entity, MultiView, Page, register_value_model, TemplateField, Template
18 from philo.utils import paginate
25 ATOM = feedgenerator.Atom1Feed.mime_type
26 RSS = feedgenerator.Rss201rev2Feed.mime_type
28 (ATOM, feedgenerator.Atom1Feed),
29 (RSS, feedgenerator.Rss201rev2Feed),
37 class FeedView(MultiView):
39 The FeedView expects to handle a number of different feeds for the
40 same object - i.e. patterns for a blog to handle all entries or
41 just entries for a certain year/month/day.
43 This class would subclass django.contrib.syndication.views.Feed, but
44 that would make it callable, which causes problems.
46 feed_type = models.CharField(max_length=50, choices=FEED_CHOICES, default=ATOM)
47 feed_suffix = models.CharField(max_length=255, blank=False, default="feed")
48 feeds_enabled = models.BooleanField(default=True)
49 feed_length = models.PositiveIntegerField(blank=True, null=True, default=15, help_text="The maximum number of items to return for this feed. All items will be returned if this field is blank.")
51 item_title_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_title_related")
52 item_description_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_description_related")
54 item_context_var = 'items'
55 object_attr = 'object'
59 def feed_patterns(self, base, get_items_attr, page_attr, reverse_name):
61 Given the name to be used to reverse this view and the names of
62 the attributes for the function that fetches the objects, returns
63 patterns suitable for inclusion in urlpatterns.
65 urlpatterns = patterns('')
66 if self.feeds_enabled:
67 feed_reverse_name = "%s_feed" % reverse_name
68 feed_view = http_not_acceptable(self.feed_view(get_items_attr, feed_reverse_name))
69 feed_pattern = r'%s%s%s$' % (base, (base and base[-1] != "^") and "/" or "", self.feed_suffix)
70 urlpatterns += patterns('',
71 url(feed_pattern, feed_view, name=feed_reverse_name),
73 urlpatterns += patterns('',
74 url(r"%s$" % base, self.page_view(get_items_attr, page_attr), name=reverse_name)
78 def get_object(self, request, **kwargs):
79 return getattr(self, self.object_attr)
81 def feed_view(self, get_items_attr, reverse_name):
83 Returns a view function that renders a list of items as a feed.
85 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
87 def inner(request, extra_context=None, *args, **kwargs):
88 obj = self.get_object(request, *args, **kwargs)
89 feed = self.get_feed(obj, request, reverse_name)
90 items, xxx = get_items(request, extra_context=extra_context, *args, **kwargs)
91 self.populate_feed(feed, items, request)
93 response = HttpResponse(mimetype=feed.mime_type)
94 feed.write(response, 'utf-8')
99 def page_view(self, get_items_attr, page_attr):
101 Returns a view function that renders a list of items as a page.
103 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
104 page = isinstance(page_attr, Page) and page_attr or getattr(self, page_attr)
106 def inner(request, extra_context=None, *args, **kwargs):
107 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
108 items, item_context = self.process_page_items(request, items)
110 context = self.get_context()
111 context.update(extra_context or {})
112 context.update(item_context or {})
114 return page.render_to_response(request, extra_context=context)
117 def process_page_items(self, request, items):
119 Hook for handling any extra processing of items based on a
120 request, such as pagination or searching. This method is
121 expected to return a list of items and a dictionary to be
122 added to the page context.
125 self.item_context_var: items
127 return items, item_context
129 def get_feed_type(self, request):
130 feed_type = self.feed_type
131 if feed_type not in FEEDS:
132 feed_type = FEEDS.keys()[0]
133 accept = request.META.get('HTTP_ACCEPT')
134 if accept and feed_type not in accept and "*/*" not in accept and "%s/*" % feed_type.split("/")[0] not in accept:
135 # Wups! They aren't accepting the chosen format. Is there another format we can use?
137 feed_type = mimeparse.best_match(FEEDS.keys(), accept)
139 for feed_type in FEEDS.keys():
140 if feed_type in accept or "%s/*" % feed_type.split("/")[0] in accept:
145 raise HttpNotAcceptable
146 return FEEDS[feed_type]
148 def get_feed(self, obj, request, reverse_name):
150 Returns an unpopulated feedgenerator.DefaultFeed object for this object.
153 current_site = Site.objects.get_current()
154 except Site.DoesNotExist:
155 current_site = RequestSite(request)
157 feed_type = self.get_feed_type(request)
159 link = node.get_absolute_url(with_domain=True, request=request, secure=request.is_secure())
162 title = self.__get_dynamic_attr('title', obj),
163 subtitle = self.__get_dynamic_attr('subtitle', obj),
165 description = self.__get_dynamic_attr('description', obj),
166 language = settings.LANGUAGE_CODE.decode(),
167 feed_url = add_domain(
169 self.__get_dynamic_attr('feed_url', obj) or node.construct_url(node.subpath, with_domain=True, request=request, secure=request.is_secure()),
172 author_name = self.__get_dynamic_attr('author_name', obj),
173 author_link = self.__get_dynamic_attr('author_link', obj),
174 author_email = self.__get_dynamic_attr('author_email', obj),
175 categories = self.__get_dynamic_attr('categories', obj),
176 feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
177 feed_guid = self.__get_dynamic_attr('feed_guid', obj),
178 ttl = self.__get_dynamic_attr('ttl', obj),
179 **self.feed_extra_kwargs(obj)
183 def populate_feed(self, feed, items, request):
184 if self.item_title_template:
185 title_template = DjangoTemplate(self.item_title_template.code)
187 title_template = None
188 if self.item_description_template:
189 description_template = DjangoTemplate(self.item_description_template.code)
191 description_template = None
195 current_site = Site.objects.get_current()
196 except Site.DoesNotExist:
197 current_site = RequestSite(request)
199 if self.feed_length is not None:
200 items = items[:self.feed_length]
203 if title_template is not None:
204 title = title_template.render(RequestContext(request, {'obj': item}))
206 title = self.__get_dynamic_attr('item_title', item)
207 if description_template is not None:
208 description = description_template.render(RequestContext(request, {'obj': item}))
210 description = self.__get_dynamic_attr('item_description', item)
212 link = node.construct_url(self.reverse(obj=item), with_domain=True, request=request, secure=request.is_secure())
215 enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
217 enc = feedgenerator.Enclosure(
218 url = smart_unicode(add_domain(
223 length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
224 mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
226 author_name = self.__get_dynamic_attr('item_author_name', item)
227 if author_name is not None:
228 author_email = self.__get_dynamic_attr('item_author_email', item)
229 author_link = self.__get_dynamic_attr('item_author_link', item)
231 author_email = author_link = None
233 pubdate = self.__get_dynamic_attr('item_pubdate', item)
234 if pubdate and not pubdate.tzinfo:
235 ltz = tzinfo.LocalTimezone(pubdate)
236 pubdate = pubdate.replace(tzinfo=ltz)
241 description = description,
242 unique_id = self.__get_dynamic_attr('item_guid', item, link),
245 author_name = author_name,
246 author_email = author_email,
247 author_link = author_link,
248 categories = self.__get_dynamic_attr('item_categories', item),
249 item_copyright = self.__get_dynamic_attr('item_copyright', item),
250 **self.item_extra_kwargs(item)
253 def __get_dynamic_attr(self, attname, obj, default=None):
255 attr = getattr(self, attname)
256 except AttributeError:
259 # Check func_code.co_argcount rather than try/excepting the
260 # function and catching the TypeError, because something inside
261 # the function may raise the TypeError. This technique is more
263 if hasattr(attr, 'func_code'):
264 argcount = attr.func_code.co_argcount
266 argcount = attr.__call__.func_code.co_argcount
267 if argcount == 2: # one argument is 'self'
273 def feed_extra_kwargs(self, obj):
275 Returns an extra keyword arguments dictionary that is used when
276 initializing the feed generator.
280 def item_extra_kwargs(self, item):
282 Returns an extra keyword arguments dictionary that is used with
283 the `add_item` call of the feed generator.
287 def item_title(self, item):
288 return escape(force_unicode(item))
290 def item_description(self, item):
291 return force_unicode(item)
297 class Blog(Entity, Titled):
299 def entry_tags(self):
300 """ Returns a QuerySet of Tags that are used on any entries in this blog. """
301 return Tag.objects.filter(blogentries__blog=self).distinct()
304 def entry_dates(self):
305 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')}
309 register_value_model(Blog)
312 class BlogEntry(Entity, Titled):
313 blog = models.ForeignKey(Blog, related_name='entries', blank=True, null=True)
314 author = models.ForeignKey(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='blogentries')
315 date = models.DateTimeField(default=None)
316 content = models.TextField()
317 excerpt = models.TextField(blank=True, null=True)
318 tags = models.ManyToManyField(Tag, related_name='blogentries', blank=True, null=True)
320 def save(self, *args, **kwargs):
321 if self.date is None:
322 self.date = datetime.now()
323 super(BlogEntry, self).save(*args, **kwargs)
327 verbose_name_plural = "blog entries"
328 get_latest_by = "date"
331 register_value_model(BlogEntry)
334 class BlogView(FeedView):
335 ENTRY_PERMALINK_STYLE_CHOICES = (
336 ('D', 'Year, month, and day'),
337 ('M', 'Year and month'),
339 ('B', 'Custom base'),
343 blog = models.ForeignKey(Blog, related_name='blogviews')
345 index_page = models.ForeignKey(Page, related_name='blog_index_related')
346 entry_page = models.ForeignKey(Page, related_name='blog_entry_related')
347 # TODO: entry_archive is misleading. Rename to ymd_page or timespan_page.
348 entry_archive_page = models.ForeignKey(Page, related_name='blog_entry_archive_related', null=True, blank=True)
349 tag_page = models.ForeignKey(Page, related_name='blog_tag_related')
350 tag_archive_page = models.ForeignKey(Page, related_name='blog_tag_archive_related', null=True, blank=True)
351 entries_per_page = models.IntegerField(blank=True, validators=[validate_pagination_count], null=True)
353 entry_permalink_style = models.CharField(max_length=1, choices=ENTRY_PERMALINK_STYLE_CHOICES)
354 entry_permalink_base = models.CharField(max_length=255, blank=False, default='entries')
355 tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags')
357 item_context_var = 'entries'
360 def __unicode__(self):
361 return u'BlogView for %s' % self.blog.title
363 def get_reverse_params(self, obj):
364 if isinstance(obj, BlogEntry):
365 if obj.blog == self.blog:
366 kwargs = {'slug': obj.slug}
367 if self.entry_permalink_style in 'DMY':
368 kwargs.update({'year': str(obj.date.year).zfill(4)})
369 if self.entry_permalink_style in 'DM':
370 kwargs.update({'month': str(obj.date.month).zfill(2)})
371 if self.entry_permalink_style == 'D':
372 kwargs.update({'day': str(obj.date.day).zfill(2)})
373 return self.entry_view, [], kwargs
374 elif isinstance(obj, Tag) or (isinstance(obj, models.query.QuerySet) and obj.model == Tag and obj):
375 if isinstance(obj, Tag):
377 slugs = [tag.slug for tag in obj if tag in self.get_tag_queryset()]
379 return 'entries_by_tag', [], {'tag_slugs': "/".join(slugs)}
380 elif isinstance(obj, (date, datetime)):
382 'year': str(obj.year).zfill(4),
383 'month': str(obj.month).zfill(2),
384 'day': str(obj.day).zfill(2)
386 return 'entries_by_day', [], kwargs
387 raise ViewCanNotProvideSubpath
390 def urlpatterns(self):
391 urlpatterns = self.feed_patterns(r'^', 'get_all_entries', 'index_page', 'index') +\
392 self.feed_patterns(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)$' % self.tag_permalink_base, 'get_entries_by_tag', 'tag_page', 'entries_by_tag')
394 if self.tag_archive_page:
395 urlpatterns += patterns('',
396 url((r'^%s$' % self.tag_permalink_base), self.tag_archive_view, name='tag_archive')
399 if self.entry_archive_page:
400 if self.entry_permalink_style in 'DMY':
401 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_year')
402 if self.entry_permalink_style in 'DM':
403 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})/(?P<month>\d{2})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_month')
404 if self.entry_permalink_style == 'D':
405 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')
407 if self.entry_permalink_style == 'D':
408 urlpatterns += patterns('',
409 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
411 elif self.entry_permalink_style == 'M':
412 urlpatterns += patterns('',
413 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
415 elif self.entry_permalink_style == 'Y':
416 urlpatterns += patterns('',
417 url(r'^(?P<year>\d{4})/(?P<slug>[-\w]+)$', self.entry_view)
419 elif self.entry_permalink_style == 'B':
420 urlpatterns += patterns('',
421 url((r'^%s/(?P<slug>[-\w]+)$' % self.entry_permalink_base), self.entry_view)
424 urlpatterns = patterns('',
425 url(r'^(?P<slug>[-\w]+)$', self.entry_view)
429 def get_context(self):
430 return {'blog': self.blog}
432 def get_entry_queryset(self):
433 return self.blog.entries.all()
435 def get_tag_queryset(self):
436 return self.blog.entry_tags
438 def get_all_entries(self, request, extra_context=None):
439 return self.get_entry_queryset(), extra_context
441 def get_entries_by_ymd(self, request, year=None, month=None, day=None, extra_context=None):
442 if not self.entry_archive_page:
444 entries = self.get_entry_queryset()
446 entries = entries.filter(date__year=year)
448 entries = entries.filter(date__month=month)
450 entries = entries.filter(date__day=day)
452 context = extra_context or {}
453 context.update({'year': year, 'month': month, 'day': day})
454 return entries, context
456 def get_entries_by_tag(self, request, tag_slugs, extra_context=None):
457 tag_slugs = tag_slugs.replace('+', '/').split('/')
458 tags = self.get_tag_queryset().filter(slug__in=tag_slugs)
463 # Raise a 404 on an incorrect slug.
464 found_slugs = [tag.slug for tag in tags]
465 for slug in tag_slugs:
466 if slug and slug not in found_slugs:
469 entries = self.get_entry_queryset()
471 entries = entries.filter(tags=tag)
473 context = extra_context or {}
474 context.update({'tags': tags})
476 return entries, context
478 def entry_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
479 entries = self.get_entry_queryset()
481 entries = entries.filter(date__year=year)
483 entries = entries.filter(date__month=month)
485 entries = entries.filter(date__day=day)
487 entry = entries.get(slug=slug)
490 context = self.get_context()
491 context.update(extra_context or {})
492 context.update({'entry': entry})
493 return self.entry_page.render_to_response(request, extra_context=context)
495 def tag_archive_view(self, request, extra_context=None):
496 if not self.tag_archive_page:
498 context = self.get_context()
499 context.update(extra_context or {})
501 'tags': self.get_tag_queryset()
503 return self.tag_archive_page.render_to_response(request, extra_context=context)
505 def feed_view(self, get_items_attr, reverse_name):
506 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
508 def inner(request, extra_context=None, *args, **kwargs):
509 obj = self.get_object(request, *args, **kwargs)
510 feed = self.get_feed(obj, request, reverse_name)
511 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
512 self.populate_feed(feed, items, request)
514 if 'tags' in extra_context:
515 tags = extra_context['tags']
516 feed.feed['link'] = request.node.construct_url(self.reverse(obj=tags), with_domain=True, request=request, secure=request.is_secure())
518 tags = obj.entry_tags
520 feed.feed['categories'] = [tag.name for tag in tags]
522 response = HttpResponse(mimetype=feed.mime_type)
523 feed.write(response, 'utf-8')
528 def process_page_items(self, request, items):
529 if self.entries_per_page:
530 page_num = request.GET.get('page', 1)
531 paginator, paginated_page, items = paginate(items, self.entries_per_page, page_num)
533 'paginator': paginator,
534 'paginated_page': paginated_page,
535 self.item_context_var: items
539 self.item_context_var: items
541 return items, item_context
543 def title(self, obj):
546 def item_title(self, item):
549 def item_description(self, item):
552 def item_author_name(self, item):
553 return item.author.get_full_name()
555 def item_pubdate(self, item):
558 def item_categories(self, item):
559 return [tag.name for tag in item.tags.all()]
562 class Newsletter(Entity, Titled):
566 register_value_model(Newsletter)
569 class NewsletterArticle(Entity, Titled):
570 newsletter = models.ForeignKey(Newsletter, related_name='articles')
571 authors = models.ManyToManyField(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='newsletterarticles')
572 date = models.DateTimeField(default=None)
573 lede = TemplateField(null=True, blank=True, verbose_name='Summary')
574 full_text = TemplateField(db_index=True)
575 tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True)
577 def save(self, *args, **kwargs):
578 if self.date is None:
579 self.date = datetime.now()
580 super(NewsletterArticle, self).save(*args, **kwargs)
583 get_latest_by = 'date'
585 unique_together = (('newsletter', 'slug'),)
588 register_value_model(NewsletterArticle)
591 class NewsletterIssue(Entity, Titled):
592 newsletter = models.ForeignKey(Newsletter, related_name='issues')
593 numbering = models.CharField(max_length=50, help_text='For example, 04.02 for volume 4, issue 2.')
594 articles = models.ManyToManyField(NewsletterArticle, related_name='issues')
597 ordering = ['-numbering']
598 unique_together = (('newsletter', 'numbering'),)
601 register_value_model(NewsletterIssue)
604 class NewsletterView(FeedView):
605 ARTICLE_PERMALINK_STYLE_CHOICES = (
606 ('D', 'Year, month, and day'),
607 ('M', 'Year and month'),
612 newsletter = models.ForeignKey(Newsletter, related_name='newsletterviews')
614 index_page = models.ForeignKey(Page, related_name='newsletter_index_related')
615 article_page = models.ForeignKey(Page, related_name='newsletter_article_related')
616 article_archive_page = models.ForeignKey(Page, related_name='newsletter_article_archive_related', null=True, blank=True)
617 issue_page = models.ForeignKey(Page, related_name='newsletter_issue_related')
618 issue_archive_page = models.ForeignKey(Page, related_name='newsletter_issue_archive_related', null=True, blank=True)
620 article_permalink_style = models.CharField(max_length=1, choices=ARTICLE_PERMALINK_STYLE_CHOICES)
621 article_permalink_base = models.CharField(max_length=255, blank=False, default='articles')
622 issue_permalink_base = models.CharField(max_length=255, blank=False, default='issues')
624 item_context_var = 'articles'
625 object_attr = 'newsletter'
627 def __unicode__(self):
628 return "NewsletterView for %s" % self.newsletter.__unicode__()
630 def get_reverse_params(self, obj):
631 if isinstance(obj, NewsletterArticle):
632 if obj.newsletter == self.newsletter:
633 kwargs = {'slug': obj.slug}
634 if self.article_permalink_style in 'DMY':
635 kwargs.update({'year': str(obj.date.year).zfill(4)})
636 if self.article_permalink_style in 'DM':
637 kwargs.update({'month': str(obj.date.month).zfill(2)})
638 if self.article_permalink_style == 'D':
639 kwargs.update({'day': str(obj.date.day).zfill(2)})
640 return self.article_view, [], kwargs
641 elif isinstance(obj, NewsletterIssue):
642 if obj.newsletter == self.newsletter:
643 return 'issue', [], {'numbering': obj.numbering}
644 elif isinstance(obj, (date, datetime)):
646 'year': str(obj.year).zfill(4),
647 'month': str(obj.month).zfill(2),
648 'day': str(obj.day).zfill(2)
650 return 'articles_by_day', [], kwargs
651 raise ViewCanNotProvideSubpath
654 def urlpatterns(self):
655 urlpatterns = self.feed_patterns(r'^', 'get_all_articles', 'index_page', 'index') + patterns('',
656 url(r'^%s/(?P<numbering>.+)$' % self.issue_permalink_base, self.page_view('get_articles_by_issue', 'issue_page'), name='issue')
658 if self.issue_archive_page:
659 urlpatterns += patterns('',
660 url(r'^%s$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive')
662 if self.article_archive_page:
663 urlpatterns += patterns('',
664 url(r'^%s' % self.article_permalink_base, include(self.feed_patterns('get_all_articles', 'article_archive_page', 'articles')))
666 if self.article_permalink_style in 'DMY':
667 urlpatterns += self.feed_patterns(r'^%s/(?P<year>\d{4})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_year')
668 if self.article_permalink_style in 'DM':
669 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')
670 if self.article_permalink_style == 'D':
671 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')
673 if self.article_permalink_style == 'Y':
674 urlpatterns += patterns('',
675 url(r'^%s/(?P<year>\d{4})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
677 elif self.article_permalink_style == 'M':
678 urlpatterns += patterns('',
679 url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
681 elif self.article_permalink_style == 'D':
682 urlpatterns += patterns('',
683 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)
686 urlpatterns += patterns('',
687 url(r'^%s/(?P<slug>[-\w]+)$' % self.article_permalink_base, self.article_view)
692 def get_context(self):
693 return {'newsletter': self.newsletter}
695 def get_article_queryset(self):
696 return self.newsletter.articles.all()
698 def get_issue_queryset(self):
699 return self.newsletter.issues.all()
701 def get_all_articles(self, request, extra_context=None):
702 return self.get_article_queryset(), extra_context
704 def get_articles_by_ymd(self, request, year, month=None, day=None, extra_context=None):
705 articles = self.get_article_queryset().filter(date__year=year)
707 articles = articles.filter(date__month=month)
709 articles = articles.filter(date__day=day)
710 return articles, extra_context
712 def get_articles_by_issue(self, request, numbering, extra_context=None):
714 issue = self.get_issue_queryset().get(numbering=numbering)
715 except NewsletterIssue.DoesNotExist:
717 context = extra_context or {}
718 context.update({'issue': issue})
719 return self.get_article_queryset().filter(issues=issue), context
721 def article_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
722 articles = self.get_article_queryset()
724 articles = articles.filter(date__year=year)
726 articles = articles.filter(date__month=month)
728 articles = articles.filter(date__day=day)
730 article = articles.get(slug=slug)
731 except NewsletterArticle.DoesNotExist:
733 context = self.get_context()
734 context.update(extra_context or {})
735 context.update({'article': article})
736 return self.article_page.render_to_response(request, extra_context=context)
738 def issue_archive_view(self, request, extra_context):
739 if not self.issue_archive_page:
741 context = self.get_context()
742 context.update(extra_context or {})
744 'issues': self.get_issue_queryset()
746 return self.issue_archive_page.render_to_response(request, extra_context=context)
748 def title(self, obj):
751 def item_title(self, item):
754 def item_description(self, item):
755 return item.full_text
757 def item_author_name(self, item):
758 authors = list(item.authors.all())
760 return "%s and %s" % (", ".join([author.get_full_name() for author in authors[:-1]]), authors[-1].get_full_name())
762 return authors[0].get_full_name()
766 def item_pubdate(self, item):
769 def item_categories(self, item):
770 return [tag.name for tag in item.tags.all()]