Python 3
authorRadek Czajka <rczajka@rczajka.pl>
Tue, 12 Mar 2019 09:43:08 +0000 (10:43 +0100)
committerRadek Czajka <rczajka@rczajka.pl>
Tue, 12 Mar 2019 12:15:30 +0000 (13:15 +0100)
95 files changed:
Makefile
requirements/requirements-test.txt
requirements/requirements.txt
src/ajaxable/templatetags/ajaxable_tags.py
src/ajaxable/utils.py
src/api/handlers.py
src/api/models.py
src/api/tests/tests.py
src/api/utils.py
src/catalogue/api/views.py
src/catalogue/constants.py
src/catalogue/fields.py
src/catalogue/helpers.py
src/catalogue/management/commands/checkcovers.py
src/catalogue/management/commands/checkintegrity.py
src/catalogue/management/commands/gencover.py
src/catalogue/management/commands/importbooks.py
src/catalogue/management/commands/load_abstracts.py
src/catalogue/management/commands/pack.py
src/catalogue/management/commands/report_dead_links.py
src/catalogue/management/commands/savemedia.py [deleted file]
src/catalogue/management/commands/update_counters.py
src/catalogue/management/commands/update_tag_description.py
src/catalogue/models/book.py
src/catalogue/models/bookmedia.py
src/catalogue/models/collection.py
src/catalogue/models/source.py
src/catalogue/models/tag.py
src/catalogue/signals.py
src/catalogue/tasks.py
src/catalogue/templatetags/catalogue_tags.py
src/catalogue/test_utils.py
src/catalogue/tests/test_bookmedia.py
src/catalogue/tests/test_cover.py
src/catalogue/tests/test_tags.py
src/catalogue/translation.py
src/catalogue/utils.py
src/catalogue/views.py
src/chunks/models.py
src/club/admin.py
src/club/forms.py
src/club/models.py
src/contact/admin.py
src/contact/models.py
src/contact/views.py
src/dictionary/models.py
src/funding/management/commands/funding_notify.py
src/funding/models.py
src/funding/utils.py
src/infopages/models.py
src/isbn/forms.py
src/isbn/management/commands/export_onix.py
src/isbn/management/commands/import_onix.py
src/isbn/models.py
src/isbn/views.py
src/lesmianator/management/commands/lesmianator.py
src/lesmianator/models.py
src/libraries/models.py
src/newsletter/models.py
src/oai/handlers.py
src/opds/views.py
src/paypal/tests.py
src/paypal/views.py
src/pdcounter/models.py
src/picture/migrations/0005_auto_20141022_1001.py
src/picture/models.py
src/picture/tasks.py
src/picture/templatetags/picture_tags.py
src/picture/views.py
src/polls/models.py
src/push/models.py
src/reporting/utils.py
src/search/custom.py
src/search/fields.py
src/search/index.py
src/search/management/commands/reindex.py
src/search/management/commands/reindex_pictures.py
src/search/management/commands/snippets.py
src/search/mock_search.py
src/search/templatetags/search_tags.py
src/social/models.py
src/social/templatetags/social_tags.py
src/sortify.py
src/sponsors/models.py
src/sponsors/widgets.py
src/stats/tasks.py
src/stats/utils.py
src/suggest/models.py
src/wolnelektury/contact_forms.py
src/wolnelektury/management/commands/clean_social_accounts.py
src/wolnelektury/management/commands/localepack.py
src/wolnelektury/management/commands/translation2po.py
src/wolnelektury/middleware.py
src/wolnelektury/settings/apps.py
src/wolnelektury/utils.py

index 1a602ba..3956cf3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,6 @@ deploy: src/wolnelektury/localsettings.py
 test:
        cd src
        coverage run --branch --source='.' ./manage.py test; true
 test:
        cd src
        coverage run --branch --source='.' ./manage.py test; true
-       rm -rf ../htmlcov
        coverage html -d ../htmlcov.new
        rm -rf ../htmlcov
        mv ../htmlcov.new ../htmlcov
        coverage html -d ../htmlcov.new
        rm -rf ../htmlcov
        mv ../htmlcov.new ../htmlcov
index 794632d..4ebc8ae 100644 (file)
@@ -1,4 +1 @@
--i https://py.mdrn.pl:8443/simple/
-
 coverage
 coverage
-mock
index 267e56f..6ecaeb0 100644 (file)
@@ -38,7 +38,7 @@ mutagen>=1.31
 sorl-thumbnail>=12.3,<12.4
 
 # home-brewed & dependencies
 sorl-thumbnail>=12.3,<12.4
 
 # home-brewed & dependencies
-librarian==1.7.1
+librarian==1.7.2
 
 # celery tasks
 celery>=3.1.12,<3.2
 
 # celery tasks
 celery>=3.1.12,<3.2
@@ -48,10 +48,9 @@ kombu>=3.0.23,<3.1
 pyenchant
 
 # OAI-PMH
 pyenchant
 
 # OAI-PMH
-pyoai==2.4.4
+pyoai==2.5.0
 
 
-## egenix-mx-base  # Doesn't play nice with mx in dist-packages.
-sunburnt
+scorched==0.12
 
 django-getpaid==1.8.0
 deprecated
 
 django-getpaid==1.8.0
 deprecated
index 31897d1..d327e2e 100644 (file)
@@ -3,7 +3,7 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django import template
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django import template
-from django.utils.encoding import force_unicode
+from django.utils.encoding import force_text
 from django.utils.safestring import mark_safe
 
 from ajaxable.utils import placeholdized
 from django.utils.safestring import mark_safe
 
 from ajaxable.utils import placeholdized
@@ -32,8 +32,8 @@ def pretty_field(field, template=None):
     return mark_safe(template % {
         'errors': field.errors,
         'input': field,
     return mark_safe(template % {
         'errors': field.errors,
         'input': field,
-        'label': ('*' if field.field.required else '') + force_unicode(field.label),
-        'helptext': force_unicode(field.help_text),
+        'label': ('*' if field.field.required else '') + force_text(field.label),
+        'helptext': force_text(field.help_text),
     })
 
 
     })
 
 
index 89dd0e2..8e34a9a 100755 (executable)
@@ -6,7 +6,7 @@ from functools import wraps
 
 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
 from django.shortcuts import render
 
 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
 from django.shortcuts import render
-from django.utils.encoding import force_unicode
+from django.utils.encoding import force_text
 from django.utils.functional import Promise
 from django.utils.http import urlquote_plus
 import json
 from django.utils.functional import Promise
 from django.utils.http import urlquote_plus
 import json
@@ -18,7 +18,7 @@ from honeypot.decorators import verify_honeypot_value
 class LazyEncoder(json.JSONEncoder):
     def default(self, obj):
         if isinstance(obj, Promise):
 class LazyEncoder(json.JSONEncoder):
     def default(self, obj):
         if isinstance(obj, Promise):
-            return force_unicode(obj)
+            return force_text(obj)
         return obj
 
 
         return obj
 
 
index d08812f..5af5cfb 100644 (file)
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
@@ -8,7 +7,7 @@ from catalogue.models import Book, Tag
 
 
 WL_BASE = lazy(
 
 
 WL_BASE = lazy(
-    lambda: u'https://' + Site.objects.get_current().domain, unicode)()
+    lambda: 'https://' + Site.objects.get_current().domain, str)()
 
 category_singular = {
     'authors': 'author',
 
 category_singular = {
     'authors': 'author',
index 71dedb5..9a86c2c 100644 (file)
@@ -76,7 +76,7 @@ class Nonce(models.Model):
     consumer_key = models.CharField(max_length=KEY_SIZE)
     key = models.CharField(max_length=255)
 
     consumer_key = models.CharField(max_length=KEY_SIZE)
     key = models.CharField(max_length=255)
 
-    def __unicode__(self):
+    def __str__(self):
         return u"Nonce %s for %s" % (self.key, self.consumer_key)
 
 
         return u"Nonce %s for %s" % (self.key, self.consumer_key)
 
 
@@ -88,7 +88,7 @@ class Consumer(models.Model):
     status = models.CharField(max_length=16, choices=CONSUMER_STATES, default='pending')
     user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='consumers')
 
     status = models.CharField(max_length=16, choices=CONSUMER_STATES, default='pending')
     user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='consumers')
 
-    def __unicode__(self):
+    def __str__(self):
         return u"Consumer %s with key %s" % (self.name, self.key)
 
 
         return u"Consumer %s with key %s" % (self.name, self.key)
 
 
@@ -105,5 +105,5 @@ class Token(models.Model):
     user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='tokens')
     consumer = models.ForeignKey(Consumer)
 
     user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='tokens')
     consumer = models.ForeignKey(Consumer)
 
-    def __unicode__(self):
+    def __str__(self):
         return u"%s Token %s for %s" % (self.get_token_type_display(), self.key, self.consumer)
         return u"%s Token %s for %s" % (self.get_token_type_display(), self.key, self.consumer)
index b3cc54f..eccdd04 100644 (file)
@@ -7,16 +7,15 @@ from os import path
 import hashlib
 import hmac
 import json
 import hashlib
 import hmac
 import json
-from StringIO import StringIO
+from io import BytesIO
 from time import time
 from time import time
-from urllib import quote, urlencode
-from urlparse import parse_qs
+from urllib.parse import quote, urlencode, parse_qs
 
 from django.contrib.auth.models import User
 from django.core.files.uploadedfile import SimpleUploadedFile
 from django.test import TestCase
 from django.test.utils import override_settings
 
 from django.contrib.auth.models import User
 from django.core.files.uploadedfile import SimpleUploadedFile
 from django.test import TestCase
 from django.test.utils import override_settings
-from mock import patch
+from unittest.mock import patch
 from api.models import Consumer, Token
 
 from catalogue.models import Book, Tag
 from api.models import Consumer, Token
 
 from catalogue.models import Book, Tag
@@ -40,7 +39,7 @@ class ApiTest(TestCase):
         return data
 
     def assert_response(self, url, name):
         return data
 
     def assert_response(self, url, name):
-        content = self.client.get(url).content.rstrip()
+        content = self.client.get(url).content.decode('utf-8').rstrip()
         filename = path.join(path.dirname(__file__), 'res', 'responses', name)
         with open(filename) as f:
             good_content = f.read().rstrip()
         filename = path.join(path.dirname(__file__), 'res', 'responses', name)
         with open(filename) as f:
             good_content = f.read().rstrip()
@@ -112,12 +111,12 @@ class PictureTests(ApiTest):
             'composition8.xml',
             open(path.join(
                 picture.tests.__path__[0], "files", slug + ".xml"
             'composition8.xml',
             open(path.join(
                 picture.tests.__path__[0], "files", slug + ".xml"
-            )).read())
+            ), 'rb').read())
         img = SimpleUploadedFile(
             'kompozycja-8.png',
             open(path.join(
                 picture.tests.__path__[0], "files", slug + ".png"
         img = SimpleUploadedFile(
             'kompozycja-8.png',
             open(path.join(
                 picture.tests.__path__[0], "files", slug + ".png"
-            )).read())
+            ), 'rb').read())
 
         import_form = PictureImportForm({}, {
             'picture_xml_file': xml,
 
         import_form = PictureImportForm({}, {
             'picture_xml_file': xml,
@@ -265,13 +264,15 @@ class OAuth1Tests(ApiTest):
             quote(base_query, safe='')
         ])
         h = hmac.new(
             quote(base_query, safe='')
         ])
         h = hmac.new(
-            quote(self.consumer_secret) + '&', raw, hashlib.sha1
+            (quote(self.consumer_secret) + '&').encode('latin1'),
+            raw.encode('latin1'),
+            hashlib.sha1
         ).digest()
         ).digest()
-        h = b64encode(h).rstrip('\n')
+        h = b64encode(h).rstrip(b'\n')
         sign = quote(h)
         query = "{}&oauth_signature={}".format(base_query, sign)
         response = self.client.get('/api/oauth/request_token/?' + query)
         sign = quote(h)
         query = "{}&oauth_signature={}".format(base_query, sign)
         response = self.client.get('/api/oauth/request_token/?' + query)
-        request_token_data = parse_qs(response.content)
+        request_token_data = parse_qs(response.content.decode('latin1'))
         request_token = request_token_data['oauth_token'][0]
         request_token_secret = request_token_data['oauth_token_secret'][0]
 
         request_token = request_token_data['oauth_token'][0]
         request_token_secret = request_token_data['oauth_token_secret'][0]
 
@@ -297,16 +298,16 @@ class OAuth1Tests(ApiTest):
             quote(base_query, safe='')
         ])
         h = hmac.new(
             quote(base_query, safe='')
         ])
         h = hmac.new(
-            quote(self.consumer_secret) + '&' +
-            quote(request_token_secret, safe=''),
-            raw,
+            (quote(self.consumer_secret) + '&' +
+             quote(request_token_secret, safe='')).encode('latin1'),
+            raw.encode('latin1'),
             hashlib.sha1
         ).digest()
             hashlib.sha1
         ).digest()
-        h = b64encode(h).rstrip('\n')
+        h = b64encode(h).rstrip(b'\n')
         sign = quote(h)
         query = u"{}&oauth_signature={}".format(base_query, sign)
         response = self.client.get(u'/api/oauth/access_token/?' + query)
         sign = quote(h)
         query = u"{}&oauth_signature={}".format(base_query, sign)
         response = self.client.get(u'/api/oauth/access_token/?' + query)
-        access_token_data = parse_qs(response.content)
+        access_token_data = parse_qs(response.content.decode('latin1'))
         access_token = access_token_data['oauth_token'][0]
 
         self.assertTrue(
         access_token = access_token_data['oauth_token'][0]
 
         self.assertTrue(
@@ -333,7 +334,7 @@ class AuthorizedTests(ApiTest):
             consumer=cls.consumer,
             token_type=Token.ACCESS,
             timestamp=time())
             consumer=cls.consumer,
             token_type=Token.ACCESS,
             timestamp=time())
-        cls.key = cls.consumer.secret + '&' + cls.token.secret
+        cls.key = (cls.consumer.secret + '&' + cls.token.secret).encode('latin1')
 
     @classmethod
     def tearDownClass(cls):
 
     @classmethod
     def tearDownClass(cls):
@@ -365,7 +366,10 @@ class AuthorizedTests(ApiTest):
                 for (k, v) in sorted(sign_params.items())))
         ])
         auth_params["oauth_signature"] = quote(b64encode(hmac.new(
                 for (k, v) in sorted(sign_params.items())))
         ])
         auth_params["oauth_signature"] = quote(b64encode(hmac.new(
-            self.key, raw, hashlib.sha1).digest()).rstrip('\n'))
+            self.key,
+            raw.encode('latin1'),
+            hashlib.sha1
+        ).digest()).rstrip(b'\n'))
         auth = 'OAuth realm="API", ' + ', '.join(
             '{}="{}"'.format(k, v) for (k, v) in auth_params.items())
 
         auth = 'OAuth realm="API", ' + ', '.join(
             '{}="{}"'.format(k, v) for (k, v) in auth_params.items())
 
@@ -447,10 +451,10 @@ class AuthorizedTests(ApiTest):
                 {"username": "test", "premium": True})
         with patch('paypal.permissions.user_is_subscribed', return_value=True):
             with patch('django.core.files.storage.Storage.open',
                 {"username": "test", "premium": True})
         with patch('paypal.permissions.user_is_subscribed', return_value=True):
             with patch('django.core.files.storage.Storage.open',
-                       return_value=StringIO("<epub>")):
+                       return_value=BytesIO(b"<epub>")):
                 self.assertEqual(
                     self.signed('/api/epub/grandchild/').content,
                 self.assertEqual(
                     self.signed('/api/epub/grandchild/').content,
-                    "<epub>")
+                    b"<epub>")
 
     def test_publish(self):
         response = self.signed('/api/books/',
 
     def test_publish(self):
         response = self.signed('/api/books/',
index c9c1f94..a3a1f89 100644 (file)
@@ -25,8 +25,9 @@ def oauthlib_request(request):
         "headers": headers,
     }
 
         "headers": headers,
     }
 
-def oauthlib_response((headers, body, status)):
+def oauthlib_response(response_tuple):
     """Creates a django.http.HttpResponse from (headers, body, status) tuple from OAuthlib."""
     """Creates a django.http.HttpResponse from (headers, body, status) tuple from OAuthlib."""
+    headers, body, status = response_tuple
     response = HttpResponse(body, status=status)
     for k, v in headers.items():
         if k == 'Location':
     response = HttpResponse(body, status=status)
     for k, v in headers.items():
         if k == 'Location':
index 62876c0..7c4b888 100644 (file)
@@ -52,6 +52,11 @@ class BookList(ListAPIView):
         new_api = self.request.query_params.get('new_api')
         after = self.request.query_params.get('after', self.kwargs.get('after'))
         count = self.request.query_params.get('count', self.kwargs.get('count'))
         new_api = self.request.query_params.get('new_api')
         after = self.request.query_params.get('after', self.kwargs.get('after'))
         count = self.request.query_params.get('count', self.kwargs.get('count'))
+        if count:
+            try:
+                count = int(count)
+            except TypeError:
+                raise Http404  # Fixme
 
         if tags:
             if self.kwargs.get('top_level'):
 
         if tags:
             if self.kwargs.get('top_level'):
index 60ceceb..1a655f4 100644 (file)
@@ -17,7 +17,8 @@ LICENSES = {
 LICENSES['http://creativecommons.org/licenses/by-sa/3.0/deed.pl'] = \
     LICENSES['http://creativecommons.org/licenses/by-sa/3.0/']
 
 LICENSES['http://creativecommons.org/licenses/by-sa/3.0/deed.pl'] = \
     LICENSES['http://creativecommons.org/licenses/by-sa/3.0/']
 
-for license, data in LICENSES.items():
+
+for license, data in list(LICENSES.items()):
     LICENSES[license.replace('http://', 'https://')] = data
 
 # Those will be generated only for books with own HTML.
     LICENSES[license.replace('http://', 'https://')] = data
 
 # Those will be generated only for books with own HTML.
index ebe4df1..2b2d6a6 100644 (file)
@@ -106,7 +106,7 @@ class BuildEbook(Task):
     def build(self, fieldfile):
         book = fieldfile.instance
         out = self.transform(book.wldocument(), fieldfile)
     def build(self, fieldfile):
         book = fieldfile.instance
         out = self.transform(book.wldocument(), fieldfile)
-        fieldfile.save(None, File(open(out.get_filename())), save=False)
+        fieldfile.save(None, File(open(out.get_filename(), 'rb')), save=False)
         self.set_file_permissions(fieldfile)
         if book.pk is not None:
             books = type(book).objects.filter(pk=book.pk)
         self.set_file_permissions(fieldfile)
         if book.pk is not None:
             books = type(book).objects.filter(pk=book.pk)
index 4dd7bda..0330e69 100644 (file)
@@ -8,14 +8,14 @@ from django.core.cache import cache
 
 from .models import Tag, Book
 from os.path import getmtime
 
 from .models import Tag, Book
 from os.path import getmtime
-import cPickle
+import pickle
 from collections import defaultdict
 
 
 BOOK_CATEGORIES = ('author', 'epoch', 'genre', 'kind')
 
 _COUNTERS = None
 from collections import defaultdict
 
 
 BOOK_CATEGORIES = ('author', 'epoch', 'genre', 'kind')
 
 _COUNTERS = None
-_COUNTER_TIME = None
+_COUNTER_TIME = 0
 
 
 def get_top_level_related_tags(tags, categories=None):
 
 
 def get_top_level_related_tags(tags, categories=None):
@@ -28,10 +28,10 @@ def get_top_level_related_tags(tags, categories=None):
     global _COUNTERS, _COUNTER_TIME
     # First, check that we have a valid and recent version of the counters.
     if getmtime(settings.CATALOGUE_COUNTERS_FILE) > _COUNTER_TIME:
     global _COUNTERS, _COUNTER_TIME
     # First, check that we have a valid and recent version of the counters.
     if getmtime(settings.CATALOGUE_COUNTERS_FILE) > _COUNTER_TIME:
-        for i in xrange(10):
+        for i in range(10):
             try:
             try:
-                with open(settings.CATALOGUE_COUNTERS_FILE) as f:
-                    _COUNTERS = cPickle.load(f)
+                with open(settings.CATALOGUE_COUNTERS_FILE, 'rb') as f:
+                    _COUNTERS = pickle.load(f)
             except (EOFError, ValueError):
                 if i < 9:
                     continue
             except (EOFError, ValueError):
                 if i < 9:
                     continue
@@ -95,8 +95,8 @@ def update_counters():
         "next": dict(next_combinations),
     }
 
         "next": dict(next_combinations),
     }
 
