Commits

Vadim Statishin committed 7057335

Rebuild migration

Comments (0)

Files changed (21)

articles/__init__.py

-__version__ = '2.4.2'
+__version__ = '2.3.0'
 
 from articles.directives import *
 try:

articles/admin.py

-import logging
-
 from django.contrib import admin
 from django.contrib.auth.models import User
 from django.utils.translation import ugettext_lazy as _
 from forms import ArticleAdminForm
 from models import Tag, Article, ArticleStatus, Attachment
 
-log = logging.getLogger('articles.admin')
-
-class TagAdmin(admin.ModelAdmin):
-    list_display = ('name', 'article_count')
-
-    def article_count(self, obj):
-        return obj.article_set.count()
-    article_count.short_description = _('Applied To')
-
 class ArticleStatusAdmin(admin.ModelAdmin):
     list_display = ('name', 'is_live')
     list_filter = ('is_live',)
     max_num = 15
 
 class ArticleAdmin(admin.ModelAdmin):
-    list_display = ('title', 'tag_count', 'status', 'author', 'publish_date',
-                    'expiration_date', 'is_active')
-    list_filter = ('author', 'status', 'is_active', 'publish_date',
-                   'expiration_date', 'sites')
+    list_display = ('title', 'status', 'author', 'publish_date', 'expiration_date', 'is_active')
+    list_filter = ('author', 'status', 'is_active', 'publish_date', 'expiration_date', 'sites')
     list_per_page = 25
     search_fields = ('title', 'keywords', 'description', 'content')
     date_hierarchy = 'publish_date'
         ('Teaser', {'fields': ('teaser',), 'classes': ('collapse',)
         }),
         (None, {'fields': ('content', 'tags', 'auto_tag', 'markup', 'status')}),
