Merge branch 'develop' of git://github.com/melinath/philo into develop
authorJoseph Spiros <joseph.spiros@ithinksw.com>
Sat, 8 Oct 2011 04:42:19 +0000 (00:42 -0400)
committerJoseph Spiros <joseph.spiros@ithinksw.com>
Sat, 8 Oct 2011 04:42:19 +0000 (00:42 -0400)
* 'develop' of git://github.com/melinath/philo:
  Tweaked AttributeMapper._fill_cache to also store values on the Attribute instance itself. Made everything about AttributeMapper._fill_cache lazier.
  Lazy-eval the values of AttributeValues instead of loading them all during AttributeMapper._fill_cache.
  Removed Node.render_to_response select_related call since it is not clearly more efficient. Delayed page evaluation in FeedView.page_view to the end of the inner function.
  Added a 'contributing' page to the philo docs. This is meant primarily as an initial effort, which can be expanded upon more later.
  Corrections to Blog.entry_tags to use taggit APIs. Tweaks to penfield migration 0005 because of South issue 428 (and some incorrect filters obscured by that issue.)
  Minor correction to EmbedWidget.js to handle window names with dashes.
  Overrides TemplateField widget on admin container forms instead of on the ModelAdmin.
  Reverted TemplateField parent to models.TextField and moved EmbedWidget into philo.admin.widgets. Added EmbedWidget use on the appropriate ModelAdmins. Cleaned up ContentletAdmin and ContentReferenceAdmin.
  Removed TagCreation.js and the admin widget that used it.
  Added django-taggit extra requirement to setup.py.
  Removed oberlin references.
  Removed references to oberlin-specific models.
  Initial philo commit to use django-taggit instead of native tags.
  Moved to a more flexible BlogView method arrangement which leverages the general presence of the results of get_object to better provide certain functionality.

28 files changed:
README
README.markdown
docs/contributing.rst [new file with mode: 0644]
docs/index.rst
philo/admin/base.py
philo/admin/forms/containers.py
philo/admin/pages.py
philo/admin/widgets.py
philo/contrib/julian/migrations/0001_initial.py
philo/contrib/julian/models.py
philo/contrib/penfield/admin.py
philo/contrib/penfield/migrations/0003_auto__add_field_newsletterview_feed_type__add_field_newsletterview_ite.py
philo/contrib/penfield/migrations/0004_auto__add_field_newsletterview_feed_length__add_field_blogview_feed_le.py
philo/contrib/penfield/migrations/0005_to_taggit.py [new file with mode: 0644]
philo/contrib/penfield/migrations/0006_delete_tag_rels.py [new file with mode: 0644]
philo/contrib/penfield/models.py
philo/contrib/winer/models.py
philo/forms/widgets.py [deleted file]
philo/migrations/0019_to_taggit.py [new file with mode: 0644]
philo/migrations/0020_from_taggit.py [new file with mode: 0644]
philo/migrations/0021_auto__del_tag.py [new file with mode: 0644]
philo/models/base.py
philo/models/fields/__init__.py
philo/models/nodes.py
philo/static/philo/js/EmbedWidget.js
philo/static/philo/js/TagCreation.js [deleted file]
philo/utils/entities.py
setup.py

diff --git a/README b/README
index dbd9cc2..69c0c52 100644 (file)
--- a/README
+++ b/README
@@ -3,15 +3,12 @@ Philo is a foundation for developing web content management systems.
 Prerequisites:
        * Python 2.5.4+ <http://www.python.org/>
        * Django 1.3+ <http://www.djangoproject.com/>
-       * django-mptt e734079+ <https://github.com/django-mptt/django-mptt/> 
+       * django-mptt e734079+ <https://github.com/django-mptt/django-mptt/>
+       * (philo.contrib.penfield) django-taggit 0.9.3+ <https://github.com/alex/django-taggit>
        * (Optional) django-grappelli 2.0+ <http://code.google.com/p/django-grappelli/>
        * (Optional) south 0.7.2+ <http://south.aeracode.org/>
        * (Optional) recaptcha-django r6 <http://code.google.com/p/recaptcha-django/>
 
-To contribute, please visit the project website <http://project.philocms.org/> and/or make a fork of the git repository on GitHub <http://github.com/ithinksw/philo> or Gitorious <http://gitorious
-.org/ithinksw/philo>. Feel free to join us on IRC at irc://irc.oftc.net/#philo.
-
-
 ====
 Using philo
 ====
@@ -22,4 +19,4 @@ After installing philo and mptt on your python path, make sure to complete the f
 3. include 'philo.urls' somewhere in your urls.py file.
 4. Optionally add a root node to your current Site.
 