-    with open(settings.CATALOGUE_COUNTERS_FILE, 'w') as f:
-        cPickle.dump(counters, f)
+    with open(settings.CATALOGUE_COUNTERS_FILE, 'wb') as f:
+        pickle.dump(counters, f)
 
 
 def get_audiobook_tags():
 
 
 def get_audiobook_tags():
index 2466728..66a69c7 100644 (file)
@@ -1,12 +1,10 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from optparse import make_option
 from django.contrib.sites.models import Site
 from django.core.management.base import BaseCommand
 from django.contrib.sites.models import Site
 from django.core.management.base import BaseCommand
-from catalogue import app_settings
 from django.utils.functional import lazy
 from django.utils.functional import lazy
+from catalogue import app_settings
 
 
 def ancestor_has_cover(book):
 
 
 def ancestor_has_cover(book):
@@ -27,12 +25,13 @@ def full_url(obj):
 
 
 class Command(BaseCommand):
 
 
 class Command(BaseCommand):
-    option_list = BaseCommand.option_list + (
-        make_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
-                    help='Suppress output'),
-    )
     help = 'Checks cover sources and licenses.'
 
     help = 'Checks cover sources and licenses.'
 
+    def add_arguments(self, parser):
+        parser.add_argument(
+                '-q', '--quiet', action='store_false', dest='verbose',
+                default=True, help='Suppress output')
+
     def handle(self, **options):
         from collections import defaultdict
         import re
     def handle(self, **options):
         from collections import defaultdict
         import re
@@ -47,7 +46,7 @@ class Command(BaseCommand):
         bad_license = defaultdict(list)
         no_license = []
 
         bad_license = defaultdict(list)
         no_license = []
 
-        re_license = re.compile(ur'.*,\s*(CC.*)')
+        re_license = re.compile(r'.*,\s*(CC.*)')
 
         redakcja_url = app_settings.REDAKCJA_URL
         good_license = re.compile("(%s)" % ")|(".join(
 
         redakcja_url = app_settings.REDAKCJA_URL
         good_license = re.compile("(%s)" % ")|(".join(
@@ -71,7 +70,7 @@ class Command(BaseCommand):
                     else:
                         no_license.append(book)
 
                     else:
                         no_license.append(book)
 
-        print """%d books with no covers, %d with inherited covers.
+        print("""%d books with no covers, %d with inherited covers.
 Bad licenses used: %s (%d covers without license).
 %d covers not from %s.
 """ % (
 Bad licenses used: %s (%d covers without license).
 %d covers not from %s.
 """ % (
@@ -81,51 +80,51 @@ Bad licenses used: %s (%d covers without license).
             len(no_license),
             len(not_redakcja),
             redakcja_url,
             len(no_license),
             len(not_redakcja),
             redakcja_url,
-            )
+            ))
 
         if verbose:
             if bad_license:
 
         if verbose:
             if bad_license:
-                print
-                print "Bad license:"
-                print "============"
+                print()
+                print("Bad license:")
+                print("============")
                 for lic, books in bad_license.items():
                 for lic, books in bad_license.items():
-                    print
-                    print lic
+                    print()
+                    print(lic)
                     for book in books:
                     for book in books:
-                        print full_url(book)
+                        print(full_url(book))
 
             if no_license:
 
             if no_license:
-                print
-                print "No license:"
-                print "==========="
+                print()
+                print("No license:")
+                print("===========")
                 for book in no_license:
                 for book in no_license:
-                    print
-                    print full_url(book)
-                    print book.extra_info.get('cover_by')
-                    print book.extra_info.get('cover_source')
-                    print book.extra_info.get('cover_url')
+                    print()
+                    print(full_url(book))
+                    print(book.extra_info.get('cover_by'))
+                    print(book.extra_info.get('cover_source'))
+                    print(book.extra_info.get('cover_url'))
 
             if not_redakcja:
 
             if not_redakcja:
-                print
-                print "Not from Redakcja or source missing:"
-                print "===================================="
+                print()
+                print("Not from Redakcja or source missing:")
+                print("====================================")
                 for book in not_redakcja:
                 for book in not_redakcja:
-                    print
-                    print full_url(book)
-                    print book.extra_info.get('cover_by')
-                    print book.extra_info.get('cover_source')
-                    print book.extra_info.get('cover_url')
+                    print()
+                    print(full_url(book))
+                    print(book.extra_info.get('cover_by'))
+                    print(book.extra_info.get('cover_source'))
+                    print(book.extra_info.get('cover_url'))
 
             if without_cover:
 
             if without_cover:
-                print
-                print "No cover:"
-                print "========="
+                print()
+                print("No cover:")
+                print("=========")
                 for book in without_cover:
                 for book in without_cover:
-                    print full_url(book)
+                    print(full_url(book))
 
             if with_ancestral_cover:
 
             if with_ancestral_cover:
-                print
-                print "With ancestral cover:"
-                print "====================="
+                print()
+                print("With ancestral cover:")
+                print("=====================")
                 for book in with_ancestral_cover:
                 for book in with_ancestral_cover:
-                    print full_url(book)
+                    print(full_url(book))
index 6f090bb..e7d5ffd 100644 (file)
@@ -1,23 +1,22 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from optparse import make_option
 from django.core.management.base import BaseCommand
 from django.core.management.base import BaseCommand
-
-from catalogue.models import Book
 from librarian import ParseError
 from librarian import ParseError
+from catalogue.models import Book
 
 
 class Command(BaseCommand):
 
 
 class Command(BaseCommand):
-    option_list = BaseCommand.option_list + (
-        make_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
-                    help='Suppress output'),
-        make_option('-d', '--dry-run', action='store_true', dest='dry_run', default=False,
-                    help="Just check for problems, don't fix them"),
-    )
     help = 'Checks integrity of catalogue data.'
 
     help = 'Checks integrity of catalogue data.'
 
+    def add_arguments(self, parser):
+        parser.add_argument(
+                '-q', '--quiet', action='store_false', dest='verbose',
+                default=True, help='Suppress output')
+        parser.add_argument(
+                '-d', '--dry-run', action='store_true', dest='dry_run',
+                default=False, help="Just check for problems, don't fix them")
+
     def handle(self, **options):
         from django.db import transaction
 
     def handle(self, **options):
         from django.db import transaction
 
@@ -29,21 +28,21 @@ class Command(BaseCommand):
                     info = book.wldocument().book_info
                 except ParseError:
                     if verbose:
                     info = book.wldocument().book_info
                 except ParseError:
                     if verbose:
-                        print "ERROR! Bad XML for book:", book.slug
-                        print "To resolve: republish."
-                        print
+                        print("ERROR! Bad XML for book:", book.slug)
+                        print("To resolve: republish.")
+                        print()
                 else:
                     should_be = [p.slug for p in info.parts]
                     is_now = [p.slug for p in book.children.all().order_by('parent_number')]
                     if should_be != is_now:
                         if verbose:
                 else:
                     should_be = [p.slug for p in info.parts]
                     is_now = [p.slug for p in book.children.all().order_by('parent_number')]
                     if should_be != is_now:
                         if verbose:
-                            print "ERROR! Wrong children for book:", book.slug
-                            # print "Is:       ", is_now
-                            # print "Should be:", should_be
+                            print("ERROR! Wrong children for book:", book.slug)
+                            # print("Is:       ", is_now)
+                            # print("Should be:", should_be)
                             from difflib import ndiff
                             from difflib import ndiff
-                            print '\n'.join(ndiff(is_now, should_be))
-                            print "To resolve: republish parent book."
-                            print
+                            print('\n'.join(ndiff(is_now, should_be)))
+                            print("To resolve: republish parent book.")
+                            print()
 
                 # Check for ancestry.
                 parents = []
 
                 # Check for ancestry.
                 parents = []
@@ -54,14 +53,14 @@ class Command(BaseCommand):
                 ancestors = list(book.ancestor.all())
                 if set(ancestors) != set(parents):
                     if options['verbose']:
                 ancestors = list(book.ancestor.all())
                 if set(ancestors) != set(parents):
                     if options['verbose']:
-                        print "Wrong ancestry for book:", book
-                        print "Is:       ", ", ".join(ancestors)
-                        print "Should be:", ", ".join(parents)
+                        print("Wrong ancestry for book:", book)
+                        print("Is:       ", ", ".join(ancestors))
+                        print("Should be:", ", ".join(parents))
                     if not options['dry_run']:
                         book.repopulate_ancestors()
                         if options['verbose']:
                     if not options['dry_run']:
                         book.repopulate_ancestors()
                         if options['verbose']:
-                            print "Fixed."
+                            print("Fixed.")
                     if options['verbose']:
                     if options['verbose']:
-                        print
+                        print()
 
                 # TODO: check metadata tags, reset counters
 
                 # TODO: check metadata tags, reset counters
index ec010a5..c0d099b 100644 (file)
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
index 5280778..b8a9aa7 100644 (file)
@@ -1,40 +1,41 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import os
 import sys
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import os
 import sys
-from optparse import make_option
 from django.conf import settings
 from django.core.management.base import BaseCommand
 from django.core.management.color import color_style
 from django.core.files import File
 from django.db import transaction
 from librarian.picture import ImageStore
 from django.conf import settings
 from django.core.management.base import BaseCommand
 from django.core.management.color import color_style
 from django.core.files import File
 from django.db import transaction
 from librarian.picture import ImageStore
-# from wolnelektury.management.profile import profile
 
 from catalogue.models import Book
 from picture.models import Picture
 
 from catalogue.models import Book
 from picture.models import Picture
-
 from search.index import Index
 
 
 class Command(BaseCommand):
 from search.index import Index
 
 
 class Command(BaseCommand):
-    option_list = BaseCommand.option_list + (
-        make_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
-                    help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
-        make_option('-f', '--force', action='store_true', dest='force', default=False,
-                    help='Overwrite works already in the catalogue'),
-        make_option('-D', '--dont-build', dest='dont_build',
-                    metavar="FORMAT,...",
-                    help="Skip building specified formats"),
-        make_option('-S', '--no-search-index', action='store_false', dest='search_index', default=True,
-                    help='Skip indexing imported works for search'),
-        make_option('-p', '--picture', action='store_true', dest='import_picture', default=False,
-                    help='Import pictures'),
-    )
     help = 'Imports books from the specified directories.'
     help = 'Imports books from the specified directories.'
-    args = 'directory [directory ...]'
+
+    def add_arguments(self, parser):
+        parser.add_argument(
+                '-q', '--quiet', action='store_false', dest='verbose', default=True,
+                help='Verbosity level; 0=minimal output, 1=normal output, 2=all output')
+        parser.add_argument(
+                '-f', '--force', action='store_true', dest='force',
+                default=False, help='Overwrite works already in the catalogue')
+        parser.add_argument(
+                '-D', '--dont-build', dest='dont_build', metavar="FORMAT,...",
+                help="Skip building specified formats")
+        parser.add_argument(
+                '-S', '--no-search-index', action='store_false',
+                dest='search_index', default=True,
+                help='Skip indexing imported works for search')
+        parser.add_argument(
+                '-p', '--picture', action='store_true', dest='import_picture',
+                default=False, help='Import pictures')
+        parser.add_argument('directory', nargs='+')
 
     def import_book(self, file_path, options):
         verbose = options.get('verbose')
 
     def import_book(self, file_path, options):
         verbose = options.get('verbose')
@@ -54,24 +55,23 @@ class Command(BaseCommand):
                     save=False
                     )
                 if verbose:
                     save=False
                     )
                 if verbose:
-                    print "Importing %s.%s" % (file_base, ebook_format)
+                    print("Importing %s.%s" % (file_base, ebook_format))
         book.save()
 
     def import_picture(self, file_path, options, continue_on_error=True):
         try:
             image_store = ImageStore(os.path.dirname(file_path))
             picture = Picture.from_xml_file(file_path, image_store=image_store, overwrite=options.get('force'))
         book.save()
 
     def import_picture(self, file_path, options, continue_on_error=True):
         try:
             image_store = ImageStore(os.path.dirname(file_path))
             picture = Picture.from_xml_file(file_path, image_store=image_store, overwrite=options.get('force'))
-        except Exception, ex:
+        except Exception as ex:
             if continue_on_error:
             if continue_on_error:
-                print "%s: %s" % (file_path, ex)
+                print("%s: %s" % (file_path, ex))
                 return
             else:
                 raise ex
         return picture
 
                 return
             else:
                 raise ex
         return picture
 
-    # @profile
     @transaction.atomic
     @transaction.atomic
-    def handle(self, *directories, **options):
+    def handle(self, **options):
         self.style = color_style()
 
         verbose = options.get('verbose')
         self.style = color_style()
 
         verbose = options.get('verbose')
@@ -82,16 +82,16 @@ class Command(BaseCommand):
             try:
                 index.index_tags()
                 index.index.commit()
             try:
                 index.index_tags()
                 index.index.commit()
-            except Exception, e:
+            except Exception as e:
                 index.index.rollback()
                 raise e
 
         files_imported = 0
         files_skipped = 0
 
                 index.index.rollback()
                 raise e
 
         files_imported = 0
         files_skipped = 0
 
-        for dir_name in directories:
+        for dir_name in options['directory']:
             if not os.path.isdir(dir_name):
             if not os.path.isdir(dir_name):
-                print self.style.ERROR("%s: Not a directory. Skipping." % dir_name)
+                print(self.style.ERROR("%s: Not a directory. Skipping." % dir_name))
             else:
                 # files queue
                 files = sorted(os.listdir(dir_name))
             else:
                 # files queue
                 files = sorted(os.listdir(dir_name))
@@ -106,7 +106,7 @@ class Command(BaseCommand):
                         continue
 
                     if verbose > 0:
                         continue
 
                     if verbose > 0:
-                        print "Parsing '%s'" % file_path
+                        print("Parsing '%s'" % file_path)
                     else:
                         sys.stdout.write('.')
                         sys.stdout.flush()
                     else:
                         sys.stdout.write('.')
                         sys.stdout.flush()
@@ -121,16 +121,16 @@ class Command(BaseCommand):
                         files_imported += 1
 
                     except (Book.AlreadyExists, Picture.AlreadyExists):
                         files_imported += 1
 
                     except (Book.AlreadyExists, Picture.AlreadyExists):
-                        print self.style.ERROR(
+                        print(self.style.ERROR(
                             '%s: Book or Picture already imported. Skipping. To overwrite use --force.' %
                             '%s: Book or Picture already imported. Skipping. To overwrite use --force.' %
-                            file_path)
+                            file_path))
                         files_skipped += 1
 
                         files_skipped += 1
 
-                    except Book.DoesNotExist, e:
+                    except Book.DoesNotExist as e:
                         if file_name not in postponed or postponed[file_name] < files_imported:
                             # push it back into the queue, maybe the missing child will show up
                             if verbose:
                         if file_name not in postponed or postponed[file_name] < files_imported:
                             # push it back into the queue, maybe the missing child will show up
                             if verbose:
-                                print self.style.NOTICE('Waiting for missing children')
+                                print(self.style.NOTICE('Waiting for missing children'))
                             files.append(file_name)
                             postponed[file_name] = files_imported
                         else:
                             files.append(file_name)
                             postponed[file_name] = files_imported
                         else:
@@ -138,7 +138,7 @@ class Command(BaseCommand):
                             raise e
 
         # Print results
                             raise e
 
         # Print results
-        print
-        print "Results: %d files imported, %d skipped, %d total." % (
-            files_imported, files_skipped, files_imported + files_skipped)
-        print
+        print()
+        print("Results: %d files imported, %d skipped, %d total." % (
+            files_imported, files_skipped, files_imported + files_skipped))
+        print()
index b828974..19ff8c4 100644 (file)
@@ -10,6 +10,6 @@ from catalogue.models import Book
 class Command(BaseCommand):
     def handle(self, *args, **options):
         for b in Book.objects.order_by('slug'):
 class Command(BaseCommand):
     def handle(self, *args, **options):
         for b in Book.objects.order_by('slug'):
-            print b.slug
+            print(b.slug)
             b.load_abstract()
             b.save()
             b.load_abstract()
             b.save()
index 98ad7d8..181c2e6 100755 (executable)
@@ -1,30 +1,32 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from optparse import make_option
-
+import zipfile
 from django.core.management.base import BaseCommand
 from django.core.management.color import color_style
 from django.core.management.base import BaseCommand
 from django.core.management.color import color_style
-import zipfile
-
 from catalogue.models import Book, Tag
 
 
 class Command(BaseCommand):
 from catalogue.models import Book, Tag
 
 
 class Command(BaseCommand):
-    option_list = BaseCommand.option_list + (
-        make_option('-t', '--tags', dest='tags', metavar='SLUG,...',
-                    help='Use only books tagged with this tags'),
-        make_option('-i', '--include', dest='include', metavar='SLUG,...',
-                    help='Include specific books by slug'),
-        make_option('-e', '--exclude', dest='exclude', metavar='SLUG,...',
-                    help='Exclude specific books by slug')
-    )
     help = 'Prepare ZIP package with files of given type.'
     help = 'Prepare ZIP package with files of given type.'
-    args = '[%s] output_path.zip' % '|'.join(Book.formats)
 
 
-    def handle(self, ftype, path, **options):
+    def add_arguments(self, parser):
+        parser.add_argument(
+                '-t', '--tags', dest='tags', metavar='SLUG,...',
+                help='Use only books tagged with this tags')
+        parser.add_argument(
+                '-i', '--include', dest='include', metavar='SLUG,...',
+                help='Include specific books by slug')
+        parser.add_argument(
+                '-e', '--exclude', dest='exclude', metavar='SLUG,...',
+                help='Exclude specific books by slug')
+        parser.add_argument('ftype', metavar='|'.join(Book.formats))
+        parser.add_argument('path', metavar='output_path.zip')
+
+    def handle(self, **options):
         self.style = color_style()
         self.style = color_style()
+        ftype = options['ftype']
+        path = options['path']
         verbose = int(options.get('verbosity'))
         tags = options.get('tags')
         include = options.get('include')
         verbose = int(options.get('verbosity'))
         tags = options.get('tags')
         include = options.get('include')
