Merge branch 'taggit' into develop
[philo.git] / philo / contrib / penfield / models.py
index 3a8e82f..a57459c 100644 (file)
@@ -1,13 +1,16 @@
+# encoding: utf-8
 from datetime import date, datetime
 
 from django.conf import settings
 from django.conf.urls.defaults import url, patterns, include
 from django.db import models
 from django.http import Http404, HttpResponse
 from datetime import date, datetime
 
 from django.conf import settings
 from django.conf.urls.defaults import url, patterns, include
 from django.db import models
 from django.http import Http404, HttpResponse
+from taggit.managers import TaggableManager
+from taggit.models import Tag, TaggedItem
 
 from philo.contrib.winer.models import FeedView
 from philo.exceptions import ViewCanNotProvideSubpath
 
 from philo.contrib.winer.models import FeedView
 from philo.exceptions import ViewCanNotProvideSubpath
-from philo.models import Tag, Entity, Page, register_value_model
+from philo.models import Entity, Page, register_value_model
 from philo.models.fields import TemplateField
 from philo.utils import paginate
 
 from philo.models.fields import TemplateField
 from philo.utils import paginate
 
@@ -26,7 +29,11 @@ class Blog(Entity):
        @property
        def entry_tags(self):
                """Returns a :class:`QuerySet` of :class:`.Tag`\ s that are used on any entries in this blog."""
        @property
        def entry_tags(self):
                """Returns a :class:`QuerySet` of :class:`.Tag`\ s that are used on any entries in this blog."""
-               return Tag.objects.filter(blogentries__blog=self).distinct()
+               entry_pks = list(self.entries.values_list('pk', flat=True))
+               kwargs = {
+                       '%s__object_id__in' % TaggedItem.tag_relname(): entry_pks
+               }
+               return TaggedItem.tags_for(BlogEntry).filter(**kwargs)
        
        @property
        def entry_dates(self):
        
        @property
        def entry_dates(self):
@@ -56,13 +63,13 @@ class BlogEntry(Entity):
        date = models.DateTimeField(default=None)
        
        #: The content of the :class:`BlogEntry`.
        date = models.DateTimeField(default=None)
        
        #: The content of the :class:`BlogEntry`.
-       content = models.TextField()
+       content = TemplateField()
        
        #: An optional brief excerpt from the :class:`BlogEntry`.
        
        #: An optional brief excerpt from the :class:`BlogEntry`.
-       excerpt = models.TextField(blank=True, null=True)
+       excerpt = TemplateField(blank=True, null=True)
        
        
-       #: :class:`.Tag`\ s for this :class:`BlogEntry`.
-       tags = models.ManyToManyField(Tag, related_name='blogentries', blank=True, null=True)
+       #: A ``django-taggit`` :class:`TaggableManager`.
+       tags = TaggableManager()
        
        def save(self, *args, **kwargs):
                if self.date is None:
        
        def save(self, *args, **kwargs):
                if self.date is None:
@@ -125,14 +132,13 @@ class BlogView(FeedView):
        tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags')
        
        item_context_var = 'entries'
        tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags')
        
        item_context_var = 'entries'
-       object_attr = 'blog'
        
        def __unicode__(self):
                return u'BlogView for %s' % self.blog.title
        
        def get_reverse_params(self, obj):
                if isinstance(obj, BlogEntry):
        
        def __unicode__(self):
                return u'BlogView for %s' % self.blog.title
        
        def get_reverse_params(self, obj):
                if isinstance(obj, BlogEntry):
-                       if obj.blog == self.blog:
+                       if obj.blog_id == self.blog_id:
                                kwargs = {'slug': obj.slug}
                                if self.entry_permalink_style in 'DMY':
                                        kwargs.update({'year': str(obj.date.year).zfill(4)})
                                kwargs = {'slug': obj.slug}
                                if self.entry_permalink_style in 'DMY':
                                        kwargs.update({'year': str(obj.date.year).zfill(4)})