-Philo should be ready to go!
+Philo should be ready to go! All that's left is to learn more <http://philo.readthedocs.org> and contribute <http://philo.readthedocs.org/en/latest/contribute.html>.
index 91a8115..c6a198a 100644 (file)
@@ -5,6 +5,7 @@ Prerequisites:
  * [Python 2.5.4+ &lt;http://www.python.org&gt;](http://www.python.org/)
  * [Django 1.3+ &lt;http://www.djangoproject.com/&gt;](http://www.djangoproject.com/)
  * [django-mptt e734079+ &lt;https://github.com/django-mptt/django-mptt/&gt;](https://github.com/django-mptt/django-mptt/)
+ * (philo.contrib.penfield) [django-taggit 0.9.3+ &lt;https://github.com/alex/django-taggit&gt;](https://github.com/alex/django-taggit)
  * (Optional) [django-grappelli 2.0+ &lt;http://code.google.com/p/django-grappelli/&gt;](http://code.google.com/p/django-grappelli/)
  * (Optional) [south 0.7.2+ &lt;http://south.aeracode.org/)](http://south.aeracode.org/)
  * (Optional) [recaptcha-django r6 &lt;http://code.google.com/p/recaptcha-django/&gt;](http://code.google.com/p/recaptcha-django/)
@@ -22,4 +23,4 @@ After installing philo and mptt on your python path, make sure to complete the f
 3. include 'philo.urls' somewhere in your urls.py file.
 4. Optionally add a root node to your current Site.
 
-Philo should be ready to go!
+Philo should be ready to go! All that's left is to [learn more](http://philo.readthedocs.org) and [contribute](http://philo.readthedocs.org/en/latest/contribute.html).
diff --git a/docs/contributing.rst b/docs/contributing.rst
new file mode 100644 (file)
index 0000000..9354c72
--- /dev/null
@@ -0,0 +1,8 @@
+Contributing to Philo
+=====================
+
+So you want to contribute to Philo? That's great! Here's some ways you can get started:
+
+* **Report bugs and request features.** :mod:`philo` uses a Redmine installation located at `http://ithinksw.org/projects/philo/issues <http://ithinksw.org/projects/philo/issues>`_ for issue tracking. In order to report an issue, you will need to register for an account with the tracker.
+* **Contribute code.** Philo uses git to manage its code. You can fork philo's repository either on `GitHub <http://github.com/ithinksw/philo>`_ or `Gitorious <http://gitorious.org/ithinksw/philo>`_. If you are contributing to Philo, you may need to submit a `Contributor License Agreement <http://en.wikipedia.org/wiki/Contributor_License_Agreement>`_.
+* **Join the discussion** on IRC at `irc://irc.oftc.net/#philo <irc://irc.oftc.net/#philo>`_ if you have any questions or suggestions or just want to chat about the project. You can also keep in touch via :mod:`philo`'s mailing lists: `philo@ithinksw.org <mailto:philo@ithinksw.org>`_ and `philo-devel@ithinksw.org <mailto:philo-devel@ithinksw.org>`_.
index 7e960a0..743fa39 100644 (file)
@@ -19,8 +19,6 @@ Prerequisites:
 * (Optional) `south 0.7.2+ <http://south.aeracode.org/>`_
 * (Optional) `recaptcha-django r6 <http://code.google.com/p/recaptcha-django/>`_
 
-To contribute, please visit the `project website <http://project.philocms.org/>`_ and/or make a fork of the git repository on `GitHub <http://github.com/ithinksw/philo>`_ or `Gitorious <http://gitorious.org/ithinksw/philo>`_. Feel free to join us on IRC at `irc://irc.oftc.net/#philo <irc://irc.oftc.net/#philo>`_.
-
 Contents
 ++++++++
 
@@ -39,6 +37,7 @@ Contents
        forms
        loaders
        contrib/intro
+       contributing
 
 Indices and tables
 ++++++++++++++++++
index 81916ab..d966c39 100644 (file)
@@ -6,10 +6,9 @@ from django.utils import simplejson as json
 from django.utils.html import escape
 from mptt.admin import MPTTModelAdmin
 
-from philo.models import Tag, Attribute
+from philo.models import Attribute
 from philo.models.fields.entities import ForeignKeyAttribute, ManyToManyAttribute
 from philo.admin.forms.attributes import AttributeForm, AttributeInlineFormSet
-from philo.admin.widgets import TagFilteredSelectMultiple
 from philo.forms.entities import EntityForm, proxy_fields_for_entity_model
 
 
@@ -137,38 +136,4 @@ class EntityAdmin(admin.ModelAdmin):
 
 
 class TreeEntityAdmin(EntityAdmin, MPTTModelAdmin):
-       pass
-
-
-class TagAdmin(admin.ModelAdmin):
-       list_display = ('name', 'slug')
-       prepopulated_fields = {"slug": ("name",)}
-       search_fields = ["name"]
-       
-       def response_add(self, request, obj, post_url_continue='../%s/'):
-               # If it's an ajax request, return a json response containing the necessary information.
-               if request.is_ajax():
-                       return HttpResponse(json.dumps({'pk': escape(obj._get_pk_val()), 'unicode': escape(obj)}))
-               return super(TagAdmin, self).response_add(request, obj, post_url_continue)
-
-
-class AddTagAdmin(admin.ModelAdmin):
-       def formfield_for_manytomany(self, db_field, request=None, **kwargs):
-               """
-               Get a form Field for a ManyToManyField.
-               """
-               # If it uses an intermediary model that isn't auto created, don't show
-               # a field in admin.
-               if not db_field.rel.through._meta.auto_created:
-                       return None
-               
-               if db_field.rel.to == Tag and db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
-                       opts = Tag._meta
-                       if request.user.has_perm(opts.app_label + '.' + opts.get_add_permission()):
-                               kwargs['widget'] = TagFilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
-                               return db_field.formfield(**kwargs)
-               
-               return super(AddTagAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
-
-
-admin.site.register(Tag, TagAdmin)
\ No newline at end of file
+       pass
\ No newline at end of file
index 987524f..0f8d117 100644 (file)
@@ -1,12 +1,11 @@
 from django import forms
-from django.contrib.admin.widgets import AdminTextareaWidget
 from django.core.exceptions import ObjectDoesNotExist
 from django.db.models import Q
 from django.forms.models import ModelForm, BaseInlineFormSet, BaseModelFormSet
 from django.forms.formsets import TOTAL_FORM_COUNT
 from django.utils.datastructures import SortedDict
 
-from philo.admin.widgets import ModelLookupWidget
+from philo.admin.widgets import ModelLookupWidget, EmbedWidget
 from philo.models import Contentlet, ContentReference
 
 
@@ -26,7 +25,7 @@ class ContainerForm(ModelForm):
 
 
 class ContentletForm(ContainerForm):
-       content = forms.CharField(required=False, widget=AdminTextareaWidget, label='Content')
+       content = forms.CharField(required=False, widget=EmbedWidget, label='Content')
        
        def should_delete(self):
                # Delete iff: the data has changed and is now empty.
index 3e8f0f1..4cdd37b 100644 (file)
@@ -5,15 +5,14 @@ from django.contrib import admin
 from philo.admin.base import COLLAPSE_CLASSES, TreeEntityAdmin
 from philo.admin.forms.containers import *
 from philo.admin.nodes import ViewAdmin
+from philo.admin.widgets import EmbedWidget
+from philo.models.fields import TemplateField
 from philo.models.pages import Page, Template, Contentlet, ContentReference
 
 
-class ContentletInline(admin.StackedInline):
-       model = Contentlet
+class ContainerInline(admin.StackedInline):
        extra = 0
        max_num = 0
-       formset = ContentletInlineFormSet
-       form = ContentletForm
        can_delete = False
        classes = ('collapse-open', 'collapse','open')
        if 'grappelli' in settings.INSTALLED_APPS:
@@ -22,18 +21,16 @@ class ContentletInline(admin.StackedInline):
                template = 'admin/philo/edit_inline/tabular_container.html'
 
 
-class ContentReferenceInline(admin.StackedInline):
+class ContentletInline(ContainerInline):
+       model = Contentlet
+       formset = ContentletInlineFormSet
+       form = ContentletForm
+
+
+class ContentReferenceInline(ContainerInline):
        model = ContentReference
-       extra = 0
-       max_num = 0
        formset = ContentReferenceInlineFormSet
        form = ContentReferenceForm
-       can_delete = False
-       classes = ('collapse-open', 'collapse','open')
-       if 'grappelli' in settings.INSTALLED_APPS:
-               template = 'admin/philo/edit_inline/grappelli_tabular_container.html'
-       else:
-               template = 'admin/philo/edit_inline/tabular_container.html'
 
 
 class PageAdmin(ViewAdmin):
@@ -73,6 +70,9 @@ class TemplateAdmin(TreeEntityAdmin):
                        'fields': ('mimetype',)
                }),
        )
+       formfield_overrides = {
+               TemplateField: {'widget': EmbedWidget}
+       }
        save_on_top = True
        save_as = True
        list_display = ('__unicode__', 'slug', 'get_path',)
index c753850..3d7d64b 100644 (file)
@@ -1,6 +1,7 @@
 from django import forms
 from django.conf import settings
-from django.contrib.admin.widgets import FilteredSelectMultiple, url_params_from_lookup_dict
+from django.contrib.admin.widgets import url_params_from_lookup_dict
+from django.utils import simplejson as json
 from django.utils.html import escape
 from django.utils.safestring import mark_safe
 from django.utils.text import truncate_words
@@ -40,29 +41,28 @@ class ModelLookupWidget(forms.TextInput):
                return mark_safe(u''.join(output))
 
 
-class TagFilteredSelectMultiple(FilteredSelectMultiple):
-       """
-       A SelectMultiple with a JavaScript filter interface.
-
-       Note that the resulting JavaScript assumes that the jsi18n
-       catalog has been loaded in the page
-       """
+class EmbedWidget(forms.Textarea):
+       """A form widget with the HTML class embedding and an embedded list of content-types."""
+       def __init__(self, attrs=None):
+               from philo.models import value_content_type_limiter
+               
+               content_types = value_content_type_limiter.classes
+               data = []
+               
+               for content_type in content_types:
+                       data.append({'app_label': content_type._meta.app_label, 'object_name': content_type._meta.object_name.lower(), 'verbose_name': unicode(content_type._meta.verbose_name)})
+               
+               json_ = json.dumps(data)
+               
+               default_attrs = {'class': 'embedding vLargeTextField', 'data-content-types': json_ }
+               
+               if attrs:
+                       default_attrs.update(attrs)
+                       
+               super(EmbedWidget, self).__init__(default_attrs)
+               
        class Media:
-               js = (
-                       settings.ADMIN_MEDIA_PREFIX + "js/core.js",
-                       settings.ADMIN_MEDIA_PREFIX + "js/SelectBox.js",
-                       settings.ADMIN_MEDIA_PREFIX + "js/SelectFilter2.js",
-                       "philo/js/TagCreation.js",
-               )
-
-       def render(self, name, value, attrs=None, choices=()):
-               if attrs is None: attrs = {}
-               attrs['class'] = 'selectfilter'
-               if self.is_stacked: attrs['class'] += 'stacked'
-               output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)]
-               output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {')
-               # TODO: "id_" is hard-coded here. This should instead use the correct
-               # API to determine the ID dynamically.
-               output.append(u'SelectFilter.init("id_%s", "%s", %s, "%s"); tagCreation.init("id_%s"); });</script>\n' % \
-                       (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX, name))
-               return mark_safe(u''.join(output))
\ No newline at end of file
+               css = {
+                       'all': ('philo/css/EmbedWidget.css',),
+               }
+               js = ('philo/js/EmbedWidget.js',)
index 3236095..21e8778 100644 (file)
@@ -219,14 +219,6 @@ class Migration(SchemaMigration):
             'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
             'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
         },
-        'oberlin.locationcoordinates': {
-            'Meta': {'unique_together': "(('location_ct', 'location_pk'),)", 'object_name': 'LocationCoordinates'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'latitude': ('django.db.models.fields.FloatField', [], {}),
-            'location_ct': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
-            'location_pk': ('django.db.models.fields.TextField', [], {}),
-            'longitude': ('django.db.models.fields.FloatField', [], {})
-        },
         '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']"}),
index 837675b..df49da5 100644 (file)
@@ -13,6 +13,7 @@ from django.db import models
 from django.db.models.query import QuerySet
 from django.http import HttpResponse, Http404
 from django.utils.encoding import force_unicode
+from taggit.managers import TaggableManager
 
 from philo.contrib.julian.feedgenerator import ICalendarFeed
 from philo.contrib.winer.models import FeedView
index d350303..31aacb1 100644 (file)
@@ -3,8 +3,10 @@ from django.contrib import admin
 from django.core.urlresolvers import reverse
 from django.http import HttpResponseRedirect, QueryDict
 
-from philo.admin import EntityAdmin, AddTagAdmin, COLLAPSE_CLASSES
+from philo.admin import EntityAdmin, COLLAPSE_CLASSES
+from philo.admin.widgets import EmbedWidget
 from philo.contrib.penfield.models import BlogEntry, Blog, BlogView, Newsletter, NewsletterArticle, NewsletterIssue, NewsletterView
+from philo.models.fields import TemplateField
 
 
 class DelayedDateForm(forms.ModelForm):
@@ -20,9 +22,8 @@ class BlogAdmin(EntityAdmin):
        list_display = ('title', 'slug')
 
 
-class BlogEntryAdmin(AddTagAdmin):
+class BlogEntryAdmin(EntityAdmin):
        form = DelayedDateForm
-       filter_horizontal = ['tags']
        list_filter = ['author', 'blog']
        date_hierarchy = 'date'
        search_fields = ('content',)
@@ -42,6 +43,9 @@ class BlogEntryAdmin(AddTagAdmin):
        )
        related_lookup_fields = {'fk': raw_id_fields}
        prepopulated_fields = {'slug': ('title',)}
+       formfield_overrides = {
+               TemplateField: {'widget': EmbedWidget}
+       }
 
 
 class BlogViewAdmin(EntityAdmin):
@@ -73,9 +77,9 @@ class NewsletterAdmin(EntityAdmin):
        list_display = ('title', 'slug')
 
 
-class NewsletterArticleAdmin(AddTagAdmin):
+class NewsletterArticleAdmin(EntityAdmin):
        form = DelayedDateForm
-       filter_horizontal = ('tags', 'authors')
+       filter_horizontal = ('authors',)
        list_filter = ('newsletter',)
        date_hierarchy = 'date'
        search_fields = ('title', 'authors__name',)
@@ -94,6 +98,9 @@ class NewsletterArticleAdmin(AddTagAdmin):
        )
        actions = ['make_issue']
        prepopulated_fields = {'slug': ('title',)}
+       formfield_overrides = {
+               TemplateField: {'widget': EmbedWidget}
+       }
        
        def author_names(self, obj):
                return ', '.join([author.get_full_name() for author in obj.authors.all()])
index eae496e..72df39b 100644 (file)
@@ -3,6 +3,7 @@ import datetime
 from south.db import db
 from south.v2 import SchemaMigration
 from django.db import models
+from philo.migrations import person_model, frozen_person
 
 class Migration(SchemaMigration):
 
@@ -85,13 +86,7 @@ class Migration(SchemaMigration):
             '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'})