+
         ('Metadata', {
             'fields': ('keywords', 'description',),
             'classes': ('collapse',)
     filter_horizontal = ('tags', 'followup_for', 'related_articles')
     prepopulated_fields = {'slug': ('title',)}
 
-    def tag_count(self, obj):
-        return str(obj.tags.count())
-    tag_count.short_description = _('Tags')
-
     def mark_active(self, request, queryset):
         queryset.update(is_active=True)
     mark_active.short_description = _('Mark select articles as active')
     def get_actions(self, request):
         actions = super(ArticleAdmin, self).get_actions(request)
 
-        def dynamic_status(name, status):
+        def dynamic(name, status):
             def status_func(self, request, queryset):
                 queryset.update(status=status)
 
 
         for status in ArticleStatus.objects.all():
             name = 'mark_status_%i' % status.id
-            actions[name] = (dynamic_status(name, status), name, _('Set status of selected to "%s"' % status))
-
-        def dynamic_tag(name, tag):
-            def status_func(self, request, queryset):
-                for article in queryset.iterator():
-                    log.debug('Dynamic tagging: applying Tag "%s" to Article "%s"' % (tag, article))
-                    article.tags.add(tag)
-                    article.save()
-
-            status_func.__name__ = name
-            status_func.short_description = _('Apply tag "%s" to selected articles' % tag)
-            return status_func
-
-        for tag in Tag.objects.all():
-            name = 'apply_tag_%s' % tag.pk
-            actions[name] = (dynamic_tag(name, tag), name, _('Apply Tag: %s' % (tag.slug,)))
+            actions[name] = (dynamic(name, status), name, _('Set status of selected to "%s"' % status))
 
         return actions
 
         else:
             return self.model._default_manager.filter(author=request.user)
 
-admin.site.register(Tag, TagAdmin)
+admin.site.register(Tag)
 admin.site.register(Article, ArticleAdmin)
 admin.site.register(ArticleStatus, ArticleStatusAdmin)
 

articles/decorators.py

-import functools
-import logging
-import time
-
-log = logging.getLogger('articles.decorators')
-
-def logtime(func):
-
-    @functools.wraps(func)
-    def wrapped(*args, **kwargs):
-        if func.__class__.__name__ == 'function':
-            executing = '%s.%s' % (func.__module__, func.__name__)
-        elif 'method' in func.__class__.__name__:
-            executing = '%s.%s.%s' % (func.__module__, func.__class__.__name__, func.__name__)
-        else:
-            executing = str(func)
-
-        log.debug('Logging execution time for %s with args: %s; kwargs: %s' % (executing, args, kwargs))
-
-        start = time.time()
-        res = func(*args, **kwargs)
-        duration = time.time() - start
-
-        log.debug('Called %s... duration: %s seconds' % (executing, duration))
-        return res
-
-    return wrapped
-
-def once_per_instance(func):
-    """Makes it so an instance method is called at most once before saving"""
-
-    @functools.wraps(func)
-    def wrapped(self, *args, **kwargs):
-        if not hasattr(self, '__run_once_methods'):
-            self.__run_once_methods = []
-
-        name = func.__name__
-        if name in self.__run_once_methods:
-            log.debug('Method %s has already been called for %s... not calling again.' % (name, self))
-            return False
-
-        res = func(self, *args, **kwargs)
-
-        self.__run_once_methods.append(name)
-        return res
-
-    return wrapped
-

articles/feeds.py

 from django.conf import settings
-from django.contrib.syndication.views import Feed, FeedDoesNotExist
+from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
 from django.contrib.sites.models import Site
 from django.core.cache import cache
 from django.core.urlresolvers import reverse
 
 class TagFeed(Feed, SiteMixin):
 
-    def get_object(self, request, slug):
-        try:
-            return Tag.objects.get(slug__iexact=slug)
-        except Tag.DoesNotExist:
+    def get_object(self, bits):
+        if len(bits) != 1:
             raise FeedDoesNotExist
 
+        return Tag.objects.get(name__iexact=bits[0])
+
     def title(self, obj):
         return "%s: Newest Articles Tagged '%s'" % (self.site.name, obj.name)
 

articles/forms.py

-import logging
-
 from django import forms
 from django.utils.translation import ugettext_lazy as _
 from models import Article, Tag
 
-log = logging.getLogger('articles.forms')
-
 def tag(name):
     """Returns a Tag object for the given name"""
 
-    slug = Tag.clean_tag(name)
-
-    log.debug('Looking for Tag with slug "%s"...' % (slug,))
-    t, created = Tag.objects.get_or_create(slug=slug, defaults={'name': name})
-    log.debug('Found Tag %s. Name: %s Slug: %s Created: %s' % (t.pk, t.name, t.slug, created))
-
+    t = Tag.objects.get_or_create(slug=Tag.clean_tag(name))[0]
     if not t.name:
         t.name = name
         t.save()
     def clean_tags(self):
         """Turns the string of tags into a list"""
 
-        tags = [tag(t.strip()) for t in self.cleaned_data['tags'].split() if len(t.strip())]
-
-        log.debug('Tagging Article %s with: %s' % (self.cleaned_data['title'], tags))
+        tags = [tag(t) for t in self.cleaned_data['tags'].split()]
         self.cleaned_data['tags'] = tags
         return self.cleaned_data['tags']
 

articles/listeners.py

-import logging
-
-from django.db.models import signals, Q
-
-from decorators import logtime
+from django.db.models import signals
 from models import Article, Tag
 
-log = logging.getLogger('articles.listeners')
+def apply_new_tag(sender, instance, created, **kwargs):
+    """
+    Applies new tags to existing articles that are marked for auto-tagging
+    """
 
-@logtime
-def apply_new_tag(sender, instance, created, using='default', **kwargs):
-    """Applies new tags to existing articles that are marked for auto-tagging"""
-
-    # attempt to find all articles that contain the new tag
-    # TODO: make sure this is standard enough... seems that both MySQL and
-    # PostgreSQL support it...
-    tag = r'[[:<:]]%s[[:>:]]' % instance.name
-
-    log.debug('Searching for auto-tag Articles using regex: %s' % (tag,))
-    applicable_articles = Article.objects.filter(
-        Q(auto_tag=True),
-        Q(content__iregex=tag) |
-        Q(title__iregex=tag) |
-        Q(description__iregex=tag) |
-        Q(keywords__iregex=tag)
-    )
-
-    log.debug('Found %s matches' % len(applicable_articles))
-    for article in applicable_articles:
-        log.debug('Applying Tag "%s" (%s) to Article "%s" (%s)' % (instance, instance.pk, article.title, article.pk))
-        article.tags.add(instance)
-        article.save()
+    for article in Article.objects.filter(auto_tag=True):
+        article.do_auto_tag()
 
 signals.post_save.connect(apply_new_tag, sender=Tag)

articles/migrations/0003_auto__add_field_tag_slug.py

         # Adding field 'Tag.slug'
         db.add_column('articles_tag', 'slug', self.gf('django.db.models.fields.CharField')(default='', max_length=64, null=True, blank=True), keep_default=False)
 
+        # find all tags with an empty slug
+        from articles.models import Tag
+        for tag in Tag.objects.filter(slug=''):
+            # trigger the automatic slug population
+            tag.save()
+
+        # now add the unique constraint
+        db.alter_column('articles_tag', 'slug', self.gf('django.db.models.fields.CharField')(default='', unique=True, max_length=64, null=True, blank=True))
 
     def backwards(self, orm):
 

articles/migrations/0004_auto__chg_field_tag_slug__add_field_article_teaser.py

+# 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):
+        
+        # Changing field 'Tag.slug'
+        db.alter_column('articles_tag', 'slug', self.gf('django.db.models.fields.CharField')(max_length=64, unique=True, null=True))
+
+        # Adding field 'Article.teaser'
+        db.add_column('articles_article', 'teaser', self.gf('django.db.models.fields.TextField')(default='', blank=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # User chose to not deal with backwards NULL issues for 'Tag.slug'
+        raise RuntimeError("Cannot reverse this migration. 'Tag.slug' and its values cannot be restored.")
+
+        # Deleting field 'Article.teaser'
+        db.delete_column('articles_article', 'teaser')
+
+
+    models = {
+        'articles.article': {
+            'Meta': {'ordering': "('-publish_date', 'title')", 'object_name': 'Article'},
+            'addthis_use_author': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'addthis_username': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '50', 'blank': 'True'}),
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'auto_tag': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'content': ('django.db.models.fields.TextField', [], {}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'expiration_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'followup_for': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'followups'", 'blank': 'True', 'to': "orm['articles.Article']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'keywords': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'markup': ('django.db.models.fields.CharField', [], {'default': "'h'", 'max_length': '1'}),
+            'publish_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'related_articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'related_articles_rel_+'", 'blank': 'True', 'to': "orm['articles.Article']"}),
+            'rendered_content': ('django.db.models.fields.TextField', [], {}),
+            'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sites.Site']", 'symmetrical': 'False', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['articles.ArticleStatus']"}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['articles.Tag']", 'symmetrical': 'False', 'blank': 'True'}),
+            'teaser': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'use_addthis_button': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        'articles.articlestatus': {
+            'Meta': {'ordering': "('ordering', 'name')", 'object_name': 'ArticleStatus'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_live': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'})
+        },
+        'articles.attachment': {
+            'Meta': {'ordering': "('-article', 'id')", 'object_name': 'Attachment'},
+            'article': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attachments'", 'to': "orm['articles.Article']"}),
+            'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+            'caption': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'articles.tag': {
+            'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
+        },
+        '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'})
+        },
+        'sites.site': {
+            'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
+            'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        }
+    }
+
+    complete_apps = ['articles']

