1. jgsogo
  2. django-articles

Commits

jgsogo  committed dece7a2 Draft Merge

merge django 1.5, preparing for release 2.5

  • Participants
  • Parent commits 3783513, 926af7f
  • Branches default
  • Tags 2.5b0

Comments (0)

Files changed (23)

File .hgignore

View file
  • Ignore whitespace
 dist/
 django_articles.egg-info
 MANIFEST
-
+*.db

File README.rst

View file
  • Ignore whitespace
 Features
 ========
 
-* Tags for articles, with a tag cloud template tag
+* Tags for articles (django-taggit if available, or built-in ones), with a tag cloud template tag
 * Auto-completion for tags in the Django admin
 * Auto-tagging: assigning existing tags to articles when they're present in the
   article content
 
 .. note:: New in 2.1.0
 
+You can install `Taggit <https://github.com/alex/django-taggit>`_ for tags
+(recomended), otherwise it will use bult-in feature.
+
+.. note:: New for 2.5
+
 Installation
 ============
 
 
     hg clone http://bitbucket.org/codekoala/django-articles/
     hg clone http://django-articles.googlecode.com/hg/ django-articles
+    hg clone https://bitbucket.org/jgsogo/django-articles (taggit feature available)
 
 Checkout from GitHub
 --------------------
 Use the following command::
 
     git clone http://github.com/codekoala/django-articles.git
+    git clone https://github.com/jgsogo/django-articles (taggit feature available)
 
 The CheeseShop
 --------------
 
     pip install django-articles
     easy_install django-articles
+    (taggit feature not available)
 
 Configuration
 =============
         ...
         'articles',
         'south',
+        'taggit',
         ...
     )
 

File articles/__init__.py

View file
  • Ignore whitespace
-__version__ = '2.4.2'
+VERSION = (2, 5, 0, "b", 0) # PEP 386
+DEV_N = None
+
+def get_version():
+    version = "%s.%s" % (VERSION[0], VERSION[1])
+    if VERSION[2]:
+        version = "%s.%s" % (version, VERSION[2])
+    if VERSION[3] != "f":
+        version = "%s%s%s" % (version, VERSION[3], VERSION[4])
+        if DEV_N:
+            version = "%s.dev%s" % (version, DEV_N)
+    return version
+
+__version__ = get_version()
+
 
 from articles.directives import *
 try:

File articles/admin.py

View file
  • Ignore whitespace
 
 from django.contrib import admin
 from django.contrib.auth.models import User
+from django.conf import settings
 from django.utils.translation import ugettext_lazy as _
-from forms import ArticleAdminForm
-from models import Tag, Article, ArticleStatus, Attachment
+from articles.forms import ArticleAdminForm
+from articles.models import Article, ArticleStatus, Attachment, Tag
 
 log = logging.getLogger('articles.admin')
 
+USE_TAGGIT = 'taggit' in settings.INSTALLED_APPS
+
 class TagAdmin(admin.ModelAdmin):
     list_display = ('name', 'article_count')
 
         }),
     )
 
-    filter_horizontal = ('tags', 'followup_for', 'related_articles')
+    if USE_TAGGIT:
+        filter_horizontal = ('followup_for', 'related_articles')
+    else:
+        filter_horizontal = ('tags', 'followup_for', 'related_articles')
     prepopulated_fields = {'slug': ('title',)}
 
     def tag_count(self, obj):
         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():
         for tag in Tag.objects.all():
             name = 'apply_tag_%s' % tag.pk
             actions[name] = (dynamic_tag(name, tag), name, _('Apply Tag: %s' % (tag.slug,)))
-
+        
         return actions
 
     actions = [mark_active, mark_inactive]
 
     def save_model(self, request, obj, form, change):
         """Set the article's author based on the logged in user and make sure at least one site is selected"""
-
         try:
             author = obj.author
         except User.DoesNotExist:
         else:
             return self.model._default_manager.filter(author=request.user)
 