@@ -33,7 +35,7 @@ class Command(BaseCommand):
         if ftype in Book.formats:
             field = "%s_file" % ftype
         else:
         if ftype in Book.formats:
             field = "%s_file" % ftype
         else:
-            print self.style.ERROR('Unknown file type.')
+            print(self.style.ERROR('Unknown file type.'))
             return
 
         books = []
             return
 
         books = []
@@ -54,11 +56,11 @@ class Command(BaseCommand):
         processed = skipped = 0
         for book in books:
             if verbose >= 2:
         processed = skipped = 0
         for book in books:
             if verbose >= 2:
-                print 'Parsing', book.slug
+                print('Parsing', book.slug)
             content = getattr(book, field)
             if not content:
                 if verbose >= 1:
             content = getattr(book, field)
             if not content:
                 if verbose >= 1:
-                    print self.style.NOTICE('%s has no %s file' % (book.slug, ftype))
+                    print(self.style.NOTICE('%s has no %s file' % (book.slug, ftype)))
                 skipped += 1
                 continue
             archive.write(content.path, str('%s.%s' % (book.slug, ftype)))
                 skipped += 1
                 continue
             archive.write(content.path, str('%s.%s' % (book.slug, ftype)))
@@ -67,11 +69,11 @@ class Command(BaseCommand):
 
         if not processed:
             if skipped:
 
         if not processed:
             if skipped:
-                print self.style.ERROR("No books with %s files found" % ftype)
+                print(self.style.ERROR("No books with %s files found" % ftype))
             else:
             else:
-                print self.style.ERROR("No books found")
+                print(self.style.ERROR("No books found"))
             return
 
         if verbose >= 1:
             return
 
         if verbose >= 1:
-            print "%d processed, %d skipped" % (processed, skipped)
-            print "Results written to %s" % path
+            print("%d processed, %d skipped" % (processed, skipped))
+            print("Results written to %s" % path)
index 1eb7312..a6ee2a9 100644 (file)
@@ -1,9 +1,6 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from __future__ import print_function, unicode_literals
-
 from django.core.management.base import BaseCommand
 
 
 from django.core.management.base import BaseCommand
 
 
@@ -13,7 +10,7 @@ class Command(BaseCommand):
     def handle(self, **options):
         from catalogue.models import Book
         from picture.models import Picture
     def handle(self, **options):
         from catalogue.models import Book
         from picture.models import Picture
-        from urllib2 import urlopen, HTTPError, URLError
+        from urllib.request import urlopen, HTTPError, URLError
         from django.core.urlresolvers import reverse
         from django.contrib.sites.models import Site
 
         from django.core.urlresolvers import reverse
         from django.contrib.sites.models import Site
 
@@ -46,10 +43,10 @@ class Command(BaseCommand):
                     if url:
                         try:
                             urlopen(url)
                     if url:
                         try:
                             urlopen(url)
-                        except (HTTPError, URLError, ValueError), e:
+                        except (HTTPError, URLError, ValueError) as e:
                             if clean:
                                 clean = False
                             if clean:
                                 clean = False
-                                print(unicode(obj).encode('utf-8'))
+                                print(str(obj).encode('utf-8'))
                                 print(('Na stronie: https://%s%s' % (domain, obj.get_absolute_url())).encode('utf-8'))
                                 print(
                                     ('Administracja: https://%s%s' % (domain, reverse(admin_name, args=[obj.pk])))
                                 print(('Na stronie: https://%s%s' % (domain, obj.get_absolute_url())).encode('utf-8'))
                                 print(
                                     ('Administracja: https://%s%s' % (domain, reverse(admin_name, args=[obj.pk])))
diff --git a/src/catalogue/management/commands/savemedia.py b/src/catalogue/management/commands/savemedia.py
deleted file mode 100755 (executable)
index ab4da51..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-import os.path
-
-from django.core.management.base import BaseCommand
-from django.db import transaction
-
-from catalogue.models import Book, BookMedia
-from catalogue.utils import ExistingFile
-
-
-class Command(BaseCommand):
-    help = "Saves uploaded media with a given book and a given name. " \
-           "If media has a source SHA1 info - matching media is replaced."
-    args = 'path slug name'
-
-    @transaction.atomic
-    def handle(self, *args, **options):
-        path, slug, name, part_name, index, parts_count = args
-        index = int(index)
-        parts_count = int(parts_count)
-
-        book = Book.objects.get(slug=slug)
-
-        root, ext = os.path.splitext(path)
-        ext = ext.lower()
-        if ext:
-            ext = ext[1:]
-            if ext == 'zip':
-                ext = 'daisy'
-
-        source_sha1 = BookMedia.read_source_sha1(path, ext)
-        print "Source file SHA1:", source_sha1
-        try:
-            assert source_sha1
-            bm = book.media.get(type=ext, source_sha1=source_sha1)
-            print "Replacing media: %s (%s)" % (bm.name.encode('utf-8'), ext)
-        except (AssertionError, BookMedia.DoesNotExist):
-            bm = BookMedia(book=book, type=ext)
-            print "Creating new media"
-        bm.name = name
-        bm.part_name = part_name
-        bm.index = index
-        bm.file.save(None, ExistingFile(path))
-        bm.save(parts_count=parts_count)
index 089dd0f..c79f614 100644 (file)
@@ -1,9 +1,6 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from __future__ import print_function, unicode_literals
-
 from django.conf import settings
 from django.core.management.base import BaseCommand
 
 from django.conf import settings
 from django.core.management.base import BaseCommand
 
index d13b120..bd00562 100644 (file)
@@ -1,15 +1,17 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.core.management import BaseCommand
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.core.management import BaseCommand
-
 from catalogue.models import Tag
 
 
 class Command(BaseCommand):
     help = "Update description for given tag."
 from catalogue.models import Tag
 
 
 class Command(BaseCommand):
     help = "Update description for given tag."
-    args = 'category slug description_filename'
+
+    def add_arguments(self, parser):
+        parser.add_argument('category')
+        parser.add_argument('slug')
+        parser.add_argument('description_filename')
 
     def handle(self, category, slug, description_filename, **options):
         tag = Tag.objects.get(category=category, slug=slug)
 
     def handle(self, category, slug, description_filename, **options):
         tag = Tag.objects.get(category=category, slug=slug)
index 923a604..206ab73 100644 (file)
@@ -127,7 +127,7 @@ class Book(models.Model):
         verbose_name_plural = _('books')
         app_label = 'catalogue'
 
         verbose_name_plural = _('books')
         app_label = 'catalogue'
 
-    def __unicode__(self):
+    def __str__(self):
         return self.title
 
     def get_initial(self):
         return self.title
 
     def get_initial(self):
@@ -188,7 +188,7 @@ class Book(models.Model):
         from sortify import sortify
 
         self.sort_key = sortify(self.title)[:120]
         from sortify import sortify
 
         self.sort_key = sortify(self.title)[:120]
-        self.title = unicode(self.title)  # ???
+        self.title = str(self.title)  # ???
 
         try:
             author = self.authors().first().sort_key
 
         try:
             author = self.authors().first().sort_key
@@ -401,7 +401,7 @@ class Book(models.Model):
                 index.index_tags()
             if commit:
                 index.index.commit()
                 index.index_tags()
             if commit:
                 index.index.commit()
-        except Exception, e:
+        except Exception as e:
             index.index.rollback()
             raise e
 
             index.index.rollback()
             raise e
 
@@ -672,7 +672,7 @@ class Book(models.Model):
 
     def publisher(self):
         publisher = self.extra_info['publisher']
 
     def publisher(self):
         publisher = self.extra_info['publisher']
-        if isinstance(publisher, basestring):
+        if isinstance(publisher, str):
             return publisher
         elif isinstance(publisher, list):
             return ', '.join(publisher)
             return publisher
         elif isinstance(publisher, list):
             return ', '.join(publisher)
index 407c419..957e982 100644 (file)
@@ -38,7 +38,7 @@ class BookMedia(models.Model):
     book = models.ForeignKey('Book', related_name='media')
     source_sha1 = models.CharField(null=True, blank=True, max_length=40, editable=False)
 
     book = models.ForeignKey('Book', related_name='media')
     source_sha1 = models.CharField(null=True, blank=True, max_length=40, editable=False)
 
-    def __unicode__(self):
+    def __str__(self):
         return "%s (%s)" % (self.name, self.file.name.split("/")[-1])
 
     class Meta:
         return "%s (%s)" % (self.name, self.file.name.split("/")[-1])
 
     class Meta:
@@ -77,7 +77,7 @@ class BookMedia(models.Model):
         remove_zip("%s_%s" % (self.book.slug, self.type))
 
         extra_info = self.extra_info
         remove_zip("%s_%s" % (self.book.slug, self.type))
 
         extra_info = self.extra_info
-        if isinstance(extra_info, basestring):
+        if isinstance(extra_info, str):
             # Walkaround for weird jsonfield 'no-decode' optimization.
             extra_info = json.loads(extra_info)
         extra_info.update(self.read_meta())
             # Walkaround for weird jsonfield 'no-decode' optimization.
             extra_info = json.loads(extra_info)
         extra_info.update(self.read_meta())
index b765abe..3c4b475 100644 (file)
@@ -24,7 +24,7 @@ class Collection(models.Model):
         verbose_name_plural = _('collections')
         app_label = 'catalogue'
 
         verbose_name_plural = _('collections')
         app_label = 'catalogue'
 
-    def __unicode__(self):
+    def __str__(self):
         return self.title
 
     def get_initial(self):
         return self.title
 
     def get_initial(self):
index bcf5254..f36850d 100644 (file)
@@ -17,7 +17,7 @@ class Source(models.Model):
         verbose_name_plural = _('sources')
         app_label = 'catalogue'
 
         verbose_name_plural = _('sources')
         app_label = 'catalogue'
 
-    def __unicode__(self):
+    def __str__(self):
         return self.netloc
 
     def save(self, *args, **kwargs):
         return self.netloc
 
     def save(self, *args, **kwargs):
index 31da256..1b41601 100644 (file)
@@ -43,7 +43,7 @@ class TagRelation(models.Model):
         db_table = 'catalogue_tag_relation'
         unique_together = (('tag', 'content_type', 'object_id'),)
 
         db_table = 'catalogue_tag_relation'
         unique_together = (('tag', 'content_type', 'object_id'),)
 
-    def __unicode__(self):
+    def __str__(self):
         try:
             return u'%s [%s]' % (self.content_type.get_object_for_this_type(pk=self.object_id), self.tag)
         except ObjectDoesNotExist:
         try:
             return u'%s [%s]' % (self.content_type.get_object_for_this_type(pk=self.object_id), self.tag)
         except ObjectDoesNotExist:
@@ -92,7 +92,7 @@ class Tag(models.Model):
         'polka': 'set',
         'obiekt': 'thing',
     }
         'polka': 'set',
         'obiekt': 'thing',
     }
-    categories_dict = dict((item[::-1] for item in categories_rev.iteritems()))
+    categories_dict = dict((item[::-1] for item in categories_rev.items()))
 
     class Meta:
         ordering = ('sort_key',)
 
     class Meta:
         ordering = ('sort_key',)
@@ -155,7 +155,7 @@ class Tag(models.Model):
         flush_ssi_includes([
             '/katalog/%s.json' % lang for lang in languages])
 
         flush_ssi_includes([
             '/katalog/%s.json' % lang for lang in languages])
 
-    def __unicode__(self):
+    def __str__(self):
         return self.name
 
     def __repr__(self):
         return self.name
 
     def __repr__(self):
@@ -234,7 +234,7 @@ class Tag(models.Model):
         for field_name, category in categories:
             try:
                 tag_names = getattr(info, field_name)
         for field_name, category in categories:
             try:
                 tag_names = getattr(info, field_name)
-            except KeyError:
+            except (AttributeError, KeyError):  # TODO: shouldn't be KeyError here at all.
                 try:
                     tag_names = [getattr(info, category)]
                 except KeyError:
                 try:
                     tag_names = [getattr(info, category)]
                 except KeyError:
@@ -250,7 +250,7 @@ class Tag(models.Model):
                     # Allow creating new tag, if it's in default language.
                     tag, created = Tag.objects.get_or_create(slug=slugify(tag_name), category=category)
                     if created:
                     # Allow creating new tag, if it's in default language.
                     tag, created = Tag.objects.get_or_create(slug=slugify(tag_name), category=category)
                     if created:
-                        tag_name = unicode(tag_name)
+                        tag_name = str(tag_name)
                         tag.name = tag_name
                         setattr(tag, "name_%s" % lang, tag_name)
                         tag.sort_key = sortify(tag_sort_key.lower())
                         tag.name = tag_name
                         setattr(tag, "name_%s" % lang, tag_name)
                         tag.sort_key = sortify(tag_sort_key.lower())
index 28d84be..d3d6ec3 100644 (file)
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
index 265897f..a7b67ae 100644 (file)
@@ -28,8 +28,8 @@ def index_book(book_id, book_info=None, **kwargs):
     from catalogue.models import Book
     try:
         return Book.objects.get(id=book_id).search_index(book_info, **kwargs)
     from catalogue.models import Book
     try:
         return Book.objects.get(id=book_id).search_index(book_info, **kwargs)
-    except Exception, e:
-        print "Exception during index: %s" % e
+    except Exception as e:
+        print("Exception during index: %s" % e)
         print_exc()
         raise e
 
         print_exc()
         raise e
 
@@ -56,7 +56,7 @@ def build_custom_pdf(book_id, customizations, file_name, waiter_id=None):
                 morefloats=settings.LIBRARIAN_PDF_MOREFLOATS,
                 ilustr_path=gallery_path(wldoc.book_info.url.slug),
                 **kwargs)
                 morefloats=settings.LIBRARIAN_PDF_MOREFLOATS,
                 ilustr_path=gallery_path(wldoc.book_info.url.slug),
                 **kwargs)
-            DefaultStorage().save(file_name, File(open(pdf.get_filename())))
+            DefaultStorage().save(file_name, File(open(pdf.get_filename(), 'rb')))
     finally:
         if waiter_id is not None:
             WaitedFile.objects.filter(pk=waiter_id).delete()
     finally:
         if waiter_id is not None:
             WaitedFile.objects.filter(pk=waiter_id).delete()
index 263f12c..70676dd 100644 (file)
@@ -3,7 +3,7 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from random import randint, random
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from random import randint, random
-from urlparse import urlparse
+from urllib.parse import urlparse
 from django.contrib.contenttypes.models import ContentType
 
 from django.conf import settings
 from django.contrib.contenttypes.models import ContentType
 
 from django.conf import settings
@@ -93,39 +93,39 @@ def title_from_tags(tags):
     # Specjalny przypadek "Twórczość w pozytywizmie", wtedy gdy tylko epoka
     # jest wybrana przez użytkownika
     if 'epoch' in self and len(self) == 1:
     # Specjalny przypadek "Twórczość w pozytywizmie", wtedy gdy tylko epoka
     # jest wybrana przez użytkownika
     if 'epoch' in self and len(self) == 1:
-        text = u'Twórczość w %s' % flection.get_case(unicode(self['epoch']), u'miejscownik')
+        text = u'Twórczość w %s' % flection.get_case(str(self['epoch']), u'miejscownik')
         return capfirst(text)
 
     # Specjalny przypadek "Dramat w twórczości Sofoklesa", wtedy gdy podane
     # są tylko rodzaj literacki i autor
     if 'kind' in self and 'author' in self and len(self) == 2:
         text = u'%s w twórczości %s' % (
         return capfirst(text)
 
     # Specjalny przypadek "Dramat w twórczości Sofoklesa", wtedy gdy podane
     # są tylko rodzaj literacki i autor
     if 'kind' in self and 'author' in self and len(self) == 2:
         text = u'%s w twórczości %s' % (
-            unicode(self['kind']), flection.get_case(unicode(self['author']), u'dopełniacz'))
+            str(self['kind']), flection.get_case(str(self['author']), u'dopełniacz'))
         return capfirst(text)
 
     # Przypadki ogólniejsze
     if 'theme' in self:
         return capfirst(text)
 
     # Przypadki ogólniejsze
     if 'theme' in self:
-        title += u'Motyw %s' % unicode(self['theme'])
+        title += u'Motyw %s' % str(self['theme'])
 
     if 'genre' in self:
         if 'theme' in self:
 
     if 'genre' in self:
         if 'theme' in self:
-            title += u' w %s' % flection.get_case(unicode(self['genre']), u'miejscownik')
+            title += u' w %s' % flection.get_case(str(self['genre']), u'miejscownik')
         else:
         else:
-            title += unicode(self['genre'])
+            title += str(self['genre'])
 
     if 'kind' in self or 'author' in self or 'epoch' in self:
         if 'genre' in self or 'theme' in self:
             if 'kind' in self:
 
     if 'kind' in self or 'author' in self or 'epoch' in self:
         if 'genre' in self or 'theme' in self:
             if 'kind' in self:
-                title += u' w %s ' % flection.get_case(unicode(self['kind']), u'miejscownik')
+                title += u' w %s ' % flection.get_case(str(self['kind']), u'miejscownik')
             else:
                 title += u' w twórczości '
         else:
             else:
                 title += u' w twórczości '
         else:
-            title += u'%s ' % unicode(self.get('kind', u'twórczość'))
+            title += u'%s ' % str(self.get('kind', u'twórczość'))
 
     if 'author' in self:
 
     if 'author' in self:
-        title += flection.get_case(unicode(self['author']), u'dopełniacz')
+        title += flection.get_case(str(self['author']), u'dopełniacz')
     elif 'epoch' in self:
     elif 'epoch' in self:
-        title += flection.get_case(unicode(self['epoch']), u'dopełniacz')
+        title += flection.get_case(str(self['epoch']), u'dopełniacz')
 
     return capfirst(title)
 
 
     return capfirst(title)
 
index 8c76b89..7ed3536 100644 (file)
@@ -58,25 +58,25 @@ class BookInfoStub(object):
     def __getattr__(self, key):
         try:
             return self.__dict[key]
     def __getattr__(self, key):
         try:
             return self.__dict[key]
-        except KeyError:
+        except KeyError as e:
             if key in self._empty_fields:
                 return None
             elif key in self._salias:
                 return [getattr(self, self._salias[key])]
             else:
             if key in self._empty_fields:
                 return None
             elif key in self._salias:
                 return [getattr(self, self._salias[key])]
             else:
-                raise
+                raise AttributeError(e)
 
     def to_dict(self):
 
     def to_dict(self):
-        return dict((key, unicode(value)) for key, value in self.__dict.items())
+        return dict((key, str(value)) for key, value in self.__dict.items())
 
 
 def info_args(title, language=None):
     """ generate some keywords for comfortable BookInfoCreation  """
 
 
 def info_args(title, language=None):
     """ generate some keywords for comfortable BookInfoCreation  """