articles/migrations/0004_set_tag_slugs.py

-# encoding: utf-8
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-class Migration(SchemaMigration):
-
-    no_dry_run = True
-
-    def forwards(self, orm):
-        """Finds all tags with an empty slug and populates it"""
-
-        for tag in orm.Tag.objects.filter(slug__isnull=True):
-            tag.save()
-
-        for tag in orm.Tag.objects.filter(slug=''):
-            tag.save()
-
-    def backwards(self, orm):
-        """Not important this time"""
-
-        pass
-
-
-    models = {
-        'articles.article': {
-            'Meta': {'ordering': "('-publish_date', 'title')", 'object_name': 'Article'},
-            'addthis_use_author': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
-            'addthis_username': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '50', 'blank': 'True'}),
-            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
-            'auto_tag': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
-            'content': ('django.db.models.fields.TextField', [], {}),
-            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'expiration_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
-            'followup_for': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'followups'", 'blank': 'True', 'to': "orm['articles.Article']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
-            'keywords': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'markup': ('django.db.models.fields.CharField', [], {'default': "'h'", 'max_length': '1'}),
-            'publish_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
-            'related_articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'related_articles_rel_+'", 'blank': 'True', 'to': "orm['articles.Article']"}),
-            'rendered_content': ('django.db.models.fields.TextField', [], {}),
-            'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sites.Site']", 'symmetrical': 'False', 'blank': 'True'}),
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
-            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['articles.ArticleStatus']"}),
-            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['articles.Tag']", 'symmetrical': 'False', 'blank': 'True'}),
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'use_addthis_button': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
-        },
-        'articles.articlestatus': {
-            'Meta': {'ordering': "('ordering', 'name')", 'object_name': 'ArticleStatus'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'is_live': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
-            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'})
-        },
-        'articles.attachment': {
-            'Meta': {'ordering': "('-article', 'id')", 'object_name': 'Attachment'},
-            'article': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attachments'", 'to': "orm['articles.Article']"}),
-            'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
-            'caption': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
-        },
-        'articles.tag': {
-            'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
-            'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
-        },
-        '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'})
-        },
-        'sites.site': {
-            'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
-            'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
-        }
-    }
-
-    complete_apps = ['articles']

articles/migrations/0005_make_slugs_unique.py