@@ -144,7 +150,7 @@ class BlogView(FeedView):
                elif isinstance(obj, Tag) or (isinstance(obj, models.query.QuerySet) and obj.model == Tag and obj):
                        if isinstance(obj, Tag):
                                obj = [obj]
                elif isinstance(obj, Tag) or (isinstance(obj, models.query.QuerySet) and obj.model == Tag and obj):
                        if isinstance(obj, Tag):
                                obj = [obj]
-                       slugs = [tag.slug for tag in obj if tag in self.get_tag_queryset()]
+                       slugs = [tag.slug for tag in obj if tag in self.get_tag_queryset(self.blog)]
                        if slugs:
                                return 'entries_by_tag', [], {'tag_slugs': "/".join(slugs)}
                elif isinstance(obj, (date, datetime)):
                        if slugs:
                                return 'entries_by_tag', [], {'tag_slugs': "/".join(slugs)}
                elif isinstance(obj, (date, datetime)):
@@ -158,21 +164,21 @@ class BlogView(FeedView):
        
        @property
        def urlpatterns(self):
        
        @property
        def urlpatterns(self):
-               urlpatterns = self.feed_patterns(r'^', 'get_all_entries', 'index_page', 'index') +\
-                       self.feed_patterns(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)' % self.tag_permalink_base, 'get_entries_by_tag', 'tag_page', 'entries_by_tag')
+               urlpatterns = self.feed_patterns(r'^', 'get_entries', 'index_page', 'index') +\
+                       self.feed_patterns(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)' % self.tag_permalink_base, 'get_entries', 'tag_page', 'entries_by_tag')
                
                
-               if self.tag_archive_page:
+               if self.tag_archive_page_id:
                        urlpatterns += patterns('',
                                url((r'^%s$' % self.tag_permalink_base), self.tag_archive_view, name='tag_archive')
                        )
                
                        urlpatterns += patterns('',
                                url((r'^%s$' % self.tag_permalink_base), self.tag_archive_view, name='tag_archive')
                        )
                
-               if self.entry_archive_page:
+               if self.entry_archive_page_id:
                        if self.entry_permalink_style in 'DMY':
                        if self.entry_permalink_style in 'DMY':
-                               urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_year')
+                               urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})', 'get_entries', 'entry_archive_page', 'entries_by_year')
                                if self.entry_permalink_style in 'DM':
                                if self.entry_permalink_style in 'DM':
-                                       urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})/(?P<month>\d{2})', 'get_entries_by_ymd', 'entry_archive_page', 'entries_by_month')
+                                       urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})/(?P<month>\d{2})', 'get_entries', 'entry_archive_page', 'entries_by_month')
                                        if self.entry_permalink_style == 'D':
                                        if self.entry_permalink_style == 'D':
-                                               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')
+                                               urlpatterns += self.feed_patterns(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})', 'get_entries', 'entry_archive_page', 'entries_by_day')
                
                if self.entry_permalink_style == 'D':
                        urlpatterns += patterns('',
                
                if self.entry_permalink_style == 'D':
                        urlpatterns += patterns('',
@@ -196,63 +202,76 @@ class BlogView(FeedView):
                        )
                return urlpatterns
        
                        )
                return urlpatterns
        
-       def get_context(self):
-               return {'blog': self.blog}
-       
-       def get_entry_queryset(self):
+       def get_entry_queryset(self, obj):
                """Returns the default :class:`QuerySet` of :class:`BlogEntry` instances for the :class:`BlogView` - all entries that are considered posted in the past. This allows for scheduled posting of entries."""
                """Returns the default :class:`QuerySet` of :class:`BlogEntry` instances for the :class:`BlogView` - all entries that are considered posted in the past. This allows for scheduled posting of entries."""