-    slug = unicode(slugify(title))
+    slug = str(slugify(title))
     if language is None:
         language = u'pol'
     return {
     if language is None:
         language = u'pol'
     return {
-        'title': unicode(title),
+        'title': str(title),
         'url': WLURI.from_slug(slug),
         'about': u"http://wolnelektury.pl/example/URI/%s" % slug,
         'language': language,
         'url': WLURI.from_slug(slug),
         'about': u"http://wolnelektury.pl/example/URI/%s" % slug,
         'language': language,
index 263e48d..b744f96 100644 (file)
@@ -15,8 +15,8 @@ class BookMediaTests(WLTestCase):
 
     def setUp(self):
         WLTestCase.setUp(self)
 
     def setUp(self):
         WLTestCase.setUp(self)
-        self.file = ContentFile('X')
-        self.file2 = ContentFile('Y')
+        self.file = ContentFile(b'X')
+        self.file2 = ContentFile(b'Y')
         self.book = models.Book.objects.create(slug='test-book', title='Test')
 
     def set_title(self, title):
         self.book = models.Book.objects.create(slug='test-book', title='Test')
 
     def set_title(self, title):
@@ -50,7 +50,7 @@ class BookMediaTests(WLTestCase):
         bm.file.save(None, self.file)
         bm.file.save(None, self.file2)
 
         bm.file.save(None, self.file)
         bm.file.save(None, self.file2)
 
-        self.assertEqual(bm.file.read(), 'Y')
+        self.assertEqual(bm.file.read(), b'Y')
         self.assertEqual(basename(bm.file.name), 'some-media.ogg')
 
     @skip('broken, but is it needed?')
         self.assertEqual(basename(bm.file.name), 'some-media.ogg')
 
     @skip('broken, but is it needed?')
@@ -67,8 +67,8 @@ class BookMediaTests(WLTestCase):
         bm2.file.save(None, self.file2)
         self.assertEqual(basename(bm.file.name), 'tytul.ogg')
         self.assertNotEqual(basename(bm2.file.name), 'tytul.ogg')
         bm2.file.save(None, self.file2)
         self.assertEqual(basename(bm.file.name), 'tytul.ogg')
         self.assertNotEqual(basename(bm2.file.name), 'tytul.ogg')
-        self.assertEqual(bm.file.read(), 'X')
-        self.assertEqual(bm2.file.read(), 'Y')
+        self.assertEqual(bm.file.read(), b'X')
+        self.assertEqual(bm2.file.read(), b'Y')
 
     def test_change_name(self):
         """
 
     def test_change_name(self):
         """
@@ -81,7 +81,7 @@ class BookMediaTests(WLTestCase):
         self.set_title("Other Title")
         bm.save()
         self.assertEqual(basename(bm.file.name), 'other-title.ogg')
         self.set_title("Other Title")
         bm.save()
         self.assertEqual(basename(bm.file.name), 'other-title.ogg')
-        self.assertEqual(bm.file.read(), 'X')
+        self.assertEqual(bm.file.read(), b'X')
 
     @skip('broken, but is it needed?')
     def test_change_name_no_clobber(self):
 
     @skip('broken, but is it needed?')
     def test_change_name_no_clobber(self):
@@ -99,8 +99,8 @@ class BookMediaTests(WLTestCase):
         self.set_title("Title")
         bm2.save()
         self.assertNotEqual(basename(bm2.file.name), 'title.ogg')
         self.set_title("Title")
         bm2.save()
         self.assertNotEqual(basename(bm2.file.name), 'title.ogg')
-        self.assertEqual(bm.file.read(), 'X')
-        self.assertEqual(bm2.file.read(), 'Y')
+        self.assertEqual(bm.file.read(), b'X')
+        self.assertEqual(bm2.file.read(), b'Y')
 
     def test_zip_audiobooks(self):
         paths = [
 
     def test_zip_audiobooks(self):
         paths = [
index 8c5d047..59a05ed 100644 (file)
@@ -5,7 +5,7 @@
 from django.core.files.base import ContentFile
 from catalogue.test_utils import BookInfoStub, PersonStub, info_args, WLTestCase
 from catalogue.models import Book
 from django.core.files.base import ContentFile
 from catalogue.test_utils import BookInfoStub, PersonStub, info_args, WLTestCase
 from catalogue.models import Book
-from mock import patch
+from unittest.mock import patch
 
 
 class CoverTests(WLTestCase):
 
 
 class CoverTests(WLTestCase):
index d5aa72c..7717782 100644 (file)
@@ -29,7 +29,7 @@ class BooksByTagTests(WLTestCase):
                                         parts=[self.child_info.url],
                                         **info_args("Parent"))
 
                                         parts=[self.child_info.url],
                                         **info_args("Parent"))
 
-        self.book_file = ContentFile('<utwor />')
+        self.book_file = ContentFile(b'<utwor />')
 
     def test_nonexistent_tag(self):
         """ Looking for a non-existent tag should yield 404 """
 
     def test_nonexistent_tag(self):
         """ Looking for a non-existent tag should yield 404 """
@@ -95,8 +95,10 @@ class TagRelatedTagsTests(WLTestCase):
                     Ala ma kota
                 <end id="m01" />
                 </akap></opowiadanie></utwor>
                     Ala ma kota
                 <end id="m01" />
                 </akap></opowiadanie></utwor>
-                """ % info.title.encode('utf-8')
-            book = models.Book.from_text_and_meta(ContentFile(book_text), info)
+                """ % info.title
+            book = models.Book.from_text_and_meta(
+                    ContentFile(book_text.encode('utf-8')),
+                    info)
             book.save()
 
         tag_empty = models.Tag(name='Empty tag', slug='empty', category='author')
             book.save()
 
         tag_empty = models.Tag(name='Empty tag', slug='empty', category='author')
@@ -157,7 +159,7 @@ class TagRelatedTagsTests(WLTestCase):
         self.assertTrue(
             ('ChildKind', 2) in [(tag.name, tag.count) for tag in cats['kind']],
             'wrong related kind tags on tag page, got: ' +
         self.assertTrue(
             ('ChildKind', 2) in [(tag.name, tag.count) for tag in cats['kind']],
             'wrong related kind tags on tag page, got: ' +
-            unicode([(tag.name, tag.count) for tag in cats['kind']]))
+            str([(tag.name, tag.count) for tag in cats['kind']]))
 
         # all occurencies of theme should be counted
         self.assertTrue(('Theme', 4) in [(tag.name, tag.count) for tag in cats['theme']],
 
         # all occurencies of theme should be counted
         self.assertTrue(('Theme', 4) in [(tag.name, tag.count) for tag in cats['theme']],
@@ -171,7 +173,7 @@ class TagRelatedTagsTests(WLTestCase):
         cats = self.client.get('/katalog/gatunek/childgenre/').context['categories']
         self.assertTrue(('Epoch', 2) in [(tag.name, tag.count) for tag in cats['epoch']],
                         'wrong related kind tags on tag page, got: ' +
         cats = self.client.get('/katalog/gatunek/childgenre/').context['categories']
         self.assertTrue(('Epoch', 2) in [(tag.name, tag.count) for tag in cats['epoch']],
                         'wrong related kind tags on tag page, got: ' +
-                        unicode([(tag.name, tag.count) for tag in cats['epoch']]))
+                        str([(tag.name, tag.count) for tag in cats['epoch']]))
 
 
 class CleanTagRelationTests(WLTestCase):
 
 
 class CleanTagRelationTests(WLTestCase):
@@ -187,7 +189,9 @@ class CleanTagRelationTests(WLTestCase):
             <end id="m01" />
             </akap></opowiadanie></utwor>
             """
             <end id="m01" />
             </akap></opowiadanie></utwor>
             """
-        self.book = models.Book.from_text_and_meta(ContentFile(book_text), book_info)
+        self.book = models.Book.from_text_and_meta(
+                ContentFile(book_text.encode('utf-8')),
+                book_info)
 
     @skip('Not implemented and not priority')
     def test_delete_objects(self):
 
     @skip('Not implemented and not priority')
     def test_delete_objects(self):
@@ -228,7 +232,9 @@ class TestIdenticalTag(WLTestCase):
 
     def test_book_tags(self):
         """ there should be all related tags in relevant categories """
 
     def test_book_tags(self):
         """ there should be all related tags in relevant categories """
-        book = models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info)
+        book = models.Book.from_text_and_meta(
+                ContentFile(self.book_text.encode('utf-8')),
+                self.book_info)
 
         related_themes = book.related_themes()
         for category in 'author', 'kind', 'genre', 'epoch':
 
         related_themes = book.related_themes()
         for category in 'author', 'kind', 'genre', 'epoch':
@@ -237,9 +243,11 @@ class TestIdenticalTag(WLTestCase):
         self.assertTrue('tag' in [tag.slug for tag in related_themes])
 
     def test_qualified_url(self):
         self.assertTrue('tag' in [tag.slug for tag in related_themes])
 
     def test_qualified_url(self):
-        models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info)
+        models.Book.from_text_and_meta(
+                ContentFile(self.book_text.encode('utf-8')),
+                self.book_info)
         categories = {'author': 'autor', 'theme': 'motyw', 'epoch': 'epoka', 'kind': 'rodzaj', 'genre': 'gatunek'}
         categories = {'author': 'autor', 'theme': 'motyw', 'epoch': 'epoka', 'kind': 'rodzaj', 'genre': 'gatunek'}
-        for cat, localcat in categories.iteritems():
+        for cat, localcat in categories.items():
             context = self.client.get('/katalog/%s/tag/' % localcat).context
             self.assertEqual(1, len(context['object_list']))
             self.assertNotEqual({}, context['categories'])
             context = self.client.get('/katalog/%s/tag/' % localcat).context
             self.assertEqual(1, len(context['object_list']))
             self.assertNotEqual({}, context['categories'])
@@ -267,8 +275,10 @@ class BookTagsTests(WLTestCase):
                     Ala ma kota
                 <end id="m01" />
                 </akap></opowiadanie></utwor>
                     Ala ma kota
                 <end id="m01" />
                 </akap></opowiadanie></utwor>
-                """ % info.title.encode('utf-8')
-            models.Book.from_text_and_meta(ContentFile(book_text), info)
+                """ % info.title
+            models.Book.from_text_and_meta(
+                    ContentFile(book_text.encode('utf-8')),
+                    info)
 
     def test_book_tags(self):
         """ book should have own tags and whole tree's themes """
 
     def test_book_tags(self):
         """ book should have own tags and whole tree's themes """
index 4946143..aec44bc 100644 (file)
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
index 380ec8e..ec938ea 100644 (file)
@@ -18,7 +18,7 @@ from django.conf import settings
 from django.core.files.storage import DefaultStorage
 from django.core.files.uploadedfile import UploadedFile
 from django.http import HttpResponse
 from django.core.files.storage import DefaultStorage
 from django.core.files.uploadedfile import UploadedFile
 from django.http import HttpResponse
-from django.utils.encoding import force_unicode
+from django.utils.encoding import force_text
 
 from reporting.utils import read_chunks
 
 
 from reporting.utils import read_chunks
 
@@ -27,14 +27,19 @@ if hasattr(random, 'SystemRandom'):
     randrange = random.SystemRandom().randrange
 else:
     randrange = random.randrange
     randrange = random.SystemRandom().randrange
 else:
     randrange = random.randrange
-MAX_SESSION_KEY = 18446744073709551616L     # 2 << 63
+MAX_SESSION_KEY = 18446744073709551616     # 2 << 63
 
 
 def get_random_hash(seed):
 
 
 def get_random_hash(seed):
-    sha_digest = hashlib.sha1('%s%s%s%s' % (
-        randrange(0, MAX_SESSION_KEY), time.time(), unicode(seed).encode('utf-8', 'replace'), settings.SECRET_KEY)
-    ).digest()
-    return urlsafe_b64encode(sha_digest).replace('=', '').replace('_', '-').lower()
+    sha_digest = hashlib.sha1((
+        '%s%s%s%s' % (
+            randrange(0, MAX_SESSION_KEY),
+            time.time(),
+            str(seed).encode('utf-8', 'replace'),
+            settings.SECRET_KEY
+        )
+    ).encode('utf-8')).digest()
+    return urlsafe_b64encode(sha_digest).decode('latin1').replace('=', '').replace('_', '-').lower()
 
 
 def split_tags(*tag_lists):
 
 
 def split_tags(*tag_lists):
@@ -241,7 +246,7 @@ def truncate_html_words(s, num, end_text='...'):
 
     This is just a version of django.utils.text.truncate_html_words with no space before the end_text.
     """
 
     This is just a version of django.utils.text.truncate_html_words with no space before the end_text.
     """
-    s = force_unicode(s)
+    s = force_text(s)
     length = int(num)
     if length <= 0:
         return u''
     length = int(num)
     if length <= 0:
         return u''
index 1f2db19..5beb6dc 100644 (file)
@@ -171,10 +171,10 @@ def analyse_tags(request, tag_str):
             raise ResponseInstead(pdcounter_views.author_detail(request, chunks[1]))
         else:
             raise Http404
             raise ResponseInstead(pdcounter_views.author_detail(request, chunks[1]))
         else:
             raise Http404
-    except Tag.MultipleObjectsReturned, e:
+    except Tag.MultipleObjectsReturned as e:
         # Ask the user to disambiguate
         raise ResponseInstead(differentiate_tags(request, e.tags, e.ambiguous_slugs))
         # Ask the user to disambiguate
         raise ResponseInstead(differentiate_tags(request, e.tags, e.ambiguous_slugs))
-    except Tag.UrlDeprecationWarning, e:
+    except Tag.UrlDeprecationWarning as e:
         raise ResponseInstead(HttpResponsePermanentRedirect(
             reverse('tagged_object_list', args=['/'.join(tag.url_chunk for tag in e.tags)])))
 
         raise ResponseInstead(HttpResponsePermanentRedirect(
             reverse('tagged_object_list', args=['/'.join(tag.url_chunk for tag in e.tags)])))
 
index 37b9f60..a9f6e7f 100644 (file)
@@ -23,7 +23,7 @@ class Chunk(models.Model):
         verbose_name = _('chunk')
         verbose_name_plural = _('chunks')
 
         verbose_name = _('chunk')
         verbose_name_plural = _('chunks')
 
-    def __unicode__(self):
+    def __str__(self):
         return self.key
 
     def save(self, *args, **kwargs):
         return self.key
 
     def save(self, *args, **kwargs):
@@ -45,5 +45,5 @@ class Attachment(models.Model):
         ordering = ('key',)
         verbose_name, verbose_name_plural = _('attachment'), _('attachments')
 
         ordering = ('key',)
         verbose_name, verbose_name_plural = _('attachment'), _('attachments')
 
-    def __unicode__(self):
+    def __str__(self):
         return self.key
         return self.key
index bfa4330..76cc71e 100644 (file)
@@ -20,6 +20,7 @@ class ScheduleAdmin(admin.ModelAdmin):
     list_search = ['email']
     list_filter = ['is_active', 'is_cancelled']
     date_hierarchy = 'started_at'
     list_search = ['email']
     list_filter = ['is_active', 'is_cancelled']
     date_hierarchy = 'started_at'
+    raw_id_fields = ['membership']
     inlines = [PaymentInline]
 
 admin.site.register(models.Schedule, ScheduleAdmin)
     inlines = [PaymentInline]
 
 admin.site.register(models.Schedule, ScheduleAdmin)
@@ -32,7 +33,9 @@ admin.site.register(models.Payment, PaymentAdmin)
 
 
 class MembershipAdmin(admin.ModelAdmin):
 
 
 class MembershipAdmin(admin.ModelAdmin):
-    pass
+    list_display = ['user']
+    raw_id_fields = ['user']
+    search_fields = ['user__username', 'user__email']
 
 admin.site.register(models.Membership, MembershipAdmin)
 
 
 admin.site.register(models.Membership, MembershipAdmin)
 
index adf8959..c5d5781 100644 (file)
@@ -1,7 +1,6 @@
 # -*- coding: utf-8
 from django import forms
 from . import models
 # -*- coding: utf-8
 from django import forms
 from . import models
-from . import widgets
 from .payment_methods import method_by_slug 
 
 
 from .payment_methods import method_by_slug 
 
 
index a8b4089..1777174 100644 (file)
@@ -31,7 +31,7 @@ class Plan(models.Model):
         verbose_name = _('plan')
         verbose_name_plural = _('plans')
 
         verbose_name = _('plan')
         verbose_name_plural = _('plans')
 
-    def __unicode__(self):
+    def __str__(self):
         return "%s %s" % (self.min_amount, self.get_interval_display())
     
     class Meta:
         return "%s %s" % (self.min_amount, self.get_interval_display())
     
     class Meta:
@@ -74,7 +74,7 @@ class Schedule(models.Model):
         verbose_name = _('schedule')
         verbose_name_plural = _('schedules')
 
         verbose_name = _('schedule')
         verbose_name_plural = _('schedules')
 
-    def __unicode__(self):
+    def __str__(self):
         return self.key
 
     def save(self, *args, **kwargs):
         return self.key
 
     def save(self, *args, **kwargs):
@@ -109,7 +109,7 @@ class Payment(models.Model):
         verbose_name = _('payment')
         verbose_name_plural = _('payments')
 
         verbose_name = _('payment')
         verbose_name_plural = _('payments')
 
-    def __unicode__(self):
+    def __str__(self):
         return "%s %s" % (self.schedule, self.payed_at)
 
 
         return "%s %s" % (self.schedule, self.payed_at)
 
 
@@ -122,8 +122,8 @@ class Membership(models.Model):
         verbose_name = _('membership')
         verbose_name_plural = _('memberships')
 
         verbose_name = _('membership')
         verbose_name_plural = _('memberships')
 
-    def __unicode__(self):
-        return u'tow. ' + unicode(self.user)
+    def __str__(self):
+        return u'tow. ' + str(self.user)
 
 
 class ReminderEmail(models.Model):
 
 
 class ReminderEmail(models.Model):
@@ -136,7 +136,7 @@ class ReminderEmail(models.Model):
         verbose_name_plural = _('reminder emails')
         ordering = ['days_before']
 
         verbose_name_plural = _('reminder emails')
         ordering = ['days_before']
 
-    def __unicode__(self):
+    def __str__(self):
         if self.days_before >= 0:
             return ungettext('a day before expiration', '%d days before expiration', n=self.days_before)
         else:
         if self.days_before >= 0:
             return ungettext('a day before expiration', '%d days before expiration', n=self.days_before)
         else:
index 4109509..a059433 100644 (file)
@@ -157,11 +157,11 @@ def extract_view(request, form_tag, extract_type_slug):
                 for key in keys:
                     if key not in record:
                         record[key] = ''
                 for key in keys:
                     if key not in record:
                         record[key] = ''
-                    if isinstance(record[key], basestring):
+                    if isinstance(record[key], str):
                         pass
                     elif isinstance(record[key], bool):
                         record[key] = 'tak' if record[key] else 'nie'
                         pass
                     elif isinstance(record[key], bool):
                         record[key] = 'tak' if record[key] else 'nie'
-                    elif isinstance(record[key], (list, tuple)) and all(isinstance(v, basestring) for v in record[key]):
+                    elif isinstance(record[key], (list, tuple)) and all(isinstance(v, str) for v in record[key]):
                         record[key] = ', '.join(record[key])
                     else:
                         record[key] = json.dumps(record[key])
                         record[key] = ', '.join(record[key])
                     else:
                         record[key] = json.dumps(record[key])
index 0ab8201..e44bd9b 100644 (file)
@@ -2,7 +2,7 @@
 import yaml
 from hashlib import sha1
 from django.db import models
 import yaml
 from hashlib import sha1
 from django.db import models
-from django.utils.encoding import smart_unicode
+from django.utils.encoding import smart_text
 from django.utils.translation import ugettext_lazy as _
 from jsonfield import JSONField
 from . import app_settings
 from django.utils.translation import ugettext_lazy as _
 from jsonfield import JSONField
 from . import app_settings
@@ -20,7 +20,7 @@ class Contact(models.Model):
         if type(value) in (tuple, list, dict):
             value = yaml.safe_dump(value, allow_unicode=True, default_flow_style=False)
             if for_html:
         if type(value) in (tuple, list, dict):
             value = yaml.safe_dump(value, allow_unicode=True, default_flow_style=False)
             if for_html:
-                value = smart_unicode(value).replace(u" ", unichr(160))
+                value = smart_text(value).replace(u" ", unichr(160))
         return value
 
     class Meta:
         return value
 
     class Meta:
@@ -28,11 +28,11 @@ class Contact(models.Model):
         verbose_name = _('submitted form')
         verbose_name_plural = _('submitted forms')
 
         verbose_name = _('submitted form')
         verbose_name_plural = _('submitted forms')
 
-    def __unicode__(self):
-        return unicode(self.created_at)
+    def __str__(self):
+        return str(self.created_at)
 
     def digest(self):
 
     def digest(self):
-        serialized_body = ';'.join(sorted('%s:%s' % item for item in self.body.iteritems()))
+        serialized_body = ';'.join(sorted('%s:%s' % item for item in self.body.items()))
         data = '%s%s%s%s%s' % (self.id, self.contact, serialized_body, self.ip, self.form_tag)
         return sha1(data).hexdigest()
 
         data = '%s%s%s%s%s' % (self.id, self.contact, serialized_body, self.ip, self.form_tag)
         return sha1(data).hexdigest()
 
index 7ec0505..0f8aad9 100644 (file)
@@ -1,5 +1,4 @@
-# -*- coding: utf-8 -*-
-from urllib import unquote
+from urllib.parse import unquote
 
 from datetime import datetime
 from django.contrib.auth.decorators import permission_required
 
 from datetime import datetime
 from django.contrib.auth.decorators import permission_required
@@ -40,7 +39,7 @@ def form(request, form_tag, force_enabled=False):
     if request.method == 'POST':
         formsets = {
             prefix: formset_class(request.POST, request.FILES, prefix=prefix)
     if request.method == 'POST':
         formsets = {
             prefix: formset_class(request.POST, request.FILES, prefix=prefix)
-            for prefix, formset_class in formset_classes.iteritems()}
+            for prefix, formset_class in formset_classes.items()}
         if form.is_valid() and all(formset.is_valid() for formset in formsets.itervalues()):
             contact = form.save(request, formsets.values())
             if form.result_page:
         if form.is_valid() and all(formset.is_valid() for formset in formsets.itervalues()):
             contact = form.save(request, formsets.values())
             if form.result_page:
@@ -48,7 +47,7 @@ def form(request, form_tag, force_enabled=False):
             else:
                 return redirect('contact_thanks', form_tag)
     else:
             else:
                 return redirect('contact_thanks', form_tag)
     else:
-        formsets = {prefix: formset_class(prefix=prefix) for prefix, formset_class in formset_classes.iteritems()}
+        formsets = {prefix: formset_class(prefix=prefix) for prefix, formset_class in formset_classes.items()}
 
     return render(
         request, ['contact/%s/form.html' % form_tag, 'contact/form.html'],
 
     return render(
         request, ['contact/%s/form.html' % form_tag, 'contact/form.html'],
index 7df6ddc..c8e086f 100644 (file)
@@ -19,7 +19,7 @@ class Qualifier(models.Model):
     class Meta:
         ordering = ['qualifier']
 
     class Meta:
         ordering = ['qualifier']
 
-    def __unicode__(self):
+    def __str__(self):
         return self.name or self.qualifier
 
 
         return self.name or self.qualifier
 
 
index 342c963..f622374 100755 (executable)
@@ -1,17 +1,17 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from optparse import make_option
 from django.core.management.base import BaseCommand
 
 
 class Command(BaseCommand):
 from django.core.management.base import BaseCommand
 
 
 class Command(BaseCommand):
-    option_list = BaseCommand.option_list + (
-        make_option('-q', '--quiet', action='store_false', dest='verbose', default=True, help='Suppress output'),
-    )
     help = 'Sends relevant funding notifications.'
 
     help = 'Sends relevant funding notifications.'
 
+    def add_arguments(self, parser):
+        parser.add_argument(
+            '-q', '--quiet', action='store_false', dest='verbose',
+            default=True, help='Suppress output')
+
     def handle(self, **options):
 
         from datetime import date, timedelta
     def handle(self, **options):
 
         from datetime import date, timedelta
@@ -24,7 +24,7 @@ class Command(BaseCommand):
 
         for offer in Offer.past().filter(notified_end=None):
             if verbose:
 
         for offer in Offer.past().filter(notified_end=None):
             if verbose:
-                print 'Notify end:', offer
+                print('Notify end:', offer)
             # The 'WL fund' list needs to be updated.
             caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
             offer.flush_includes()
             # The 'WL fund' list needs to be updated.
             caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
             offer.flush_includes()
@@ -35,5 +35,5 @@ class Command(BaseCommand):
                 current.end <= date.today() + timedelta(app_settings.DAYS_NEAR - 1) and
                 not current.notified_near):
             if verbose:
                 current.end <= date.today() + timedelta(app_settings.DAYS_NEAR - 1) and
                 not current.notified_near):
             if verbose:
-                print 'Notify near:', current
+                print('Notify near:', current)
             current.notify_near()
             current.notify_near()
index b126c76..6ca0261 100644 (file)
@@ -3,7 +3,7 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from datetime import date, datetime
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from datetime import date, datetime
-from urllib import urlencode
+from urllib.parse import urlencode
 from django.conf import settings
 from django.contrib.sites.models import Site
 from django.core.urlresolvers import reverse
 from django.conf import settings
 from django.contrib.sites.models import Site
 from django.core.urlresolvers import reverse
@@ -48,7 +48,7 @@ class Offer(models.Model):
         verbose_name_plural = _('offers')
         ordering = ['-end']
 
         verbose_name_plural = _('offers')
         ordering = ['-end']
 
-    def __unicode__(self):
+    def __str__(self):
         return u"%s - %s" % (self.author, self.title)
 
     def get_absolute_url(self):
         return u"%s - %s" % (self.author, self.title)
 
     def get_absolute_url(self):
@@ -218,7 +218,7 @@ class Perk(models.Model):
         verbose_name_plural = _('perks')
         ordering = ['-price']
 
         verbose_name_plural = _('perks')
         ordering = ['-price']
 
-    def __unicode__(self):
+    def __str__(self):
         return "%s (%s%s)" % (self.name, self.price, u" for %s" % self.offer if self.offer else "")
 
 
         return "%s (%s%s)" % (self.name, self.price, u" for %s" % self.offer if self.offer else "")
 
 
@@ -248,8 +248,8 @@ class Funding(models.Model):
         """ QuerySet for all completed payments. """
         return cls.objects.exclude(payed_at=None)
 
         """ QuerySet for all completed payments. """
         return cls.objects.exclude(payed_at=None)
 
-    def __unicode__(self):
-        return unicode(self.offer)
+    def __str__(self):
+        return str(self.offer)
 
     def get_absolute_url(self):
         return reverse('funding_funding', args=[self.pk])
 
     def get_absolute_url(self):
         return reverse('funding_funding', args=[self.pk])
@@ -318,8 +318,8 @@ class Spent(models.Model):
         verbose_name_plural = _('money spent on books')
         ordering = ['-timestamp']
 
         verbose_name_plural = _('money spent on books')
         ordering = ['-timestamp']
 
-    def __unicode__(self):
-        return u"Spent: %s" % unicode(self.book)
+    def __str__(self):
+        return u"Spent: %s" % str(self.book)
 
 
 @receiver(getpaid.signals.new_payment_query)
 
 
 @receiver(getpaid.signals.new_payment_query)
index 5808336..e93d816 100644 (file)
@@ -10,8 +10,8 @@ from fnpdjango.utils.text import char_map
 # Punctuation is handled correctly and escaped as needed,
 # with the notable exception of backslash.
 sane_in_payu_title = re.escape(
 # Punctuation is handled correctly and escaped as needed,
 # with the notable exception of backslash.
 sane_in_payu_title = re.escape(
-    string.uppercase +
-    string.lowercase +
+    string.ascii_uppercase +
+    string.ascii_lowercase +
     u'ąćęłńóśźżĄĆĘŁŃÓŚŹŻ' +
     string.digits +
     ' ' +
     u'ąćęłńóśźżĄĆĘŁŃÓŚŹŻ' +
     string.digits +
     ' ' +
@@ -25,4 +25,4 @@ def replace_char(m):
 
 
 def sanitize_payment_title(value):
 
 
 def sanitize_payment_title(value):
-    return re.sub('[^%s]' % sane_in_payu_title, replace_char, unicode(value))
+    return re.sub('[^%s]' % sane_in_payu_title, replace_char, str(value))
index c9c31e7..8999d99 100644 (file)
@@ -20,7 +20,7 @@ class InfoPage(models.Model):
         verbose_name = _('info page')
         verbose_name_plural = _('info pages')
 
         verbose_name = _('info page')
         verbose_name_plural = _('info pages')
 
-    def __unicode__(self):
+    def __str__(self):
         return self.title
 
     @models.permalink
         return self.title
 
     @models.permalink
index 2acc34a..47a72ba 100644 (file)
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 from datetime import date
 # -*- coding: utf-8 -*-
 from datetime import date
-from urllib2 import urlopen
+from urllib.request import urlopen
 
 from django import forms
 from django.utils.translation import ugettext_lazy as _
 
 from django import forms
 from django.utils.translation import ugettext_lazy as _
index 19f3166..386dd1b 100644 (file)
@@ -113,7 +113,7 @@ class Command(BaseCommand):
         for record in ONIXRecord.objects.all():
             xml += self.render_product(record)
         xml += FOOTER
         for record in ONIXRecord.objects.all():
             xml += self.render_product(record)
         xml += FOOTER
-        print xml.encode('utf-8')
+        print(xml.encode('utf-8'))
 
     def render_product(self, record):
         if record.product_form_detail:
 
     def render_product(self, record):
         if record.product_form_detail:
index 796b7aa..85d54ec 100644 (file)
@@ -30,7 +30,7 @@ def parse_date(date_str):
 
 
 def get_descendants(element, tags):
 
 
 def get_descendants(element, tags):
-    if isinstance(tags, basestring):
+    if isinstance(tags, str):
         tags = [tags]
     return element.findall('.//' + '/'.join(ONIXNS(tag) for tag in tags))
 
         tags = [tags]
     return element.findall('.//' + '/'.join(ONIXNS(tag) for tag in tags))
 
@@ -44,14 +44,17 @@ def get_field(element, tags, allow_multiple=False):
 
 class Command(BaseCommand):
     help = "Import data from ONIX."
 
 class Command(BaseCommand):
     help = "Import data from ONIX."
-    args = 'filename'
 
 
-    def handle(self, filename, *args, **options):
+    def add_arguments(self, parser):
+        parser.add_argument('filename')
+
+    def handle(self, **options):
+        filename = options['filename']
         tree = etree.parse(open(filename))
         for product in get_descendants(tree, 'Product'):
             isbn = get_field(product, ['ProductIdentifier', 'IDValue'])
             assert len(isbn) == 13
         tree = etree.parse(open(filename))
         for product in get_descendants(tree, 'Product'):
             isbn = get_field(product, ['ProductIdentifier', 'IDValue'])
             assert len(isbn) == 13
-            pool = ISBNPool.objects.get(prefix__in=[isbn[:i] for i in xrange(8, 11)])
+            pool = ISBNPool.objects.get(prefix__in=[isbn[:i] for i in range(8, 11)])
             contributors = [
                 self.parse_contributor(contributor)
                 for contributor in get_descendants(product, 'Contributor')]
             contributors = [
                 self.parse_contributor(contributor)
                 for contributor in get_descendants(product, 'Contributor')]
@@ -62,7 +65,7 @@ class Command(BaseCommand):
                     get_field(product, ['PublishingDate', 'Date'], allow_multiple=True)),
                 'contributors': contributors,
             }
                     get_field(product, ['PublishingDate', 'Date'], allow_multiple=True)),
                 'contributors': contributors,
             }