-admin.site.register(Tag, TagAdmin)
+if not USE_TAGGIT:
+    admin.site.register(Tag, TagAdmin)
 admin.site.register(Article, ArticleAdmin)
 admin.site.register(ArticleStatus, ArticleStatusAdmin)
 

File articles/feeds.py

View file
  • Ignore whitespace
         articles = cache.get(key)
 
         if articles is None:
-            articles = list(obj.article_set.live().order_by('-publish_date'))
+            articles = list(Article.objects.filter(tags__slug__in=[obj.slug]).distinct().order_by('-publish_date'))
             cache.set(key, articles, FEED_TIMEOUT)
 
         return articles

File articles/forms.py

View file
  • Ignore whitespace
 import logging
 
 from django import forms
+from django.conf import settings
 from django.utils.translation import ugettext_lazy as _
-from models import Article, Tag
+from articles.models import Article, Tag
 
 log = logging.getLogger('articles.forms')
 
+USE_TAGGIT = 'taggit' in settings.INSTALLED_APPS
+
 def tag(name):
     """Returns a Tag object for the given name"""
 
     return t
 
 class ArticleAdminForm(forms.ModelForm):
-    tags = forms.CharField(initial='', required=False,
-                           widget=forms.TextInput(attrs={'size': 100}),
-                           help_text=_('Words that describe this article'))
+    if not USE_TAGGIT:
+        tags = forms.CharField(initial='', required=False,
+                               widget=forms.TextInput(attrs={'size': 100}),
+                               help_text=_('Words that describe this article'))
 
-    def __init__(self, *args, **kwargs):
-        """Sets the list of tags to be a string"""
-
-        instance = kwargs.get('instance', None)
-        if instance:
-            init = kwargs.get('initial', {})
-            init['tags'] = ' '.join([t.name for t in instance.tags.all()])
-            kwargs['initial'] = init
-
-        super(ArticleAdminForm, self).__init__(*args, **kwargs)
-
-    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))
-        self.cleaned_data['tags'] = tags
-        return self.cleaned_data['tags']
+        def __init__(self, *args, **kwargs):
+            """Sets the list of tags to be a string"""
+        
+            instance = kwargs.get('instance', None)
+            if instance:
+                init = kwargs.get('initial', {})
+                init['tags'] = ', '.join([t.name for t in instance.tags.all()])
+                kwargs['initial'] = init
+        
+            super(ArticleAdminForm, self).__init__(*args, **kwargs)
+        
+        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))
+            self.cleaned_data['tags'] = tags
+            return self.cleaned_data['tags']
 
     def save(self, *args, **kwargs):
         """Remove any old tags that may have been set that we no longer need"""

File articles/listeners.py

View file
  • Ignore whitespace
 from django.db.models import signals, Q
 
 from decorators import logtime
-from models import Article, Tag
+from articles.models import Article, Tag
 
 log = logging.getLogger('articles.listeners')
 

File articles/management/commands/convert_categories_to_tags.py

View file
  • Ignore whitespace
 from django.core.management.base import NoArgsCommand
 from articles.models import Article, Tag
 
-class Command(NoArgsCommand):
+class Command(NoArgsCommand):    
     help = """Converts our old categories into tags"""
 
     def handle_noargs(self, **opts):

File articles/migrations/0006_taggit_app_for_tags.py

View file
  • Ignore whitespace
+# -*- coding: 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('articles_tag')
+
+        # Removing M2M table for field tags on 'Article'
+        db.delete_table('articles_article_tags')
+
+    def backwards(self, orm):
+        # Adding model 'Tag'
+        db.create_table('articles_tag', (
+            ('slug', self.gf('django.db.models.fields.CharField')(unique=True, max_length=64, null=True, blank=True)),
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=64, unique=True)),
+        ))
+        db.send_create_signal('articles', ['Tag'])
+
+        # Adding M2M table for field tags on 'Article'
+        db.create_table('articles_article_tags', (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('article', models.ForeignKey(orm['articles.article'], null=False)),
+            ('tag', models.ForeignKey(orm['articles.tag'], null=False))
+        ))
+        db.create_unique('articles_article_tags', ['article_id', 'tag_id'])
+
+    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'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['articles.ArticleStatus']"}),
+            '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'})
+        },
+        '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'})
+        },
+        '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'})
+        },
+        '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 = ['articles']