-        },
+        person_model: frozen_person,
         'penfield.blog': {
             'Meta': {'object_name': 'Blog'},
             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
@@ -100,7 +95,7 @@ class Migration(SchemaMigration):
         },
         'penfield.blogentry': {
             'Meta': {'object_name': 'BlogEntry'},
-            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blogentries'", 'to': "orm['oberlin.Person']"}),
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blogentries'", 'to': "orm['%s']" % person_model}),
             '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'}),
@@ -137,7 +132,7 @@ class Migration(SchemaMigration):
         },
         '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']"}),
+            'authors': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'newsletterarticles'", 'symmetrical': 'False', 'to': "orm['%s']" % person_model}),
             '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'}),
index 9b9ffa7..e48e0d7 100644 (file)
@@ -3,6 +3,7 @@ import datetime
 from south.db import db
 from south.v2 import SchemaMigration
 from django.db import models
+from philo.migrations import person_model, frozen_person
 
 class Migration(SchemaMigration):
 
@@ -61,13 +62,7 @@ class Migration(SchemaMigration):
             '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'})
-        },
+        person_model: frozen_person,
         'penfield.blog': {
             'Meta': {'object_name': 'Blog'},
             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
@@ -76,7 +71,7 @@ class Migration(SchemaMigration):
         },
         'penfield.blogentry': {
             'Meta': {'object_name': 'BlogEntry'},
-            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blogentries'", 'to': "orm['oberlin.Person']"}),
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blogentries'", 'to': "orm['%s']" % person_model}),
             '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'}),