-               return self.blog.entries.filter(date__lte=datetime.now())
+               return obj.entries.filter(date__lte=datetime.now())
        
        
-       def get_tag_queryset(self):
+       def get_tag_queryset(self, obj):
                """Returns the default :class:`QuerySet` of :class:`.Tag`\ s for the :class:`BlogView`'s :meth:`get_entries_by_tag` and :meth:`tag_archive_view`."""
                """Returns the default :class:`QuerySet` of :class:`.Tag`\ s for the :class:`BlogView`'s :meth:`get_entries_by_tag` and :meth:`tag_archive_view`."""
-               return self.blog.entry_tags
+               return obj.entry_tags
        
        
-       def get_all_entries(self, request, extra_context=None):
-               """Used to generate :meth:`~.FeedView.feed_patterns` for all entries."""
-               return self.get_entry_queryset(), extra_context
-       
-       def get_entries_by_ymd(self, request, year=None, month=None, day=None, extra_context=None):
-               """Used to generate :meth:`~.FeedView.feed_patterns` for entries with a specific year, month, and day."""
-               if not self.entry_archive_page:
-                       raise Http404
-               entries = self.get_entry_queryset()
-               if year:
-                       entries = entries.filter(date__year=year)
-               if month:
-                       entries = entries.filter(date__month=month)
-               if day:
-                       entries = entries.filter(date__day=day)
+       def get_object(self, request, year=None, month=None, day=None, tag_slugs=None):
+               """Returns a dictionary representing the parameters for a feed which will be exposed."""
+               if tag_slugs is None:
+                       tags = None
+               else:
+                       tag_slugs = tag_slugs.replace('+', '/').split('/')
+                       tags = self.get_tag_queryset(self.blog).filter(slug__in=tag_slugs)
+                       if not tags:
+                               raise Http404
+                       
+                       # Raise a 404 on an incorrect slug.
+                       found_slugs = set([tag.slug for tag in tags])
+                       for slug in tag_slugs:
+                               if slug and slug not in found_slugs:
+                                       raise Http404
                
                
-               context = extra_context or {}
-               context.update({'year': year, 'month': month, 'day': day})
-               return entries, context
-       
-       def get_entries_by_tag(self, request, tag_slugs, extra_context=None):
-               """Used to generate :meth:`~.FeedView.feed_patterns` for entries with all of the given tags."""
-               tag_slugs = tag_slugs.replace('+', '/').split('/')
-               tags = self.get_tag_queryset().filter(slug__in=tag_slugs)
+               try:
+                       if year and month and day:
+                               context_date = date(int(year), int(month), int(day))
+                       elif year and month:
+                               context_date = date(int(year), int(month), 1)
+                       elif year:
+                               context_date = date(int(year), 1, 1)
+                       else:
+                               context_date = None
+               except TypeError, ValueError:
+                       context_date = None
                
                
-               if not tags:
-                       raise Http404
+               return {
+                       'blog': self.blog,
+                       'tags': tags,
+                       'year': year,
+                       'month': month,
+                       'day': day,
+                       'date': context_date
+               }
+       
+       def get_entries(self, obj, request, year=None, month=None, day=None, tag_slugs=None, extra_context=None):
+               """Returns the :class:`BlogEntry` objects which will be exposed for the given object, as returned from :meth:`get_object`."""
+               entries = self.get_entry_queryset(obj['blog'])
                
                
-               # Raise a 404 on an incorrect slug.
-               found_slugs = [tag.slug for tag in tags]
-               for slug in tag_slugs:
-                       if slug and slug not in found_slugs:
-                               raise Http404
-
-               entries = self.get_entry_queryset()
-               for tag in tags:
-                       entries = entries.filter(tags=tag)
+               if obj['tags'] is not None:
+                       tags = obj['tags']
+                       for tag in tags:
+                               entries = entries.filter(tags=tag)
+               
+               if obj['date'] is not None:
+                       if year:
+                               entries = entries.filter(date__year=year)
+                       if month:
+                               entries = entries.filter(date__month=month)
+                       if day:
+                               entries = entries.filter(date__day=day)
                
                context = extra_context or {}
                
                context = extra_context or {}