-            for field, tag in DIRECT_FIELDS.iteritems():
+            for field, tag in DIRECT_FIELDS.items():
                 record_data[field] = get_field(product, tag) or ''
             record = ONIXRecord.objects.create(**record_data)
             ONIXRecord.objects.filter(pk=record.pk).update(datestamp=parse_date(product.attrib['datestamp']))
                 record_data[field] = get_field(product, tag) or ''
             record = ONIXRecord.objects.create(**record_data)
             ONIXRecord.objects.filter(pk=record.pk).update(datestamp=parse_date(product.attrib['datestamp']))
@@ -78,7 +81,7 @@ class Command(BaseCommand):
         contributor_data = {
             'role': get_field(contributor, 'ContributorRole'),
         }
         contributor_data = {
             'role': get_field(contributor, 'ContributorRole'),
         }
-        for key, value in data.iteritems():
+        for key, value in data.items():
             if value:
                 contributor_data[key] = value
         if contributor_data.get('name') == UNKNOWN:
             if value:
                 contributor_data[key] = value
         if contributor_data.get('name') == UNKNOWN:
index a4fa28a..ca8dbee 100644 (file)
@@ -22,7 +22,7 @@ class ISBNPool(models.Model):
     next_suffix = models.IntegerField()
     purpose = models.CharField(max_length=4, choices=PURPOSE_CHOICES)
 
     next_suffix = models.IntegerField()
     purpose = models.CharField(max_length=4, choices=PURPOSE_CHOICES)
 
-    def __unicode__(self):
+    def __str__(self):
         return self.prefix
 
     @classmethod
         return self.prefix
 
     @classmethod
index 8cf7b5d..8073c75 100644 (file)
@@ -78,7 +78,7 @@ def get_format(record):
     if record.product_form_detail:
         return PRODUCT_FORMATS[record.product_form_detail][0]
     else:
     if record.product_form_detail:
         return PRODUCT_FORMATS[record.product_form_detail][0]
     else:
-        return [key for key, value in PRODUCT_FORMS.iteritems() if value == record.product_form][0]
+        return [key for key, value in PRODUCT_FORMS.items() if value == record.product_form][0]
 
 
 @permission_required('add_onixrecord')
 
 
 @permission_required('add_onixrecord')
index a890f86..bcad601 100644 (file)
@@ -1,10 +1,8 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import re
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import re
-from cPickle import dump
-from optparse import make_option
+from pickle import dump
 
 from django.core.management.base import BaseCommand
 from django.core.management.color import color_style
 
 from django.core.management.base import BaseCommand
 from django.core.management.color import color_style
@@ -17,15 +15,18 @@ re_text = re.compile(r'\n{3,}(.*?)\n*-----\n', re.S).search
 
 
 class Command(BaseCommand):
 
 
 class Command(BaseCommand):
-    option_list = BaseCommand.option_list + (
-        make_option('-t', '--tags', dest='tags', metavar='SLUG,...',
-                    help='Use only books tagged with this tags'),
-        make_option('-i', '--include', dest='include', metavar='SLUG,...',
-                    help='Include specific books by slug'),
-        make_option('-e', '--exclude', dest='exclude', metavar='SLUG,...',
-                    help='Exclude specific books by slug')
-    )
-    help = 'Prepare data for Lesmianator.'
+    help = 'Prepare data for Leśmianator.'
+
+    def add_arguments(self, parser):
+        parser.add_argument(
+            '-t', '--tags', dest='tags', metavar='SLUG,...',
+            help='Use only books tagged with this tags')
+        parser.add_argument(
+            '-i', '--include', dest='include', metavar='SLUG,...',
+            help='Include specific books by slug')
+        parser.add_argument(
+            '-e', '--exclude', dest='exclude', metavar='SLUG,...',
+            help='Exclude specific books by slug')
 
     def handle(self, *args, **options):
         self.style = color_style()
 
     def handle(self, *args, **options):
         self.style = color_style()
@@ -37,7 +38,7 @@ class Command(BaseCommand):
         try:
             path = settings.LESMIANATOR_PICKLE
         except AttributeError:
         try:
             path = settings.LESMIANATOR_PICKLE
         except AttributeError:
-            print self.style.ERROR('LESMIANATOR_PICKLE not set in the settings.')
+            print(self.style.ERROR('LESMIANATOR_PICKLE not set in the settings.'))
             return
 
         books = []
             return
 
         books = []
@@ -59,22 +60,22 @@ class Command(BaseCommand):
         processed = skipped = 0
         for book in books:
             if verbose >= 2:
         processed = skipped = 0
         for book in books:
             if verbose >= 2:
-                print 'Parsing', book.slug
+                print('Parsing', book.slug)
             if not book.txt_file:
                 if verbose >= 1:
             if not book.txt_file:
                 if verbose >= 1:
-                    print self.style.NOTICE('%s has no TXT file' % book.slug)
+                    print(self.style.NOTICE('%s has no TXT file' % book.slug))
                 skipped += 1
                 continue
             f = open(book.txt_file.path)
             m = re_text(f.read())
             if not m:
                 skipped += 1
                 continue
             f = open(book.txt_file.path)
             m = re_text(f.read())
             if not m:
-                print self.style.ERROR("Unknown text format: %s" % book.slug)
+                print(self.style.ERROR("Unknown text format: %s" % book.slug))
                 skipped += 1
                 continue
 
             processed += 1
             last_word = ''
                 skipped += 1
                 continue
 
             processed += 1
             last_word = ''
-            text = unicode(m.group(1), 'utf-8').lower()
+            text = str(m.group(1), 'utf-8').lower()
             for letter in text:
                 mydict = lesmianator.setdefault(last_word, {})
                 mydict.setdefault(letter, 0)
             for letter in text:
                 mydict = lesmianator.setdefault(last_word, {})
                 mydict.setdefault(letter, 0)
@@ -84,18 +85,18 @@ class Command(BaseCommand):
 
         if not processed:
             if skipped:
 
         if not processed:
             if skipped:
-                print self.style.ERROR("No books with TXT files found")
+                print(self.style.ERROR("No books with TXT files found"))
             else:
             else:
-                print self.style.ERROR("No books found")
+                print(self.style.ERROR("No books found"))
             return
 
         try:
             dump(lesmianator, open(path, 'w'))
         except IOError:
             return
 
         try:
             dump(lesmianator, open(path, 'w'))
         except IOError:
-            print self.style.ERROR("Couldn't write to $s" % path)
+            print(self.style.ERROR("Couldn't write to $s" % path))
             return
 
         dump(lesmianator, open(path, 'w'))
         if verbose >= 1:
             return
 
         dump(lesmianator, open(path, 'w'))
         if verbose >= 1:
-            print "%d processed, %d skipped" % (processed, skipped)
-            print "Results dumped to %s" % path
+            print("%d processed, %d skipped" % (processed, skipped))
+            print("Results dumped to %s" % path)
index 684d1c4..0595810 100644 (file)
@@ -2,11 +2,10 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-import cPickle
-from cPickle import PickleError
+import pickle
+from pickle import PickleError
 from datetime import datetime
 from random import randint
 from datetime import datetime
 from random import randint
-from StringIO import StringIO
 
 from django.core.files.base import ContentFile
 from django.db import models
 
 from django.core.files.base import ContentFile
 from django.db import models
@@ -33,7 +32,7 @@ class Poem(models.Model):
 
     try:
         f = open(settings.LESMIANATOR_PICKLE)
 
     try:
         f = open(settings.LESMIANATOR_PICKLE)
