diff --git a/.gitignore b/.gitignore index 7e99e36..7f987b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -*.pyc \ No newline at end of file +*.pyc +build/ +dist/ +django_articles.*/ diff --git a/articles/__init__.py b/articles/__init__.py index bece0f0..0652866 100644 --- a/articles/__init__.py +++ b/articles/__init__.py @@ -1,9 +1,3 @@ __version__ = '2.4.2' -from articles.directives import * -try: - import listeners -except ImportError: - # this happens when setup.py is grabbing __version__; nothing to worry - # about - pass +from .directives import * diff --git a/articles/admin.py b/articles/admin.py index 751dcbb..372b142 100644 --- a/articles/admin.py +++ b/articles/admin.py @@ -3,8 +3,8 @@ 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 +from .forms import ArticleAdminForm +from .models import Tag, Article, ArticleStatus, Attachment log = logging.getLogger('articles.admin') diff --git a/articles/directives.py b/articles/directives.py index bdecbea..696fc5e 100644 --- a/articles/directives.py +++ b/articles/directives.py @@ -34,6 +34,8 @@ :license: BSD, see LICENSE for more details. """ +from __future__ import unicode_literals + # Options # ~~~~~~~ @@ -66,7 +68,7 @@ def pygments_directive(name, arguments, options, content, lineno, lexer = TextLexer() # take an arbitrary option if more than one is given formatter = options and VARIANTS[options.keys()[0]] or DEFAULT - parsed = highlight(u'\n'.join(content), lexer, formatter) + parsed = highlight('\n'.join(content), lexer, formatter) parsed = '
%s
' % parsed return [nodes.raw('', parsed, format='html')] diff --git a/articles/feeds.py b/articles/feeds.py index cde8c33..c4c4bcf 100644 --- a/articles/feeds.py +++ b/articles/feeds.py @@ -5,7 +5,7 @@ from django.core.urlresolvers import reverse from django.utils.feedgenerator import Atom1Feed -from articles.models import Article, Tag +from .models import Article, Tag # default to 24 hours for feed caching FEED_TIMEOUT = getattr(settings, 'ARTICLE_FEED_TIMEOUT', 86400) diff --git a/articles/forms.py b/articles/forms.py index 8be2d70..dde1228 100644 --- a/articles/forms.py +++ b/articles/forms.py @@ -2,7 +2,7 @@ from django import forms from django.utils.translation import ugettext_lazy as _ -from models import Article, Tag +from .models import Article, Tag log = logging.getLogger('articles.forms') diff --git a/articles/listeners.py b/articles/listeners.py index 362c23c..ca954ba 100644 --- a/articles/listeners.py +++ b/articles/listeners.py @@ -2,8 +2,8 @@ from django.db.models import signals, Q -from decorators import logtime -from models import Article, Tag +from .decorators import logtime +from .models import Article, Tag log = logging.getLogger('articles.listeners') diff --git a/articles/management/commands/check_for_articles_from_email.py b/articles/management/commands/check_for_articles_from_email.py index 9761fa7..aee42f2 100644 --- a/articles/management/commands/check_for_articles_from_email.py +++ b/articles/management/commands/check_for_articles_from_email.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from base64 import b64decode from datetime import datetime from email.parser import FeedParser @@ -112,7 +114,7 @@ def connect(self): M.login(self.username, self.password) M.select() - except socket.error, err: + except socket.error as err: raise else: return M @@ -165,7 +167,7 @@ def connect(self): M.user(self.username) M.pass_(self.password) - except socket.error, err: + except socket.error as err: raise else: return M @@ -208,7 +210,7 @@ class Command(BaseCommand): def log(self, message, level=2): if self.verbosity >= level: - print message + print(message) def handle(self, *args, **options): """Main entry point for the command""" @@ -317,7 +319,7 @@ def create_articles(self, emails): try: # try to grab the timestamp from the email message publish_date = datetime.fromtimestamp(time.mktime(parsedate(email['Date']))) - except StandardError, err: + except StandardError as err: self.log("An error occurred when I tried to convert the email's timestamp into a datetime object: %s" % (err,)) publish_date = datetime.now() @@ -334,7 +336,7 @@ def create_articles(self, emails): try: article.save() self.log('Article created.') - except StandardError, err: + except StandardError as err: # log it and move on to the next message self.log('Error creating article: %s' % (err,), 0) continue @@ -355,7 +357,7 @@ def create_articles(self, emails): if ack: # notify the user when the article is posted - subject = u'%s: %s' % (_("Article Posted"), title) + subject = '%s: %s' % (_("Article Posted"), title) message = _("""Your email (%(title)s) has been posted as an article on %(site_name)s. http://%(domain)s%(article_url)s""") % { diff --git a/articles/management/commands/convert_comments_to_disqus.py b/articles/management/commands/convert_comments_to_disqus.py index f7818df..a60c0d4 100644 --- a/articles/management/commands/convert_comments_to_disqus.py +++ b/articles/management/commands/convert_comments_to_disqus.py @@ -20,7 +20,7 @@ class Command(NoArgsCommand): def handle_noargs(self, **opts): if hasattr(settings, 'DISQUS_USER_API_KEY'): self.forum_id = self.determine_forum() - #print self.get_value_from_api('get_thread_list', {'forum_id': self.forum_id, 'limit': 1000}) + #print(self.get_value_from_api('get_thread_list', {'forum_id': self.forum_id, 'limit': 1000})) self.import_comments(self.forum_id) else: sys.exit('Please specify your DISQUS_USER_API_KEY in settings.py') @@ -47,8 +47,8 @@ def get_value_from_api(self, url, args={}, method='GET'): url = 'http://disqus.com/api/%s/%s' % (url, additional) try: handle = urllib2.urlopen(url, data) - except urllib2.HTTPError, err: - print 'Failed to %s %s with args %s' % (method, url, args) + except urllib2.HTTPError as err: + print('Failed to %s %s with args %s' % (method, url, args)) return None else: json_obj = json.loads(handle.read())['message'] @@ -68,12 +68,12 @@ def determine_forum(self): forum_id = None while forum_id not in possible_ids: if forum_id is not None: - print 'Invalid forum ID. Please try again.' + print('Invalid forum ID. Please try again.') - print 'You have the following forums on Disqus:\n' + print('You have the following forums on Disqus:\n') for forum in forums: - print '\t%s. %s' % (forum['id'], forum['name']) + print('\t%s. %s' % (forum['id'], forum['name'])) forum_id = raw_input('\nInto which forum do you want to import your existing comments? ') @@ -86,7 +86,7 @@ def forum_api_key(self): return self._forum_api_key[self.forum_id] def import_comments(self, forum_id): - print 'Importing into forum %s' % self.forum_id + print('Importing into forum %s' % self.forum_id) article_ct = ContentType.objects.get_for_model(Article) for comment in Comment.objects.filter(content_type=article_ct): @@ -102,7 +102,7 @@ def import_comments(self, forum_id): 'title': article.title, 'url': 'http://%s%s' % (Site.objects.get_current().domain, article.get_absolute_url()), }, method='POST') - print 'Created new thread for %s' % article.title + print('Created new thread for %s' % article.title) # create the comment on disqus comment_obj = self.get_value_from_api('create_post', { @@ -117,7 +117,9 @@ def import_comments(self, forum_id): 'state': self.get_state(comment) }, method='POST') - print 'Imported comment for %s by %s on %s' % (article, comment.user_name, comment.submit_date) + print('Imported comment for %s by %s on %s' % ( + article, comment.user_name, comment.submit_date) + ) def get_state(self, comment): """Determines a comment's state on Disqus based on its properties in Django""" diff --git a/articles/models.py b/articles/models.py index 94ea88d..a8e3028 100644 --- a/articles/models.py +++ b/articles/models.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from hashlib import sha1 from datetime import datetime import logging @@ -13,10 +15,11 @@ from django.core.cache import cache from django.conf import settings from django.template.defaultfilters import slugify, striptags +from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django.utils.text import truncate_html_words -from decorators import logtime, once_per_instance +from .decorators import logtime, once_per_instance WORD_LIMIT = getattr(settings, 'ARTICLES_TEASER_LIMIT', 75) AUTO_TAG = getattr(settings, 'ARTICLES_AUTO_TAG', True) @@ -72,11 +75,12 @@ def get_name(user): return name User.get_name = get_name +@python_2_unicode_compatible class Tag(models.Model): name = models.CharField(max_length=64, unique=True) slug = models.CharField(max_length=64, unique=True, null=True, blank=True) - def __unicode__(self): + def __str__(self): return self.name @staticmethod @@ -124,6 +128,7 @@ def default(self): else: return default[0] +@python_2_unicode_compatible class ArticleStatus(models.Model): name = models.CharField(max_length=50) ordering = models.IntegerField(default=0) @@ -135,9 +140,9 @@ class Meta: ordering = ('ordering', 'name') verbose_name_plural = _('Article statuses') - def __unicode__(self): + def __str__(self): if self.is_live: - return u'%s (live)' % self.name + return '%s (live)' % self.name else: return self.name @@ -174,6 +179,7 @@ def live(self, user=None):
  • Textile Guide
  • """) +@python_2_unicode_compatible class Article(models.Model): title = models.CharField(max_length=100) slug = models.SlugField(unique_for_year='publish_date') @@ -223,7 +229,7 @@ def __init__(self, *args, **kwargs): if not self.rendered_content or not len(self.rendered_content.strip()): self.save() - def __unicode__(self): + def __str__(self): return self.title def save(self, *args, **kwargs): @@ -494,6 +500,7 @@ class Meta: ordering = ('-publish_date', 'title') get_latest_by = 'publish_date' +@python_2_unicode_compatible class Attachment(models.Model): upload_to = lambda inst, fn: 'attach/%s/%s/%s' % (datetime.now().year, inst.article.slug, fn) @@ -504,8 +511,8 @@ class Attachment(models.Model): class Meta: ordering = ('-article', 'id') - def __unicode__(self): - return u'%s: %s' % (self.article, self.caption) + def __str__(self): + return '%s: %s' % (self.article, self.caption) @property def filename(self): @@ -522,3 +529,4 @@ def content_type_class(self): return content_type +from . import listeners # last to avoid circular import issue diff --git a/articles/templates/articles/_articles.html b/articles/templates/articles/_articles.html index ccad23a..489d13e 100644 --- a/articles/templates/articles/_articles.html +++ b/articles/templates/articles/_articles.html @@ -1,10 +1,11 @@ {% load i18n %} +{% load url from future %} {% if forloop.first %}
      {% endif %}
    1. {{ article.title }}

      {% trans 'Posted on' %} {{ article.publish_date|date:"F jS, Y" }} - {% trans 'by' %} {{ article.author.get_name }} + {% trans 'by' %} {{ article.author.get_name }}
    2. -{% if forloop.last %}
    {% endif %} \ No newline at end of file +{% if forloop.last %}{% endif %} diff --git a/articles/templates/articles/_meta.html b/articles/templates/articles/_meta.html index c6a7557..06675a8 100644 --- a/articles/templates/articles/_meta.html +++ b/articles/templates/articles/_meta.html @@ -1,11 +1,12 @@ {% load i18n humanize %} +{% load url from future %}

    Meta

    {% trans 'Published' %}: {{ article.publish_date|naturalday }}

    -

    {% trans 'Author' %}: {{ article.author.get_name }}

    +

    {% trans 'Author' %}: {{ article.author.get_name }}

    {% trans 'Comments' %}:  

    diff --git a/articles/templates/articles/article_detail.html b/articles/templates/articles/article_detail.html index fbdcb74..02da8e4 100644 --- a/articles/templates/articles/article_detail.html +++ b/articles/templates/articles/article_detail.html @@ -1,5 +1,6 @@ {% extends 'articles/base.html' %} {% load i18n %} +{% load url from future %} {% block title %}{% trans article.title %}{% endblock %} {% block meta-keywords %}{{ article.keywords|escape }}{% endblock %} @@ -7,8 +8,8 @@ {% block extra-head %} {{ block.super }} {% for tag in article.tags.all %} - -{% endfor %} + +{% endfor %} {% endblock %} {% block content %} diff --git a/articles/templates/articles/base.html b/articles/templates/articles/base.html index 91efc5e..3cadd15 100644 --- a/articles/templates/articles/base.html +++ b/articles/templates/articles/base.html @@ -1,10 +1,11 @@ {% extends 'base.html' %} +{% load url from future %} {% load article_tags i18n %} {% block extra-head %} {{ block.super }} - - + + {% endblock %} {% block content %} @@ -17,7 +18,7 @@

    {% trans 'Article Archives' %}

    {{ year.0 }}
     
    diff --git a/articles/templates/articles/display_tag.html b/articles/templates/articles/display_tag.html index ef61c9a..663cbfb 100644 --- a/articles/templates/articles/display_tag.html +++ b/articles/templates/articles/display_tag.html @@ -1,11 +1,12 @@ {% extends 'articles/base.html' %} +{% load url from future %} {% load i18n %} {% block title %}{% trans 'Articles Tagged' %}: {{ tag.name }}{% endblock %} {% block extra-head %} {{ block.super }} - - + + {% endblock %} {% block articles-content %} diff --git a/articles/templatetags/article_tags.py b/articles/templatetags/article_tags.py index b09e29f..9ac20f2 100644 --- a/articles/templatetags/article_tags.py +++ b/articles/templatetags/article_tags.py @@ -127,7 +127,7 @@ def render(self, context): pub = article.publish_date # see if we already have an article in this year - if not archives.has_key(pub.year): + if not pub.year in archives: # if not, initialize a dict for the year archives[pub.year] = {} diff --git a/articles/tests.py b/articles/tests.py index a325326..01afa84 100644 --- a/articles/tests.py +++ b/articles/tests.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals + from datetime import datetime, timedelta from django.contrib.auth.models import User, Permission @@ -7,7 +9,7 @@ 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 .models import Article, ArticleStatus, Tag, get_name, MARKUP_HTML, MARKUP_MARKDOWN, MARKUP_REST, MARKUP_TEXTILE class ArticleUtilMixin(object): @@ -42,7 +44,7 @@ def setUp(self): def test_unicode_tag(self): """Unicode characters in tags (issue #10)""" - name = u'Căutare avansată' + name = 'Căutare avansată' t = Tag.objects.create(name=name) self.assertEqual(t.slug, 'cutare-avansat') @@ -75,10 +77,10 @@ def setUp(self): def test_instantiation(self): _as = ArticleStatus(name='Fake', ordering=5, is_live=True) - self.assertEqual(unicode(_as), u'Fake (live)') + self.assertEqual(unicode(_as), 'Fake (live)') _as.is_live = False - self.assertEqual(unicode(_as), u'Fake') + self.assertEqual(unicode(_as), 'Fake') class ArticleTestCase(TestCase, ArticleUtilMixin): fixtures = ['users'] @@ -170,7 +172,7 @@ def test_markup_markdown(self): regular paragraph.''', markup=MARKUP_MARKDOWN) a.do_render_markup() - print a.rendered_content + print(a.rendered_content) def test_markup_rest(self): """Makes sure reStructuredText works""" @@ -186,7 +188,7 @@ def test_markup_rest(self): regular paragraph.''', markup=MARKUP_REST) a.do_render_markup() - print a.rendered_content + print(a.rendered_content) def test_markup_textile(self): """Makes sure textile works""" @@ -202,7 +204,7 @@ def test_markup_textile(self): regular paragraph.''', markup=MARKUP_TEXTILE) a.do_render_markup() - print a.rendered_content + print(a.rendered_content) def test_markup_html(self): """Makes sure HTML works (derp)""" diff --git a/articles/urls.py b/articles/urls.py index 564c7ea..486db88 100644 --- a/articles/urls.py +++ b/articles/urls.py @@ -1,7 +1,7 @@ from django.conf.urls.defaults import * -from articles import views -from articles.feeds import TagFeed, LatestEntries, TagFeedAtom, LatestEntriesAtom +from . import views +from .feeds import TagFeed, LatestEntries, TagFeedAtom, LatestEntriesAtom tag_rss = TagFeed() latest_rss = LatestEntries() diff --git a/articles/views.py b/articles/views.py index 7184c17..de9378a 100644 --- a/articles/views.py +++ b/articles/views.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import logging from django.conf import settings @@ -8,7 +10,7 @@ from django.http import HttpResponsePermanentRedirect, Http404, HttpResponseRedirect, HttpResponse from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext -from articles.models import Article, Tag +from .models import Article, Tag from datetime import datetime ARTICLE_PAGINATION = getattr(settings, 'ARTICLE_PAGINATION', 20) @@ -106,7 +108,7 @@ def ajax_tag_autocomplete(request): return response tags = list(Tag.objects.filter(name__istartswith=q)[:10]) - response = HttpResponse(u'\n'.join(tag.name for tag in tags)) + response = HttpResponse('\n'.join(tag.name for tag in tags)) cache.set(key, response, 300) return response