-# 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):
-        """Adds the unique constraint on tag slugs"""
-
-        db.alter_column('articles_tag', 'slug', self.gf('django.db.models.fields.CharField')(default='', unique=True, max_length=64, null=True, blank=True))
-
-
-    def backwards(self, orm):
-        """Drops the unique constraint"""
-
-        db.alter_column('articles_tag', 'slug', self.gf('django.db.models.fields.CharField')(default='', unique=False, max_length=64, null=True, blank=True))
-
-
-    models = {
-        'articles.article': {
-            'Meta': {'ordering': "('-publish_date', 'title')", 'object_name': 'Article'},
-            'addthis_use_author': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
-            'addthis_username': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '50', 'blank': 'True'}),
-            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
-            'auto_tag': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
-            'content': ('django.db.models.fields.TextField', [], {}),
-            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'expiration_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
-            'followup_for': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'followups'", 'blank': 'True', 'to': "orm['articles.Article']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
-            'keywords': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'markup': ('django.db.models.fields.CharField', [], {'default': "'h'", 'max_length': '1'}),
-            'publish_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
-            'related_articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'related_articles_rel_+'", 'blank': 'True', 'to': "orm['articles.Article']"}),
-            'rendered_content': ('django.db.models.fields.TextField', [], {}),
-            'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sites.Site']", 'symmetrical': 'False', 'blank': 'True'}),
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
-            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['articles.ArticleStatus']"}),
-            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['articles.Tag']", 'symmetrical': 'False', 'blank': 'True'}),
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'use_addthis_button': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
-        },
-        'articles.articlestatus': {
-            'Meta': {'ordering': "('ordering', 'name')", 'object_name': 'ArticleStatus'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'is_live': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
-            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'})
-        },
-        'articles.attachment': {
-            'Meta': {'ordering': "('-article', 'id')", 'object_name': 'Attachment'},
-            'article': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attachments'", 'to': "orm['articles.Article']"}),
-            'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
-            'caption': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
-        },
-        'articles.tag': {
-            'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
-            'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
-        },
-        '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'})
-        },
-        'sites.site': {
-            'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
-            'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
-        }
-    }
-
-    complete_apps = ['articles']

articles/migrations/0006_auto__add_field_article_teaser.py

-# encoding: utf-8
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-class Migration(SchemaMigration):
-
-    def forwards(self, orm):
-        
-        # Adding field 'Article.teaser'
-        db.add_column('articles_article', 'teaser', self.gf('django.db.models.fields.TextField')(default='', blank=True), keep_default=False)
-
-
-    def backwards(self, orm):
-        
-        # Deleting field 'Article.teaser'
-        db.delete_column('articles_article', 'teaser')
-
-
-    models = {
-        'articles.article': {
-            'Meta': {'ordering': "('-publish_date', 'title')", 'object_name': 'Article'},
-            'addthis_use_author': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
-            'addthis_username': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '50', 'blank': 'True'}),
-            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
-            'auto_tag': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
-            'content': ('django.db.models.fields.TextField', [], {}),
-            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'expiration_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
-            'followup_for': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'followups'", 'blank': 'True', 'to': "orm['articles.Article']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
-            'keywords': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'markup': ('django.db.models.fields.CharField', [], {'default': "'h'", 'max_length': '1'}),
-            'publish_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
-            'related_articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'related_articles_rel_+'", 'blank': 'True', 'to': "orm['articles.Article']"}),
-            'rendered_content': ('django.db.models.fields.TextField', [], {}),
-            'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sites.Site']", 'symmetrical': 'False', 'blank': 'True'}),
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
-            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['articles.ArticleStatus']"}),
-            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['articles.Tag']", 'symmetrical': 'False', 'blank': 'True'}),
-            'teaser': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'use_addthis_button': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
-        },
-        'articles.articlestatus': {
-            'Meta': {'ordering': "('ordering', 'name')", 'object_name': 'ArticleStatus'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'is_live': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
-            'ordering': ('django.db.models.fields.IntegerField', [], {'default': '0'})
-        },
-        'articles.attachment': {
-            'Meta': {'ordering': "('-article', 'id')", 'object_name': 'Attachment'},
-            'article': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attachments'", 'to': "orm['articles.Article']"}),
-            'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
-            'caption': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
-        },
-        'articles.tag': {
-            'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
-            'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
-        },
-        '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'})
-        },
-        'sites.site': {
-            'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
-            'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
-        }
-    }
-
-    complete_apps = ['articles']

articles/models.py

-from hashlib import sha1
+from base64 import encodestring
 from datetime import datetime
 import logging
 import mimetypes
 from django.template.defaultfilters import slugify, striptags
 from django.utils.translation import ugettext_lazy as _
 