-        global_dictionary = cPickle.load(f)
+        global_dictionary = pickle.load(f)
         f.close()
     except (IOError, AttributeError, PickleError):
         global_dictionary = {}
         f.close()
     except (IOError, AttributeError, PickleError):
         global_dictionary = {}
@@ -43,7 +42,7 @@ class Poem(models.Model):
         self.seen_at = datetime.utcnow().replace(tzinfo=utc)
         self.save()
 
         self.seen_at = datetime.utcnow().replace(tzinfo=utc)
         self.save()
 
-    def __unicode__(self):
+    def __str__(self):
         return "%s (%s...)" % (self.slug, self.text[:20])
 
     @staticmethod
         return "%s (%s...)" % (self.slug, self.text[:20])
 
     @staticmethod
@@ -109,8 +108,8 @@ class Continuations(models.Model):
     class Meta:
         unique_together = (('content_type', 'object_id'), )
 
     class Meta:
         unique_together = (('content_type', 'object_id'), )
 
-    def __unicode__(self):
-        return "Continuations for: %s" % unicode(self.content_object)
+    def __str__(self):
+        return "Continuations for: %s" % str(self.content_object)
 
     @staticmethod
     def join_conts(a, b):
 
     @staticmethod
     def join_conts(a, b):
@@ -124,7 +123,6 @@ class Continuations(models.Model):
     @classmethod
     def for_book(cls, book, length=3):
         # count from this book only
     @classmethod
     def for_book(cls, book, length=3):
         # count from this book only
-        output = StringIO()
         wldoc = book.wldocument(parse_dublincore=False)
         output = wldoc.as_text(('raw-text',)).get_bytes()
         del wldoc
         wldoc = book.wldocument(parse_dublincore=False)
         output = wldoc.as_text(('raw-text',)).get_bytes()
         del wldoc
@@ -158,7 +156,7 @@ class Continuations(models.Model):
             if not obj.pickle:
                 raise cls.DoesNotExist
             f = open(obj.pickle.path)
             if not obj.pickle:
                 raise cls.DoesNotExist
             f = open(obj.pickle.path)
-            keys, conts = cPickle.load(f)
+            keys, conts = pickle.load(f)
             f.close()
             if set(keys) != should_keys:
                 raise cls.DoesNotExist
             f.close()
             if set(keys) != should_keys:
                 raise cls.DoesNotExist
@@ -172,6 +170,6 @@ class Continuations(models.Model):
                 raise NotImplementedError('Lesmianator continuations: only Book and Tag supported')
 
             c, created = cls.objects.get_or_create(content_type=object_type, object_id=sth.id)
                 raise NotImplementedError('Lesmianator continuations: only Book and Tag supported')
 
             c, created = cls.objects.get_or_create(content_type=object_type, object_id=sth.id)
-            c.pickle.save(sth.slug+'.p', ContentFile(cPickle.dumps((should_keys, conts))))
+            c.pickle.save(sth.slug+'.p', ContentFile(pickle.dumps((should_keys, conts))))
             c.save()
             return conts
             c.save()
             return conts
index 588620f..0ad0586 100644 (file)
@@ -16,7 +16,7 @@ class Catalog(models.Model):
         verbose_name = _('catalog')\r
         verbose_name_plural = _('catalogs')\r
 \r
         verbose_name = _('catalog')\r
         verbose_name_plural = _('catalogs')\r
 \r
-    def __unicode__(self):\r
+    def __str__(self):\r
         return self.name\r
 \r
     @models.permalink\r
         return self.name\r
 \r
     @models.permalink\r
@@ -37,7 +37,7 @@ class Library(models.Model):
         verbose_name = _('library')\r
         verbose_name_plural = _('libraries')\r
 \r
         verbose_name = _('library')\r
         verbose_name_plural = _('libraries')\r
 \r
-    def __unicode__(self):\r
+    def __str__(self):\r
         return self.name\r
 \r
     @models.permalink\r
         return self.name\r
 \r
     @models.permalink\r
index 65fc435..0cbf867 100644 (file)
@@ -16,7 +16,7 @@ class Subscription(Model):
         verbose_name = _('subscription')
         verbose_name_plural = _('subscriptions')
 
         verbose_name = _('subscription')
         verbose_name_plural = _('subscriptions')
 
-    def __unicode__(self):
+    def __str__(self):
         return self.email
 
     def hashcode(self):
         return self.email
 
     def hashcode(self):