File articles/models.py

View file
  • Ignore whitespace
 from hashlib import sha1
-from datetime import datetime
 import logging
 import mimetypes
 import re
 import urllib
+import htmlentitydefs
+from datetime import datetime
 
 from django.db import models
 from django.db.models import Q
 from django.template.defaultfilters import slugify, striptags
 from django.utils.translation import ugettext_lazy as _
 from django.utils.text import truncate_html_words
+from django.utils.timezone import now
 
-from decorators import logtime, once_per_instance
+from articles.decorators import logtime, once_per_instance
+
+USE_TAGGIT = 'taggit' in settings.INSTALLED_APPS
+if USE_TAGGIT:
+    from taggit.managers import TaggableManager
+    from taggit.models import Tag
+
 
 WORD_LIMIT = getattr(settings, 'ARTICLES_TEASER_LIMIT', 75)
 AUTO_TAG = getattr(settings, 'ARTICLES_AUTO_TAG', True)
 DEFAULT_ADDTHIS_USER = getattr(settings, 'DEFAULT_ADDTHIS_USER', None)
 
 # regex used to find links in an article
-LINK_RE = re.compile('<a.*?href="(.*?)".*?>(.*?)</a>', re.I|re.M)
-TITLE_RE = re.compile('<title.*?>(.*?)</title>', re.I|re.M)
+LINK_RE = re.compile(ur'<a.*?href="(.*?)".*?>(.*?)</a>', re.I|re.M)
+TITLE_RE = re.compile(ur'<title.*?>(.*?)</title>', re.I|re.M)
 TAG_RE = re.compile('[^a-z0-9\-_\+\:\.]?', re.I)
 
 log = logging.getLogger('articles.models')
 
+def unescape(text):
+    """
+    This function converts HTML entities and character references to ordinary characters.
+    \sa http://effbot.org/zone/re-sub.htm#unescape-html
+    ##
+    # Removes HTML or XML character references and entities from a text string.
+    #
+    # @param text The HTML (or XML) source text.
+    # @return The plain text, as a Unicode string, if necessary.
+    """
+    def fixup(m):
+        text = m.group(0)
+        if text[:2] == "&#":
+            # character reference
+            try:
+                if text[:3] == "&#x":
+                    return unichr(int(text[3:-1], 16))
+                else:
+                    return unichr(int(text[2:-1]))
+            except ValueError:
+                pass
+        else:
+            # named entity
+            try:
+                text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
+            except KeyError:
+                pass
+        return text # leave as is
+    return re.sub("&#?\w+;", fixup, text)
+
 def get_name(user):
     """
     Provides a way to fall back to a user's username if their full name has not
     return name
 User.get_name = get_name
 
-class Tag(models.Model):
-    name = models.CharField(max_length=64, unique=True)
-    slug = models.CharField(max_length=64, unique=True, null=True, blank=True)
+if USE_TAGGIT:
+    """ Adding some functions to taggit's Tag model to reduce modifications in django-articles """
+    if not getattr(Tag, 'rss_name', None):
+        log.debug('Adding rss_name method to model Tag')
+        @property
+        def rss_name(self):
+            return self.cleaned
+        Tag.rss_name = rss_name
+    if not getattr(Tag, 'cleaned', None):
+        log.debug('Adding cleaned method to model Tag')
+        @property
+        def cleaned(self):
+            return self.slug or Tag.clean_tag(self_name)
+        Tag.cleaned = cleaned
+    if not getattr(Tag, 'get_absolute_url', None):
+        log.debug('Adding get_absolute_url method to model Tag')
+        @models.permalink
+        def get_absolute_url(self):
+            log.debug('Tag::get_absolute_url for %s' % self)
+            return ('articles_display_tag', (self.cleaned,))
+        Tag.get_absolute_url = get_absolute_url
 