-               context.update({'tags': tags})
+               context.update(obj)
                
                return entries, context
        
        def entry_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
                """Renders :attr:`entry_page` with the entry specified by the given parameters."""
                
                return entries, context
        
        def entry_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
                """Renders :attr:`entry_page` with the entry specified by the given parameters."""
-               entries = self.get_entry_queryset()
+               entries = self.get_entry_queryset(self.blog)
                if year:
                        entries = entries.filter(date__year=year)
                if month:
                if year:
                        entries = entries.filter(date__year=year)
                if month:
@@ -275,34 +294,10 @@ class BlogView(FeedView):
                context = self.get_context()
                context.update(extra_context or {})
                context.update({
                context = self.get_context()
                context.update(extra_context or {})
                context.update({
-                       'tags': self.get_tag_queryset()
+                       'tags': self.get_tag_queryset(self.blog)
                })
                return self.tag_archive_page.render_to_response(request, extra_context=context)
        
                })
                return self.tag_archive_page.render_to_response(request, extra_context=context)
        
-       def feed_view(self, get_items_attr, reverse_name, feed_type=None):
-               """Overrides :meth:`.FeedView.feed_view` to add :class:`.Tag`\ s to the feed as categories."""
-               get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
-               
-               def inner(request, extra_context=None, *args, **kwargs):
-                       obj = self.get_object(request, *args, **kwargs)
-                       feed = self.get_feed(obj, request, reverse_name, feed_type, *args, **kwargs)
-                       items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
-                       self.populate_feed(feed, items, request)
-                       
-                       if 'tags' in extra_context:
-                               tags = extra_context['tags']
-                               feed.feed['link'] = request.node.construct_url(self.reverse(obj=tags), with_domain=True, request=request, secure=request.is_secure())
-                       else:
-                               tags = obj.entry_tags
-                       
-                       feed.feed['categories'] = [tag.name for tag in tags]
-                       
-                       response = HttpResponse(mimetype=feed.mime_type)
-                       feed.write(response, 'utf-8')
-                       return response
-               
-               return inner
-       
        def process_page_items(self, request, items):
                """Overrides :meth:`.FeedView.process_page_items` to add pagination."""
                if self.entries_per_page:
        def process_page_items(self, request, items):
                """Overrides :meth:`.FeedView.process_page_items` to add pagination."""
                if self.entries_per_page:
@@ -320,7 +315,25 @@ class BlogView(FeedView):
                return items, item_context
        
        def title(self, obj):
                return items, item_context
        
        def title(self, obj):
-               return obj.title
+               title = obj['blog'].title
+               if obj['tags']:
+                       title += u" – %s" % u", ".join((tag.name for tag in obj['tags']))
+               date = obj['date']
+               if date:
+                       if obj['day']:
+                               datestr = date.strftime("%F %j, %Y")
+                       elif obj['month']:
+                               datestr = date.strftime("%F, %Y")
+                       elif obj['year']:
+                               datestr = date.strftime("%Y")
+                       title += u" – %s" % datestr
+               return title
+       
+       def categories(self, obj):
+               tags = obj['tags']
+               if tags:
+                       return (tag.name for tag in tags)
+               return None
        
        def item_title(self, item):
                return item.title
        
        def item_title(self, item):
                return item.title
@@ -368,8 +381,8 @@ class NewsletterArticle(Entity):
        lede = TemplateField(null=True, blank=True, verbose_name='Summary')
        #: A :class:`.TemplateField` containing the full text of the article.
        full_text = TemplateField(db_index=True)
        lede = TemplateField(null=True, blank=True, verbose_name='Summary')
        #: A :class:`.TemplateField` containing the full text of the article.
        full_text = TemplateField(db_index=True)
-       #: A :class:`ManyToManyField` to :class:`.Tag`\ s for the :class:`NewsletterArticle`.
-       tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True)
+       #: A ``django-taggit`` :class:`TaggableManager`.
+       tags = TaggableManager()
        
        def save(self, *args, **kwargs):
                if self.date is None:
        
        def save(self, *args, **kwargs):
                if self.date is None:
@@ -455,7 +468,7 @@ class NewsletterView(FeedView):
        
        def get_reverse_params(self, obj):
                if isinstance(obj, NewsletterArticle):
        
        def get_reverse_params(self, obj):
                if isinstance(obj, NewsletterArticle):
-                       if obj.newsletter == self.newsletter:
+                       if obj.newsletter_id == self.newsletter_id:
                                kwargs = {'slug': obj.slug}
                                if self.article_permalink_style in 'DMY':
                                        kwargs.update({'year': str(obj.date.year).zfill(4)})
                                kwargs = {'slug': obj.slug}
                                if self.article_permalink_style in 'DMY':
                                        kwargs.update({'year': str(obj.date.year).zfill(4)})
@@ -465,7 +478,7 @@ class NewsletterView(FeedView):
                                                        kwargs.update({'day': str(obj.date.day).zfill(2)})
                                return self.article_view, [], kwargs
                elif isinstance(obj, NewsletterIssue):
                                                        kwargs.update({'day': str(obj.date.day).zfill(2)})
                                return self.article_view, [], kwargs
                elif isinstance(obj, NewsletterIssue):
-                       if obj.newsletter == self.newsletter:
+                       if obj.newsletter_id == self.newsletter_id:
                                return 'issue', [], {'numbering': obj.numbering}
                elif isinstance(obj, (date, datetime)):
                        kwargs = {
                                return 'issue', [], {'numbering': obj.numbering}
                elif isinstance(obj, (date, datetime)):
                        kwargs = {
@@ -481,14 +494,12 @@ class NewsletterView(FeedView):
                urlpatterns = self.feed_patterns(r'^', 'get_all_articles', 'index_page', 'index') + patterns('',
                        url(r'^%s/(?P<numbering>.+)$' % self.issue_permalink_base, self.page_view('get_articles_by_issue', 'issue_page'), name='issue')
                )
                urlpatterns = self.feed_patterns(r'^', 'get_all_articles', 'index_page', 'index') + patterns('',
                        url(r'^%s/(?P<numbering>.+)$' % self.issue_permalink_base, self.page_view('get_articles_by_issue', 'issue_page'), name='issue')
                )
-               if self.issue_archive_page:
+               if self.issue_archive_page_id:
                        urlpatterns += patterns('',
                                url(r'^%s$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive')
                        )
                        urlpatterns += patterns('',
                                url(r'^%s$' % self.issue_permalink_base, self.issue_archive_view, 'issue_archive')
                        )
-               if self.article_archive_page:
-                       urlpatterns += patterns('',
-                               url(r'^%s' % self.article_permalink_base, include(self.feed_patterns('get_all_articles', 'article_archive_page', 'articles')))
-                       )
+               if self.article_archive_page_id:
+                       urlpatterns += self.feed_patterns(r'^%s' % self.article_permalink_base, 'get_all_articles', 'article_archive_page', 'articles')
                        if self.article_permalink_style in 'DMY':
                                urlpatterns += self.feed_patterns(r'^%s/(?P<year>\d{4})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_year')
                                if self.article_permalink_style in 'DM':
                        if self.article_permalink_style in 'DMY':
                                urlpatterns += self.feed_patterns(r'^%s/(?P<year>\d{4})' % self.article_permalink_base, 'get_articles_by_ymd', 'article_archive_page', 'articles_by_year')
                                if self.article_permalink_style in 'DM':
@@ -518,40 +529,40 @@ class NewsletterView(FeedView):
        def get_context(self):
                return {'newsletter': self.newsletter}
        
        def get_context(self):
                return {'newsletter': self.newsletter}
        
-       def get_article_queryset(self):
+       def get_article_queryset(self, obj):
                """Returns the default :class:`QuerySet` of :class:`NewsletterArticle` instances for the :class:`NewsletterView` - all articles that are considered posted in the past. This allows for scheduled posting of articles."""
                """Returns the default :class:`QuerySet` of :class:`NewsletterArticle` instances for the :class:`NewsletterView` - all articles that are considered posted in the past. This allows for scheduled posting of articles."""
-               return self.newsletter.articles.filter(date__lte=datetime.now())
+               return obj.articles.filter(date__lte=datetime.now())
        
        
-       def get_issue_queryset(self):
+       def get_issue_queryset(self, obj):
                """Returns the default :class:`QuerySet` of :class:`NewsletterIssue` instances for the :class:`NewsletterView`."""
                """Returns the default :class:`QuerySet` of :class:`NewsletterIssue` instances for the :class:`NewsletterView`."""
-               return self.newsletter.issues.all()
+               return obj.issues.all()
        
        
-       def get_all_articles(self, request, extra_context=None):
+       def get_all_articles(self, obj, request, extra_context=None):
                """Used to generate :meth:`~.FeedView.feed_patterns` for all entries."""
                """Used to generate :meth:`~.FeedView.feed_patterns` for all entries."""
-               return self.get_article_queryset(), extra_context
+               return self.get_article_queryset(obj), extra_context
        
        
-       def get_articles_by_ymd(self, request, year, month=None, day=None, extra_context=None):
+       def get_articles_by_ymd(self, obj, request, year, month=None, day=None, extra_context=None):
                """Used to generate :meth:`~.FeedView.feed_patterns` for a specific year, month, and day."""
                """Used to generate :meth:`~.FeedView.feed_patterns` for a specific year, month, and day."""
-               articles = self.get_article_queryset().filter(date__year=year)
+               articles = self.get_article_queryset(obj).filter(date__year=year)
                if month:
                        articles = articles.filter(date__month=month)
                if day:
                        articles = articles.filter(date__day=day)
                return articles, extra_context
        
                if month:
                        articles = articles.filter(date__month=month)
                if day:
                        articles = articles.filter(date__day=day)
                return articles, extra_context
        
-       def get_articles_by_issue(self, request, numbering, extra_context=None):
+       def get_articles_by_issue(self, obj, request, numbering, extra_context=None):
                """Used to generate :meth:`~.FeedView.feed_patterns` for articles from a certain issue."""
                try:
                """Used to generate :meth:`~.FeedView.feed_patterns` for articles from a certain issue."""
                try:
-                       issue = self.get_issue_queryset().get(numbering=numbering)
+                       issue = self.get_issue_queryset(obj).get(numbering=numbering)
                except NewsletterIssue.DoesNotExist:
                        raise Http404
                context = extra_context or {}
                context.update({'issue': issue})
                except NewsletterIssue.DoesNotExist:
                        raise Http404
                context = extra_context or {}
                context.update({'issue': issue})
-               return self.get_article_queryset().filter(issues=issue), context
+               return self.get_article_queryset(obj).filter(issues=issue), context
        
        def article_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
                """Renders :attr:`article_page` with the article specified by the given parameters."""
        
        def article_view(self, request, slug, year=None, month=None, day=None, extra_context=None):
                """Renders :attr:`article_page` with the article specified by the given parameters."""
-               articles = self.get_article_queryset()
+               articles = self.get_article_queryset(self.newsletter)
                if year:
                        articles = articles.filter(date__year=year)
                if month:
                if year:
                        articles = articles.filter(date__year=year)
                if month:
@@ -574,7 +585,7 @@ class NewsletterView(FeedView):
                context = self.get_context()
                context.update(extra_context or {})
                context.update({
                context = self.get_context()
                context.update(extra_context or {})
                context.update({
-                       'issues': self.get_issue_queryset()
+                       'issues': self.get_issue_queryset(self.newsletter)
                })
                return self.issue_archive_page.render_to_response(request, extra_context=context)
        
                })
                return self.issue_archive_page.render_to_response(request, extra_context=context)