@@ -114,7 +109,7 @@ class Migration(SchemaMigration):
         },
         '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']"}),
+            'authors': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'newsletterarticles'", 'symmetrical': 'False', 'to': "orm['%s']" % person_model}),
             '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'}),
diff --git a/philo/contrib/penfield/migrations/0005_to_taggit.py b/philo/contrib/penfield/migrations/0005_to_taggit.py
new file mode 100644 (file)
index 0000000..52090c6
--- /dev/null
@@ -0,0 +1,248 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+from philo.migrations import person_model, frozen_person
+
+class Migration(DataMigration):
+
+       depends_on = (
+               ("philo", "0019_to_taggit"),
+       )
+       
+       needed_by = (
+               ("philo", "0020_from_taggit"),
+       )
+
+       def forwards(self, orm):
+               "Write your forwards methods here."
+               BlogEntry = orm['penfield.BlogEntry']
+               NewsletterArticle = orm['penfield.NewsletterArticle']
+               TaggitTag = orm['taggit.Tag']
+               TaggedItem = orm['taggit.TaggedItem']
+               ContentType = orm['contenttypes.contenttype']
+               
+               entry_ct = ContentType.objects.get(app_label="penfield", model="blogentry")
+               article_ct = ContentType.objects.get(app_label="penfield", model="newsletterarticle")
+               
+               for entry in BlogEntry.objects.all():
+                       for tag in entry.tags.all():
+                               ttag = TaggitTag.objects.get(slug=tag.slug)
+                               TaggedItem.objects.get_or_create(tag=ttag, content_type=entry_ct, object_id=entry.pk)
+               
+               for article in NewsletterArticle.objects.all():
+                       for tag in article.tags.all():
+                               ttag = TaggitTag.objects.get(slug=tag.slug)
+                               TaggedItem.objects.get_or_create(tag=ttag, content_type=article_ct, object_id=article.pk)
+
+
+       def backwards(self, orm):
+               "Write your backwards methods here."
+               BlogEntry = orm['penfield.BlogEntry']
+               NewsletterArticle = orm['penfield.NewsletterArticle']
+               Tag = orm['philo.Tag']
+               TaggitTag = orm['taggit.Tag']
+               TaggedItem = orm['taggit.TaggedItem']
+               ContentType = orm['contenttypes.contenttype']
+               
+               entry_ct = ContentType.objects.get(app_label="penfield", model="blogentry")
+               article_ct = ContentType.objects.get(app_label="penfield", model="newsletterarticle")
+               
+               for entry in BlogEntry.objects.all():
+                       tag_slugs = list(TaggitTag.objects.filter(taggit_taggeditem_items__content_type=entry_ct, taggit_taggeditem_items__object_id=entry.pk).values_list('slug', flat=True).distinct())
+                       entry.tags = Tag.objects.filter(slug__in=tag_slugs)
+                       entry.save()
+               
+               for article in NewsletterArticle.objects.all():
+                       tag_slugs = list(TaggitTag.objects.filter(taggit_taggeditem_items__content_type=article_ct, taggit_taggeditem_items__object_id=article.pk).values_list('slug', flat=True).distinct())
+                       article.tags = Tag.objects.filter(slug__in=tag_slugs)
+                       article.save()
+
+
+       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': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", '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'}),
+                       'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+                       'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+                       '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': {'ordering': "('name',)", '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'})
+               },
+               person_model: frozen_person,
+               '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': {'ordering': "['-date']", 'object_name': 'BlogEntry'},
+                       'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blogentries'", 'to': "orm['%s']" % person_model}),
+                       '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_length': ('django.db.models.fields.PositiveIntegerField', [], {'default': '15', 'null': 'True', 'blank': 'True'}),
+                       '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'}),
+                       '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': {'ordering': "['-date']", 'unique_together': "(('newsletter', 'slug'),)", 'object_name': 'NewsletterArticle'},
+                       'authors': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'newsletterarticles'", 'symmetrical': 'False', 'to': "orm['%s']" % person_model}),
+                       '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': {'ordering': "['-numbering']", '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_length': ('django.db.models.fields.PositiveIntegerField', [], {'default': '15', 'null': 'True', 'blank': 'True'}),
+                       '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'}),
+                       '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', [], {'db_index': 'True'}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+                       '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', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+               },
+               'philo.node': {
+                       'Meta': {'unique_together': "(('parent', 'slug'),)", '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', [], {'blank': 'True', 'related_name': "'node_view_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+                       'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+               },
+               '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': {'ordering': "('name',)", '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': {'unique_together': "(('parent', 'slug'),)", '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'})
+               },
+               'taggit.tag': {
+                       'Meta': {'object_name': 'Tag'},
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+                       'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'})
+               },
+               'taggit.taggeditem': {
+                       'Meta': {'object_name': 'TaggedItem'},
+                       'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+                       'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
+               }
+       }
+
+       complete_apps = ['penfield', 'taggit']
+       symmetrical = True
diff --git a/philo/contrib/penfield/migrations/0006_delete_tag_rels.py b/philo/contrib/penfield/migrations/0006_delete_tag_rels.py
new file mode 100644 (file)
index 0000000..d3bba00
--- /dev/null
@@ -0,0 +1,218 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+from philo.migrations import person_model, frozen_person
+
+class Migration(SchemaMigration):
+
+       needed_by = (
+               ('philo', '0021_auto__del_tag'),
+       )
+
+       def forwards(self, orm):
+               
+               # Removing M2M table for field tags on 'BlogEntry'
+               db.delete_table('penfield_blogentry_tags')
+
+               # Removing M2M table for field tags on 'NewsletterArticle'
+               db.delete_table('penfield_newsletterarticle_tags')
+
+
+       def backwards(self, orm):
+               
+               # Adding M2M table for field tags on 'BlogEntry'
+               db.create_table('penfield_blogentry_tags', (
+                       ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+                       ('blogentry', models.ForeignKey(orm['penfield.blogentry'], null=False)),
+                       ('tag', models.ForeignKey(orm['philo.tag'], null=False))
+               ))
+               db.create_unique('penfield_blogentry_tags', ['blogentry_id', 'tag_id'])
+
+               # Adding M2M table for field tags on 'NewsletterArticle'
+               db.create_table('penfield_newsletterarticle_tags', (
+                       ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+                       ('newsletterarticle', models.ForeignKey(orm['penfield.newsletterarticle'], null=False)),
+                       ('tag', models.ForeignKey(orm['philo.tag'], null=False))
+               ))
+               db.create_unique('penfield_newsletterarticle_tags', ['newsletterarticle_id', 'tag_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': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", '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'}),
+                       'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+                       'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+                       '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': {'ordering': "('name',)", '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'})
+               },
+               person_model: frozen_person,
+               '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['%s']" % person_model}),
+                       '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'}),
+                       '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_length': ('django.db.models.fields.PositiveIntegerField', [], {'default': '15', 'null': 'True', 'blank': 'True'}),
+                       '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'}),
+                       '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': {'ordering': "['-date']", 'unique_together': "(('newsletter', 'slug'),)", 'object_name': 'NewsletterArticle'},
+                       'authors': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'newsletterarticles'", 'symmetrical': 'False', 'to': "orm['%s']" % person_model}),
+                       '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'}),
+                       'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+               },
+               'penfield.newsletterissue': {
+                       'Meta': {'ordering': "['-numbering']", '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_length': ('django.db.models.fields.PositiveIntegerField', [], {'default': '15', 'null': 'True', 'blank': 'True'}),
+                       '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'}),
+                       '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', [], {'db_index': 'True'}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+                       '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', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+               },
+               'philo.node': {
+                       'Meta': {'unique_together': "(('parent', 'slug'),)", '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', [], {'blank': 'True', 'related_name': "'node_view_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+                       'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+               },
+               '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.template': {
+                       'Meta': {'unique_together': "(('parent', 'slug'),)", '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'})
+               },
+               'taggit.tag': {
+                       'Meta': {'object_name': 'Tag'},
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+                       'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'})
+               },
+               'taggit.taggeditem': {
+                       'Meta': {'object_name': 'TaggedItem'},
+                       'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+                       'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
+               }
+       }
+
+       complete_apps = ['penfield', 'taggit']
index 53ae9c5..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 taggit.managers import TaggableManager
+from taggit.models import Tag, TaggedItem
 
 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
 