index 0eeea8c..779406d 100644 (file)
@@ -116,7 +116,7 @@ class Catalogue(common.ResumptionOAIPMH):
     def identify(self, **kw):
         ident = common.Identify(
             'Wolne Lektury',  # generate
     def identify(self, **kw):
         ident = common.Identify(
             'Wolne Lektury',  # generate
-            '%s/oaipmh' % unicode(WL_BASE),  # generate
+            '%s/oaipmh' % str(WL_BASE),  # generate
             '2.0',  # version
             [m[1] for m in settings.MANAGERS],  # adminEmails
             make_time_naive(self.earliest_datestamp),  # earliest datestamp of any change
             '2.0',  # version
             [m[1] for m in settings.MANAGERS],  # adminEmails
             make_time_naive(self.earliest_datestamp),  # earliest datestamp of any change
index 004ee5b..1afbcda 100644 (file)
@@ -3,7 +3,7 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import os.path
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import os.path
-from urlparse import urljoin
+from urllib.parse import urljoin
 
 from django.contrib.syndication.views import Feed
 from django.core.urlresolvers import reverse
 
 from django.contrib.syndication.views import Feed
 from django.core.urlresolvers import reverse
@@ -78,13 +78,13 @@ class OPDSFeed(Atom1Feed):
 
     _book_parent_img = lazy(lambda: full_url(os.path.join(settings.STATIC_URL, "img/book-parent.png")), str)()
     try:
 
     _book_parent_img = lazy(lambda: full_url(os.path.join(settings.STATIC_URL, "img/book-parent.png")), str)()
     try:
-        _book_parent_img_size = unicode(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book-parent.png")))
+        _book_parent_img_size = str(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book-parent.png")))
     except OSError:
         _book_parent_img_size = ''
 
     _book_img = lazy(lambda: full_url(os.path.join(settings.STATIC_URL, "img/book.png")), str)()
     try:
     except OSError:
         _book_parent_img_size = ''
 
     _book_img = lazy(lambda: full_url(os.path.join(settings.STATIC_URL, "img/book.png")), str)()
     try:
-        _book_img_size = unicode(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book.png")))
+        _book_img_size = str(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book.png")))
     except OSError:
         _book_img_size = ''
 
     except OSError:
         _book_img_size = ''
 
index a7badbe..7de047a 100644 (file)
@@ -1,9 +1,8 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.contrib.auth.models import User
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.contrib.auth.models import User
-from mock import MagicMock, Mock, patch, DEFAULT
+from unittest.mock import MagicMock, Mock, patch, DEFAULT
 from catalogue.test_utils import WLTestCase
 from .models import BillingAgreement, BillingPlan
 from .rest import user_is_subscribed
 from catalogue.test_utils import WLTestCase
 from .models import BillingAgreement, BillingPlan
 from .rest import user_is_subscribed
index c78a19a..c541ba8 100644 (file)
@@ -25,7 +25,7 @@ def paypal_form(request, app=False):
             try:
                 approval_url = agreement_approval_url(amount, app=app)
             except PaypalError as e:
             try:
                 approval_url = agreement_approval_url(amount, app=app)
             except PaypalError as e:
-                return render(request, 'paypal/error_page.html', {'error': e.message})
+                return render(request, 'paypal/error_page.html', {'error': str(e)})
             return HttpResponseRedirect(approval_url)
     else:
         form = PaypalSubscriptionForm()
             return HttpResponseRedirect(approval_url)
     else:
         form = PaypalSubscriptionForm()
index 24866fe..4454e5a 100644 (file)
@@ -28,7 +28,7 @@ class Author(models.Model):
     def category(self):
         return "author"
 
     def category(self):
         return "author"
 
-    def __unicode__(self):
+    def __str__(self):
         return self.name
 
     def __repr__(self):
         return self.name
 
     def __repr__(self):
@@ -71,7 +71,7 @@ class BookStub(models.Model):
         verbose_name = _('book stub')
         verbose_name_plural = _('book stubs')
 
         verbose_name = _('book stub')
         verbose_name_plural = _('book stubs')
 
-    def __unicode__(self):
+    def __str__(self):
         return self.title
 
     @permalink
         return self.title
 
     @permalink
index 677877e..7f8d346 100644 (file)
@@ -17,10 +17,10 @@ def rebuild_extra_info(apps, schema_editor):
         for field in areas_json[u'things'].values():
             field[u'object'] = field[u'object'].capitalize()
         pic.areas_json = areas_json
         for field in areas_json[u'things'].values():
             field[u'object'] = field[u'object'].capitalize()
         pic.areas_json = areas_json
-        html_text = unicode(render_to_string('picture/picture_info.html', {
+        html_text = render_to_string('picture/picture_info.html', {
                     'things': pic.areas_json['things'],
                     'themes': pic.areas_json['themes'],
                     'things': pic.areas_json['things'],
                     'themes': pic.areas_json['themes'],
-                    }))
+                    })
         pic.html_file.save("%s.html" % pic.slug, ContentFile(html_text))
         pic.save()
 
         pic.html_file.save("%s.html" % pic.slug, ContentFile(html_text))
         pic.save()
 
index a3c098c..4d8dac8 100644 (file)
@@ -15,7 +15,7 @@ from ssify import flush_ssi_includes
 from catalogue.models.tag import prefetched_relations
 from catalogue.utils import split_tags
 from picture import tasks
 from catalogue.models.tag import prefetched_relations
 from catalogue.utils import split_tags
 from picture import tasks
-from StringIO import StringIO
+from io import BytesIO
 import jsonfield
 import itertools
 import logging
 import jsonfield
 import itertools
 import logging
@@ -123,7 +123,7 @@ class Picture(models.Model):
 
         return ret
 
 
         return ret
 
-    def __unicode__(self):
+    def __str__(self):
         return self.title
 
     def authors(self):
         return self.title
 
     def authors(self):
@@ -179,7 +179,7 @@ class Picture(models.Model):
         close_image_file = False
 
         if image_file is not None and not isinstance(image_file, File):
         close_image_file = False
 
         if image_file is not None and not isinstance(image_file, File):
-            image_file = File(open(image_file))
+            image_file = File(open(image_file, 'rb'))
             close_image_file = True
 
         if not isinstance(xml_file, File):
             close_image_file = True
 
         if not isinstance(xml_file, File):
@@ -197,7 +197,7 @@ class Picture(models.Model):
                 raise Picture.AlreadyExists('Picture %s already exists' % picture_xml.slug)
 
             picture.areas.all().delete()
                 raise Picture.AlreadyExists('Picture %s already exists' % picture_xml.slug)
 
             picture.areas.all().delete()
-            picture.title = unicode(picture_xml.picture_info.title)
+            picture.title = str(picture_xml.picture_info.title)
             picture.extra_info = picture_xml.picture_info.to_dict()
 
             picture_tags = set(catalogue.models.Tag.tags_from_info(picture_xml.picture_info))
             picture.extra_info = picture_xml.picture_info.to_dict()
 
             picture_tags = set(catalogue.models.Tag.tags_from_info(picture_xml.picture_info))
@@ -282,7 +282,7 @@ class Picture(models.Model):
 
             picture.width, picture.height = modified.size
 
 
             picture.width, picture.height = modified.size
 
-            modified_file = StringIO()
+            modified_file = BytesIO()
             modified.save(modified_file, format='JPEG', quality=95)
             # FIXME: hardcoded extension - detect from DC format or orginal filename
             picture.image_file.save(path.basename(picture_xml.image_path), File(modified_file))
             modified.save(modified_file, format='JPEG', quality=95)
             # FIXME: hardcoded extension - detect from DC format or orginal filename
             picture.image_file.save(path.basename(picture_xml.image_path), File(modified_file))
@@ -372,6 +372,6 @@ class Picture(models.Model):
                 index.index_tags()
             if commit:
                 index.index.commit()
                 index.index_tags()
             if commit:
                 index.index.commit()
-        except Exception, e:
+        except Exception as e:
             index.index.rollback()
             raise e
             index.index.rollback()
             raise e
index cae7db7..e80b0fc 100644 (file)
@@ -14,10 +14,10 @@ def generate_picture_html(picture_id):
     import picture.models
     pic = picture.models.Picture.objects.get(pk=picture_id)
 
     import picture.models
     pic = picture.models.Picture.objects.get(pk=picture_id)
 
-    html_text = unicode(render_to_string('picture/picture_info.html', {
+    html_text = render_to_string('picture/picture_info.html', {
                 'things': pic.areas_json['things'],
                 'themes': pic.areas_json['themes'],
                 'things': pic.areas_json['things'],
                 'themes': pic.areas_json['themes'],
-                }))
+                })
     pic.html_file.save("%s.html" % pic.slug, ContentFile(html_text))
 
 
     pic.html_file.save("%s.html" % pic.slug, ContentFile(html_text))
 
 
@@ -26,7 +26,7 @@ def index_picture(picture_id, picture_info=None, **kwargs):
     from picture.models import Picture
     try:
         return Picture.objects.get(id=picture_id).search_index(picture_info, **kwargs)
     from picture.models import Picture
     try:
         return Picture.objects.get(id=picture_id).search_index(picture_info, **kwargs)
-    except Exception, e:
-        print "Exception during index: %s" % e
+    except Exception as e:
+        print("Exception during index: %s" % e)
         print_exc()
         raise e
         print_exc()
         raise e
index a766b44..973d862 100644 (file)
@@ -57,7 +57,7 @@ def area_thumbnail_url(area, geometry):
             crop="%dpx %dpx %dpx %dpx" % tuple(map(lambda d: max(0, d), tuple(coords[0] + coords[1]))))
     except ZeroDivisionError:
         return ''
             crop="%dpx %dpx %dpx %dpx" % tuple(map(lambda d: max(0, d), tuple(coords[0] + coords[1]))))
     except ZeroDivisionError:
         return ''
-    except Exception, e:
+    except Exception as e:
         logging.exception("Error creating a thumbnail for PictureArea")
         return ''
 
         logging.exception("Error creating a thumbnail for PictureArea")
         return ''
 
index a43c095..ecd5d66 100644 (file)
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
index bfc3639..4b1882d 100644 (file)
@@ -26,7 +26,7 @@ class Poll(models.Model):
             raise ValidationError(_('Slug of an open poll needs to be unique'))
         return super(Poll, self).clean()
 
             raise ValidationError(_('Slug of an open poll needs to be unique'))
         return super(Poll, self).clean()
 
-    def __unicode__(self):
+    def __str__(self):
         return self.question[:100] + ' (' + self.slug + ')'
 
     def get_absolute_url(self):
         return self.question[:100] + ' (' + self.slug + ')'
 
     def get_absolute_url(self):
@@ -50,8 +50,8 @@ class PollItem(models.Model):
         verbose_name = _('vote item')
         verbose_name_plural = _('vote items')
 
         verbose_name = _('vote item')
         verbose_name_plural = _('vote items')
 
-    def __unicode__(self):
-        return self.content + ' @ ' + unicode(self.poll)
+    def __str__(self):
+        return self.content + ' @ ' + str(self.poll)
 
     @property
     def vote_ratio(self):
 
     @property
     def vote_ratio(self):
index 79e5551..bb84b99 100644 (file)
@@ -16,5 +16,5 @@ class Notification(models.Model):
     class Meta:
         ordering = ['-timestamp']
 
     class Meta:
         ordering = ['-timestamp']
 
-    def __unicode__(self):
+    def __str__(self):
         return '%s: %s' % (self.timestamp, self.title)
         return '%s: %s' % (self.timestamp, self.title)
index f5d4c33..955f7d9 100755 (executable)
@@ -22,7 +22,7 @@ def render_to_pdf(output_path, template, context=None, add_files=None):
     :param dict add_files: a dictionary of additional files XeTeX will need
     """
 
     :param dict add_files: a dictionary of additional files XeTeX will need
     """
 
-    from StringIO import StringIO
+    from io import BytesIO
     import shutil
     from tempfile import mkdtemp
     import subprocess
     import shutil
     from tempfile import mkdtemp
     import subprocess
@@ -30,7 +30,7 @@ def render_to_pdf(output_path, template, context=None, add_files=None):
     from django.template.loader import render_to_string
 
     rendered = render_to_string(template, context)
     from django.template.loader import render_to_string
 
     rendered = render_to_string(template, context)
-    texml = StringIO(rendered.encode('utf-8'))
+    texml = BytesIO(rendered.encode('utf-8'))
     tempdir = mkdtemp(prefix="render_to_pdf-")
     tex_path = os.path.join(tempdir, "doc.tex")
     with open(tex_path, 'w') as tex_file:
     tempdir = mkdtemp(prefix="render_to_pdf-")
     tex_path = os.path.join(tempdir, "doc.tex")
     with open(tex_path, 'w') as tex_file:
index da21e01..a944687 100644 (file)
@@ -2,11 +2,11 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from sunburnt import sunburnt
+from scorched import connection
 from lxml import etree
 from lxml import etree
-import urllib
+from urllib.parse import urlencode
 import warnings
 import warnings
-from sunburnt import search
+from scorched import search
 import copy
 from httplib2 import socket
 import re
 import copy
 from httplib2 import socket
 import re
@@ -25,7 +25,7 @@ class TermVectorOptions(search.Options):
     def update(self, positions=False, fields=None):
         if fields is None:
             fields = []
     def update(self, positions=False, fields=None):
         if fields is None:
             fields = []
-        if isinstance(fields, basestring):
+        if isinstance(fields, (str, bytes)):
             fields = [fields]
         self.schema.check_fields(fields, {"stored": True})
         self.fields.update(fields)
             fields = [fields]
         self.schema.check_fields(fields, {"stored": True})
         self.fields.update(fields)
@@ -42,13 +42,13 @@ class TermVectorOptions(search.Options):
         return opts
 
 
         return opts
 
 
-class CustomSolrConnection(sunburnt.SolrConnection):
+class CustomSolrConnection(connection.SolrConnection):
     def __init__(self, *args, **kw):
         super(CustomSolrConnection, self).__init__(*args, **kw)
         self.analysis_url = self.url + "analysis/field/"
 
     def analyze(self, params):
     def __init__(self, *args, **kw):
         super(CustomSolrConnection, self).__init__(*args, **kw)
         self.analysis_url = self.url + "analysis/field/"
 
     def analyze(self, params):
-        qs = urllib.urlencode(params)
+        qs = urlencode(params)
         url = "%s?%s" % (self.analysis_url, qs)
         if len(url) > self.max_length_get_url:
             warnings.warn("Long query URL encountered - POSTing instead of GETting. "
         url = "%s?%s" % (self.analysis_url, qs)
         if len(url) > self.max_length_get_url:
             warnings.warn("Long query URL encountered - POSTing instead of GETting. "
@@ -63,11 +63,11 @@ class CustomSolrConnection(sunburnt.SolrConnection):
             kwargs = dict(method="GET")
         r, c = self.request(url, **kwargs)
         if r.status != 200:
             kwargs = dict(method="GET")
         r, c = self.request(url, **kwargs)
         if r.status != 200:
-            raise sunburnt.SolrError(r, c)
+            raise connection.SolrError(r, c)
         return c
 
 
         return c
 
 
-# monkey patching sunburnt SolrSearch
+# monkey patching scorched SolrSearch
 search.SolrSearch.option_modules += ('term_vectorer',)
 
 
 search.SolrSearch.option_modules += ('term_vectorer',)
 
 
@@ -85,10 +85,10 @@ __original__init_common_modules = search.SolrSearch._init_common_modules
 setattr(search.SolrSearch, '_init_common_modules', __patched__init_common_modules)
 
 
 setattr(search.SolrSearch, '_init_common_modules', __patched__init_common_modules)
 
 
-class CustomSolrInterface(sunburnt.SolrInterface):
+class CustomSolrInterface(connection.SolrInterface):
     # just copied from parent and SolrConnection -> CustomSolrConnection
     def __init__(self, url, schemadoc=None, http_connection=None, mode='', retry_timeout=-1,
     # just copied from parent and SolrConnection -> CustomSolrConnection
     def __init__(self, url, schemadoc=None, http_connection=None, mode='', retry_timeout=-1,
-                 max_length_get_url=sunburnt.MAX_LENGTH_GET_URL):
+                 max_length_get_url=connection.MAX_LENGTH_GET_URL):
         self.conn = CustomSolrConnection(url, http_connection, retry_timeout, max_length_get_url)
         self.schemadoc = schemadoc
         if 'w' not in mode:
         self.conn = CustomSolrConnection(url, http_connection, retry_timeout, max_length_get_url)
         self.schemadoc = schemadoc
         if 'w' not in mode:
@@ -97,8 +97,8 @@ class CustomSolrInterface(sunburnt.SolrInterface):
             self.readable = False
         try:
             self.init_schema()
             self.readable = False
         try:
             self.init_schema()
-        except socket.error, e:
-            raise socket.error, "Cannot connect to Solr server, and search indexing is enabled (%s)" % str(e)
+        except socket.error as e:
+            raise socket.error("Cannot connect to Solr server, and search indexing is enabled (%s)" % str(e))
 
     def _analyze(self, **kwargs):
         if not self.readable:
 
     def _analyze(self, **kwargs):
         if not self.readable:
@@ -115,7 +115,7 @@ class CustomSolrInterface(sunburnt.SolrInterface):
         if 'query' in kwargs:
             args['q'] = kwargs['q']
 
         if 'query' in kwargs:
             args['q'] = kwargs['q']
 
-        params = map(lambda (k, v): (k.replace('_', '.'), v), sunburnt.params_from_dict(**args))
+        params = map(lambda k, v: (k.replace('_', '.'), v), connection.params_from_dict(**args))
 
         content = self.conn.analyze(params)
         doc = etree.fromstring(content)
 
         content = self.conn.analyze(params)
         doc = etree.fromstring(content)
@@ -139,7 +139,7 @@ class CustomSolrInterface(sunburnt.SolrInterface):
     def analyze(self, **kwargs):
         doc = self._analyze(**kwargs)
         terms = doc.xpath("//lst[@name='index']/arr[last()]/lst/str[1]")
     def analyze(self, **kwargs):
         doc = self._analyze(**kwargs)
         terms = doc.xpath("//lst[@name='index']/arr[last()]/lst/str[1]")
-        terms = map(lambda n: unicode(n.text), terms)
+        terms = map(lambda n: str(n.text), terms)
         return terms
 
     def expand_margins(self, text, start, end):
         return terms
 
     def expand_margins(self, text, start, end):
index e2cfb54..c27cce1 100755 (executable)
@@ -4,7 +4,7 @@
 #
 from django import forms
 from django.forms.utils import flatatt
 #
 from django import forms
 from django.forms.utils import flatatt
-from django.utils.encoding import smart_unicode
+from django.utils.encoding import smart_text
 from django.utils.safestring import mark_safe
 from json import dumps
 
 from django.utils.safestring import mark_safe
 from json import dumps
 
@@ -21,7 +21,7 @@ class JQueryAutoCompleteWidget(forms.TextInput):
         final_attrs = self.build_attrs(self.attrs, attrs)
         final_attrs["name"] = name
         if value:
         final_attrs = self.build_attrs(self.attrs, attrs)
         final_attrs["name"] = name
         if value:
-            final_attrs['value'] = smart_unicode(value)
+            final_attrs['value'] = smart_text(value)
 
         if 'id' not in self.attrs:
             final_attrs['id'] = 'id_%s' % name
 
         if 'id' not in self.attrs:
             final_attrs['id'] = 'id_%s' % name
index d3377b1..f943a4d 100644 (file)
@@ -13,8 +13,8 @@ import catalogue.models
 import picture.models
 from pdcounter.models import Author as PDCounterAuthor, BookStub as PDCounterBook
 from itertools import chain
 import picture.models
 from pdcounter.models import Author as PDCounterAuthor, BookStub as PDCounterBook
 from itertools import chain
-import sunburnt
-import custom
+import scorched
+from . import custom
 import operator
 import logging
 from wolnelektury.utils import makedirs
 import operator
 import logging
 from wolnelektury.utils import makedirs
@@ -129,7 +129,7 @@ class Index(SolrIndex):
         """
         uids = set()
         for q in queries:
         """
         uids = set()
         for q in queries:
-            if isinstance(q, sunburnt.search.LuceneQuery):
+            if isinstance(q, scorched.search.LuceneQuery):
                 q = self.index.query(q)
             q.field_limiter.update(['uid'])
             st = 0
                 q = self.index.query(q)
             q.field_limiter.update(['uid'])
             st = 0
@@ -324,9 +324,9 @@ class Index(SolrIndex):
                 elif type_indicator == dcparser.as_person:
                     p = getattr(book_info, field.name)
                     if isinstance(p, dcparser.Person):
                 elif type_indicator == dcparser.as_person:
                     p = getattr(book_info, field.name)
                     if isinstance(p, dcparser.Person):
-                        persons = unicode(p)
+                        persons = str(p)
                     else:
                     else:
-                        persons = ', '.join(map(unicode, p))
+                        persons = ', '.join(map(str, p))
                     fields[field.name] = persons
                 elif type_indicator == dcparser.as_date:
                     dt = getattr(book_info, field.name)
                     fields[field.name] = persons
                 elif type_indicator == dcparser.as_date:
                     dt = getattr(book_info, field.name)
@@ -478,7 +478,7 @@ class Index(SolrIndex):
                         fid = start.attrib['id'][1:]
                         handle_text.append(lambda text: None)
                         if start.text is not None:
                         fid = start.attrib['id'][1:]
                         handle_text.append(lambda text: None)
                         if start.text is not None:
-                            fragments[fid]['themes'] += map(unicode.strip, map(unicode, (start.text.split(','))))
+                            fragments[fid]['themes'] += map(str.strip, map(str, (start.text.split(','))))
                     elif end is not None and end.tag == 'motyw':
                         handle_text.pop()
 
                     elif end is not None and end.tag == 'motyw':
                         handle_text.pop()
 
@@ -610,14 +610,14 @@ class SearchResult(object):
         result._book = book
         return result
 
         result._book = book
         return result
 
-    def __unicode__(self):
+    def __str__(self):
         return u"<SR id=%d %d(%d) hits score=%f %d snippets>" % \
             (self.book_id, len(self._hits),
              len(self._processed_hits) if self._processed_hits else -1,
              self._score, len(self.snippets))
 
         return u"<SR id=%d %d(%d) hits score=%f %d snippets>" % \
             (self.book_id, len(self._hits),
              len(self._processed_hits) if self._processed_hits else -1,
              self._score, len(self.snippets))
 
-    def __str__(self):
-        return unicode(self).encode('utf-8')
+    def __bytes__(self):
+        return str(self).encode('utf-8')
 
     @property
     def score(self):
 
     @property
     def score(self):
@@ -705,7 +705,7 @@ class SearchResult(object):
             if self.query_terms is not None:
                 for i in range(0, len(f[self.OTHER]['themes'])):
                     tms = f[self.OTHER]['themes'][i].split(r' +') + f[self.OTHER]['themes_pl'][i].split(' ')
             if self.query_terms is not None:
                 for i in range(0, len(f[self.OTHER]['themes'])):
                     tms = f[self.OTHER]['themes'][i].split(r' +') + f[self.OTHER]['themes_pl'][i].split(' ')
-                    tms = map(unicode.lower, tms)
+                    tms = map(str.lower, tms)
                     for qt in self.query_terms:
                         if qt in tms:
                             themes_hit.add(f[self.OTHER]['themes'][i])
                     for qt in self.query_terms:
                         if qt in tms:
                             themes_hit.add(f[self.OTHER]['themes'][i])
@@ -791,11 +791,11 @@ class PictureResult(object):
 
             self._hits.append(hit)
 
 
             self._hits.append(hit)
 
-    def __unicode__(self):
+    def __str__(self):
         return u"<PR id=%d score=%f >" % (self.picture_id, self._score)
 
     def __repr__(self):
         return u"<PR id=%d score=%f >" % (self.picture_id, self._score)
 
     def __repr__(self):
-        return unicode(self)
+        return str(self)
 
     @property
     def score(self):
 
     @property
     def score(self):
@@ -829,7 +829,7 @@ class PictureResult(object):
             if self.query_terms is not None:
                 for i in range(0, len(hit[self.OTHER]['themes'])):
                     tms = hit[self.OTHER]['themes'][i].split(r' +') + hit[self.OTHER]['themes_pl'][i].split(' ')
             if self.query_terms is not None:
                 for i in range(0, len(hit[self.OTHER]['themes'])):
                     tms = hit[self.OTHER]['themes'][i].split(r' +') + hit[self.OTHER]['themes_pl'][i].split(' ')
-                    tms = map(unicode.lower, tms)
+                    tms = map(str.lower, tms)
                     for qt in self.query_terms:
                         if qt in tms:
                             themes_hit.add(hit[self.OTHER]['themes'][i])
                     for qt in self.query_terms:
                         if qt in tms:
                             themes_hit.add(hit[self.OTHER]['themes'][i])
@@ -965,7 +965,7 @@ class Search(SolrIndex):
                         num -= 1
                 idx += 1
 
                         num -= 1
                 idx += 1
 
-        except IOError, e:
+        except IOError as e:
             book = catalogue.models.Book.objects.filter(id=book_id)
             if not book:
                 log.error("Book does not exist for book id = %d" % book_id)
             book = catalogue.models.Book.objects.filter(id=book_id)
             if not book:
                 log.error("Book does not exist for book id = %d" % book_id)
index da4574f..c2fe78e 100755 (executable)
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
@@ -7,8 +6,6 @@ import traceback
 
 from django.core.management.base import BaseCommand
 
 
 from django.core.management.base import BaseCommand
 
-from optparse import make_option
-
 
 def query_yes_no(question, default="yes"):
     """Ask a yes/no question via raw_input() and return their answer.
 
 def query_yes_no(question, default="yes"):
     """Ask a yes/no question via raw_input() and return their answer.
@@ -44,26 +41,31 @@ def query_yes_no(question, default="yes"):
 
 class Command(BaseCommand):
     help = 'Reindex everything.'
 
 class Command(BaseCommand):
     help = 'Reindex everything.'
-    args = ''
-    
-    option_list = BaseCommand.option_list + (
-        make_option('-n', '--book-id', action='store_true', dest='book_id', default=False,
-                    help='book id instead of slugs'),
-        make_option('-t', '--just-tags', action='store_true', dest='just_tags', default=False,
-                    help='just reindex tags'),
-        make_option('--start', dest='start_from', default=None, help='start from this slug'),
-        make_option('--stop', dest='stop_after', default=None, help='stop after this slug'),
-    )
 
 
-    def handle(self, *args, **opts):
+    def add_arguments(self, parser):
+        parser.add_argument(
+                '-n', '--book-id', action='store_true', dest='book_id',
+                default=False, help='book id instead of slugs')
+        parser.add_argument(
+                '-t', '--just-tags', action='store_true', dest='just_tags',
+                default=False, help='just reindex tags')
+        parser.add_argument(
+                '--start', dest='start_from', default=None,
+                help='start from this slug')
+        parser.add_argument(
+                '--stop', dest='stop_after', default=None,
+                help='stop after this slug')
+        parser.add_argument('args', nargs='*', metavar='slug/id')
+
+    def handle(self, **opts):
         from catalogue.models import Book
         from search.index import Index
         idx = Index()
         
         if not opts['just_tags']:
         from catalogue.models import Book
         from search.index import Index
         idx = Index()
         
         if not opts['just_tags']:
-            if args:
+            if opts['args']:
                 books = []
                 books = []
-                for a in args:
+                for a in opts['args']:
                     if opts['book_id']:
                         books += Book.objects.filter(id=int(a)).all()
                     else:
                     if opts['book_id']:
                         books += Book.objects.filter(id=int(a)).all()
                     else:
@@ -83,7 +85,7 @@ class Command(BaseCommand):
                     if stop_after and slug > stop_after:
                         break
                     if not start_from or slug >= start_from:
                     if stop_after and slug > stop_after:
                         break
                     if not start_from or slug >= start_from:
-                        print b.slug
+                        print(b.slug)
                         idx.index_book(b)
                         idx.index.commit()
                     books.pop(0)
                         idx.index_book(b)
                         idx.index.commit()
                     books.pop(0)
@@ -98,6 +100,6 @@ class Command(BaseCommand):
                     if not retry:
                         break
 
                     if not retry:
                         break
 
-        print 'Reindexing tags.'
+        print('Reindexing tags.')
         idx.index_tags()
         idx.index.commit()
         idx.index_tags()
         idx.index.commit()
index bb6b50f..8505189 100644 (file)
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
@@ -7,8 +6,6 @@ import traceback
 
 from django.core.management.base import BaseCommand
 
 
 from django.core.management.base import BaseCommand
 
-from optparse import make_option
-
 
 def query_yes_no(question, default="yes"):
     """Ask a yes/no question via raw_input() and return their answer.
 
 def query_yes_no(question, default="yes"):
     """Ask a yes/no question via raw_input() and return their answer.
@@ -44,21 +41,21 @@ def query_yes_no(question, default="yes"):
 
 class Command(BaseCommand):
     help = 'Reindex pictures.'
 
 class Command(BaseCommand):
     help = 'Reindex pictures.'
-    args = ''
 
 
-    option_list = BaseCommand.option_list + (
-        make_option('-n', '--picture-id', action='store_true', dest='picture_id', default=False,
-                    help='picture id instead of slugs'),
-    )
+    def add_arguments(self, parser):
+        self.add_argument(
+                '-n', '--picture-id', action='store_true', dest='picture_id',
+                default=False, help='picture id instead of slugs')
+        self.add_argument('slug/id', nargs='*', metavar='slug/id')
 
 
-    def handle(self, *args, **opts):
+    def handle(self, **opts):
         from picture.models import Picture
         from search.index import Index
         idx = Index()
 
         from picture.models import Picture
         from search.index import Index
         idx = Index()
 
-        if args:
+        if opts['args']:
             pictures = []
             pictures = []
-            for a in args:
+            for a in opts['args']:
                 if opts['picture_id']:
                     pictures += Picture.objects.filter(id=int(a)).all()
                 else:
                 if opts['picture_id']:
                     pictures += Picture.objects.filter(id=int(a)).all()
                 else:
@@ -68,7 +65,7 @@ class Command(BaseCommand):
         while pictures:
             try:
                 p = pictures[0]
         while pictures:
             try:
                 p = pictures[0]
-                print p.slug
+                print(p.slug)
                 idx.index_picture(p)
                 idx.index.commit()
                 pictures.pop(0)
                 idx.index_picture(p)
                 idx.index.commit()
                 pictures.pop(0)
index af80143..62512c9 100755 (executable)
@@ -1,26 +1,23 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from django.core.management.base import BaseCommand
-
 from glob import glob
 from os import path
 from django.conf import settings
 from glob import glob
 from os import path
 from django.conf import settings
+from django.core.management.base import BaseCommand
 
 
 class Command(BaseCommand):
     help = 'Check snippets.'
 
 
 class Command(BaseCommand):
     help = 'Check snippets.'
-    args = ''
 
     def handle(self, *args, **opts):
         sfn = glob(settings.SEARCH_INDEX+'snippets/*')
         for fn in sfn:
 
     def handle(self, *args, **opts):
         sfn = glob(settings.SEARCH_INDEX+'snippets/*')
         for fn in sfn:
-            print fn
+            print(fn)
             bkid = path.basename(fn)
             with open(fn) as f:
                 cont = f.read()
                 try:
                     cont.decode('utf-8')
                 except UnicodeDecodeError:
             bkid = path.basename(fn)
             with open(fn) as f:
                 cont = f.read()
                 try:
                     cont.decode('utf-8')
                 except UnicodeDecodeError:
-                    print "error in snippets %s" % bkid
+                    print("error in snippets %s" % bkid)
index 344e79f..6c43040 100644 (file)
@@ -1,8 +1,7 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from mock import Mock
+from unittest.mock import Mock
 from catalogue.models import Book, Tag
 from random import randint, choice
 
 from catalogue.models import Book, Tag
 from random import randint, choice
 
index d0cbb5c..0975c2a 100644 (file)
@@ -12,7 +12,7 @@ register = template.Library()
 def book_searched(context, result):
     # We don't need hits which lead to sections but do not have
     # snippets.
 def book_searched(context, result):
     # We don't need hits which lead to sections but do not have
     # snippets.
-    hits = filter(lambda (idx, h):
+    hits = filter(lambda idx, h:
                   result.snippets[idx] is not None or ('fragment' in h and h['themes_hit']),
                   enumerate(result.hits))
     # print "[tmpl: from %d hits selected %d]" % (len(result.hits), len(hits))
                   result.snippets[idx] is not None or ('fragment' in h and h['themes_hit']),
                   enumerate(result.hits))
     # print "[tmpl: from %d hits selected %d]" % (len(result.hits), len(hits))
index 979c0d4..b9417ba 100644 (file)
@@ -37,7 +37,7 @@ class Cite(models.Model):
         verbose_name = _('cite')
         verbose_name_plural = _('cites')
 
         verbose_name = _('cite')
         verbose_name_plural = _('cites')
 
-    def __unicode__(self):
+    def __str__(self):
         return u"%s: %s…" % (self.vip, self.text[:60])
 
     def get_absolute_url(self):
         return u"%s: %s…" % (self.vip, self.text[:60])
 
     def get_absolute_url(self):
index ce61985..898d3ba 100755 (executable)
@@ -46,4 +46,4 @@ def book_shelf_tags(request, book_id):
             return ''
         ctx = {'tags': tags}
         return template.loader.render_to_string('social/shelf_tags.html', ctx)
             return ''
         ctx = {'tags': tags}
         return template.loader.render_to_string('social/shelf_tags.html', ctx)
-    return lazy(get_value, unicode)()
+    return lazy(get_value, str)()
index 61cb9d8..941f50f 100644 (file)
@@ -33,8 +33,8 @@ def sortify(value):
 
     """
 
 
     """
 
-    if not isinstance(value, unicode):
-        value = unicode(value, 'utf-8')
+    if not isinstance(value, str):
+        value = str(value, 'utf-8')
 
     # try to replace chars
     value = re.sub('[^a-zA-Z0-9\\s\\-]', replace_char, value)
 
     # try to replace chars
     value = re.sub('[^a-zA-Z0-9\\s\\-]', replace_char, value)
index 89b06ff..8b50552 100644 (file)
@@ -4,7 +4,7 @@
 #
 import json
 import time
 #
 import json
 import time
-from StringIO import StringIO
+from io import BytesIO
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 from django.template.loader import render_to_string
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 from django.template.loader import render_to_string
@@ -24,7 +24,7 @@ class Sponsor(models.Model):
     logo = models.ImageField(_('logo'), upload_to='sponsorzy/sponsor/logo')
     url = models.URLField(_('url'), blank=True)
 
     logo = models.ImageField(_('logo'), upload_to='sponsorzy/sponsor/logo')
     url = models.URLField(_('url'), blank=True)
 
-    def __unicode__(self):
+    def __str__(self):
         return self.name
 
     def description(self):
         return self.name
 
     def description(self):
@@ -75,7 +75,7 @@ class SponsorPage(models.Model):
                     (THUMB_WIDTH - simg.size[0]) / 2,
                     i * THUMB_HEIGHT + (THUMB_HEIGHT - simg.size[1]) / 2,
                     ))
                     (THUMB_WIDTH - simg.size[0]) / 2,
                     i * THUMB_HEIGHT + (THUMB_HEIGHT - simg.size[1]) / 2,
                     ))
-        imgstr = StringIO()
+        imgstr = BytesIO()
         sprite.save(imgstr, 'png')
 
         if self.sprite:
         sprite.save(imgstr, 'png')
 
         if self.sprite:
@@ -88,7 +88,7 @@ class SponsorPage(models.Model):
     html = property(fget=html)
 
     def save(self, *args, **kwargs):
     html = property(fget=html)
 
     def save(self, *args, **kwargs):
-        if isinstance(self.sponsors, basestring):
+        if isinstance(self.sponsors, str):
             # Walkaround for weird jsonfield 'no-decode' optimization.
             self.sponsors = json.loads(self.sponsors)
         self.render_sprite()
             # Walkaround for weird jsonfield 'no-decode' optimization.
             self.sponsors = json.loads(self.sponsors)
         self.render_sprite()
@@ -103,5 +103,5 @@ class SponsorPage(models.Model):
     def flush_includes(self):
         flush_ssi_includes(['/sponsors/page/%s.html' % self.name])
 
     def flush_includes(self):
         flush_ssi_includes(['/sponsors/page/%s.html' % self.name])
 
-    def __unicode__(self):
+    def __str__(self):
         return self.name
         return self.name
index 7641549..06bc304 100644 (file)
@@ -23,7 +23,7 @@ class SponsorPageWidget(forms.Textarea):
 
     def render(self, name, value, attrs=None):
         output = [super(SponsorPageWidget, self).render(name, value, attrs)]
 
     def render(self, name, value, attrs=None):
         output = [super(SponsorPageWidget, self).render(name, value, attrs)]
-        sponsors = [(unicode(obj), obj.pk, obj.logo.url) for obj in models.Sponsor.objects.all().iterator()]
+        sponsors = [(str(obj), obj.pk, obj.logo.url) for obj in models.Sponsor.objects.all().iterator()]
         sponsors_js = ', '.join('{name: "%s", id: %d, image: "%s"}' % sponsor for sponsor in sponsors)
         output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {')
         # TODO: "id_" is hard-coded here. This should instead use the correct
         sponsors_js = ', '.join('{name: "%s", id: %d, image: "%s"}' % sponsor for sponsor in sponsors)
         output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {')
         # TODO: "id_" is hard-coded here. This should instead use the correct
index dcd0a4b..cb2ec83 100644 (file)
@@ -4,9 +4,9 @@
 #
 from celery.task import task
 from django.conf import settings
 #
 from celery.task import task
 from django.conf import settings
-import httplib
+from http.client import HTTPConnection
 import logging
 import logging
-import urlparse
+from urllib.parse import urlsplit
 
 logger = logging.getLogger(__name__)
 
 
 logger = logging.getLogger(__name__)
 
@@ -15,7 +15,7 @@ PIWIK_API_VERSION = 1
 
 # Retrieve piwik information
 try:
 
 # Retrieve piwik information
 try:
-    _host = urlparse.urlsplit(settings.PIWIK_URL).netloc
+    _host = urlsplit(settings.PIWIK_URL).netloc
 except AttributeError:
     logger.debug("PIWIK_URL not configured.")
     _host = None
 except AttributeError:
     logger.debug("PIWIK_URL not configured.")
     _host = None
@@ -24,6 +24,6 @@ except AttributeError:
 @task(ignore_result=True)
 def track_request(piwik_args):
     piwik_url = "%s%s%s" % (settings.PIWIK_URL, u"/piwik.php?", piwik_args)
 @task(ignore_result=True)
 def track_request(piwik_args):
     piwik_url = "%s%s%s" % (settings.PIWIK_URL, u"/piwik.php?", piwik_args)
-    conn = httplib.HTTPConnection(_host)
+    conn = HTTPConnection(_host)
     conn.request('GET', piwik_url)
     conn.close()
     conn.request('GET', piwik_url)
     conn.close()
index 86ce54c..eba7202 100644 (file)
@@ -10,7 +10,7 @@ import urllib
 from random import random
 from inspect import isclass
 
 from random import random
 from inspect import isclass
 
-from django.utils.encoding import force_str
+from django.utils.encoding import force_bytes
 
 from .tasks import track_request
 
 
 from .tasks import track_request
 
@@ -21,10 +21,10 @@ def piwik_url(request):
     return urllib.urlencode(dict(
         idsite=getattr(settings, 'PIWIK_SITE_ID', '0'),
         rec=1,
     return urllib.urlencode(dict(
         idsite=getattr(settings, 'PIWIK_SITE_ID', '0'),
         rec=1,
-        url=force_str('http://%s%s' % (request.META['HTTP_HOST'], request.path)),
+        url=force_bytes('http://%s%s' % (request.META['HTTP_HOST'], request.path)),
         rand=int(random() * 0x10000),
         apiv=PIWIK_API_VERSION,
         rand=int(random() * 0x10000),
         apiv=PIWIK_API_VERSION,
-        urlref=force_str(request.META.get('HTTP_REFERER', '')),
+        urlref=force_bytes(request.META.get('HTTP_REFERER', '')),
         ua=request.META.get('HTTP_USER_AGENT', ''),
         lang=request.META.get('HTTP_ACCEPT_LANGUAGE', ''),
         token_auth=getattr(settings, 'PIWIK_TOKEN', ''),
         ua=request.META.get('HTTP_USER_AGENT', ''),
         lang=request.META.get('HTTP_ACCEPT_LANGUAGE', ''),
         token_auth=getattr(settings, 'PIWIK_TOKEN', ''),
index b499ee8..b5fb06f 100644 (file)
@@ -22,8 +22,8 @@ class Suggestion(models.Model):
         verbose_name = _('suggestion')
         verbose_name_plural = _('suggestions')
 
         verbose_name = _('suggestion')
         verbose_name_plural = _('suggestions')
 
-    def __unicode__(self):
-        return unicode(self.created_at)
+    def __str__(self):
+        return str(self.created_at)
 
 
 class PublishingSuggestion(models.Model):
 
 
 class PublishingSuggestion(models.Model):
@@ -69,5 +69,5 @@ class PublishingSuggestion(models.Model):
             spam = True
         return spam
 
             spam = True
         return spam
 
-    def __unicode__(self):
-        return unicode(self.created_at)
+    def __str__(self):
+        return str(self.created_at)
index 25ff0c0..6d436a0 100644 (file)
@@ -9,7 +9,7 @@ from contact.forms import ContactForm
 from contact.fields import HeaderField
 from django import forms
 
 from contact.fields import HeaderField
 from django import forms
 
-mark_safe_lazy = lazy(mark_safe, unicode)
+mark_safe_lazy = lazy(mark_safe, str)
 
 
 class KonkursForm(ContactForm):
 
 
 class KonkursForm(ContactForm):
index 263ee2c..ca7692e 100644 (file)
@@ -14,8 +14,8 @@ KEPT_FIELDS = {
 
 class Command(BaseCommand):
     def handle(self, *args, **options):
 
 class Command(BaseCommand):
     def handle(self, *args, **options):
-        for provider, kept_fields in KEPT_FIELDS.iteritems():
+        for provider, kept_fields in KEPT_FIELDS.items():
             for sa in SocialAccount.objects.filter(provider=provider):
             for sa in SocialAccount.objects.filter(provider=provider):
-                trimmed_data = {k: v for k, v in sa.extra_data.iteritems() if k in kept_fields}
+                trimmed_data = {k: v for k, v in sa.extra_data.items() if k in kept_fields}
                 sa.extra_data = trimmed_data
                 sa.save()
                 sa.extra_data = trimmed_data
                 sa.save()
index 76ea473..d0e5d6c 100644 (file)
@@ -1,20 +1,17 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from optparse import make_option
+import os
+import shutil
+import sys
+import tempfile
+import allauth
 from django.conf import settings
 from django.core.management.base import BaseCommand
 from django.core.management import call_command
 from django.conf import settings
 from django.core.management.base import BaseCommand
 from django.core.management import call_command
-from .translation2po import get_languages
 from wolnelektury.utils import makedirs
 from wolnelektury.utils import makedirs
+from .translation2po import get_languages
 
 
-import os
-import shutil
-import tempfile
-import sys
-
-import allauth
 
 ROOT = settings.ROOT_DIR
 
 
 ROOT = settings.ROOT_DIR
 
@@ -144,30 +141,42 @@ for appn in settings.INSTALLED_APPS:
     if is_our_app(app):
         try:
             SOURCES.append(AppLocale(app))
     if is_our_app(app):
         try:
             SOURCES.append(AppLocale(app))
-        except LookupError, e:
-            print "no locales in %s" % app.__name__
+        except LookupError as e:
+            print("no locales in %s" % app.__name__)
 
 SOURCES.append(ModelTranslation('infopages', 'infopages_db'))
 SOURCES.append(CustomLocale(os.path.dirname(allauth.__file__), name='contrib'))
 
 
 class Command(BaseCommand):
 
 SOURCES.append(ModelTranslation('infopages', 'infopages_db'))
 SOURCES.append(CustomLocale(os.path.dirname(allauth.__file__), name='contrib'))
 
 
 class Command(BaseCommand):
-    option_list = BaseCommand.option_list + (
-        make_option('-l', '--load', help='load locales back to source', action='store_true', dest='load',
-                    default=False),
-        make_option('-c', '--compile', help='compile messages', action='store_true', dest='compile',
-                    default=False),
-        make_option('-g', '--generate', help='generate messages', action='store_true', dest='generate',
-                    default=False),
-        make_option('-L', '--lang', help='load just one language', dest='lang', default=None),
-        make_option('-d', '--directory', help='load from this directory', dest='directory', default=None),
-        make_option('-o', '--outfile', help='Resulting zip file', dest='outfile', default='./wl-locale.zip'),
-        make_option('-m', '--merge', help='Use git to merge. Please use with clean working directory.',
-                    action='store_true', dest='merge', default=False),
-        make_option('-M', '--message', help='commit message', dest='message', default='New locale'),
-    )
     help = 'Make a locale pack'
     help = 'Make a locale pack'
-    args = ''
+
+    def add_arguments(self, parser):
+        parser.add_argument(
+                '-l', '--load', help='load locales back to source',
+                action='store_true', dest='load', default=False)
+        parser.add_argument(
+                '-c', '--compile', help='compile messages',
+                action='store_true', dest='compile', default=False)
+        parser.add_argument(
+                '-g', '--generate', help='generate messages',
+                action='store_true', dest='generate', default=False)
+        parser.add_argument(
+                '-L', '--lang', help='load just one language',
+                dest='lang', default=None)
+        parser.add_argument(
+                '-d', '--directory', help='load from this directory',
+                dest='directory', default=None)
+        parser.add_argument(
+                '-o', '--outfile', help='Resulting zip file',
+                dest='outfile', default='./wl-locale.zip')
+        parser.add_argument(
+                '-m', '--merge', action='store_true',
+                dest='merge', default=False,
+                help='Use git to merge. Please use with clean working directory.')
+        parser.add_argument(
+                '-M', '--message', help='commit message',
+                dest='message', default='New locale')
 
     def current_rev(self):
         return os.popen('git rev-parse HEAD').read()
 
     def current_rev(self):
         return os.popen('git rev-parse HEAD').read()
@@ -227,10 +236,10 @@ class Command(BaseCommand):
         for src in SOURCES:
             src.compile()
 
         for src in SOURCES:
             src.compile()
 
-    def handle(self, *a, **options):
+    def handle(self, **options):
         if options['load']:
             if not options['directory'] or not os.path.exists(options['directory']):
         if options['load']:
             if not options['directory'] or not os.path.exists(options['directory']):
-                print "Directory not provided or does not exist, please use -d"
+                print("Directory not provided or does not exist, please use -d")
                 sys.exit(1)
 
             if options['merge']:
                 sys.exit(1)
 
             if options['merge']:
index 9fcb8bb..283470b 100644 (file)
@@ -1,17 +1,13 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import os
 import time
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import os
 import time
-from optparse import make_option
 from django.conf import settings
 from django.core.management.base import BaseCommand
 from django.db import models
 from django.conf import settings
 from django.core.management.base import BaseCommand
 from django.db import models
-
 import polib
 from modeltranslation.translator import translator, NotRegistered
 import polib
 from modeltranslation.translator import translator, NotRegistered
-
 from wolnelektury.utils import makedirs
 
 
 from wolnelektury.utils import makedirs
 
 
@@ -48,18 +44,26 @@ def get_languages(langs):
 
 
 class Command(BaseCommand):
 
 
 class Command(BaseCommand):
-    option_list = BaseCommand.option_list + (
-        make_option('-d', '--directory', help='Specify which directory should hold generated PO files',
-                    dest='directory', default=''),
-        make_option('-l', '--load', help='load locales back to source', action='store_true', dest='load',
-                    default=False),
-        make_option('-L', '--language', help='locales to load', dest='lang', default=None),
-        make_option('-n', '--poname', help='name of the po file [no extension]', dest='poname', default=None),
-        make_option('-k', '--keep-running', help='keep running even when missing an input file', dest='keep_running',
-                    default=False, action='store_true'),
-    )
     help = 'Export models from app to po files'
     help = 'Export models from app to po files'
-    args = 'app'
+
+    def add_arguments(self, parser):
+        parser.add_argument(
+                '-d', '--directory', dest='directory', default='',
+                help='Specify which directory should hold generated PO files')
+        parser.add_argument(
+                '-l', '--load', help='load locales back to source',
+                action='store_true', dest='load', default=False)
+        parser.add_argument(
+                '-L', '--language', help='locales to load',
+                dest='lang', default=None)
+        parser.add_argument(
+                '-n', '--poname', help='name of the po file [no extension]',
+                dest='poname', default=None)
+        parser.add_argument(
+                '-k', '--keep-running', dest='keep_running',
+                help='keep running even when missing an input file',
+                default=False, action='store_true')
+        parser.add_argument('app')
 
     def get_models(self, app):
         for mdname in dir(app.models):
 
     def get_models(self, app):
         for mdname in dir(app.models):
@@ -78,7 +82,8 @@ class Command(BaseCommand):
             else:
                 yield (md, opts)
 
             else:
                 yield (md, opts)
 
-    def handle(self, appname, **options):
+    def handle(self, **options):
+        appname = options['app']
         if not options['poname']:
             options['poname'] = appname
         app = __import__(appname)
         if not options['poname']:
             options['poname'] = appname
         app = __import__(appname)
index 652c80c..956f979 100644 (file)
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # Original version taken from http://www.djangosnippets.org/snippets/186/
 # Original author: udfalkso
 # Modified by: Shwagroo Team
 # Original version taken from http://www.djangosnippets.org/snippets/186/
 # Original author: udfalkso
 # Modified by: Shwagroo Team
@@ -9,7 +8,7 @@ import re
 import hotshot
 import hotshot.stats
 import tempfile
 import hotshot
 import hotshot.stats
 import tempfile
-import StringIO
+import io
 import pprint
 
 from django.conf import settings
 import pprint
 
 from django.conf import settings
@@ -101,7 +100,7 @@ class ProfileMiddleware(object):
         if (settings.DEBUG or request.user.is_superuser) and 'prof' in request.GET:
             self.prof.close()
 
         if (settings.DEBUG or request.user.is_superuser) and 'prof' in request.GET:
             self.prof.close()
 
-            out = StringIO.StringIO()
+            out = io.BytesIO()
             old_stdout = sys.stdout
             sys.stdout = out
 
             old_stdout = sys.stdout
             sys.stdout = out
 
index d13dfe1..3446695 100644 (file)
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
@@ -60,11 +59,7 @@ INSTALLED_APPS_CONTRIB = [
     'ssify',
     'django_extensions',
     'raven.contrib.django.raven_compat',
     'ssify',
     'django_extensions',
     'raven.contrib.django.raven_compat',
-
     'club.apps.ClubConfig',
     'club.apps.ClubConfig',
-    'django_comments',
-    'django_comments_xtd',
-    'django_gravatar',
 
     # allauth stuff
     'allauth',
 
     # allauth stuff
     'allauth',
index 142d33f..c65a172 100644 (file)
@@ -1,18 +1,16 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import codecs
 import csv
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import codecs
 import csv
-import cStringIO
+from functools import wraps
+from inspect import getargspec
+from io import BytesIO
 import json
 import os
 import json
 import os
-from functools import wraps
-
 import pytz
 import pytz
-from inspect import getargspec
-
 import re
 import re
+
 from django.core.mail import send_mail
 from django.http import HttpResponse
 from django.template.loader import render_to_string
 from django.core.mail import send_mail
 from django.http import HttpResponse
 from django.template.loader import render_to_string
@@ -20,6 +18,7 @@ from django.utils import timezone
 from django.conf import settings
 from django.utils.translation import ugettext
 
 from django.conf import settings
 from django.utils.translation import ugettext
 
+
 tz = pytz.timezone(settings.TIME_ZONE)
 
 
 tz = pytz.timezone(settings.TIME_ZONE)
 
 
@@ -40,7 +39,7 @@ def makedirs(path):
 
 def stringify_keys(dictionary):
     return dict((keyword.encode('ascii'), value)
 
 def stringify_keys(dictionary):
     return dict((keyword.encode('ascii'), value)
-                for keyword, value in dictionary.iteritems())
+                for keyword, value in dictionary.items())
 
 
 def json_encode(obj, sort_keys=True, ensure_ascii=False):
 
 
 def json_encode(obj, sort_keys=True, ensure_ascii=False):
@@ -87,7 +86,7 @@ def ajax(login_required=False, method=None, template=None, permission_required=N
                 if request_params:
                     request_params = dict(
                         (key, json_decode_fallback(value))
                 if request_params:
                     request_params = dict(
                         (key, json_decode_fallback(value))
-                        for key, value in request_params.iteritems()
+                        for key, value in request_params.items()
                         if fun_kwargs or key in fun_params)
                     kwargs.update(stringify_keys(request_params))
                 res = None
                         if fun_kwargs or key in fun_params)
                     kwargs.update(stringify_keys(request_params))
                 res = None
@@ -129,7 +128,7 @@ class UnicodeCSVWriter(object):
 
     def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
         # Redirect output to a queue
 
     def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
         # Redirect output to a queue
-        self.queue = cStringIO.StringIO()
+        self.queue = BytesIO()
         self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
         self.stream = f
         self.encoder = codecs.getincrementalencoder(encoding)()
         self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
         self.stream = f
         self.encoder = codecs.getincrementalencoder(encoding)()