Moved FeedMultiViewMixin to models.py and renamed to FeedView. Improved feed type...
authorStephen Burrows <stephen.r.burrows@gmail.com>
Mon, 7 Feb 2011 18:10:52 +0000 (13:10 -0500)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Mon, 7 Feb 2011 18:19:58 +0000 (13:19 -0500)
contrib/penfield/admin.py
contrib/penfield/migrations/0003_auto__add_field_newsletterview_feed_type__add_field_newsletterview_ite.py [new file with mode: 0644]
contrib/penfield/models.py
contrib/penfield/templates/penfield/feeds/blog_entry/description.html [deleted file]
contrib/penfield/templates/penfield/feeds/blog_entry/title.html [deleted file]
contrib/penfield/templates/penfield/feeds/newsletter_article/description.html [deleted file]
contrib/penfield/templates/penfield/feeds/newsletter_article/title.html [deleted file]
contrib/penfield/utils.py [deleted file]

index d8fcd90..950539d 100644 (file)
@@ -41,6 +41,7 @@ class BlogEntryAdmin(TitledAdmin, AddTagAdmin):
                        'classes': COLLAPSE_CLASSES
                })
        )
                        'classes': COLLAPSE_CLASSES
                })
        )
+       related_lookup_fields = {'fk': raw_id_fields}
 
 
 class BlogViewAdmin(EntityAdmin):
 
 
 class BlogViewAdmin(EntityAdmin):
@@ -54,16 +55,17 @@ class BlogViewAdmin(EntityAdmin):
                ('Archive Pages', {
                        'fields': ('entry_archive_page', 'tag_archive_page')
                }),
                ('Archive Pages', {
                        'fields': ('entry_archive_page', 'tag_archive_page')
                }),
-               ('Permalinks', {
-                       'fields': ('entry_permalink_style', 'entry_permalink_base', 'tag_permalink_base'),
+               ('General Settings', {
+                       'fields': ('entry_permalink_style', 'entry_permalink_base', 'tag_permalink_base', 'entries_per_page'),
                        'classes': COLLAPSE_CLASSES
                }),
                        'classes': COLLAPSE_CLASSES
                }),
-               ('Feeds', {
-                       'fields': ('feed_suffix', 'feeds_enabled'),
+               ('Feed Settings', {
+                       'fields': ( 'feeds_enabled', 'feed_suffix', 'feed_type', 'item_title_template', 'item_description_template',),
                        'classes': COLLAPSE_CLASSES
                })
        )
                        'classes': COLLAPSE_CLASSES
                })
        )
-       raw_id_fields = ('index_page', 'entry_page', 'tag_page', 'entry_archive_page', 'tag_archive_page',)
+       raw_id_fields = ('index_page', 'entry_page', 'tag_page', 'entry_archive_page', 'tag_archive_page', 'item_title_template', 'item_description_template',)
+       related_lookup_fields = {'fk': raw_id_fields}
 
 
 class NewsletterAdmin(TitledAdmin):
 
 
 class NewsletterAdmin(TitledAdmin):
@@ -115,11 +117,12 @@ class NewsletterViewAdmin(EntityAdmin):
                        'classes': COLLAPSE_CLASSES
                }),
                ('Feeds', {
                        'classes': COLLAPSE_CLASSES
                }),
                ('Feeds', {
-                       'fields': ('feed_suffix', 'feeds_enabled'),
+                       'fields': ( 'feeds_enabled', 'feed_suffix', 'feed_type', 'item_title_template', 'item_description_template',),
                        'classes': COLLAPSE_CLASSES
                })
        )
                        'classes': COLLAPSE_CLASSES
                })
        )
-       raw_id_fields = ('index_page', 'article_page', 'issue_page', 'article_archive_page', 'issue_archive_page',)
+       raw_id_fields = ('index_page', 'article_page', 'issue_page', 'article_archive_page', 'issue_archive_page', 'item_title_template', 'item_description_template',)
+       related_lookup_fields = {'fk': raw_id_fields}
 
 
 admin.site.register(Blog, BlogAdmin)
 
 
 admin.site.register(Blog, BlogAdmin)