@@ -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."""
-               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):
@@ -56,13 +63,13 @@ class BlogEntry(Entity):
        date = models.DateTimeField(default=None)
        
        #: The content of the :class:`BlogEntry`.
-       content = models.TextField()
+       content = TemplateField()
        
        #: 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:
@@ -125,7 +132,6 @@ class BlogView(FeedView):
        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
@@ -158,8 +164,8 @@ class BlogView(FeedView):
        
        @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_id:
                        urlpatterns += patterns('',
@@ -168,11 +174,11 @@ class BlogView(FeedView):
                
                if self.entry_archive_page_id:
                        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':
-                                       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':
-                                               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('',
@@ -196,9 +202,6 @@ class BlogView(FeedView):
                        )
                return urlpatterns
        
-       def get_context(self):
-               return {'blog': self.blog}
-       
        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."""
                return obj.entries.filter(date__lte=datetime.now())
@@ -207,46 +210,62 @@ class BlogView(FeedView):
                """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 obj.entry_tags
        
-       def get_all_entries(self, obj, request, extra_context=None):
-               """Used to generate :meth:`~.FeedView.feed_patterns` for all entries."""
-               return self.get_entry_queryset(obj), extra_context
-       
-       def get_entries_by_ymd(self, obj, 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(obj)
-               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, obj, 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(obj).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(obj)
-               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.update({'tags': tags})
+               context.update(obj)
                
                return entries, context
        
@@ -279,30 +298,6 @@ class BlogView(FeedView):
                })
                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:
@@ -320,7 +315,25 @@ class BlogView(FeedView):
                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
@@ -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)
-       #: 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:
index 09014eb..4acf5d1 100644 (file)
@@ -135,7 +135,6 @@ class FeedView(MultiView):
                
                """
                get_items = get_items_attr if callable(get_items_attr) else getattr(self, get_items_attr)
-               page = page_attr if isinstance(page_attr, Page) else getattr(self, page_attr)
                
                def inner(request, extra_context=None, *args, **kwargs):
                        obj = self.get_object(request, *args, **kwargs)
@@ -146,6 +145,7 @@ class FeedView(MultiView):
                        context.update(extra_context or {})
                        context.update(item_context or {})
                        
+                       page = page_attr if isinstance(page_attr, Page) else getattr(self, page_attr)
                        return page.render_to_response(request, extra_context=context)
                return inner
        