-from decorators import logtime, once_per_instance
-
 WORD_LIMIT = getattr(settings, 'ARTICLES_TEASER_LIMIT', 75)
 AUTO_TAG = getattr(settings, 'ARTICLES_AUTO_TAG', True)
 DEFAULT_DB = getattr(settings, 'ARTICLES_DEFAULT_DB', 'default')
 TAG_RE = re.compile('[^a-z0-9\-_\+\:\.]?', re.I)
 META_CLEAN_RE = re.compile(r"<.*?>")
 
-log = logging.getLogger('articles.models')
+log = logging.getLogger(__file__)
 
 def clean_html(content):
     try:
         tokens = treewalkers.getTreeWalker("simpleTree")(document)
 
         ret=""
-        for text in serializer.HTMLSerializer().serialize(tokens, encoding='utf-8'):
+        for text in serializer.HTMLSerializer().serialize(tokens):
             ret = ret + text
         return ret
     except ImportError:
         return content
 
 
-
 def get_name(user):
     """
     Provides a way to fall back to a user's username if their full name has not
 
     @property
     def rss_name(self):
-        return self.cleaned
+        return u'tags/%s' % self.cleaned
 
     class Meta:
         ordering = ('name',)
         """Turns any markup into HTML"""
 
         original = self.rendered_content
-
+        text=""
         if self.markup == MARKUP_MARKDOWN:
             text = markup.markdown(self.content)
         elif self.markup == MARKUP_REST:
     def do_teaser(self):
         """
         If meta description is empty, sets it to the article's content and clean html.
-
+ 
         Returns True if an additional save is required, False otherwise.
         """
         text = self.teaser
 
             if len(words) > WORD_LIMIT:
                 text = '%s...' % ' '.join(words[:WORD_LIMIT])
-
             self.teaser = clean_html(text)
 
             return True
 
     def do_meta_description(self):
         """