-    def __unicode__(self):
-        return self.name
+else:
+    class Tag(models.Model):
+        name = models.CharField(max_length=64, unique=True)
+        slug = models.CharField(max_length=64, unique=True, null=True, blank=True)
 
-    @staticmethod
-    def clean_tag(name):
-        """Replace spaces with dashes, in case someone adds such a tag manually"""
+        def __unicode__(self):
+            return self.name
 
-        name = name.replace(' ', '-').encode('ascii', 'ignore')
-        name = TAG_RE.sub('', name)
-        clean = name.lower().strip(", ")
+        @staticmethod
+        def clean_tag(name):
+            """Replace spaces with dashes, in case someone adds such a tag manually"""
 
-        log.debug('Cleaned tag "%s" to "%s"' % (name, clean))
-        return clean
+            name = name.replace(' ', '-').encode('ascii', 'ignore')
+            name = TAG_RE.sub('', name)
+            clean = name.lower().strip(", ")
 
-    def save(self, *args, **kwargs):
-        """Cleans up any characters I don't want in a URL"""
+            log.debug('Cleaned tag "%s" to "%s"' % (name, clean))
+            return clean
 
-        log.debug('Ensuring that tag "%s" has a slug' % (self,))
-        self.slug = Tag.clean_tag(self.name)
-        super(Tag, self).save(*args, **kwargs)
+        def save(self, *args, **kwargs):
+            """Cleans up any characters I don't want in a URL"""
 
-    @models.permalink
-    def get_absolute_url(self):
-        return ('articles_display_tag', (self.cleaned,))
+            log.debug('Ensuring that tag "%s" has a slug' % (self,))
+            self.slug = Tag.clean_tag(self.name)
+            super(Tag, self).save(*args, **kwargs)
 
-    @property
-    def cleaned(self):
-        """Returns the clean version of the tag"""
+        @models.permalink
+        def get_absolute_url(self):
+            return ('articles_display_tag', (self.cleaned,))
 
-        return self.slug or Tag.clean_tag(self.name)
+        @property
+        def cleaned(self):
+            """Returns the clean version of the tag"""
 
-    @property
-    def rss_name(self):
-        return self.cleaned
+            return self.slug or Tag.clean_tag(self.name)
 