diff --git a/philo/forms/widgets.py b/philo/forms/widgets.py
deleted file mode 100644 (file)
index d223605..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-from django.forms.widgets import Textarea
-from django.utils import simplejson as json
-
-__all__ = ('EmbedWidget',)
-
-class EmbedWidget(Textarea):
-       """A form widget with the HTML class embedding and an embedded list of content-types."""
-       def __init__(self, attrs=None):
-               from philo.models import value_content_type_limiter
-               
-               content_types = value_content_type_limiter.classes
-               data = []
-               
-               for content_type in content_types:
-                       data.append({'app_label': content_type._meta.app_label, 'object_name': content_type._meta.object_name.lower(), 'verbose_name': unicode(content_type._meta.verbose_name)})
-               
-               json_ = json.dumps(data)
-               
-               default_attrs = {'class': 'embedding vLargeTextField', 'data-content-types': json_ }
-               
-               if attrs:
-                       default_attrs.update(attrs)
-                       
-               super(EmbedWidget, self).__init__(default_attrs)
-               
-       class Media:
-               css = {
-                       'all': ('philo/css/EmbedWidget.css',),
-               }
-               js = ('philo/js/EmbedWidget.js',)
\ No newline at end of file
diff --git a/philo/migrations/0019_to_taggit.py b/philo/migrations/0019_to_taggit.py
new file mode 100644 (file)
index 0000000..fb5e8f0
--- /dev/null
@@ -0,0 +1,155 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+       def forwards(self, orm):
+               "Write your forwards methods here."
+               # If any tags are longer than 100, this will result in some data loss.
+               PhiloTag = orm['philo.Tag']
+               Tag = orm['taggit.Tag']
+               
+               for tag in PhiloTag.objects.all():
+                       Tag.objects.get_or_create(name=tag.name, slug=tag.slug)
+
+
+       def backwards(self, orm):
+               "Write your backwards methods here."
+               pass
+
+
+       models = {
+               'contenttypes.contenttype': {
+                       'Meta': {'ordering': "('name',)", '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'})
+               },
+               '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', [], {'db_index': 'True'}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+                       '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', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+               },
+               'philo.collection': {
+                       'Meta': {'object_name': 'Collection'},
+                       'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+               },
+               'philo.collectionmember': {
+                       'Meta': {'object_name': 'CollectionMember'},
+                       'collection': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'members'", 'to': "orm['philo.Collection']"}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'index': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+                       'member_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+                       'member_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+               },
+               'philo.contentlet': {
+                       'Meta': {'object_name': 'Contentlet'},
+                       'content': ('philo.models.fields.TemplateField', [], {}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+                       'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentlets'", 'to': "orm['philo.Page']"})
+               },
+               'philo.contentreference': {
+                       'Meta': {'object_name': 'ContentReference'},
+                       'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+                       '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': '255', 'db_index': 'True'}),
+                       'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentreferences'", 'to': "orm['philo.Page']"})
+               },
+               'philo.file': {
+                       'Meta': {'object_name': 'File'},
+                       'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+                       'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+               },
+               'philo.foreignkeyvalue': {
+                       'Meta': {'object_name': 'ForeignKeyValue'},
+                       'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+               },
+               'philo.jsonvalue': {
+                       'Meta': {'object_name': 'JSONValue'},
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'value': ('philo.models.fields.JSONField', [], {'default': "'null'", 'db_index': 'True'})
+               },
+               'philo.manytomanyvalue': {
+                       'Meta': {'object_name': 'ManyToManyValue'},
+                       'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'values': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['philo.ForeignKeyValue']", 'null': 'True', 'blank': 'True'})
+               },
+               'philo.node': {
+                       'Meta': {'unique_together': "(('parent', 'slug'),)", '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', [], {'blank': 'True', 'related_name': "'node_view_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+                       'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+               },
+               '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.redirect': {
+                       'Meta': {'object_name': 'Redirect'},
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'reversing_parameters': ('philo.models.fields.JSONField', [], {'blank': 'True'}),
+                       'status_code': ('django.db.models.fields.IntegerField', [], {'default': '302'}),
+                       'target_node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'philo_redirect_related'", 'null': 'True', 'to': "orm['philo.Node']"}),
+                       'url_or_subpath': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
+               },
+               'philo.tag': {
+                       'Meta': {'ordering': "('name',)", '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': {'unique_together': "(('parent', 'slug'),)", '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'})
+               },
+               'taggit.tag': {
+                       'Meta': {'object_name': 'Tag'},
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+                       'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'})
+               },
+               'taggit.taggeditem': {
+                       'Meta': {'object_name': 'TaggedItem'},
+                       'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+                       'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
+               }
+       }
+
+       complete_apps = ['taggit', 'philo']
diff --git a/philo/migrations/0020_from_taggit.py b/philo/migrations/0020_from_taggit.py
new file mode 100644 (file)
index 0000000..9a43df9
--- /dev/null
@@ -0,0 +1,154 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+       def forwards(self, orm):
+               "Write your forwards methods here."
+               pass
+
+
+       def backwards(self, orm):
+               "Write your backwards methods here."
+               PhiloTag = orm['philo.Tag']
+               Tag = orm['taggit.Tag']
+               
+               for tag in Tag.objects.all():
+                       PhiloTag.objects.get_or_create(name=tag.name, slug=tag.slug)
+
+
+       models = {
+               'contenttypes.contenttype': {
+                       'Meta': {'ordering': "('name',)", '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'})
+               },
+               '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', [], {'db_index': 'True'}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+                       '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', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+               },
+               'philo.collection': {
+                       'Meta': {'object_name': 'Collection'},
+                       'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+               },
+               'philo.collectionmember': {
+                       'Meta': {'object_name': 'CollectionMember'},
+                       'collection': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'members'", 'to': "orm['philo.Collection']"}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'index': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+                       'member_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+                       'member_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+               },
+               'philo.contentlet': {
+                       'Meta': {'object_name': 'Contentlet'},
+                       'content': ('philo.models.fields.TemplateField', [], {}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+                       'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentlets'", 'to': "orm['philo.Page']"})
+               },
+               'philo.contentreference': {
+                       'Meta': {'object_name': 'ContentReference'},
+                       'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+                       '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': '255', 'db_index': 'True'}),
+                       'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentreferences'", 'to': "orm['philo.Page']"})
+               },
+               'philo.file': {
+                       'Meta': {'object_name': 'File'},
+                       'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+                       'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+               },
+               'philo.foreignkeyvalue': {
+                       'Meta': {'object_name': 'ForeignKeyValue'},
+                       'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+               },
+               'philo.jsonvalue': {
+                       'Meta': {'object_name': 'JSONValue'},
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'value': ('philo.models.fields.JSONField', [], {'default': "'null'", 'db_index': 'True'})
+               },
+               'philo.manytomanyvalue': {
+                       'Meta': {'object_name': 'ManyToManyValue'},
+                       'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'values': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['philo.ForeignKeyValue']", 'null': 'True', 'blank': 'True'})
+               },
+               'philo.node': {
+                       'Meta': {'unique_together': "(('parent', 'slug'),)", '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', [], {'blank': 'True', 'related_name': "'node_view_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+                       'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+               },
+               '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.redirect': {
+                       'Meta': {'object_name': 'Redirect'},
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'reversing_parameters': ('philo.models.fields.JSONField', [], {'blank': 'True'}),
+                       'status_code': ('django.db.models.fields.IntegerField', [], {'default': '302'}),
+                       'target_node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'philo_redirect_related'", 'null': 'True', 'to': "orm['philo.Node']"}),
+                       'url_or_subpath': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
+               },
+               'philo.tag': {
+                       'Meta': {'ordering': "('name',)", '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': {'unique_together': "(('parent', 'slug'),)", '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'})
+               },
+               'taggit.tag': {
+                       'Meta': {'object_name': 'Tag'},
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+                       'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'})
+               },
+               'taggit.taggeditem': {
+                       'Meta': {'object_name': 'TaggedItem'},
+                       'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
+                       'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+                       'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+                       'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
+               }
+       }
+
+       complete_apps = ['taggit', 'philo']
diff --git a/philo/migrations/0021_auto__del_tag.py b/philo/migrations/0021_auto__del_tag.py
new file mode 100644 (file)
index 0000000..f63b906
--- /dev/null
@@ -0,0 +1,138 @@
+# 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):
+        
+        # Deleting model 'Tag'
+        db.delete_table('philo_tag')
+
+
+    def backwards(self, orm):
+        
+        # Adding model 'Tag'
+        db.create_table('philo_tag', (
+            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=255, unique=True, db_index=True)),
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
+        ))
+        db.send_create_signal('philo', ['Tag'])
+
+
+    models = {
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", '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'})
+        },
+        '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', [], {'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+            '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', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+        },
+        'philo.collection': {
+            'Meta': {'object_name': 'Collection'},
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'philo.collectionmember': {
+            'Meta': {'object_name': 'CollectionMember'},
+            'collection': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'members'", 'to': "orm['philo.Collection']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'index': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'member_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'member_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+        },
+        'philo.contentlet': {
+            'Meta': {'object_name': 'Contentlet'},
+            'content': ('philo.models.fields.TemplateField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+            'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentlets'", 'to': "orm['philo.Page']"})
+        },
+        'philo.contentreference': {
+            'Meta': {'object_name': 'ContentReference'},
+            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            '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': '255', 'db_index': 'True'}),
+            'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentreferences'", 'to': "orm['philo.Page']"})
+        },
+        'philo.file': {
+            'Meta': {'object_name': 'File'},
+            'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'philo.foreignkeyvalue': {
+            'Meta': {'object_name': 'ForeignKeyValue'},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+        },
+        'philo.jsonvalue': {
+            'Meta': {'object_name': 'JSONValue'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'value': ('philo.models.fields.JSONField', [], {'default': "'null'", 'db_index': 'True'})
+        },
+        'philo.manytomanyvalue': {
+            'Meta': {'object_name': 'ManyToManyValue'},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'values': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['philo.ForeignKeyValue']", 'null': 'True', 'blank': 'True'})
+        },
+        'philo.node': {
+            'Meta': {'unique_together': "(('parent', 'slug'),)", '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', [], {'blank': 'True', 'related_name': "'node_view_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+            'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+        },
+        '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.redirect': {
+            'Meta': {'object_name': 'Redirect'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'reversing_parameters': ('philo.models.fields.JSONField', [], {'blank': 'True'}),
+            'status_code': ('django.db.models.fields.IntegerField', [], {'default': '302'}),
+            'target_node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'philo_redirect_related'", 'null': 'True', 'to': "orm['philo.Node']"}),
+            'url_or_subpath': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
+        },
+        'philo.template': {
+            'Meta': {'unique_together': "(('parent', 'slug'),)", '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 = ['philo']
index 8df67c3..e7918f5 100644 (file)
@@ -16,23 +16,7 @@ from philo.utils.entities import AttributeMapper, TreeAttributeMapper
 from philo.validators import json_validator
 
 
-__all__ = ('Tag', 'value_content_type_limiter', 'register_value_model', 'unregister_value_model', 'JSONValue', 'ForeignKeyValue', 'ManyToManyValue', 'Attribute', 'Entity', 'TreeEntity', 'SlugTreeEntity')
-
-
-class Tag(models.Model):
-       """A simple, generic model for tagging."""
-       #: A CharField (max length 255) which contains the name of the tag.
-       name = models.CharField(max_length=255)
-       #: A CharField (max length 255) which contains the tag's unique slug.
-       slug = models.SlugField(max_length=255, unique=True)
-       
-       def __unicode__(self):
-               """Returns the value of the :attr:`name` field"""
-               return self.name
-       
-       class Meta:
-               app_label = 'philo'
-               ordering = ('name',)
+__all__ = ('value_content_type_limiter', 'register_value_model', 'unregister_value_model', 'JSONValue', 'ForeignKeyValue', 'ManyToManyValue', 'Attribute', 'Entity', 'TreeEntity', 'SlugTreeEntity')
 
 
 #: An instance of :class:`.ContentTypeRegistryLimiter` which is used to track the content types which can be related to by :class:`ForeignKeyValue`\ s and :class:`ManyToManyValue`\ s.
@@ -44,9 +28,6 @@ def register_value_model(model):
        value_content_type_limiter.register_class(model)
 
 
-register_value_model(Tag)
-
-
 def unregister_value_model(model):
        """Registers a model as a valid content type for a :class:`ForeignKeyValue` or :class:`ManyToManyValue` through the :data:`value_content_type_limiter`."""
        value_content_type_limiter.unregister_class(model)
index 575b3a4..7ab4326 100644 (file)
@@ -9,20 +9,14 @@ from django.utils.translation import ugettext_lazy as _
 from philo.forms.fields import JSONFormField
 from philo.utils.registry import RegistryIterator
 from philo.validators import TemplateValidator, json_validator
-from philo.forms.widgets import EmbedWidget
 #from philo.models.fields.entities import *
 
 
-class TemplateField(models.Field):
+class TemplateField(models.TextField):
        """A :class:`TextField` which is validated with a :class:`.TemplateValidator`. ``allow``, ``disallow``, and ``secure`` will be passed into the validator's construction."""
        def __init__(self, allow=None, disallow=None, secure=True, *args, **kwargs):
                super(TemplateField, self).__init__(*args, **kwargs)
                self.validators.append(TemplateValidator(allow, disallow, secure))
-       
-       def formfield(self, **kwargs):
-               defaults = {'widget': EmbedWidget}
-               defaults.update(kwargs)
-               return super(TemplateField, self).formfield(**defaults)
 
 
 class JSONDescriptor(object):
index 58d1b96..647ba81 100644 (file)
@@ -55,7 +55,7 @@ class Node(SlugTreeEntity):
                """This is a shortcut method for :meth:`View.render_to_response`"""
                if self.view_object_id and self.view_content_type_id:
                        view_model = ContentType.objects.get_for_id(self.view_content_type_id).model_class()
-                       self.view = view_model._default_manager.select_related(depth=1).get(pk=self.view_object_id)
+                       self.view = view_model._default_manager.get(pk=self.view_object_id)
                        return self.view.render_to_response(request, extra_context)
                raise Http404
        
index f946740..7293125 100644 (file)
@@ -28,7 +28,7 @@
                        oldDismissRelatedLookupPopup = window.dismissRelatedLookupPopup;
                        window.dismissRelatedLookupPopup = function (win, chosenId) {
                                var name = windowname_to_id(win.name),
-                                       elem = $('#'+win.name), val;
+                                       elem = $('#'+name), val;
                                // if the original element was an embed widget, run our script
                                if (elem.parent().hasClass('embed-widget')) {
                                        contenttype = $('select',elem.parent()).val();
diff --git a/philo/static/philo/js/TagCreation.js b/philo/static/philo/js/TagCreation.js
deleted file mode 100644 (file)
index a23e609..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-var tagCreation = window.tagCreation;
-
-(function($) {
-       location_re = new RegExp("^https?:\/\/" + window.location.host + "/")
-       
-       $('html').ajaxSend(function(event, xhr, settings) {
-               function getCookie(name) {
-                       var cookieValue = null;
-                       if (document.cookie && document.cookie != '') {
-                               var cookies = document.cookie.split(';');
-                               for (var i = 0; i < cookies.length; i++) {
-                                       var cookie = $.trim(cookies[i]);
-                                       // Does this cookie string begin with the name we want?
-                                       if (cookie.substring(0, name.length + 1) == (name + '=')) {
-                                               cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
-                                               break;
-                                       }
-                               }
-                       }
-                       return cookieValue;
-               }
-               if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url)) || location_re.test(settings.url)) {
-                       // Only send the token to relative URLs i.e. locally.
-                       xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
-               }
-       });
-       tagCreation = {
-               'cache': {},
-               'addTagFromSlug': function(triggeringLink) {
-                       var id = triggeringLink.id.replace(/^ajax_add_/, '') + '_input';
-                       var slug = document.getElementById(id).value;
-       
-                       var name = slug.split(' ');
-                       for(var i=0;i<name.length;i++) {
-                               name[i] = name[i].substr(0,1).toUpperCase() + name[i].substr(1);
-                       }
-                       name = name.join(' ');
-                       slug = name.toLowerCase().replace(/ /g, '-').replace(/[^\w-]/g, '');
-       
-                       var href = triggeringLink.href;
-                       var data = {
-                               'name': name,
-                               'slug': slug
-                       };
-                       $.post(href, data, function(data){
-                               newId = html_unescape(data.pk);
-                               newRepr = html_unescape(data.unicode);
-                               var toId = id.replace(/_input$/, '_to');
-                               elem = document.getElementById(toId);
-                               var o = new Option(newRepr, newId);
-                               SelectBox.add_to_cache(toId, o);
-                               SelectBox.redisplay(toId);
-                       }, "json")
-               },
-               'init': function(id) {
-                       tagCreation.cache[id] = {}
-                       var input = tagCreation.cache[id].input = document.getElementById(id + '_input');
-                       var select = tagCreation.cache[id].select = document.getElementById(id + '_from');
-                       var addLinkTemplate = document.getElementById('add_' + input.id.replace(/_input$/, '')).cloneNode(true);
-                       var addLink = tagCreation.cache[id].addLink = document.createElement('A');
-                       addLink.id = 'ajax_add_' + id;
-                       addLink.className = addLinkTemplate.className;
-                       addLink.href = addLinkTemplate.href;
-                       addLink.appendChild($(addLinkTemplate).children()[0].cloneNode(false));
-                       addLink.innerHTML += " <span style='vertical-align:text-top;'>Add this tag</span>"
-                       addLink.style.marginLeft = "20px";
-                       addLink.style.display = "block";
-                       addLink.style.backgroundPosition = "10px 5px";
-                       addLink.style.width = "120px";
-                       $(input).after(addLink);
-                       if (window.grappelli) {
-                               addLink.parentNode.style.backgroundPosition = "6px 8px";
-                       } else {
-                               addLink.style.marginTop = "5px";
-                       }
-                       tagCreation.toggleButton(id);
-                       addEvent(input, 'keyup', function() {
-                               tagCreation.toggleButton(id);
-                       });
-                       addEvent(addLink, 'click', function(e) {
-                               e.preventDefault();
-                               tagCreation.addTagFromSlug(addLink);
-                       });
-                       // SelectFilter actually mistakenly allows submission on enter. We disallow it.
-                       addEvent(input, 'keypress', function(e) {
-                               if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
-                                       e.preventDefault();
-                                       if (select.options.length == 0) {
-                                               tagCreation.addTagFromSlug(addLink);
-                                       }
-                               }
-                       })
-               },
-               'toggleButton': function(id) {
-                       var addLink = tagCreation.cache[id].addLink;
-                       var select = $(tagCreation.cache[id].select);
-                       var input = tagCreation.cache[id].input;
-                       if (input.value != "") {
-                               if (addLink.style.display == 'none') {
-                                       addLink.style.display = 'block';
-                                       select.height(select.height() - $(addLink).outerHeight(false))
-                               }
-                       } else {
-                               if (addLink.style.display == 'block') {
-                                       select[0].style.height = null;
-                                       addLink.style.display = 'none';
-                               }
-                       }
-               }
-       }
-}(django.jQuery))
\ No newline at end of file
index 754a5dc..830276e 100644 (file)
@@ -1,8 +1,11 @@
+from functools import partial
 from UserDict import DictMixin
 
 from django.db import models
 from django.contrib.contenttypes.models import ContentType
 
+from philo.utils.lazycompat import SimpleLazyObject
+
 
 ### AttributeMappers
 
@@ -86,14 +89,21 @@ class AttributeMapper(object, DictMixin):
                        value_lookups.setdefault(a.value_content_type_id, []).append(a.value_object_id)
                        self._attributes_cache[a.key] = a
                
-               values_bulk = {}
+               values_bulk = dict(((ct_pk, SimpleLazyObject(partial(ContentType.objects.get_for_id(ct_pk).model_class().objects.in_bulk, pks))) for ct_pk, pks in value_lookups.items()))
+               
+               cache = {}
                
-               for ct_pk, pks in value_lookups.items():
-                       values_bulk[ct_pk] = ContentType.objects.get_for_id(ct_pk).model_class().objects.in_bulk(pks)
+               for a in attributes:
+                       cache[a.key] = SimpleLazyObject(partial(self._lazy_value_from_bulk, values_bulk, a))
+                       a._value_cache = cache[a.key]
                
-               self._cache.update(dict([(a.key, getattr(values_bulk[a.value_content_type_id].get(a.value_object_id), 'value', None)) for a in attributes]))
+               self._cache.update(cache)
                self._cache_filled = True
        
+       def _lazy_value_from_bulk(self, bulk, attribute):
+               v = bulk[attribute.value_content_type_id].get(attribute.value_object_id)
+               return getattr(v, 'value', None)
+       
        def clear_cache(self):
                """Clears the cache."""
                self._cache = {}
index 8f13ea5..3d740f8 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -75,6 +75,7 @@ setup(
                'waldo-recaptcha': ['recaptcha-django'],
                'sobol-eventlet': ['eventlet'],
                'sobol-scrape': ['BeautifulSoup'],
+               'penfield': ['django-taggit>=0.9'],
        },
        dependency_links = [
                'https://github.com/django-mptt/django-mptt/tarball/master#egg=django-mptt-dev'