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)
50 item_title_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_title_related")
51 item_description_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_description_related")
53 item_context_var = 'items'
54 object_attr = 'object'
58 def feed_patterns(self, base, get_items_attr, page_attr, reverse_name):
60 Given the name to be used to reverse this view and the names of
61 the attributes for the function that fetches the objects, returns
62 patterns suitable for inclusion in urlpatterns.
64 urlpatterns = patterns('')
65 if self.feeds_enabled:
66 feed_reverse_name = "%s_feed" % reverse_name
67 feed_view = http_not_acceptable(self.feed_view(get_items_attr, feed_reverse_name))
68 feed_pattern = r'%s%s%s$' % (base, (base and base[-1] != "^") and "/" or "", self.feed_suffix)
69 urlpatterns += patterns('',
70 url(feed_pattern, feed_view, name=feed_reverse_name),
72 urlpatterns += patterns('',
73 url(r"%s$" % base, self.page_view(get_items_attr, page_attr), name=reverse_name)
77 def get_object(self, request, **kwargs):
78 return getattr(self, self.object_attr)
80 def feed_view(self, get_items_attr, reverse_name):
82 Returns a view function that renders a list of items as a feed.
84 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
86 def inner(request, extra_context=None, *args, **kwargs):
87 obj = self.get_object(request, *args, **kwargs)
88 feed = self.get_feed(obj, request, reverse_name)
89 items, xxx = get_items(request, extra_context=extra_context, *args, **kwargs)
90 self.populate_feed(feed, items, request)
92 response = HttpResponse(mimetype=feed.mime_type)
93 feed.write(response, 'utf-8')
98 def page_view(self, get_items_attr, page_attr):
100 Returns a view function that renders a list of items as a page.
102 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
103 page = isinstance(page_attr, Page) and page_attr or getattr(self, page_attr)
105 def inner(request, extra_context=None, *args, **kwargs):
106 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
107 items, item_context = self.process_page_items(request, items)
109 context = self.get_context()
110 context.update(extra_context or {})
111 context.update(item_context or {})
113 return page.render_to_response(request, extra_context=context)
116 def process_page_items(self, request, items):
118 Hook for handling any extra processing of items based on a
119 request, such as pagination or searching. This method is
120 expected to return a list of items and a dictionary to be
121 added to the page context.
124 self.item_context_var: items
126 return items, item_context
128 def get_feed_type(self, request):
129 feed_type = self.feed_type
130 if feed_type not in FEEDS:
131 feed_type = FEEDS.keys()[0]
132 accept = request.META.get('HTTP_ACCEPT')
133 if accept and feed_type not in accept and "*/*" not in accept and "%s/*" % feed_type.split("/")[0] not in accept:
134 # Wups! They aren't accepting the chosen format. Is there another format we can use?
136 feed_type = mimeparse.best_match(FEEDS.keys(), accept)
138 for feed_type in FEEDS.keys():
139 if feed_type in accept or "%s/*" % feed_type.split("/")[0] in accept:
144 raise HttpNotAcceptable
145 return FEEDS[feed_type]
147 def get_feed(self, obj, request, reverse_name):
149 Returns an unpopulated feedgenerator.DefaultFeed object for this object.
152 current_site = Site.objects.get_current()
153 except Site.DoesNotExist:
154 current_site = RequestSite(request)
156 feed_type = self.get_feed_type(request)
158 link = node.get_absolute_url(with_domain=True, request=request, secure=request.is_secure())
161 title = self.__get_dynamic_attr('title', obj),
162 subtitle = self.__get_dynamic_attr('subtitle', obj),
164 description = self.__get_dynamic_attr('description', obj),
165 language = settings.LANGUAGE_CODE.decode(),
166 feed_url = add_domain(
168 self.__get_dynamic_attr('feed_url', obj) or node.construct_url(node.subpath, with_domain=True, request=request, secure=request.is_secure()),
171 author_name = self.__get_dynamic_attr('author_name', obj),
172 author_link = self.__get_dynamic_attr('author_link', obj),
173 author_email = self.__get_dynamic_attr('author_email', obj),
174 categories = self.__get_dynamic_attr('categories', obj),
175 feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
176 feed_guid = self.__get_dynamic_attr('feed_guid', obj),
177 ttl = self.__get_dynamic_attr('ttl', obj),
178 **self.feed_extra_kwargs(obj)
182 def populate_feed(self, feed, items, request):
183 if self.item_title_template:
184 title_template = DjangoTemplate(self.item_title_template.code)
186 title_template = None
187 if self.item_description_template:
188 description_template = DjangoTemplate(self.item_description_template.code)
190 description_template = None
194 current_site = Site.objects.get_current()
195 except Site.DoesNotExist:
196 current_site = RequestSite(request)
199 if title_template is not None:
200 title = title_template.render(RequestContext(request, {'obj': item}))
202 title = self.__get_dynamic_attr('item_title', item)
203 if description_template is not None:
204 description = description_template.render(RequestContext(request, {'obj': item}))
206 description = self.__get_dynamic_attr('item_description', item)
208 link = node.construct_url(self.reverse(obj=item), with_domain=True, request=request, secure=request.is_secure())
211 enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
213 enc = feedgenerator.Enclosure(
214 url = smart_unicode(add_domain(
219 length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
220 mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
222 author_name = self.__get_dynamic_attr('item_author_name', item)
223 if author_name is not None:
224 author_email = self.__get_dynamic_attr('item_author_email', item)
225 author_link = self.__get_dynamic_attr('item_author_link', item)
227 author_email = author_link = None
229 pubdate = self.__get_dynamic_attr('item_pubdate', item)
230 if pubdate and not pubdate.tzinfo:
231 ltz = tzinfo.LocalTimezone(pubdate)
232 pubdate = pubdate.replace(tzinfo=ltz)
237 description = description,
238 unique_id = self.__get_dynamic_attr('item_guid', item, link),
241 author_name = author_name,
242 author_email = author_email,
243 author_link = author_link,
244 categories = self.__get_dynamic_attr('item_categories', item),
245 item_copyright = self.__get_dynamic_attr('item_copyright', item),
246 **self.item_extra_kwargs(item)
249 def __get_dynamic_attr(self, attname, obj, default=None):
251 attr = getattr(self, attname)
252 except AttributeError:
255 # Check func_code.co_argcount rather than try/excepting the
256 # function and catching the TypeError, because something inside
257 # the function may raise the TypeError. This technique is more
259 if hasattr(attr, 'func_code'):
260 argcount = attr.func_code.co_argcount
262 argcount = attr.__call__.func_code.co_argcount
263 if argcount == 2: # one argument is 'self'
269 def feed_extra_kwargs(self, obj):
271 Returns an extra keyword arguments dictionary that is used when
272 initializing the feed generator.
276 def item_extra_kwargs(self, item):
278 Returns an extra keyword arguments dictionary that is used with
279 the `add_item` call of the feed generator.
283 def item_title(self, item):
284 return escape(force_unicode(item))
286 def item_description(self, item):
287 return force_unicode(item)
293 class Blog(Entity, Titled):
295 def entry_tags(self):
296 """ Returns a QuerySet of Tags that are used on any entries in this blog. """
297 return Tag.objects.filter(blogentries__blog=self).distinct()
300 def entry_dates(self):
301 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')}
305 register_value_model(Blog)
308 class BlogEntry(Entity, Titled):
309 blog = models.ForeignKey(Blog, related_name='entries', blank=True, null=True)
310 author = models.ForeignKey(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='blogentries')
311 date = models.DateTimeField(default=None)
312 content = models.TextField()
313 excerpt = models.TextField(blank=True, null=True)
314 tags = models.ManyToManyField(Tag, related_name='blogentries', blank=True, null=True)
316 def save(self, *args, **kwargs):
317 if self.date is None:
318 self.date = datetime.now()
319 super(BlogEntry, self).save(*args, **kwargs)
323 verbose_name_plural = "blog entries"
324 get_latest_by = "date"
327 register_value_model(BlogEntry)
330 class BlogView(FeedView):
331 ENTRY_PERMALINK_STYLE_CHOICES = (
332 ('D', 'Year, month, and day'),
333 ('M', 'Year and month'),
335 ('B', 'Custom base'),
339 blog = models.ForeignKey(Blog, related_name='blogviews')
341 index_page = models.ForeignKey(Page, related_name='blog_index_related')
342 entry_page = models.ForeignKey(Page, related_name='blog_entry_related')
343 # TODO: entry_archive is misleading. Rename to ymd_page or timespan_page.
344 entry_archive_page = models.ForeignKey(Page, related_name='blog_entry_archive_related', null=True, blank=True)
345 tag_page = models.ForeignKey(Page, related_name='blog_tag_related')
346 tag_archive_page = models.ForeignKey(Page, related_name='blog_tag_archive_related', null=True, blank=True)
347 entries_per_page = models.IntegerField(blank=True, validators=[validate_pagination_count], null=True)
349 entry_permalink_style = models.CharField(max_length=1, choices=ENTRY_PERMALINK_STYLE_CHOICES)
350 entry_permalink_base = models.CharField(max_length=255, blank=False, default='entries')
351 tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags')
353 item_context_var = 'entries'
356 def __unicode__(self):
357 return u'BlogView for %s' % self.blog.title
359 def get_reverse_params(self, obj):
360 if isinstance(obj, BlogEntry):
361 if obj.blog == self.blog:
362 kwargs = {'slug': obj.slug}
363 if self.entry_permalink_style in 'DMY':
364 kwargs.update({'year': str(obj.date.year).zfill(4)})
365 if self.entry_permalink_style in 'DM':
366 kwargs.update({'month': str(obj.date.month).zfill(2)})
367 if self.entry_permalink_style == 'D':
368 kwargs.update({'day': str(obj.date.day).zfill(2)})
369 return self.entry_view, [], kwargs
370 elif isinstance(obj, Tag) or (isinstance(obj, models.query.QuerySet) and obj.model == Tag and obj):
371 if isinstance(obj, Tag):
373 slugs = [tag.slug for tag in obj if tag in self.get_tag_queryset()]
375 return 'entries_by_tag', [], {'tag_slugs': "/".join(slugs)}
376 elif isinstance(obj, (date, datetime)):
378 'year': str(obj.year).zfill(4),
379 'month': str(obj.month).zfill(2),
380 'day': str(obj.day).zfill(2)
382 return 'entries_by_day', [], kwargs
383 raise ViewCanNotProvideSubpath
386 def urlpatterns(self):
387 urlpatterns = self.feed_patterns(r'^', 'get_all_entries', 'index_page', 'index') +\
388 self.feed_patterns(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)$' % self.tag_permalink_base, 'get_entries_by_tag', 'tag_page', 'entries_by_tag')
390 if self.tag_archive_page:
391 urlpatterns += patterns('',
392 url((r'^%s$' % self.tag_permalink_base), self.tag_archive_view, name='tag_archive')
395 if self.entry_archive_page:
396 if self.entry_permalink_style in 'DMY':
397 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_year')
398 if self.entry_permalink_style in 'DM':
399 urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})/(?P<month>\d{2})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_month')
400 if self.entry_permalink_style == 'D':
401 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')
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})/(?P<slug>[-\w]+)$', self.entry_view)
407 elif self.entry_permalink_style == 'M':
408 urlpatterns += patterns('',
409 url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)$', self.entry_view)
411 elif self.entry_permalink_style == 'Y':
412 urlpatterns += patterns('',
413 url(r'^(?P<year>\d{4})/(?P<slug>[-\w]+)$', self.entry_view)
415 elif self.entry_permalink_style == 'B':
416 urlpatterns += patterns('',
417 url((r'^%s/(?P<slug>[-\w]+)$' % self.entry_permalink_base), self.entry_view)
420 urlpatterns = patterns('',
421 url(r'^(?P<slug>[-\w]+)$', self.entry_view)
425 def get_context(self):
426 return {'blog': self.blog}
428 def get_entry_queryset(self):
429 return self.blog.entries.all()
431 def get_tag_queryset(self):
432 return self.blog.entry_tags
434 def get_all_entries(self, request, extra_context=None):
435 return self.get_entry_queryset(), extra_context
437 def get_entries_by_ymd(self, request, year=None, month=None, day=None, extra_context=None):
438 if not self.entry_archive_page:
440 entries = self.get_entry_queryset()
442 entries = entries.filter(date__year=year)
444 entries = entries.filter(date__month=month)
446 entries = entries.filter(date__day=day)
448 context = extra_context or {}
449 context.update({'year': year, 'month': month, 'day': day})
450 return entries, context
452 def get_entries_by_tag(self, request, tag_slugs, extra_context=None):
453 tag_slugs = tag_slugs.replace('+', '/').split('/')
454 tags = self.get_tag_queryset().filter(slug__in=tag_slugs)
459 # Raise a 404 on an incorrect slug.
460 found_slugs = [tag.slug for tag in tags]
461 for slug in tag_slugs:
462 if slug and slug not in found_slugs:
465 entries = self.get_entry_queryset()
467 entries = entries.filter(tags=tag)
469 context = extra_context or {}
470 context.update({'tags': tags})
472 return entries, context
474 def entry_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
475 entries = self.get_entry_queryset()
477 entries = entries.filter(date__year=year)
479 entries = entries.filter(date__month=month)
481 entries = entries.filter(date__day=day)
483 entry = entries.get(slug=slug)
486 context = self.get_context()
487 context.update(extra_context or {})
488 context.update({'entry': entry})
489 return self.entry_page.render_to_response(request, extra_context=context)
491 def tag_archive_view(self, request, extra_context=None):
492 if not self.tag_archive_page:
494 context = self.get_context()
495 context.update(extra_context or {})
497 'tags': self.get_tag_queryset()
499 return self.tag_archive_page.render_to_response(request, extra_context=context)
501 def feed_view(self, get_items_attr, reverse_name):
502 get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
504 def inner(request, extra_context=None, *args, **kwargs):
505 obj = self.get_object(request, *args, **kwargs)
506 feed = self.get_feed(obj, request, reverse_name)
507 items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
508 self.populate_feed(feed, items, request)
510 if 'tags' in extra_context:
511 tags = extra_context['tags']
512 feed.feed['link'] = request.node.construct_url(self.reverse(obj=tags), with_domain=True, request=request, secure=request.is_secure())
514 tags = obj.entry_tags
516 feed.feed['categories'] = [tag.name for tag in tags]
518 response = HttpResponse(mimetype=feed.mime_type)
519 feed.write(response, 'utf-8')
524 def process_page_items(self, request, items):
525 if self.entries_per_page:
526 page_num = request.GET.get('page', 1)
527 paginator, paginated_page, items = paginate(items, self.entries_per_page, page_num)
529 'paginator': paginator,
530 'paginated_page': paginated_page,
531 self.item_context_var: items
535 self.item_context_var: items
537 return items, item_context
539 def title(self, obj):
542 def item_title(self, item):
545 def item_description(self, item):
548 def item_author_name(self, item):
549 return item.author.get_full_name()
551 def item_pubdate(self, item):
554 def item_categories(self, item):
555 return [tag.name for tag in item.tags.all()]
558 class Newsletter(Entity, Titled):
562 register_value_model(Newsletter)
565 class NewsletterArticle(Entity, Titled):
566 newsletter = models.ForeignKey(Newsletter, related_name='articles')
567 authors = models.ManyToManyField(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='newsletterarticles')
568 date = models.DateTimeField(default=None)
569 lede = TemplateField(null=True, blank=True, verbose_name='Summary')
570 full_text = TemplateField(db_index=True)
571 tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True)
573 def save(self, *args, **kwargs):
574 if self.date is None:
575 self.date = datetime.now()
576 super(NewsletterArticle, self).save(*args, **kwargs)
579 get_latest_by = 'date'
581 unique_together = (('newsletter', 'slug'),)
584 register_value_model(NewsletterArticle)
587 class NewsletterIssue(Entity, Titled):
588 newsletter = models.ForeignKey(Newsletter, related_name='issues')
589 numbering = models.CharField(max_length=50, help_text='For example, 04.02 for volume 4, issue 2.')
590 articles = models.ManyToManyField(NewsletterArticle, related_name='issues')
593 ordering = ['-numbering']
594 unique_together = (('newsletter', 'numbering'),)
597 register_value_model(NewsletterIssue)
600 class NewsletterView(FeedView):
601 ARTICLE_PERMALINK_STYLE_CHOICES = (
602 ('D', 'Year, month, and day'),
603 ('M', 'Year and month'),
608 newsletter = models.ForeignKey(Newsletter, related_name='newsletterviews')
610 index_page = models.ForeignKey(Page, related_name='newsletter_index_related')
611 article_page = models.ForeignKey(Page, related_name='newsletter_article_related')
612 article_archive_page = models.ForeignKey(Page, related_name='newsletter_article_archive_related', null=True, blank=True)
613 issue_page = models.ForeignKey(Page, related_name='newsletter_issue_related')
614 issue_archive_page = models.ForeignKey(Page, related_name='newsletter_issue_archive_related', null=True, blank=True)
616 article_permalink_style = models.CharField(max_length=1, choices=ARTICLE_PERMALINK_STYLE_CHOICES)
617 article_permalink_base = models.CharField(max_length=255, blank=False, default='articles')
618 issue_permalink_base = models.CharField(max_length=255, blank=False, default='issues')
620 item_context_var = 'articles'
621 object_attr = 'newsletter'
623 def __unicode__(self):
624 return "NewsletterView for %s" % self.newsletter.__unicode__()
626 def get_reverse_params(self, obj):
627 if isinstance(obj, NewsletterArticle):
628 if obj.newsletter == self.newsletter:
629 kwargs = {'slug': obj.slug}
630 if self.article_permalink_style in 'DMY':
631 kwargs.update({'year': str(obj.date.year).zfill(4)})
632 if self.article_permalink_style in 'DM':
633 kwargs.update({'month': str(obj.date.month).zfill(2)})
634 if self.article_permalink_style == 'D':
635 kwargs.update({'day': str(obj.date.day).zfill(2)})
636 return self.article_view, [], kwargs
637 elif isinstance(obj, NewsletterIssue):
638 if obj.newsletter == self.newsletter:
639 return 'issue', [], {'numbering': obj.numbering}
640 elif isinstance(obj, (date, datetime)):
642 'year': str(obj.year).zfill(4),
643 'month': str(obj.month).zfill(2),
644 'day': str(obj.day).zfill(2)
646 return 'articles_by_day', [], kwargs
647 raise ViewCanNotProvideSubpath
650 def urlpatterns(self):
651 urlpatterns = self.feed_patterns(r'^', 'get_all_articles', 'index_page', 'index') + patterns('',
652 url(r'^%s/(?P<numbering>.+)$' % self.issue_permalink_base, self.page_view('get_articles_by_issue', 'issue_page'), name='issue')
654 if self.issue_archive_page:
655 urlpatterns += patterns('',
656 url(r'^%s$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive')
658 if self.article_archive_page:
659 urlpatterns += patterns('',
660 url(r'^%s' % self.article_permalink_base, include(self.feed_patterns('get_all_articles', 'article_archive_page', 'articles')))
662 if self.article_permalink_style in 'DMY':
663 urlpatterns += self.feed_patterns(r'^%s/(?P<year>\d{4})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_year')
664 if self.article_permalink_style in 'DM':
665 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')
666 if self.article_permalink_style == 'D':
667 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')
669 if self.article_permalink_style == 'Y':
670 urlpatterns += patterns('',
671 url(r'^%s/(?P<year>\d{4})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
673 elif self.article_permalink_style == 'M':
674 urlpatterns += patterns('',
675 url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[\w-]+)$' % self.article_permalink_base, self.article_view)
677 elif self.article_permalink_style == 'D':
678 urlpatterns += patterns('',
679 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)
682 urlpatterns += patterns('',
683 url(r'^%s/(?P<slug>[-\w]+)$' % self.article_permalink_base, self.article_view)
688 def get_context(self):
689 return {'newsletter': self.newsletter}
691 def get_article_queryset(self):
692 return self.newsletter.articles.all()
694 def get_issue_queryset(self):
695 return self.newsletter.issues.all()
697 def get_all_articles(self, request, extra_context=None):
698 return self.get_article_queryset(), extra_context
700 def get_articles_by_ymd(self, request, year, month=None, day=None, extra_context=None):
701 articles = self.get_article_queryset().filter(date__year=year)
703 articles = articles.filter(date__month=month)
705 articles = articles.filter(date__day=day)
706 return articles, extra_context
708 def get_articles_by_issue(self, request, numbering, extra_context=None):
710 issue = self.get_issue_queryset().get(numbering=numbering)
711 except NewsletterIssue.DoesNotExist:
713 context = extra_context or {}
714 context.update({'issue': issue})
715 return self.get_article_queryset().filter(issues=issue), context
717 def article_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
718 articles = self.get_article_queryset()
720 articles = articles.filter(date__year=year)
722 articles = articles.filter(date__month=month)
724 articles = articles.filter(date__day=day)
726 article = articles.get(slug=slug)
727 except NewsletterArticle.DoesNotExist:
729 context = self.get_context()
730 context.update(extra_context or {})
731 context.update({'article': article})
732 return self.article_page.render_to_response(request, extra_context=context)
734 def issue_archive_view(self, request, extra_context):
735 if not self.issue_archive_page:
737 context = self.get_context()
738 context.update(extra_context or {})
740 'issues': self.get_issue_queryset()
742 return self.issue_archive_page.render_to_response(request, extra_context=context)
744 def title(self, obj):
747 def item_title(self, item):
750 def item_description(self, item):
751 return item.full_text
753 def item_author_name(self, item):
754 authors = list(item.authors.all())
756 return "%s and %s" % (", ".join([author.get_full_name() for author in authors[:-1]]), authors[-1].get_full_name())
758 return authors[0].get_full_name()
762 def item_pubdate(self, item):
765 def item_categories(self, item):
766 return [tag.name for tag in item.tags.all()]