-    class Meta:
-        ordering = ('name',)
+        @property
+        def rss_name(self):
+            return self.cleaned
+
+        class Meta:
+            ordering = ('name',)
 
 class ArticleStatusManager(models.Manager):
 
         Retrieves all active articles which have been published and have not
         yet expired.
         """
-        now = datetime.now()
         return self.get_query_set().filter(
                 Q(expiration_date__isnull=True) |
-                Q(expiration_date__gte=now),
+                Q(expiration_date__gte=now()),
                 publish_date__lte=now,
                 is_active=True)
 
     content = models.TextField()
     rendered_content = models.TextField()
 
-    tags = models.ManyToManyField(Tag, help_text=_('Tags that describe this article'), blank=True)
+    if USE_TAGGIT:
+        tags = TaggableManager(blank=True)
+    else:
+        tags = models.ManyToManyField(Tag, help_text=_('Tags that describe this article'), blank=True)
+
     auto_tag = models.BooleanField(default=AUTO_TAG, blank=True, help_text=_('Check this if you want to automatically assign any existing tags to this article based on its content.'))
     followup_for = models.ManyToManyField('self', symmetrical=False, blank=True, help_text=_('Select any other articles that this article follows up on.'), related_name='followups')
     related_articles = models.ManyToManyField('self', blank=True)
 
-    publish_date = models.DateTimeField(default=datetime.now, help_text=_('The date and time this article shall appear online.'))
+    publish_date = models.DateTimeField(default=now, help_text=_('The date and time this article shall appear online.'))
     expiration_date = models.DateTimeField(blank=True, null=True, help_text=_('Leave blank if the article does not expire.'))
 
     is_active = models.BooleanField(default=True, blank=True)
 
         if self.id:
             # mark the article as inactive if it's expired and still active
-            if self.expiration_date and self.expiration_date <= datetime.now() and self.is_active:
+            if self.expiration_date and self.expiration_date <= now() and self.is_active:
                 self.is_active = False
                 self.save()
 
     def __unicode__(self):
         return self.title
 
+    def is_online(self):
+        return Article.objects.live().filter(pk=self.pk).exists()
+    online = property(is_online)
+
     def save(self, *args, **kwargs):
         """Renders the article using the appropriate markup language."""
 
         log.debug('Locating links in article: %s' % (self,))
         for link in LINK_RE.finditer(self.rendered_content):
             url = link.group(1)
+            url = unescape(url)
             log.debug('Do we have a title for "%s"?' % (url,))
             key = 'href_title_' + sha1(url).hexdigest()
 
                         # open the URL
                         c = urllib.urlopen(url)
                         html = c.read()
+                        # html can be on any language, check encoding
+                        encoding=c.headers['content-type'].split('charset=')[-1]
+                        html = unicode(html, encoding)
                         c.close()
 
                         # try to determine the title of the target
                         log.warn('Failed to retrieve the title for "%s"; using link text "%s"' % (url, title))
 
                 # cache the page title for a week
-                log.debug('Using "%s" as title for "%s"' % (title, url))
+                log.debug(u'Using "%s" as title for "%s"' % (title, url))
                 cache.set(key, title, 604800)
 
             # add it to the list of links and titles
         get_latest_by = 'publish_date'
 
 class Attachment(models.Model):
-    upload_to = lambda inst, fn: 'attach/%s/%s/%s' % (datetime.now().year, inst.article.slug, fn)
+    upload_to = lambda inst, fn: 'attach/%s/%s/%s' % (now().year, inst.article.slug, fn)
 
     article = models.ForeignKey(Article, related_name='attachments')
     attachment = models.FileField(upload_to=upload_to)

File articles/static/articles/js/tag_autocomplete.js

View file
  • Ignore whitespace
 $(document).ready(function () {
     $('#id_tags').autocomplete(
-        '/blog/ajax/tag/autocomplete/', // if your prefix for articles differs, fix this
+        '/ajax/tag/autocomplete/', // if your prefix for articles differs, fix this
         {multiple: true, multipleSeparator: ' '}
     );
 });

File articles/templates/articles/_articles.html

View file
  • Ignore whitespace
         <h3><a href="{{ article.get_absolute_url }}" title="{% trans 'Read this article' %}">{{ article.title }}</a></h3>
         <div class="quiet">
             {% trans 'Posted on' %} {{ article.publish_date|date:"F jS, Y" }}
-            {% trans 'by' %} <a href="{% url articles_by_author article.author.username %}" title="{% trans 'View articles posted by' %} {{ article.author.get_name }}">{{ article.author.get_name }}</a>
+            {% trans 'by' %} <a href="{% url 'articles_by_author' article.author.username %}" title="{% trans 'View articles posted by' %} {{ article.author.get_name }}">{{ article.author.get_name }}</a>
         </div>
     </li>
-{% if forloop.last %}</ol>{% endif %}
+{% if forloop.last %}</ol>{% endif %}

File articles/templates/articles/_comments.html

View file
  • Ignore whitespace
 {% if disqus_forum %}
 <div id="disqus_thread"></div>
 <script type="application/javascript">
-var disqus_identifier = {{ article.id }};
+var disqus_identifier = 'article_{{ article.id }}';
 </script>
 <script type="application/javascript" src="http://disqus.com/forums/{{ disqus_forum }}/embed.js"></script>
 <noscript><a href="http://disqus.com/forums/{{ disqus_forum }}/?url=ref">View the discussion thread.</a></noscript>

File articles/templates/articles/_meta.html

View file
  • Ignore whitespace
 
   <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>
 

File articles/templates/articles/article_detail.html

View file
  • Ignore whitespace
 {% 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_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 %}
 {% endblock %}
 
 {% block content %}

File articles/templates/articles/base.html

View file
  • Ignore whitespace
 
 {% 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_rss_feed_latest' %}" />
+<link rel="alternate" type="application/atom+xml" title="Latest Blog Articles Atom Feed" href="{% url 'articles_atom_feed_latest' %}" />
 {% endblock %}
 
 {% block content %}
             <strong>{{ year.0 }}</strong>
             <ul class="months">
             {% for month in year.1 %}
-                <li><a href="{% url articles_in_month month.year,month.month %}" title="{% trans 'View articles posted in this month' %}">{{ month|date:"N" }}</a></li>
+                <li><a href="{% url 'articles_in_month' month.0.year month.0.month %}" title="{% trans 'View articles posted in this month' %}">{{ month.0|date:"N" }}</a></li>
             {% endfor %}
             </ul>
             <div class="clear">&nbsp;</div>

File articles/templates/articles/display_tag.html

View file
  • Ignore whitespace
 {% 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_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 %}" />
 {% endblock %}
 
 {% block articles-content %}

File articles/templatetags/article_tags.py

View file
  • Ignore whitespace
+import logging
+
 from django import template
 from django.core.cache import cache
 from django.core.urlresolvers import resolve, reverse, Resolver404
                     archives[pub.year] = {}
 
                 # make sure we know that we have an article posted in this month/year
-                archives[pub.year][pub.month] = True
+                if pub.month in archives[pub.year]:
+                    archives[pub.year][pub.month] += 1
+                else:
+                    archives[pub.year][pub.month] = 1
+                #archives[pub.year][pub.month] = True
 
             dt_archives = []
 
                 m.sort()
 
                 # now create a list of datetime objects for each month/year
-                months = [datetime(year, month, 1) for month in m]
+                months = [(datetime(year, month, 1), archives[year][month]) for month in m]
 
                 # append this list to our final collection
                 dt_archives.append( ( year, tuple(months) ) )
             # go no further
             return {}
 
-        min_count = max_count = tags[0].article_set.count()
+        min_count = max_count = Article.objects.filter(tags__slug__in=[tags[0]]).count()
         for tag in tags:
             if tag.count < min_count:
                 min_count = tag.count

File articles/tests.py

View file
  • Ignore whitespace
 from django.test import TestCase
 from django.test.client import Client
 
-from models import Article, ArticleStatus, Tag, get_name, MARKUP_HTML, MARKUP_MARKDOWN, MARKUP_REST, MARKUP_TEXTILE
+from articles.models import Article, ArticleStatus, Tag, get_name, MARKUP_HTML, MARKUP_MARKDOWN, MARKUP_REST, MARKUP_TEXTILE
 
 class ArticleUtilMixin(object):
 

File articles/views.py

View file
  • Ignore whitespace
             # for backwards-compatibility
             tag = get_object_or_404(Tag, name__iexact=tag)
 
-        articles = tag.article_set.live(user=request.user).select_related()
+        articles = Article.objects.live(user=request.user).filter(tags__slug__in=[tag.slug]).distinct().select_related()
         template = 'articles/display_tag.html'
         context['tag'] = tag
 

File requirements.txt

View file
  • Ignore whitespace
 django
 docutils
 south
+taggit

File sample/articles_demo/demo.db

  • Ignore whitespace
Binary file modified.

File sample/articles_demo/settings.py

View file
  • Ignore whitespace
 
     'articles',
     'south',
-
+    'taggit',
     'django_coverage',
 )