1 from datetime import date, datetime
3 from django.conf import settings
4 from django.conf.urls.defaults import url, patterns, include
5 from django.contrib.sites.models import Site, RequestSite
6 from django.contrib.syndication.views import add_domain
7 from django.db import models
8 from django.http import Http404, HttpResponse
9 from django.template import RequestContext, Template as DjangoTemplate
10 from django.utils import feedgenerator, tzinfo
11 from django.utils.datastructures import SortedDict
12 from django.utils.encoding import smart_unicode, force_unicode
13 from django.utils.html import escape
15 from philo.contrib.penfield.exceptions import HttpNotAcceptable
16 from philo.contrib.penfield.middleware import http_not_acceptable
17 from philo.contrib.penfield.validators import validate_pagination_count
18 from philo.exceptions import ViewCanNotProvideSubpath
19 from philo.models import Tag, Titled, Entity, MultiView, Page, register_value_model, TemplateField, Template
20 from philo.utils import paginate
28 ATOM = feedgenerator.Atom1Feed.mime_type
29 RSS = feedgenerator.Rss201rev2Feed.mime_type
31 (ATOM, feedgenerator.Atom1Feed),
32 (RSS, feedgenerator.Rss201rev2Feed),
40 class FeedView(MultiView):
42 The FeedView expects to handle a number of different feeds for the
43 same object - i.e. patterns for a blog to handle all entries or
44 just entries for a certain year/month/day.
46 This class would subclass django.contrib.syndication.views.Feed, but
47 that would make it callable, which causes problems.
49 feed_type = models.CharField(max_length=50, choices=FEED_CHOICES, default=ATOM)
50 feed_suffix = models.CharField(max_length=255, blank=False, default="feed")
51 feeds_enabled = models.BooleanField(default=True)
52 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.")
54 item_title_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_title_related")
55 item_description_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_description_related")
57 item_context_var = 'items'
58 object_attr = 'object'
62 def feed_patterns(self, base, get_items_attr, page_attr, reverse_name):
64 Given the name to be used to reverse this view and the names of
65 the attributes for the function that fetches the objects, returns
66 patterns suitable for inclusion in urlpatterns.
68 urlpatterns = patterns('')
69 if self.feeds_enabled:
70 feed_reverse_name = "%s_feed" % reverse_name
71 feed_view = http_not_acceptable(self.feed_view(get_items_attr, feed_reverse_name))
72 feed_pattern = r'%s%s%s$' % (base, (base and base[-1] != "^") and "/" or "", self.feed_suffix)
73 urlpatterns += patterns('',
74 url(feed_pattern, feed_view, name=feed_reverse_name),
76 urlpatterns += patterns('',
77 url(r"%s$" % base, self.page_view(get_items_attr, page_attr), name=reverse_name)
81 def get_object(self, request, **kwargs):
82 return getattr(self, self.object_attr)
84 def feed_view(self, get_items_attr, reverse_name):
86 Returns a view function that renders a list of items as a feed.
88 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
90 def inner(request, extra_context=None, *args, **kwargs):
91 obj = self.get_object(request, *args, **kwargs)
92 feed = self.get_feed(obj, request, reverse_name)
93 items, xxx = get_items(request, extra_context=extra_context, *args, **kwargs)
94 self.populate_feed(feed, items, request)
96 response = HttpResponse(mimetype=feed.mime_type)
97 feed.write(response, 'utf-8')
102 def page_view(self, get_items_attr, page_attr):
104 Returns a view function that renders a list of items as a page.
106 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
107 page = isinstance(page_attr, Page) and page_attr or getattr(self, page_attr)
109 def inner(request, extra_context=None, *args, **kwargs):
110 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
111 items, item_context = self.process_page_items(request, items)
113 context = self.get_context()
114 context.update(extra_context or {})
115 context.update(item_context or {})
117 return page.render_to_response(request, extra_context=context)
120 def process_page_items(self, request, items):
122 Hook for handling any extra processing of items based on a
123 request, such as pagination or searching. This method is
124 expected to return a list of items and a dictionary to be
125 added to the page context.
128 self.item_context_var: items
130 return items, item_context
132 def get_feed_type(self, request):
133 feed_type = self.feed_type
134 if feed_type not in FEEDS:
135 feed_type = FEEDS.keys()[0]
136 accept = request.META.get('HTTP_ACCEPT')
137 if accept and feed_type not in accept and "*/*" not in accept and "%s/*" % feed_type.split("/")[0] not in accept:
138 # Wups! They aren't accepting the chosen format. Is there another format we can use?
140 feed_type = mimeparse.best_match(FEEDS.keys(), accept)
142 for feed_type in FEEDS.keys():
143 if feed_type in accept or "%s/*" % feed_type.split("/")[0] in accept:
148 raise HttpNotAcceptable
149 return FEEDS[feed_type]
151 def get_feed(self, obj, request, reverse_name):
153 Returns an unpopulated feedgenerator.DefaultFeed object for this object.
156 current_site = Site.objects.get_current()
157 except Site.DoesNotExist:
158 current_site = RequestSite(request)
160 feed_type = self.get_feed_type(request)
162 link = node.get_absolute_url(with_domain=True, request=request, secure=request.is_secure())
165 title = self.__get_dynamic_attr('title', obj),
166 subtitle = self.__get_dynamic_attr('subtitle', obj),
168 description = self.__get_dynamic_attr('description', obj),
169 language = settings.LANGUAGE_CODE.decode(),
170 feed_url = add_domain(
172 self.__get_dynamic_attr('feed_url', obj) or node.construct_url(node.subpath, with_domain=True, request=request, secure=request.is_secure()),
175 author_name = self.__get_dynamic_attr('author_name', obj),
176 author_link = self.__get_dynamic_attr('author_link', obj),
177 author_email = self.__get_dynamic_attr('author_email', obj),
178 categories = self.__get_dynamic_attr('categories', obj),
179 feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
180 feed_guid = self.__get_dynamic_attr('feed_guid', obj),
181 ttl = self.__get_dynamic_attr('ttl', obj),
182 **self.feed_extra_kwargs(obj)
186 def populate_feed(self, feed, items, request):
187 if self.item_title_template:
188 title_template = DjangoTemplate(self.item_title_template.code)
190 title_template = None
191 if self.item_description_template:
192 description_template = DjangoTemplate(self.item_description_template.code)
194 description_template = None
198 current_site = Site.objects.get_current()
199 except Site.DoesNotExist:
200 current_site = RequestSite(request)
202 if self.feed_length is not None:
203 items = items[:self.feed_length]
206 if title_template is not None:
207 title = title_template.render(RequestContext(request, {'obj': item}))
209 title = self.__get_dynamic_attr('item_title', item)
210 if description_template is not None:
211 description = description_template.render(RequestContext(request, {'obj': item}))
213 description = self.__get_dynamic_attr('item_description', item)
215 link = node.construct_url(self.reverse(obj=item), with_domain=True, request=request, secure=request.is_secure())
218 enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
220 enc = feedgenerator.Enclosure(
221 url = smart_unicode(add_domain(
226 length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
227 mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
229 author_name = self.__get_dynamic_attr('item_author_name', item)
230 if author_name is not None:
231 author_email = self.__get_dynamic_attr('item_author_email', item)
232 author_link = self.__get_dynamic_attr('item_author_link', item)
234 author_email = author_link = None
236 pubdate = self.__get_dynamic_attr('item_pubdate', item)
237 if pubdate and not pubdate.tzinfo:
238 ltz = tzinfo.LocalTimezone(pubdate)
239 pubdate = pubdate.replace(tzinfo=ltz)
244 description = description,
245 unique_id = self.__get_dynamic_attr('item_guid', item, link),
248 author_name = author_name,
249 author_email = author_email,
250 author_link = author_link,
251 categories = self.__get_dynamic_attr('item_categories', item),
252 item_copyright = self.__get_dynamic_attr('item_copyright', item),
253 **self.item_extra_kwargs(item)
256 def __get_dynamic_attr(self, attname, obj, default=None):
258 attr = getattr(self, attname)
259 except AttributeError:
262 # Check func_code.co_argcount rather than try/excepting the
263 # function and catching the TypeError, because something inside
264 # the function may raise the TypeError. This technique is more
266 if hasattr(attr, 'func_code'):
267 argcount = attr.func_code.co_argcount
269 argcount = attr.__call__.func_code.co_argcount
270 if argcount == 2: # one argument is 'self'
276 def feed_extra_kwargs(self, obj):
278 Returns an extra keyword arguments dictionary that is used when
279 initializing the feed generator.
283 def item_extra_kwargs(self, item):
285 Returns an extra keyword arguments dictionary that is used with
286 the `add_item` call of the feed generator.
290 def item_title(self, item):
291 return escape(force_unicode(item))
293 def item_description(self, item):
294 return force_unicode(item)
300 class Blog(Entity, Titled):
302 def entry_tags(self):
303 """ Returns a QuerySet of Tags that are used on any entries in this blog. """
304 return Tag.objects.filter(blogentries__blog=self).distinct()
307 def entry_dates(self):
308 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')}
312 register_value_model(Blog)
315 class BlogEntry(Entity, Titled):
316 blog = models.ForeignKey(Blog, related_name='entries', blank=True, null=True)
317 author = models.ForeignKey(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='blogentries')
318 date = models.DateTimeField(default=None)
319 content = models.TextField()
320 excerpt = models.TextField(blank=True, null=True)
321 tags = models.ManyToManyField(Tag, related_name='blogentries', blank=True, null=True)
323 def save(self, *args, **kwargs):
324 if self.date is None:
325 self.date = datetime.now()
326 super(BlogEntry, self).save(*args, **kwargs)
330 verbose_name_plural = "blog entries"
331 get_latest_by = "date"
334 register_value_model(BlogEntry)
337 class BlogView(FeedView):
338 ENTRY_PERMALINK_STYLE_CHOICES = (
339 ('D', 'Year, month, and day'),
340 ('M', 'Year and month'),
342 ('B', 'Custom base'),
346 blog = models.ForeignKey(Blog, related_name='blogviews')
348 index_page = models.ForeignKey(Page, related_name='blog_index_related')
349 entry_page = models.ForeignKey(Page, related_name='blog_entry_related')
350 # TODO: entry_archive is misleading. Rename to ymd_page or timespan_page.
351 entry_archive_page = models.ForeignKey(Page, related_name='blog_entry_archive_related', null=True, blank=True)
352 tag_page = models.ForeignKey(Page, related_name='blog_tag_related')
353 tag_archive_page = models.ForeignKey(Page, related_name='blog_tag_archive_related', null=True, blank=True)
354 entries_per_page = models.IntegerField(blank=True, validators=[validate_pagination_count], null=True)
356 entry_permalink_style = models.CharField(max_length=1, choices=ENTRY_PERMALINK_STYLE_CHOICES)
357 entry_permalink_base = models.CharField(max_length=255, blank=False, default='entries')
358 tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags')
360 item_context_var = 'entries'
363 def __unicode__(self):
364 return u'BlogView for %s' % self.blog.title
366 def get_reverse_params(self, obj):
367 if isinstance(obj, BlogEntry):
368 if obj.blog == self.blog:
369 kwargs = {'slug': obj.slug}
370 if self.entry_permalink_style in 'DMY':
371 kwargs.update({'year': str(obj.date.year).zfill(4)})
372 if self.entry_permalink_style in 'DM':
373 kwargs.update({'month': str(obj.date.month).zfill(2)})
374 if self.entry_permalink_style == 'D':
375 kwargs.update({'day': str(obj.date.day).zfill(2)})
376 return self.entry_view, [], kwargs
377 elif isinstance(obj, Tag) or (isinstance(obj, models.query.QuerySet) and obj.model == Tag and obj):
378 if isinstance(obj, Tag):
380 slugs = [tag.slug for tag in obj if tag in self.get_tag_queryset()]
382 return 'entries_by_tag', [], {'tag_slugs': "/".join(slugs)}
383 elif isinstance(obj, (date, datetime)):
385 'year': str(obj.year).zfill(4),
386 'month': str(obj.month).zfill(2),
387 'day': str(obj.day).zfill(2)
389 return 'entries_by_day', [], kwargs
390 raise ViewCanNotProvideSubpath
393 def urlpatterns(self):
394 urlpatterns = self.feed_patterns(r'^', 'get_all_entries', 'index_page', 'index') +\
395 self.feed_patterns(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)$' % self.tag_permalink_base, 'get_entries_by_tag', 'tag_page', 'entries_by_tag')
397 if self.tag_archive_page:
398 urlpatterns += patterns('',
399 url((r'^%s$' % self.tag_permalink_base), self.tag_archive_view, name='tag_archive')
402 if self.entry_archive_page:
403 if self.entry_permalink_style in 'DMY':
404 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_year')
405 if self.entry_permalink_style in 'DM':
406 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})/(?P<month>\d{2})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_month')
407 if self.entry_permalink_style == 'D':
408 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')
410 if self.entry_permalink_style == 'D':
411 urlpatterns += patterns('',
412 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
414 elif self.entry_permalink_style == 'M':
415 urlpatterns += patterns('',
416 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
418 elif self.entry_permalink_style == 'Y':
419 urlpatterns += patterns('',
420 url(r'^(?P<year>\d{4})/(?P<slug>[-\w]+)$', self.entry_view)
422 elif self.entry_permalink_style == 'B':
423 urlpatterns += patterns('',
424 url((r'^%s/(?P<slug>[-\w]+)$' % self.entry_permalink_base), self.entry_view)
427 urlpatterns = patterns('',
428 url(r'^(?P<slug>[-\w]+)$', self.entry_view)
432 def get_context(self):
433 return {'blog': self.blog}
435 def get_entry_queryset(self):
436 return self.blog.entries.all()
438 def get_tag_queryset(self):
439 return self.blog.entry_tags
441 def get_all_entries(self, request, extra_context=None):
442 return self.get_entry_queryset(), extra_context
444 def get_entries_by_ymd(self, request, year=None, month=None, day=None, extra_context=None):
445 if not self.entry_archive_page:
447 entries = self.get_entry_queryset()
449 entries = entries.filter(date__year=year)
451 entries = entries.filter(date__month=month)
453 entries = entries.filter(date__day=day)
455 context = extra_context or {}
456 context.update({'year': year, 'month': month, 'day': day})
457 return entries, context
459 def get_entries_by_tag(self, request, tag_slugs, extra_context=None):
460 tag_slugs = tag_slugs.replace('+', '/').split('/')
461 tags = self.get_tag_queryset().filter(slug__in=tag_slugs)
466 # Raise a 404 on an incorrect slug.
467 found_slugs = [tag.slug for tag in tags]
468 for slug in tag_slugs:
469 if slug and slug not in found_slugs:
472 entries = self.get_entry_queryset()
474 entries = entries.filter(tags=tag)
476 context = extra_context or {}
477 context.update({'tags': tags})
479 return entries, context
481 def entry_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
482 entries = self.get_entry_queryset()
484 entries = entries.filter(date__year=year)
486 entries = entries.filter(date__month=month)
488 entries = entries.filter(date__day=day)
490 entry = entries.get(slug=slug)
493 context = self.get_context()
494 context.update(extra_context or {})
495 context.update({'entry': entry})
496 return self.entry_page.render_to_response(request, extra_context=context)
498 def tag_archive_view(self, request, extra_context=None):
499 if not self.tag_archive_page:
501 context = self.get_context()
502 context.update(extra_context or {})
504 'tags': self.get_tag_queryset()
506 return self.tag_archive_page.render_to_response(request, extra_context=context)
508 def feed_view(self, get_items_attr, reverse_name):
509 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
511 def inner(request, extra_context=None, *args, **kwargs):
512 obj = self.get_object(request, *args, **kwargs)
513 feed = self.get_feed(obj, request, reverse_name)
514 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
515 self.populate_feed(feed, items, request)
517 if 'tags' in extra_context:
518 tags = extra_context['tags']
519 feed.feed['link'] = request.node.construct_url(self.reverse(obj=tags), with_domain=True, request=request, secure=request.is_secure())
521 tags = obj.entry_tags
523 feed.feed['categories'] = [tag.name for tag in tags]
525 response = HttpResponse(mimetype=feed.mime_type)
526 feed.write(response, 'utf-8')
531 def process_page_items(self, request, items):
532 if self.entries_per_page:
533 page_num = request.GET.get('page', 1)
534 paginator, paginated_page, items = paginate(items, self.entries_per_page, page_num)
536 'paginator': paginator,
537 'paginated_page': paginated_page,
538 self.item_context_var: items
542 self.item_context_var: items
544 return items, item_context
546 def title(self, obj):
549 def item_title(self, item):
552 def item_description(self, item):
555 def item_author_name(self, item):
556 return item.author.get_full_name()
558 def item_pubdate(self, item):
561 def item_categories(self, item):
562 return [tag.name for tag in item.tags.all()]
565 class Newsletter(Entity, Titled):
569 register_value_model(Newsletter)
572 class NewsletterArticle(Entity, Titled):
573 newsletter = models.ForeignKey(Newsletter, related_name='articles')
574 authors = models.ManyToManyField(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='newsletterarticles')
575 date = models.DateTimeField(default=None)
576 lede = TemplateField(null=True, blank=True, verbose_name='Summary')
577 full_text = TemplateField(db_index=True)
578 tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True)
580 def save(self, *args, **kwargs):
581 if self.date is None:
582 self.date = datetime.now()
583 super(NewsletterArticle, self).save(*args, **kwargs)
586 get_latest_by = 'date'
588 unique_together = (('newsletter', 'slug'),)
591 register_value_model(NewsletterArticle)
594 class NewsletterIssue(Entity, Titled):
595 newsletter = models.ForeignKey(Newsletter, related_name='issues')
596 numbering = models.CharField(max_length=50, help_text='For example, 04.02 for volume 4, issue 2.')
597 articles = models.ManyToManyField(NewsletterArticle, related_name='issues')
600 ordering = ['-numbering']
601 unique_together = (('newsletter', 'numbering'),)
604 register_value_model(NewsletterIssue)
607 class NewsletterView(FeedView):
608 ARTICLE_PERMALINK_STYLE_CHOICES = (
609 ('D', 'Year, month, and day'),
610 ('M', 'Year and month'),
615 newsletter = models.ForeignKey(Newsletter, related_name='newsletterviews')
617 index_page = models.ForeignKey(Page, related_name='newsletter_index_related')
618 article_page = models.ForeignKey(Page, related_name='newsletter_article_related')
619 article_archive_page = models.ForeignKey(Page, related_name='newsletter_article_archive_related', null=True, blank=True)
620 issue_page = models.ForeignKey(Page, related_name='newsletter_issue_related')
621 issue_archive_page = models.ForeignKey(Page, related_name='newsletter_issue_archive_related', null=True, blank=True)
623 article_permalink_style = models.CharField(max_length=1, choices=ARTICLE_PERMALINK_STYLE_CHOICES)
624 article_permalink_base = models.CharField(max_length=255, blank=False, default='articles')
625 issue_permalink_base = models.CharField(max_length=255, blank=False, default='issues')
627 item_context_var = 'articles'
628 object_attr = 'newsletter'
630 def __unicode__(self):
631 return "NewsletterView for %s" % self.newsletter.__unicode__()
633 def get_reverse_params(self, obj):
634 if isinstance(obj, NewsletterArticle):
635 if obj.newsletter == self.newsletter:
636 kwargs = {'slug': obj.slug}
637 if self.article_permalink_style in 'DMY':
638 kwargs.update({'year': str(obj.date.year).zfill(4)})
639 if self.article_permalink_style in 'DM':
640 kwargs.update({'month': str(obj.date.month).zfill(2)})
641 if self.article_permalink_style == 'D':
642 kwargs.update({'day': str(obj.date.day).zfill(2)})
643 return self.article_view, [], kwargs
644 elif isinstance(obj, NewsletterIssue):
645 if obj.newsletter == self.newsletter:
646 return 'issue', [], {'numbering': obj.numbering}
647 elif isinstance(obj, (date, datetime)):
649 'year': str(obj.year).zfill(4),
650 'month': str(obj.month).zfill(2),
651 'day': str(obj.day).zfill(2)
653 return 'articles_by_day', [], kwargs
654 raise ViewCanNotProvideSubpath
657 def urlpatterns(self):
658 urlpatterns = self.feed_patterns(r'^', 'get_all_articles', 'index_page', 'index') + patterns('',
659 url(r'^%s/(?P<numbering>.+)$' % self.issue_permalink_base, self.page_view('get_articles_by_issue', 'issue_page'), name='issue')
661 if self.issue_archive_page:
662 urlpatterns += patterns('',
663 url(r'^%s$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive')
665 if self.article_archive_page:
666 urlpatterns += patterns('',
667 url(r'^%s' % self.article_permalink_base, include(self.feed_patterns('get_all_articles', 'article_archive_page', 'articles')))
669 if self.article_permalink_style in 'DMY':
670 urlpatterns += self.feed_patterns(r'^%s/(?P<year>\d{4})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_year')
671 if self.article_permalink_style in 'DM':
672 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')
673 if self.article_permalink_style == 'D':
674 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')
676 if self.article_permalink_style == 'Y':
677 urlpatterns += patterns('',
678 url(r'^%s/(?P<year>\d{4})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
680 elif self.article_permalink_style == 'M':
681 urlpatterns += patterns('',
682 url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
684 elif self.article_permalink_style == 'D':
685 urlpatterns += patterns('',
686 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)
689 urlpatterns += patterns('',
690 url(r'^%s/(?P<slug>[-\w]+)$' % self.article_permalink_base, self.article_view)
695 def get_context(self):
696 return {'newsletter': self.newsletter}
698 def get_article_queryset(self):
699 return self.newsletter.articles.all()
701 def get_issue_queryset(self):
702 return self.newsletter.issues.all()
704 def get_all_articles(self, request, extra_context=None):
705 return self.get_article_queryset(), extra_context
707 def get_articles_by_ymd(self, request, year, month=None, day=None, extra_context=None):
708 articles = self.get_article_queryset().filter(date__year=year)
710 articles = articles.filter(date__month=month)
712 articles = articles.filter(date__day=day)
713 return articles, extra_context
715 def get_articles_by_issue(self, request, numbering, extra_context=None):
717 issue = self.get_issue_queryset().get(numbering=numbering)
718 except NewsletterIssue.DoesNotExist:
720 context = extra_context or {}
721 context.update({'issue': issue})
722 return self.get_article_queryset().filter(issues=issue), context
724 def article_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
725 articles = self.get_article_queryset()
727 articles = articles.filter(date__year=year)
729 articles = articles.filter(date__month=month)
731 articles = articles.filter(date__day=day)
733 article = articles.get(slug=slug)
734 except NewsletterArticle.DoesNotExist:
736 context = self.get_context()
737 context.update(extra_context or {})
738 context.update({'article': article})
739 return self.article_page.render_to_response(request, extra_context=context)
741 def issue_archive_view(self, request, extra_context):
742 if not self.issue_archive_page:
744 context = self.get_context()
745 context.update(extra_context or {})
747 'issues': self.get_issue_queryset()
749 return self.issue_archive_page.render_to_response(request, extra_context=context)
751 def title(self, obj):
754 def item_title(self, item):
757 def item_description(self, item):
758 return item.full_text
760 def item_author_name(self, item):
761 authors = list(item.authors.all())
763 return "%s and %s" % (", ".join([author.get_full_name() for author in authors[:-1]]), authors[-1].get_full_name())
765 return authors[0].get_full_name()
769 def item_pubdate(self, item):
772 def item_categories(self, item):
773 return [tag.name for tag in item.tags.all()]