diff --git a/contrib/penfield/migrations/0003_auto__add_field_newsletterview_feed_type__add_field_newsletterview_ite.py b/contrib/penfield/migrations/0003_auto__add_field_newsletterview_feed_type__add_field_newsletterview_ite.py
new file mode 100644 (file)
index 0000000..1f6d829
--- /dev/null
@@ -0,0 +1,226 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding field 'NewsletterView.feed_type'
+        db.add_column('penfield_newsletterview', 'feed_type', self.gf('django.db.models.fields.CharField')(default='atom', max_length=50), keep_default=False)
+
+        # Adding field 'NewsletterView.item_title_template'
+        db.add_column('penfield_newsletterview', 'item_title_template', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='penfield_newsletterview_title_related', null=True, to=orm['philo.Template']), keep_default=False)
+
+        # Adding field 'NewsletterView.item_description_template'
+        db.add_column('penfield_newsletterview', 'item_description_template', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='penfield_newsletterview_description_related', null=True, to=orm['philo.Template']), keep_default=False)
+
+        # Adding field 'BlogView.feed_type'
+        db.add_column('penfield_blogview', 'feed_type', self.gf('django.db.models.fields.CharField')(default='atom', max_length=50), keep_default=False)
+
+        # Adding field 'BlogView.item_title_template'
+        db.add_column('penfield_blogview', 'item_title_template', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='penfield_blogview_title_related', null=True, to=orm['philo.Template']), keep_default=False)
+
+        # Adding field 'BlogView.item_description_template'
+        db.add_column('penfield_blogview', 'item_description_template', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='penfield_blogview_description_related', null=True, to=orm['philo.Template']), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'NewsletterView.feed_type'
+        db.delete_column('penfield_newsletterview', 'feed_type')
+
+        # Deleting field 'NewsletterView.item_title_template'
+        db.delete_column('penfield_newsletterview', 'item_title_template_id')
+
+        # Deleting field 'NewsletterView.item_description_template'
+        db.delete_column('penfield_newsletterview', 'item_description_template_id')
+
+        # Deleting field 'BlogView.feed_type'
+        db.delete_column('penfield_blogview', 'feed_type')
+
+        # Deleting field 'BlogView.item_title_template'
+        db.delete_column('penfield_blogview', 'item_title_template_id')
+
+        # Deleting field 'BlogView.item_description_template'
+        db.delete_column('penfield_blogview', 'item_description_template_id')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'oberlin.person': {
+            'Meta': {'object_name': 'Person'},
+            'bio': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '70', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
+        },
+        'penfield.blog': {
+            'Meta': {'object_name': 'Blog'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'penfield.blogentry': {
+            'Meta': {'object_name': 'BlogEntry'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blogentries'", 'to': "orm['oberlin.Person']"}),
+            'blog': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'entries'", 'null': 'True', 'to': "orm['penfield.Blog']"}),
+            'content': ('django.db.models.fields.TextField', [], {}),
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
+            'excerpt': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'blogentries'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['philo.Tag']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'penfield.blogview': {
+            'Meta': {'object_name': 'BlogView'},
+            'blog': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blogviews'", 'to': "orm['penfield.Blog']"}),
+            'entries_per_page': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'entry_archive_page': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'blog_entry_archive_related'", 'null': 'True', 'to': "orm['philo.Page']"}),
+            'entry_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blog_entry_related'", 'to': "orm['philo.Page']"}),
+            'entry_permalink_base': ('django.db.models.fields.CharField', [], {'default': "'entries'", 'max_length': '255'}),
+            'entry_permalink_style': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+            'feed_suffix': ('django.db.models.fields.CharField', [], {'default': "'feed'", 'max_length': '255'}),
+            'feed_type': ('django.db.models.fields.CharField', [], {'default': "'atom'", 'max_length': '50'}),
+            'feeds_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'index_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blog_index_related'", 'to': "orm['philo.Page']"}),
+            'item_description_template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'penfield_blogview_description_related'", 'null': 'True', 'to': "orm['philo.Template']"}),
+            'item_title_template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'penfield_blogview_title_related'", 'null': 'True', 'to': "orm['philo.Template']"}),
+            'tag_archive_page': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'blog_tag_archive_related'", 'null': 'True', 'to': "orm['philo.Page']"}),
+            'tag_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blog_tag_related'", 'to': "orm['philo.Page']"}),
+            'tag_permalink_base': ('django.db.models.fields.CharField', [], {'default': "'tags'", 'max_length': '255'})
+        },
+        'penfield.newsletter': {
+            'Meta': {'object_name': 'Newsletter'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'penfield.newsletterarticle': {
+            'Meta': {'unique_together': "(('newsletter', 'slug'),)", 'object_name': 'NewsletterArticle'},
+            'authors': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'newsletterarticles'", 'symmetrical': 'False', 'to': "orm['oberlin.Person']"}),
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
+            'full_text': ('philo.models.fields.TemplateField', [], {'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'lede': ('philo.models.fields.TemplateField', [], {'null': 'True', 'blank': 'True'}),
+            'newsletter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'articles'", 'to': "orm['penfield.Newsletter']"}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'newsletterarticles'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['philo.Tag']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'penfield.newsletterissue': {
+            'Meta': {'unique_together': "(('newsletter', 'numbering'),)", 'object_name': 'NewsletterIssue'},
+            'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'issues'", 'symmetrical': 'False', 'to': "orm['penfield.NewsletterArticle']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'newsletter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'issues'", 'to': "orm['penfield.Newsletter']"}),
+            'numbering': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'penfield.newsletterview': {
+            'Meta': {'object_name': 'NewsletterView'},
+            'article_archive_page': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'newsletter_article_archive_related'", 'null': 'True', 'to': "orm['philo.Page']"}),
+            'article_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'newsletter_article_related'", 'to': "orm['philo.Page']"}),
+            'article_permalink_base': ('django.db.models.fields.CharField', [], {'default': "'articles'", 'max_length': '255'}),
+            'article_permalink_style': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+            'feed_suffix': ('django.db.models.fields.CharField', [], {'default': "'feed'", 'max_length': '255'}),
+            'feed_type': ('django.db.models.fields.CharField', [], {'default': "'atom'", 'max_length': '50'}),
+            'feeds_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'index_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'newsletter_index_related'", 'to': "orm['philo.Page']"}),
+            'issue_archive_page': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'newsletter_issue_archive_related'", 'null': 'True', 'to': "orm['philo.Page']"}),
+            'issue_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'newsletter_issue_related'", 'to': "orm['philo.Page']"}),
+            'issue_permalink_base': ('django.db.models.fields.CharField', [], {'default': "'issues'", 'max_length': '255'}),
+            'item_description_template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'penfield_newsletterview_description_related'", 'null': 'True', 'to': "orm['philo.Template']"}),
+            'item_title_template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'penfield_newsletterview_title_related'", 'null': 'True', 'to': "orm['philo.Template']"}),
+            'newsletter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'newsletterviews'", 'to': "orm['penfield.Newsletter']"})
+        },
+        'philo.attribute': {
+            'Meta': {'unique_together': "(('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))", 'object_name': 'Attribute'},
+            'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_entity_set'", 'to': "orm['contenttypes.ContentType']"}),
+            'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'value_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_value_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'value_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'philo.node': {
+            'Meta': {'object_name': 'Node'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Node']"}),
+            'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+            'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'view_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'node_view_set'", 'to': "orm['contenttypes.ContentType']"}),
+            'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+        },
+        'philo.page': {
+            'Meta': {'object_name': 'Page'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': "orm['philo.Template']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'philo.tag': {
+            'Meta': {'object_name': 'Tag'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
+        },
+        'philo.template': {
+            'Meta': {'object_name': 'Template'},
+            'code': ('philo.models.fields.TemplateField', [], {}),
+            'documentation': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'mimetype': ('django.db.models.fields.CharField', [], {'default': "'text/html'", 'max_length': '255'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Template']"}),
+            'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+            'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
+        }
+    }
+
+    complete_apps = ['penfield']
index 27e5b5d..c018247 100644 (file)
@@ -2,14 +2,265 @@ from django.conf import settings
 from django.conf.urls.defaults import url, patterns, include
 from django.db import models
 from django.http import Http404
 from django.conf.urls.defaults import url, patterns, include
 from django.db import models
 from django.http import Http404
-from django.template import loader, Context
-from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
+from django.template import RequestContext, Template as DjangoTemplate
+from django.utils import feedgenerator, tzinfo
+from django.utils.datastructures import SortedDict
+from django.utils.encoding import smart_unicode, force_unicode
+from django.utils.html import escape
 from datetime import date, datetime
 from datetime import date, datetime
-from philo.contrib.penfield.utils import FeedMultiViewMixin
 from philo.contrib.penfield.validators import validate_pagination_count
 from philo.exceptions import ViewCanNotProvideSubpath
 from philo.contrib.penfield.validators import validate_pagination_count
 from philo.exceptions import ViewCanNotProvideSubpath
-from philo.models import Tag, Titled, Entity, MultiView, Page, register_value_model, TemplateField
+from philo.models import Tag, Titled, Entity, MultiView, Page, register_value_model, TemplateField, Template
 from philo.utils import paginate
 from philo.utils import paginate
+try:
+       import mimeparse
+except:
+       mimeparse = None
+
+ATOM = feedgenerator.Atom1Feed.mime_type
+RSS = feedgenerator.Rss201rev2Feed.mime_type
+FEEDS = SortedDict([
+       (ATOM, feedgenerator.Atom1Feed),
+       (RSS, feedgenerator.Rss201rev2Feed),
+])
+FEED_CHOICES = (
+       (ATOM, "Atom"),
+       (RSS, "RSS"),
+)
+
+
+class FeedView(MultiView):
+       """
+       The FeedView expects to handle a number of different feeds for the
+       same object - i.e. patterns for a blog to handle all entries or
+       just entries for a certain year/month/day.
+       
+       This class would subclass django.contrib.syndication.views.Feed, but
+       that would make it callable, which causes problems.
+       """
+       feed_type = models.CharField(max_length=50, choices=FEED_CHOICES, default=ATOM)
+       feed_suffix = models.CharField(max_length=255, blank=False, default="feed")
+       feeds_enabled = models.BooleanField(default=True)
+       
+       item_title_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_title_related")
+       item_description_template = models.ForeignKey(Template, blank=True, null=True, related_name="%(app_label)s_%(class)s_description_related")
+       
+       item_context_var = 'items'
+       object_attr = 'object'
+       
+       description = ""
+       
+       def feed_patterns(self, get_items_attr, page_attr, reverse_name):
+               """
+               Given the name to be used to reverse this view and the names of
+               the attributes for the function that fetches the objects, returns
+               patterns suitable for inclusion in urlpatterns.
+               """
+               urlpatterns = patterns('',
+                       url(r'^$', self.page_view(get_items_attr, page_attr), name=reverse_name)
+               )
+               if self.feeds_enabled:
+                       feed_reverse_name = "%s_feed" % reverse_name
+                       urlpatterns += patterns('',
+                               url(r'^%s$' % self.feed_suffix, self.feed_view(get_items_attr, feed_reverse_name), name=feed_reverse_name),
+                       )
+               return urlpatterns
+       
+       def get_object(self, request, **kwargs):
+               return getattr(self, self.object_attr)
+       
+       def feed_view(self, get_items_attr, reverse_name):
+               """
+               Returns a view function that renders a list of items as a feed.
+               """
+               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)
+                       items, xxx = get_items(request, extra_context=extra_context, *args, **kwargs)
+                       
+                       response = HttpResponse(mimetype=feed.mime_type)
+                       feed.write(response, 'utf-8')
+                       return response
+               
+               return inner
+       
+       def page_view(self, get_items_attr, page_attr):
+               """
+               Returns a view function that renders a list of items as a page.
+               """
+               get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
+               page = isinstance(page_attr, Page) and page_attr or getattr(self, page_attr)
+               
+               def inner(request, extra_context=None, *args, **kwargs):
+                       items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
+                       items, item_context = self.process_page_items(request, items)
+                       
+                       context = self.get_context()
+                       context.update(extra_context or {})
+                       context.update(item_context or {})
+                       
+                       return page.render_to_response(request, extra_context=context)
+               return inner
+       
+       def process_page_items(self, request, items):
+               """
+               Hook for handling any extra processing of items based on a
+               request, such as pagination or searching. This method is
+               expected to return a list of items and a dictionary to be
+               added to the page context.
+               """
+               item_context = {
+                       self.item_context_var: items
+               }
+               return items, item_context
+       
+       def get_feed_type(self, request):
+               feed_type = self.feed_type
+               accept = request.META.get('HTTP_ACCEPT')
+               if accept and feed_type not in accept and "*/*" not in accept and "%s/*" % feed_type.split("/")[0] not in accept:
+                       # Wups! They aren't accepting the chosen format. Is there another format we can use?
+                       if mimeparse:
+                               feed_type = mimeparse.best_match(FEEDS.keys(), accept)
+                       else:
+                               for feed_type in FEEDS.keys():
+                                       if feed_type in accept or "%s/*" % feed_type.split("/")[0] in accept:
+                                               break
+                               else:
+                                       feed_type = None
+                       if not feed_type:
+                               # See RFC 2616
+                               return HttpResponse(status=406)
+               return FEEDS[feed_type]
+       
+       def get_feed(self, obj, request, reverse_name):
+               """
+               Returns an unpopulated feedgenerator.DefaultFeed object for this object.
+               """
+               feed_type = self.get_feed_type(request)
+               node = request.node
+               link = node.get_absolute_url(with_domain=True, request=request, secure=request.is_secure())
+               
+               feed = feed_type(
+                       title = self.__get_dynamic_attr('title', obj),
+                       subtitle = self.__get_dynamic_attr('subtitle', obj),
+                       link = link,
+                       description = self.__get_dynamic_attr('description', obj),
+                       language = settings.LANGUAGE_CODE.decode(),
+                       feed_url = node.construct_url(self.reverse(reverse_name), with_domain=True, request=request, secure=request.is_secure()),
+                       author_name = self.__get_dynamic_attr('author_name', obj),
+                       author_link = self.__get_dynamic_attr('author_link', obj),
+                       author_email = self.__get_dynamic_attr('author_email', obj),
+                       categories = self.__get_dynamic_attr('categories', obj),
+                       feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
+                       feed_guid = self.__get_dynamic_attr('feed_guid', obj),
+                       ttl = self.__get_dynamic_attr('ttl', obj),
+                       **self.feed_extra_kwargs(obj)
+               )
+               return feed
+       
+       def populate_feed(self, feed, items, request):
+               if self.item_title_template:
+                       title_template = Template(self.item_title_template.code)
+               else:
+                       title_template = None
+               if self.item_description_template:
+                       description_template = Template(self.item_description_template.code)
+               else:
+                       description_template = None
+               
+               node = request.node
+               
+               for item in items:
+                       if title_template is not None:
+                               title = title_template.render(RequestContext(request, {'obj': item}))
+                       else:
+                               title = self.__get_dynamic_attr('item_title', item)
+                       if description_template is not None:
+                               description = description_template.render(RequestContext(request, {'obj': item}))
+                       else:
+                               description = self.__get_dynamic_attr('item_description', item)
+                       
+                       link = node.construct_url(self.reverse(obj=item), with_domain=True, request=request, secure=request.is_secure())
+                       
+                       enc = None
+                       enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
+                       if enc_url:
+                               enc = feedgenerator.Enclosure(
+                                       url = smart_unicode(enc_url),
+                                       length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
+                                       mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
+                               )
+                       author_name = self.__get_dynamic_attr('item_author_name', item)
+                       if author_name is not None:
+                               author_email = self.__get_dynamic_attr('item_author_email', item)
+                               author_link = self.__get_dynamic_attr('item_author_link', item)
+                       else:
+                               author_email = author_link = None
+                       
+                       pubdate = self.__get_dynamic_attr('item_pubdate', item)
+                       if pubdate and not pubdate.tzinfo:
+                               ltz = tzinfo.LocalTimezone(pubdate)
+                               pubdate = pubdate.replace(tzinfo=ltz)
+                       
+                       feed.add_item(
+                               title = title,
+                               link = link,
+                               description = description,
+                               unique_id = self.__get_dynamic_attr('item_guid', item, link),
+                               enclosure = enc,
+                               pubdate = pubdate,
+                               author_name = author_name,
+                               author_email = author_email,
+                               author_link = author_link,
+                               categories = self.__get_dynamic_attr('item_categories', item),
+                               item_copyright = self.__get_dynamic_attr('item_copyright', item),
+                               **self.item_extra_kwargs(item)
+                       )
+       
+       def __get_dynamic_attr(self, attname, obj, default=None):
+               try:
+                       attr = getattr(self, attname)
+               except AttributeError:
+                       return default
+               if callable(attr):
+                       # Check func_code.co_argcount rather than try/excepting the
+                       # function and catching the TypeError, because something inside
+                       # the function may raise the TypeError. This technique is more
+                       # accurate.
+                       if hasattr(attr, 'func_code'):
+                               argcount = attr.func_code.co_argcount
+                       else:
+                               argcount = attr.__call__.func_code.co_argcount
+                       if argcount == 2: # one argument is 'self'
+                               return attr(obj)
+                       else:
+                               return attr()
+               return attr
+       
+       def feed_extra_kwargs(self, obj):
+               """
+               Returns an extra keyword arguments dictionary that is used when
+               initializing the feed generator.
+               """
+               return {}
+       
+       def item_extra_kwargs(self, item):
+               """
+               Returns an extra keyword arguments dictionary that is used with
+               the `add_item` call of the feed generator.
+               """
+               return {}
+       
+       def item_title(self, item):
+               return escape(force_unicode(item))
+       
+       def item_description(self, item):
+               return force_unicode(item)
+       
+       class Meta:
+               abstract=True
 
 
 class Blog(Entity, Titled):
 
 
 class Blog(Entity, Titled):
@@ -49,7 +300,7 @@ class BlogEntry(Entity, Titled):
 register_value_model(BlogEntry)
 
 
 register_value_model(BlogEntry)
 
 
-class BlogView(MultiView, FeedMultiViewMixin):
+class BlogView(FeedView):
        ENTRY_PERMALINK_STYLE_CHOICES = (
                ('D', 'Year, month, and day'),
                ('M', 'Year and month'),
        ENTRY_PERMALINK_STYLE_CHOICES = (
                ('D', 'Year, month, and day'),
                ('M', 'Year and month'),
@@ -70,17 +321,13 @@ class BlogView(MultiView, FeedMultiViewMixin):
        entry_permalink_style = models.CharField(max_length=1, choices=ENTRY_PERMALINK_STYLE_CHOICES)
        entry_permalink_base = models.CharField(max_length=255, blank=False, default='entries')
        tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags')
        entry_permalink_style = models.CharField(max_length=1, choices=ENTRY_PERMALINK_STYLE_CHOICES)
        entry_permalink_base = models.CharField(max_length=255, blank=False, default='entries')
        tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags')
-       feed_suffix = models.CharField(max_length=255, blank=False, default=FeedMultiViewMixin.feed_suffix)
-       feeds_enabled = models.BooleanField()
-       list_var = 'entries'
+       
+       item_context_var = 'entries'
+       object_attr = 'blog'
        
        def __unicode__(self):
                return u'BlogView for %s' % self.blog.title
        
        
        def __unicode__(self):
                return u'BlogView for %s' % self.blog.title
        
-       @property
-       def per_page(self):
-               return self.entries_per_page
-       
        def get_reverse_params(self, obj):
                if isinstance(obj, BlogEntry):
                        if obj.blog == self.blog:
        def get_reverse_params(self, obj):
                if isinstance(obj, BlogEntry):
                        if obj.blog == self.blog:
@@ -92,9 +339,12 @@ class BlogView(MultiView, FeedMultiViewMixin):
                                                if self.entry_permalink_style == 'D':
                                                        kwargs.update({'day': str(obj.date.day).zfill(2)})
                                return self.entry_view, [], kwargs
                                                if self.entry_permalink_style == 'D':
                                                        kwargs.update({'day': str(obj.date.day).zfill(2)})
                                return self.entry_view, [], kwargs
-               elif isinstance(obj, Tag):
-                       if obj in self.get_tag_queryset():
-                               return 'entries_by_tag', [], {'tag_slugs': obj.slug}
+               elif isinstance(obj, Tag) or (isinstance(obj, models.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()]
+                       if slugs:
+                               return 'entries_by_tag', [], {'tag_slugs': "/".join(slugs)}
                elif isinstance(obj, (date, datetime)):
                        kwargs = {
                                'year': str(obj.year).zfill(4),
                elif isinstance(obj, (date, datetime)):
                        kwargs = {
                                'year': str(obj.year).zfill(4),
@@ -107,14 +357,14 @@ class BlogView(MultiView, FeedMultiViewMixin):
        @property
        def urlpatterns(self):
                urlpatterns = patterns('',
        @property
        def urlpatterns(self):
                urlpatterns = patterns('',
-                       url(r'^', include(self.feed_patterns(self.get_all_entries, self.index_page, 'index'))),
+                       url(r'^', include(self.feed_patterns('get_all_entries', 'index_page', 'index'))),
                )
                if self.feeds_enabled:
                        urlpatterns += patterns('',
                )
                if self.feeds_enabled:
                        urlpatterns += patterns('',
-                               url(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)/%s$' % (self.tag_permalink_base, self.feed_suffix), self.feed_view(self.get_entries_by_tag, 'entries_by_tag_feed'), name='entries_by_tag_feed'),
+                               url(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)/%s$' % (self.tag_permalink_base, self.feed_suffix), self.feed_view('get_entries_by_tag', 'entries_by_tag_feed'), name='entries_by_tag_feed'),
                        )
                urlpatterns += patterns('',
                        )
                urlpatterns += patterns('',
-                       url(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)$' % self.tag_permalink_base, self.page_view(self.get_entries_by_tag, self.tag_page), name='entries_by_tag')
+                       url(r'^%s/(?P<tag_slugs>[-\w]+[-+/\w]*)$' % self.tag_permalink_base, self.page_view('get_entries_by_tag', 'tag_page'), name='entries_by_tag')
                )
                if self.tag_archive_page:
                        urlpatterns += patterns('',
                )
                if self.tag_archive_page:
                        urlpatterns += patterns('',
@@ -124,15 +374,15 @@ class BlogView(MultiView, FeedMultiViewMixin):
                if self.entry_archive_page:
                        if self.entry_permalink_style in 'DMY':
                                urlpatterns += patterns('',
                if self.entry_archive_page:
                        if self.entry_permalink_style in 'DMY':
                                urlpatterns += patterns('',
-                                       url(r'^(?P<year>\d{4})', include(self.feed_patterns(self.get_entries_by_ymd, self.entry_archive_page, 'entries_by_year')))
+                                       url(r'^(?P<year>\d{4})', include(self.feed_patterns('get_entries_by_ymd', 'entry_archive_page', 'entries_by_year')))
                                )
                                if self.entry_permalink_style in 'DM':
                                        urlpatterns += patterns('',
                                )
                                if self.entry_permalink_style in 'DM':
                                        urlpatterns += patterns('',
-                                               url(r'^(?P<year>\d{4})/(?P<month>\d{2})$', include(self.feed_patterns(self.get_entries_by_ymd, self.entry_archive_page, 'entries_by_month'))),
+                                               url(r'^(?P<year>\d{4})/(?P<month>\d{2})$', include(self.feed_patterns('get_entries_by_ymd', 'entry_archive_page', 'entries_by_month'))),
                                        )
                                        if self.entry_permalink_style == 'D':
                                                urlpatterns += patterns('',
                                        )
                                        if self.entry_permalink_style == 'D':
                                                urlpatterns += patterns('',
-                                                       url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})$', include(self.feed_patterns(self.get_entries_by_ymd, self.entry_archive_page, 'entries_by_day')))
+                                                       url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})$', include(self.feed_patterns('get_entries_by_ymd', 'entry_archive_page', 'entries_by_day')))
                                                )
                
                if self.entry_permalink_style == 'D':
                                                )
                
                if self.entry_permalink_style == 'D':
@@ -233,30 +483,60 @@ class BlogView(MultiView, FeedMultiViewMixin):
                })
                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 add_item(self, feed, obj, kwargs=None):
-               title = loader.get_template("penfield/feeds/blog_entry/title.html")
-               description = loader.get_template("penfield/feeds/blog_entry/description.html")
-               defaults = {
-                       'title': title.render(Context({'entry': obj})),
-                       'description': description.render(Context({'entry': obj})),
-                       'author_name': obj.author.get_full_name(),
-                       'pubdate': obj.date
-               }
-               defaults.update(kwargs or {})
-               super(BlogView, self).add_item(feed, obj, defaults)
-       
-       def get_feed(self, feed_type, extra_context, kwargs=None):
-               tags = (extra_context or {}).get('tags', None)
-               title = self.blog.title
+       def feed_view(self, get_items_attr, reverse_name):
+               get_items = callable(get_items_attr) and get_items_attr or getattr(self, get_items_attr)
                
                
-               if tags is not None:
-                       title += " - %s" % ', '.join([tag.name for tag in tags])
+               def inner(request, extra_context=None, *args, **kwargs):
+                       obj = self.get_object(request, *args, **kwargs)
+                       feed = self.get_feed(obj, request, reverse_name)
+                       items, extra_context = get_items(request, extra_context=extra_context, *args, **kwargs)
+                       
+                       if 'tags' in extra_context:
+                               tags = extra_context['tags']
+                               feed.feed['link'] = request.node.construct_url(self.reverse(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
                
                
-               defaults = {
-                       'title': title
-               }
-               defaults.update(kwargs or {})
-               return super(BlogView, self).get_feed(feed_type, extra_context, defaults)
+               return inner
+       
+       def process_page_items(self, request, items):
+               if self.entries_per_page:
+                       page_num = request.GET.get('page', 1)
+                       paginator, paginated_page, items = paginate(items, self.entries_per_page, page_num)
+                       item_context = {
+                               'paginator': paginator,
+                               'paginated_page': paginated_page,
+                               self.item_context_var: objects
+                       }
+               else:
+                       item_context = {
+                               self.item_context_var: items
+                       }
+               return items, item_context
+       
+       def title(self, obj):
+               return obj.title
+       
+       def item_title(self, item):
+               return item.title
+       
+       def item_description(self, item):
+               return item.content
+       
+       def item_author_name(self, item):
+               return item.author.get_full_name()
+       
+       def item_pubdate(self, item):
+               return item.date
+       
+       def item_categories(self, item):
+               return [tag.name for tag in item.tags.all()]
 
 
 class Newsletter(Entity, Titled):
 
 
 class Newsletter(Entity, Titled):
@@ -301,7 +581,7 @@ class NewsletterIssue(Entity, Titled):
 register_value_model(NewsletterIssue)
 
 
 register_value_model(NewsletterIssue)
 
 
-class NewsletterView(MultiView, FeedMultiViewMixin):
+class NewsletterView(FeedView):
        ARTICLE_PERMALINK_STYLE_CHOICES = (
                ('D', 'Year, month, and day'),
                ('M', 'Year and month'),
        ARTICLE_PERMALINK_STYLE_CHOICES = (
                ('D', 'Year, month, and day'),
                ('M', 'Year and month'),
@@ -321,12 +601,11 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
        article_permalink_base = models.CharField(max_length=255, blank=False, default='articles')
        issue_permalink_base = models.CharField(max_length=255, blank=False, default='issues')
        
        article_permalink_base = models.CharField(max_length=255, blank=False, default='articles')
        issue_permalink_base = models.CharField(max_length=255, blank=False, default='issues')
        
-       feed_suffix = models.CharField(max_length=255, blank=False, default=FeedMultiViewMixin.feed_suffix)
-       feeds_enabled = models.BooleanField()
-       list_var = 'articles'
+       item_context_var = 'articles'
+       object_attr = 'newsletter'
        
        def __unicode__(self):
        
        def __unicode__(self):
-               return self.newsletter.__unicode__()
+               return "NewsletterView for %s" % self.newsletter.__unicode__()
        
        def get_reverse_params(self, obj):
                if isinstance(obj, NewsletterArticle):
        
        def get_reverse_params(self, obj):
                if isinstance(obj, NewsletterArticle):
@@ -354,8 +633,8 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
        @property
        def urlpatterns(self):
                urlpatterns = patterns('',
        @property
        def urlpatterns(self):
                urlpatterns = patterns('',
-                       url(r'^', include(self.feed_patterns(self.get_all_articles, self.index_page, 'index'))),
-                       url(r'^%s/(?P<numbering>.+)' % self.issue_permalink_base, include(self.feed_patterns(self.get_articles_by_issue, self.issue_page, 'issue')))
+                       url(r'^', include(self.feed_patterns('get_all_articles', 'index_page', 'index'))),
+                       url(r'^%s/(?P<numbering>.+)' % self.issue_permalink_base, include(self.feed_patterns('get_articles_by_issue', 'issue_page', 'issue')))
                )
                if self.issue_archive_page:
                        urlpatterns += patterns('',
                )
                if self.issue_archive_page:
                        urlpatterns += patterns('',
@@ -363,19 +642,19 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
                        )
                if self.article_archive_page:
                        urlpatterns += patterns('',
                        )
                if self.article_archive_page:
                        urlpatterns += patterns('',
-                               url(r'^%s' % self.article_permalink_base, include(self.feed_patterns(self.get_all_articles, self.article_archive_page, 'articles')))
+                               url(r'^%s' % self.article_permalink_base, include(self.feed_patterns('get_all_articles', 'article_archive_page', 'articles')))
                        )
                        if self.article_permalink_style in 'DMY':
                                urlpatterns += patterns('',
                        )
                        if self.article_permalink_style in 'DMY':
                                urlpatterns += patterns('',
-                                       url(r'^%s/(?P<year>\d{4})' % self.article_permalink_base, include(self.feed_patterns(self.get_articles_by_ymd, self.article_archive_page, 'articles_by_year')))
+                                       url(r'^%s/(?P<year>\d{4})' % self.article_permalink_base, include(self.feed_patterns('get_articles_by_ymd', 'article_archive_page', 'articles_by_year')))
                                )
                                if self.article_permalink_style in 'DM':
                                        urlpatterns += patterns('',
                                )
                                if self.article_permalink_style in 'DM':
                                        urlpatterns += patterns('',
-                                               url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})' % self.article_permalink_base, include(self.feed_patterns(self.get_articles_by_ymd, self.article_archive_page, 'articles_by_month')))
+                                               url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})' % self.article_permalink_base, include(self.feed_patterns('get_articles_by_ymd', 'article_archive_page', 'articles_by_month')))
                                        )
                                        if self.article_permalink_style == 'D':
                                                urlpatterns += patterns('',
                                        )
                                        if self.article_permalink_style == 'D':
                                                urlpatterns += patterns('',
-                                                       url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})' % self.article_permalink_base, include(self.feed_patterns(self.get_articles_by_ymd, self.article_archive_page, 'articles_by_day')))
+                                                       url(r'^%s/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})' % self.article_permalink_base, include(self.feed_patterns('get_articles_by_ymd', 'article_archive_page', 'articles_by_day')))
                                                )
                
                if self.article_permalink_style == 'Y':
                                                )
                
                if self.article_permalink_style == 'Y':
@@ -453,24 +732,26 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
                })
                return self.issue_archive_page.render_to_response(request, extra_context=context)
        
                })
                return self.issue_archive_page.render_to_response(request, extra_context=context)
        
-       def add_item(self, feed, obj, kwargs=None):
-               title = loader.get_template("penfield/feeds/newsletter_article/title.html")
-               description = loader.get_template("penfield/feeds/newsletter_article/description.html")
-               defaults = {
-                       'title': title.render(Context({'article': obj})),
-                       'author_name': ', '.join([author.get_full_name() for author in obj.authors.all()]),
-                       'pubdate': obj.date,
-                       'description': description.render(Context({'article': obj})),
-                       'categories': [tag.name for tag in obj.tags.all()]
-               }
-               defaults.update(kwargs or {})
-               super(NewsletterView, self).add_item(feed, obj, defaults)
+       def title(self, obj):
+               return obj.title
        
        
-       def get_feed(self, feed_type, extra_context, kwargs=None):
-               title = self.newsletter.title
-               
-               defaults = {
-                       'title': title
-               }
-               defaults.update(kwargs or {})
-               return super(NewsletterView, self).get_feed(feed_type, extra_context, defaults)
+       def item_title(self, item):
+               return item.title
+       
+       def item_description(self, item):
+               return item.full_text
+       
+       def item_author_name(self, item):
+               authors = list(item.authors.all())
+               if len(authors) > 1:
+                       return "%s and %s" % (", ".join([author.get_full_name() for author in authors[:-1]]), authors[-1].get_full_name())
+               elif authors:
+                       return authors[0].get_full_name()
+               else:
+                       return ''
+       
+       def item_pubdate(self, item):
+               return item.date
+       
+       def item_categories(self, item):
+               return [tag.name for tag in item.tags.all()]
\ No newline at end of file
diff --git a/contrib/penfield/templates/penfield/feeds/blog_entry/description.html b/contrib/penfield/templates/penfield/feeds/blog_entry/description.html
deleted file mode 100644 (file)
index 61060d5..0000000
+++ /dev/null
@@ -1 +0,0 @@
-{{ entry.content }}
\ No newline at end of file
diff --git a/contrib/penfield/templates/penfield/feeds/blog_entry/title.html b/contrib/penfield/templates/penfield/feeds/blog_entry/title.html
deleted file mode 100644 (file)
index f7167dd..0000000
+++ /dev/null
@@ -1 +0,0 @@
-{{ entry.title }}
\ No newline at end of file
diff --git a/contrib/penfield/templates/penfield/feeds/newsletter_article/description.html b/contrib/penfield/templates/penfield/feeds/newsletter_article/description.html
deleted file mode 100644 (file)
index 78e19ce..0000000
+++ /dev/null
@@ -1 +0,0 @@
-{{ article.full_text }}
\ No newline at end of file
diff --git a/contrib/penfield/templates/penfield/feeds/newsletter_article/title.html b/contrib/penfield/templates/penfield/feeds/newsletter_article/title.html
deleted file mode 100644 (file)
index 1f96b1f..0000000
+++ /dev/null
@@ -1 +0,0 @@
-{{ article.title }}
\ No newline at end of file
diff --git a/contrib/penfield/utils.py b/contrib/penfield/utils.py
deleted file mode 100644 (file)
index bfa08d0..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
-from django.conf.urls.defaults import url, patterns
-from django.http import HttpResponse
-from philo.utils import paginate
-
-
-class FeedMultiViewMixin(object):
-       """
-       This mixin provides common methods for adding feeds to multiviews. In order to use this mixin,
-       the multiview must define feed_title (probably as properties that return values
-       on related objects.) feed_description may also be defined; it defaults to an empty string.
-       """
-       feed_suffix = 'feed'
-       feeds_enabled = True
-       atom_feed = Atom1Feed
-       rss_feed = Rss201rev2Feed
-       feed_title = None
-       feed_description = None
-       list_var = 'objects'
-       
-       def page_view(self, func, page):
-               """
-               Wraps an object-fetching function and renders the results as a page.
-               """
-               def inner(request, extra_context=None, **kwargs):
-                       objects, extra_context = func(request=request, extra_context=extra_context, **kwargs)
-
-                       context = self.get_context()
-                       context.update(extra_context or {})
-
-                       if 'page' in kwargs or 'page' in request.GET or (hasattr(self, 'per_page') and self.per_page):
-                               page_num = kwargs.get('page', request.GET.get('page', 1))
-                               paginator, paginated_page, objects = paginate(objects, self.per_page, page_num)
-                               context.update({'paginator': paginator, 'paginated_page': paginated_page, self.list_var: objects})
-                       else:
-                               context.update({self.list_var: objects})
-
-                       return page.render_to_response(request, extra_context=context)
-
-               return inner
-       
-       def feed_view(self, func, reverse_name):
-               """
-               Wraps an object-fetching function and renders the results as a rss or atom feed.
-               """
-               def inner(request, extra_context=None, **kwargs):
-                       objects, extra_context = func(request=request, extra_context=extra_context, **kwargs)
-       
-                       if 'HTTP_ACCEPT' in request.META and 'rss' in request.META['HTTP_ACCEPT'] and 'atom' not in request.META['HTTP_ACCEPT']:
-                               feed_type = 'rss'
-                       else:
-                               feed_type = 'atom'
-                       
-                       feed_kwargs = {
-                               'link': request.node.construct_url(subpath=self.reverse(reverse_name, kwargs=kwargs), request=request, with_domain=True)
-                       }
-                       feed = self.get_feed(feed_type, extra_context, feed_kwargs)
-                       
-                       for obj in objects:
-                               kwargs = {
-                                       'link': request.node.construct_url(subpath=self.reverse(obj=obj), request=request, with_domain=True)
-                               }
-                               self.add_item(feed, obj, kwargs=kwargs)
-       
-                       response = HttpResponse(mimetype=feed.mime_type)
-                       feed.write(response, 'utf-8')
-                       return response
-
-               return inner
-       
-       def get_feed(self, feed_type, extra_context, kwargs=None):
-               defaults = {
-                       'description': ''
-               }
-               defaults.update(kwargs or {})
-               
-               if feed_type == 'rss':
-                       return self.rss_feed(**defaults)
-               
-               if 'description' in defaults and defaults['description'] and 'subtitle' not in defaults:
-                       defaults['subtitle'] = defaults['description']
-               
-               return self.atom_feed(**defaults)
-       
-       def feed_patterns(self, object_fetcher, page, base_name):
-               urlpatterns = patterns('',
-                       url(r'^$', self.page_view(object_fetcher, page), name=base_name)
-               )
-               if self.feeds_enabled:
-                       feed_name = '%s_feed' % base_name
-                       urlpatterns = patterns('',
-                               url(r'^%s$' % self.feed_suffix, self.feed_view(object_fetcher, feed_name), name=feed_name),
-                       ) + urlpatterns
-               return urlpatterns
-       
-       def add_item(self, feed, obj, kwargs=None):
-               defaults = kwargs or {}
-               feed.add_item(**defaults)
\ No newline at end of file