-        If meta description is empty, sets it to the article's content and remove html tag.
+        If meta description is empty, sets it to the article's teaser.
 
         Returns True if an additional save is required, False otherwise.
         """
+
         if len(self.description.strip()) == 0:
             self.description = META_CLEAN_RE.sub("",self.teaser)
-
             return True
 
         return False
 
-    @logtime
-    @once_per_instance
     def do_auto_tag(self, using=DEFAULT_DB):
         """
         Performs the auto-tagging work if necessary.
         Returns True if an additional save is required, False otherwise.
         """
 
-        if not self.auto_tag:
-            log.debug('Article "%s" (ID: %s) is not marked for auto-tagging. Skipping.' % (self.title, self.pk))
-            return False
+        found = False
+        if self.auto_tag:
+            # don't clobber any existing tags!
+            existing_ids = [t.id for t in self.tags.all()]
 
-        # don't clobber any existing tags!
-        existing_ids = [t.id for t in self.tags.all()]
-        log.debug('Article %s already has these tags: %s' % (self.pk, existing_ids))
+            unused = Tag.objects.all()
+            if hasattr(unused, 'using'):
+                unused = unused.using(using)
+            unused = unused.exclude(id__in=existing_ids)
 
-        unused = Tag.objects.all()
-        if hasattr(unused, 'using'):
-            unused = unused.using(using)
-        unused = unused.exclude(id__in=existing_ids)
-
-        found = False
-        to_search = (self.content, self.title, self.description, self.keywords)
-        for tag in unused:
-            regex = re.compile(r'\b%s\b' % tag.name, re.I)
-            if any(regex.search(text) for text in to_search):
-                log.debug('Applying Tag "%s" (%s) to Article %s' % (tag, tag.pk, self.pk))
-                self.tags.add(tag)
-                found = True
+            for tag in unused:
+                regex = re.compile(r'\b%s\b' % tag.name, re.I)
+                if regex.search(self.content) or regex.search(self.title) or \
+                   regex.search(self.description) or regex.search(self.keywords):
+                    self.tags.add(tag)
+                    found = True
 
         return found
 
         for link in LINK_RE.finditer(self.rendered_content):
             url = link.group(1)
             log.debug('Do we have a title for "%s"?' % (url,))
-            key = 'href_title_' + sha1(url).hexdigest()
+            key = 'href_title_' + encodestring(url).strip()
 
             # look in the cache for the link target's title
             title = cache.get(key)

articles/templates/articles/_meta.html

 {% load i18n humanize %}
 
 <div id="article-meta">
-  <h4>Meta</h4>
+    <h4>Meta</h4>
 
-  <p><strong>{% trans 'Published' %}</strong>: {{ article.publish_date|naturalday }}</p>
+    <p><strong>{% trans 'Published' %}</strong>: {{ article.publish_date|naturalday }}</p>
 
-  <p><strong>{% trans 'Author' %}</strong>: <a href="{% url articles_by_author article.author.username %}" title="{% trans 'Read other articles by this author' %}">{{ article.author.get_name }}</a></p>
+    <p><strong>{% trans 'Author' %}</strong>: <a href="{% url articles_by_author article.author.username %}" title="{% trans 'Read other articles by this author' %}">{{ article.author.get_name }}</a></p>
 
-  <p><strong>{% trans 'Comments' %}</strong>: <a href="#disqus_thread">&nbsp;</a></p>
+    <p><strong>{% trans 'Comments' %}</strong>: <a href="#disqus_thread">&nbsp;</a></p>
 
-  <p><strong>{% trans 'Word Count' %}</strong>: {{ article.word_count|intcomma }}</p>
+    <p><strong>{% trans 'Word Count' %}</strong>: {{ article.word_count|intcomma }}</p>
 
-  {% if article.get_next_article %}
-  <p>
-    <strong>{% trans 'Next' %}</strong>:
-    <a href="{{ article.get_next_article.get_absolute_url }}">{{ article.get_next_article.title }}</a>
-  </p>
-  {% endif %}
+    {% if article.get_next_article %}
+    <p>
+        <strong>{% trans 'Next' %}</strong>:
+        <a href="{{ article.get_next_article.get_absolute_url }}">{{ article.get_next_article.title }}</a>
+    </p>
+    {% endif %}
 
-  {% if article.get_previous_article %}
-  <p>
-    <strong>{% trans 'Previous' %}</strong>:
-    <a href="{{ article.get_previous_article.get_absolute_url }}">{{ article.get_previous_article.title }}</a>
-  </p>
-  {% endif %}
+    {% if article.get_previous_article %}
+    <p>
+        <strong>{% trans 'Previous' %}</strong>:
+        <a href="{{ article.get_previous_article.get_absolute_url }}">{{ article.get_previous_article.title }}</a>
+    </p>
+    {% endif %}
 
-  {% if article.use_addthis_button and article.addthis_username %}
-  <!-- AddThis Button BEGIN -->
-  <div>
-    <script type="application/javascript">var addthis_pub="{{ article.addthis_username }}";</script>
-    <a href="http://www.addthis.com/bookmark.php?v=20" onmouseover="return addthis_open(this, '', '[URL]', '[TITLE]')" onmouseout="addthis_close()" onclick="return addthis_sendto()"><img src="http://s7.addthis.com/static/btn/lg-share-en.gif" width="125" height="16" alt="Bookmark and Share" style="border:0"/></a>
-    <script type="application/javascript" src="http://s7.addthis.com/js/200/addthis_widget.js"></script>
-  </div>
-  <!-- AddThis Button END -->
-  {% endif %}
+    {% if article.use_addthis_button and article.addthis_username %}
+    <!-- AddThis Button BEGIN -->
+    <div>
+        <script type="application/javascript">var addthis_pub="{{ article.addthis_username }}";</script>
+        <a href="http://www.addthis.com/bookmark.php?v=20" onmouseover="return addthis_open(this, '', '[URL]', '[TITLE]')" onmouseout="addthis_close()" onclick="return addthis_sendto()"><img src="http://s7.addthis.com/static/btn/lg-share-en.gif" width="125" height="16" alt="Bookmark and Share" style="border:0"/></a>
+        <script type="application/javascript" src="http://s7.addthis.com/js/200/addthis_widget.js"></script>
+    </div>
+    <!-- AddThis Button END -->
+    {% endif %}
 
-  <script type="application/javascript" src="http://tweetmeme.com/i/scripts/button.js"></script>
+    <script type="application/javascript" src="http://tweetmeme.com/i/scripts/button.js"></script>
 
-  <h4>{% trans 'Tags' %}</h4>
-  <p>{% if article.tags.count %}{% for tag in article.tags.all %}<a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a> {% endfor %}{% else %}None{% endif %}</p>
+    <h4>{% trans 'Tags' %}</h4>
+    <p>{% if article.tags.count %}{% for tag in article.tags.all %}<a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a> {% endfor %}{% else %}None{% endif %}</p>
 
-  {% for fu in article.followups.live %}
-  {% if forloop.first %}<h4 class="hasfollowup-header">{% trans 'Follow-Up Articles' %}</h4>
+    {% for fu in article.followups.live %}
+    {% if forloop.first %}<h4 class="hasfollowup-header">{% trans 'Follow-Up Articles' %}</h4>
 
-  <ul class="followups">{% endif %}
-    <li>
-      <a href="{{ fu.get_absolute_url }}" title="{% trans 'Read this follow-up article' %}">{{ fu.title }}</a>, {% trans 'posted' %} {{ fu.publish_date|naturalday }}
-    </li>
-  {% if forloop.last %}</ul>{% endif %}
-  {% endfor %}
+    <ul class="followups">{% endif %}
+        <li>
+            <a href="{{ fu.get_absolute_url }}" title="{% trans 'Read this follow-up article' %}">{{ fu.title }}</a>, {% trans 'posted' %} {{ fu.publish_date|naturalday }}
+        </li>
+    {% if forloop.last %}</ul>{% endif %}
+    {% endfor %}
 
-  {% for fu in article.followup_for.live %}
-  {% if forloop.first %}<h4 class="followup-header">{% trans 'Follows Up On' %}</h4>
+    {% for fu in article.followup_for.live %}
+    {% if forloop.first %}<h4 class="followup-header">{% trans 'Follows Up On' %}</h4>
 
-  <ul class="followups">{% endif %}
-    <li>
-      <a href="{{ fu.get_absolute_url }}" title="{% trans 'Read this article' %}">{{ fu.title }}</a>, {% trans 'posted' %} {{ fu.publish_date|naturalday }}
-    </li>
-  {% if forloop.last %}</ul>{% endif %}
-  {% endfor %}
+    <ul class="followups">{% endif %}
+        <li>
+            <a href="{{ fu.get_absolute_url }}" title="{% trans 'Read this article' %}">{{ fu.title }}</a>, {% trans 'posted' %} {{ fu.publish_date|naturalday }}
+        </li>
+    {% if forloop.last %}</ul>{% endif %}
+    {% endfor %}
 
-  {% for ra in article.related_articles.live %}
-  {% if forloop.first %}<h4 class="related-header">{% trans 'Related Articles' %}</h4>
+    {% for ra in article.related_articles.live %}
+    {% if forloop.first %}<h4 class="related-header">{% trans 'Related Articles' %}</h4>
 
-  <ul class="related-articles">{% endif %}
-    <li>
-      <a href="{{ ra.get_absolute_url }}" title="{% trans 'Read this related article' %}">{{ ra.title }}</a>, {% trans 'posted' %}  {{ ra.publish_date|naturalday }}
-    </li>
-  {% if forloop.last %}</ul>{% endif %}
-  {% endfor %}
+    <ul class="related-articles">{% endif %}
+        <li>
+            <a href="{{ ra.get_absolute_url }}" title="{% trans 'Read this related article' %}">{{ ra.title }}</a>, {% trans 'posted' %}  {{ ra.publish_date|naturalday }}
+        </li>
+    {% if forloop.last %}</ul>{% endif %}
+    {% endfor %}
 
-  {% for url,title in article.links %}
-  {% if forloop.first %}<h4>{% trans 'Article Links' %}</h4>
-  <ol>{% endif %}
-    <li><a href="{{ url }}" title="{{ title }}">{{ title|safe|truncatewords:10 }}</a></li>
-  {% if forloop.last %}</ol>{% endif %}
-  {% endfor %}
+    {% for url,title in article.links %}
+    {% if forloop.first %}<h4>{% trans 'Article Links' %}</h4>
+    <ol>{% endif %}
+        <li><a href="{{ url }}" title="{{ title }}">{{ title|safe|truncatewords:10 }}</a></li>
+    {% if forloop.last %}</ol>{% endif %}
+    {% endfor %}
 </div>
 <div class="clear"></div>
 

articles/templates/articles/_tag_cloud.html

 <div id="articles-tag-cloud">
-  {% for tag in tags %}
-  <a href="{{ tag.get_absolute_url }}" class="tag-cloud-{{ tag.weight }}">{{ tag.name }}</a>
-  {% endfor %}
+    {% for tag in tags %}
+    <a href="{{ tag.get_absolute_url }}" class="tag-cloud-{{ tag.weight }}">{{ tag.name }}</a>
+    {% endfor %}
 </div>

articles/templates/articles/article_detail.html

 {% block extra-head %}
 {{ block.super }}
 {% for tag in article.tags.all %}
-<link rel="alternate" type="application/rss+xml" title="Blog Articles Tagged '{{ tag.name }}' RSS Feed" href="{% url articles_rss_feed_tag tag.rss_name %}" />
-<link rel="alternate" type="application/atom+xml" title="Blog Articles Tagged '{{ tag.name }}' Atom Feed" href="{% url articles_atom_feed_tag tag.rss_name %}" />{% endfor %}
+<link rel="alternate" type="application/rss+xml" title="Blog Articles Tagged '{{ tag.name }}' RSS Feed" href="{% url articles_feed tag.rss_name %}" />{% endfor %}
 {% endblock %}
 
 {% block content %}

articles/templates/articles/base.html

 
 {% block extra-head %}
 {{ block.super }}
-<link rel="alternate" type="application/rss+xml" title="Latest Blog Articles RSS Feed" href="{% url articles_rss_feed_latest %}" />
-<link rel="alternate" type="application/atom+xml" title="Latest Blog Articles Atom Feed" href="{% url articles_atom_feed_latest %}" />
+<link rel="alternate" type="application/rss+xml" title="Latest Blog Articles RSS Feed" href="{% url articles_feed 'latest' %}" />
 {% endblock %}
 
 {% block content %}

articles/templates/articles/display_tag.html

 {% block title %}{% trans 'Articles Tagged' %}: {{ tag.name }}{% endblock %}
 {% block extra-head %}
 {{ block.super }}
-<link rel="alternate" type="application/rss+xml" title="Blog Articles Tagged '{{ tag.name }}' RSS Feed" href="{% url articles_rss_feed_tag tag.rss_name %}" />
-<link rel="alternate" type="application/atom+xml" title="Blog Articles Tagged '{{ tag.name }}' Atom Feed" href="{% url articles_atom_feed_tag tag.rss_name %}" />
+<link rel="alternate" type="application/rss+xml" title="Blog Articles Tagged '{{ tag.name }}' RSS Feed" href="{% url articles_feed tag.rss_name %}" />
 {% endblock %}
 
 {% block articles-content %}

articles/templates/articles/uncategorized_article_list.html

 {% for article in page_obj.object_list %}
 {% include 'articles/_articles.html' %}
 {% endfor %}
-{% endblock %}
+{% endblock %}

articles/templatetags/article_tags.py

         try:
             # determine what view we are using based upon the path of this page
             view, args, kwargs = resolve(context['request'].path)
-        except (Resolver404, KeyError):
+        except (Resolver404, KeyError) as err:
             raise ValueError('Invalid pagination page.')
         else:
             # set the page parameter for this view
 from django.conf.urls.defaults import *
+from django.contrib.syndication.views import feed
 
 from articles import views
 from articles.feeds import TagFeed, LatestEntries, TagFeedAtom, LatestEntriesAtom
 
-tag_rss = TagFeed()
-latest_rss = LatestEntries()
-tag_atom = TagFeedAtom()
-latest_atom = LatestEntriesAtom()
+feeds = {
+  'latest': LatestEntries,
+  'tags': TagFeed
+}
+feed_dict = {'feed_dict': feeds}
+
+atom_feeds = {
+    'latest': LatestEntriesAtom,
+    'tags': TagFeedAtom,
+}
+atom_dict = {'feed_dict': atom_feeds}
 
 urlpatterns = patterns('',
     (r'^(?P<year>\d{4})/(?P<month>.{3})/(?P<day>\d{1,2})/(?P<slug>.*)/$', views.redirect_to_article),
     url(r'^ajax/tag/autocomplete/$', views.ajax_tag_autocomplete, name='articles_tag_autocomplete'),
 
     # RSS
-    url(r'^feeds/latest\.rss$', latest_rss, name='articles_rss_feed_latest'),
-    url(r'^feeds/latest/$', latest_rss),
-    url(r'^feeds/tag/(?P<slug>[\w_-]+)\.rss$', tag_rss, name='articles_rss_feed_tag'),
-    url(r'^feeds/tag/(?P<slug>[\w_-]+)/$', tag_rss),
-
-    # Atom
-    url(r'^feeds/atom/latest\.xml$', latest_atom, name='articles_atom_feed_latest'),
-    url(r'^feeds/atom/tag/(?P<slug>[\w_-]+)\.xml$', tag_atom, name='articles_atom_feed_tag'),
+    url(r'^feeds/atom/(?P<url>.*)\.xml$', feed, atom_dict, name='articles_feed_atom'),
+    (r'^feeds/(?P<url>.*)/$', feed, feed_dict),
+    url(r'^feeds/(?P<url>.*)\.rss$', feed, feed_dict, name='articles_feed'),
 
 )

articles/views.py

-import logging
-
 from django.conf import settings
 from django.contrib.auth.models import User
 from django.core.cache import cache
 
 ARTICLE_PAGINATION = getattr(settings, 'ARTICLE_PAGINATION', 20)
 
-log = logging.getLogger('articles.views')
-
 def display_blog_page(request, tag=None, username=None, year=None, month=None, page=1):
     """
     Handles all of the magic behind the pages that list articles in any way.