From 967eed676fc83d15b26149047f353ac61faa8217 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Tue, 12 Mar 2019 10:43:08 +0100 Subject: [PATCH] Python 3 --- Makefile | 1 - requirements/requirements-test.txt | 3 - requirements/requirements.txt | 7 +- src/ajaxable/templatetags/ajaxable_tags.py | 6 +- src/ajaxable/utils.py | 4 +- src/api/handlers.py | 3 +- src/api/models.py | 6 +- src/api/tests/tests.py | 42 +++++----- src/api/utils.py | 3 +- src/catalogue/api/views.py | 5 ++ src/catalogue/constants.py | 3 +- src/catalogue/fields.py | 2 +- src/catalogue/helpers.py | 14 ++-- .../management/commands/checkcovers.py | 79 +++++++++---------- .../management/commands/checkintegrity.py | 47 ++++++----- src/catalogue/management/commands/gencover.py | 1 - .../management/commands/importbooks.py | 70 ++++++++-------- .../management/commands/load_abstracts.py | 2 +- src/catalogue/management/commands/pack.py | 46 +++++------ .../management/commands/report_dead_links.py | 9 +-- .../management/commands/savemedia.py | 47 ----------- .../management/commands/update_counters.py | 3 - .../commands/update_tag_description.py | 8 +- src/catalogue/models/book.py | 8 +- src/catalogue/models/bookmedia.py | 4 +- src/catalogue/models/collection.py | 2 +- src/catalogue/models/source.py | 2 +- src/catalogue/models/tag.py | 10 +-- src/catalogue/signals.py | 1 - src/catalogue/tasks.py | 6 +- src/catalogue/templatetags/catalogue_tags.py | 20 ++--- src/catalogue/test_utils.py | 10 +-- src/catalogue/tests/test_bookmedia.py | 16 ++-- src/catalogue/tests/test_cover.py | 2 +- src/catalogue/tests/test_tags.py | 32 +++++--- src/catalogue/translation.py | 1 - src/catalogue/utils.py | 19 +++-- src/catalogue/views.py | 4 +- src/chunks/models.py | 4 +- src/club/admin.py | 5 +- src/club/forms.py | 1 - src/club/models.py | 12 +-- src/contact/admin.py | 4 +- src/contact/models.py | 10 +-- src/contact/views.py | 7 +- src/dictionary/models.py | 2 +- .../management/commands/funding_notify.py | 14 ++-- src/funding/models.py | 14 ++-- src/funding/utils.py | 6 +- src/infopages/models.py | 2 +- src/isbn/forms.py | 2 +- src/isbn/management/commands/export_onix.py | 2 +- src/isbn/management/commands/import_onix.py | 15 ++-- src/isbn/models.py | 2 +- src/isbn/views.py | 2 +- .../management/commands/lesmianator.py | 45 +++++------ src/lesmianator/models.py | 18 ++--- src/libraries/models.py | 4 +- src/newsletter/models.py | 2 +- src/oai/handlers.py | 2 +- src/opds/views.py | 6 +- src/paypal/tests.py | 3 +- src/paypal/views.py | 2 +- src/pdcounter/models.py | 4 +- .../migrations/0005_auto_20141022_1001.py | 4 +- src/picture/models.py | 12 +-- src/picture/tasks.py | 8 +- src/picture/templatetags/picture_tags.py | 2 +- src/picture/views.py | 1 - src/polls/models.py | 6 +- src/push/models.py | 2 +- src/reporting/utils.py | 4 +- src/search/custom.py | 28 +++---- src/search/fields.py | 4 +- src/search/index.py | 28 +++---- src/search/management/commands/reindex.py | 38 ++++----- .../management/commands/reindex_pictures.py | 21 +++-- src/search/management/commands/snippets.py | 9 +-- src/search/mock_search.py | 3 +- src/search/templatetags/search_tags.py | 2 +- src/social/models.py | 2 +- src/social/templatetags/social_tags.py | 2 +- src/sortify.py | 4 +- src/sponsors/models.py | 10 +-- src/sponsors/widgets.py | 2 +- src/stats/tasks.py | 8 +- src/stats/utils.py | 6 +- src/suggest/models.py | 8 +- src/wolnelektury/contact_forms.py | 2 +- .../commands/clean_social_accounts.py | 4 +- .../management/commands/localepack.py | 65 ++++++++------- .../management/commands/translation2po.py | 37 +++++---- src/wolnelektury/middleware.py | 5 +- src/wolnelektury/settings/apps.py | 5 -- src/wolnelektury/utils.py | 17 ++-- 95 files changed, 528 insertions(+), 559 deletions(-) delete mode 100755 src/catalogue/management/commands/savemedia.py diff --git a/Makefile b/Makefile index 1a602bad4..3956cf34c 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,6 @@ deploy: src/wolnelektury/localsettings.py 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 diff --git a/requirements/requirements-test.txt b/requirements/requirements-test.txt index 794632d4d..4ebc8aea5 100644 --- a/requirements/requirements-test.txt +++ b/requirements/requirements-test.txt @@ -1,4 +1 @@ --i https://py.mdrn.pl:8443/simple/ - coverage -mock diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 267e56f8e..6ecaeb09c 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -38,7 +38,7 @@ mutagen>=1.31 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 @@ -48,10 +48,9 @@ kombu>=3.0.23,<3.1 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 diff --git a/src/ajaxable/templatetags/ajaxable_tags.py b/src/ajaxable/templatetags/ajaxable_tags.py index 31897d1b0..d327e2eb6 100644 --- a/src/ajaxable/templatetags/ajaxable_tags.py +++ b/src/ajaxable/templatetags/ajaxable_tags.py @@ -3,7 +3,7 @@ # 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 @@ -32,8 +32,8 @@ def pretty_field(field, template=None): 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), }) diff --git a/src/ajaxable/utils.py b/src/ajaxable/utils.py index 89dd0e2d9..8e34a9a57 100755 --- a/src/ajaxable/utils.py +++ b/src/ajaxable/utils.py @@ -6,7 +6,7 @@ from functools import wraps 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 @@ -18,7 +18,7 @@ from honeypot.decorators import verify_honeypot_value class LazyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, Promise): - return force_unicode(obj) + return force_text(obj) return obj diff --git a/src/api/handlers.py b/src/api/handlers.py index d08812f41..5af5cfb64 100644 --- a/src/api/handlers.py +++ b/src/api/handlers.py @@ -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. # @@ -8,7 +7,7 @@ from catalogue.models import Book, Tag WL_BASE = lazy( - lambda: u'https://' + Site.objects.get_current().domain, unicode)() + lambda: 'https://' + Site.objects.get_current().domain, str)() category_singular = { 'authors': 'author', diff --git a/src/api/models.py b/src/api/models.py index 71dedb56c..9a86c2c4b 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -76,7 +76,7 @@ class Nonce(models.Model): 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) @@ -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') - def __unicode__(self): + def __str__(self): 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) - def __unicode__(self): + def __str__(self): return u"%s Token %s for %s" % (self.get_token_type_display(), self.key, self.consumer) diff --git a/src/api/tests/tests.py b/src/api/tests/tests.py index b3cc54fc9..eccdd04a8 100644 --- a/src/api/tests/tests.py +++ b/src/api/tests/tests.py @@ -7,16 +7,15 @@ from os import path import hashlib import hmac import json -from StringIO import StringIO +from io import BytesIO 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 mock import patch +from unittest.mock import patch 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): - 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() @@ -112,12 +111,12 @@ class PictureTests(ApiTest): '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" - )).read()) + ), 'rb').read()) import_form = PictureImportForm({}, { 'picture_xml_file': xml, @@ -265,13 +264,15 @@ class OAuth1Tests(ApiTest): 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() - 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) - 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] @@ -297,16 +298,16 @@ class OAuth1Tests(ApiTest): 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() - 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) - 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( @@ -333,7 +334,7 @@ class AuthorizedTests(ApiTest): 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): @@ -365,7 +366,10 @@ class AuthorizedTests(ApiTest): 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()) @@ -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', - return_value=StringIO("")): + return_value=BytesIO(b"")): self.assertEqual( self.signed('/api/epub/grandchild/').content, - "") + b"") def test_publish(self): response = self.signed('/api/books/', diff --git a/src/api/utils.py b/src/api/utils.py index c9c1f94cc..a3a1f893a 100644 --- a/src/api/utils.py +++ b/src/api/utils.py @@ -25,8 +25,9 @@ def oauthlib_request(request): "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.""" + headers, body, status = response_tuple response = HttpResponse(body, status=status) for k, v in headers.items(): if k == 'Location': diff --git a/src/catalogue/api/views.py b/src/catalogue/api/views.py index 62876c0cc..7c4b88825 100644 --- a/src/catalogue/api/views.py +++ b/src/catalogue/api/views.py @@ -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')) + if count: + try: + count = int(count) + except TypeError: + raise Http404 # Fixme if tags: if self.kwargs.get('top_level'): diff --git a/src/catalogue/constants.py b/src/catalogue/constants.py index 60cecebe5..1a655f428 100644 --- a/src/catalogue/constants.py +++ b/src/catalogue/constants.py @@ -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/'] -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. diff --git a/src/catalogue/fields.py b/src/catalogue/fields.py index ebe4df17e..2b2d6a6da 100644 --- a/src/catalogue/fields.py +++ b/src/catalogue/fields.py @@ -106,7 +106,7 @@ class BuildEbook(Task): 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) diff --git a/src/catalogue/helpers.py b/src/catalogue/helpers.py index 4dd7bda9f..0330e692c 100644 --- a/src/catalogue/helpers.py +++ b/src/catalogue/helpers.py @@ -8,14 +8,14 @@ from django.core.cache import cache 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 -_COUNTER_TIME = None +_COUNTER_TIME = 0 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: - for i in xrange(10): + for i in range(10): 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 @@ -95,8 +95,8 @@ def update_counters(): "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(): diff --git a/src/catalogue/management/commands/checkcovers.py b/src/catalogue/management/commands/checkcovers.py index 2466728a9..66a69c78f 100644 --- a/src/catalogue/management/commands/checkcovers.py +++ b/src/catalogue/management/commands/checkcovers.py @@ -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. # -from optparse import make_option 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 catalogue import app_settings def ancestor_has_cover(book): @@ -27,12 +25,13 @@ def full_url(obj): 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.' + 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 @@ -47,7 +46,7 @@ class Command(BaseCommand): 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( @@ -71,7 +70,7 @@ class Command(BaseCommand): 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. """ % ( @@ -81,51 +80,51 @@ Bad licenses used: %s (%d covers without license). len(no_license), len(not_redakcja), redakcja_url, - ) + )) if verbose: if bad_license: - print - print "Bad license:" - print "============" + print() + print("Bad license:") + print("============") for lic, books in bad_license.items(): - print - print lic + print() + print(lic) for book in books: - print full_url(book) + print(full_url(book)) if no_license: - print - print "No license:" - print "===========" + print() + print("No license:") + print("===========") 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: - print - print "Not from Redakcja or source missing:" - print "====================================" + print() + print("Not from Redakcja or source missing:") + print("====================================") 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: - print - print "No cover:" - print "=========" + print() + print("No cover:") + print("=========") for book in without_cover: - print full_url(book) + print(full_url(book)) if with_ancestral_cover: - print - print "With ancestral cover:" - print "=====================" + print() + print("With ancestral cover:") + print("=====================") for book in with_ancestral_cover: - print full_url(book) + print(full_url(book)) diff --git a/src/catalogue/management/commands/checkintegrity.py b/src/catalogue/management/commands/checkintegrity.py index 6f090bb8c..e7d5ffd78 100644 --- a/src/catalogue/management/commands/checkintegrity.py +++ b/src/catalogue/management/commands/checkintegrity.py @@ -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. # -from optparse import make_option from django.core.management.base import BaseCommand - -from catalogue.models import Book from librarian import ParseError +from catalogue.models import Book 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.' + 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 @@ -29,21 +28,21 @@ class Command(BaseCommand): 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: - 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 - 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 = [] @@ -54,14 +53,14 @@ class Command(BaseCommand): 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']: - print "Fixed." + print("Fixed.") if options['verbose']: - print + print() # TODO: check metadata tags, reset counters diff --git a/src/catalogue/management/commands/gencover.py b/src/catalogue/management/commands/gencover.py index ec010a566..c0d099b60 100644 --- a/src/catalogue/management/commands/gencover.py +++ b/src/catalogue/management/commands/gencover.py @@ -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. # diff --git a/src/catalogue/management/commands/importbooks.py b/src/catalogue/management/commands/importbooks.py index 528077893..b8a9aa7bf 100644 --- a/src/catalogue/management/commands/importbooks.py +++ b/src/catalogue/management/commands/importbooks.py @@ -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 -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 wolnelektury.management.profile import profile from catalogue.models import Book from picture.models import Picture - 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.' - 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') @@ -54,24 +55,23 @@ class Command(BaseCommand): 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')) - except Exception, ex: + except Exception as ex: if continue_on_error: - print "%s: %s" % (file_path, ex) + print("%s: %s" % (file_path, ex)) return else: raise ex return picture - # @profile @transaction.atomic - def handle(self, *directories, **options): + def handle(self, **options): self.style = color_style() verbose = options.get('verbose') @@ -82,16 +82,16 @@ class Command(BaseCommand): 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 - for dir_name in directories: + for dir_name in options['directory']: 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)) @@ -106,7 +106,7 @@ class Command(BaseCommand): continue if verbose > 0: - print "Parsing '%s'" % file_path + print("Parsing '%s'" % file_path) else: sys.stdout.write('.') sys.stdout.flush() @@ -121,16 +121,16 @@ class Command(BaseCommand): 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.' % - file_path) + file_path)) 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: - 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: @@ -138,7 +138,7 @@ class Command(BaseCommand): 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() diff --git a/src/catalogue/management/commands/load_abstracts.py b/src/catalogue/management/commands/load_abstracts.py index b828974a2..19ff8c437 100644 --- a/src/catalogue/management/commands/load_abstracts.py +++ b/src/catalogue/management/commands/load_abstracts.py @@ -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'): - print b.slug + print(b.slug) b.load_abstract() b.save() diff --git a/src/catalogue/management/commands/pack.py b/src/catalogue/management/commands/pack.py index 98ad7d836..181c2e6c2 100755 --- a/src/catalogue/management/commands/pack.py +++ b/src/catalogue/management/commands/pack.py @@ -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. # -from optparse import make_option - +import zipfile 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): - 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.' - 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() + ftype = options['ftype'] + path = options['path'] 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: - print self.style.ERROR('Unknown file type.') + print(self.style.ERROR('Unknown file type.')) return books = [] @@ -54,11 +56,11 @@ class Command(BaseCommand): 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: - 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))) @@ -67,11 +69,11 @@ class Command(BaseCommand): 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: - print self.style.ERROR("No books found") + print(self.style.ERROR("No books found")) 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) diff --git a/src/catalogue/management/commands/report_dead_links.py b/src/catalogue/management/commands/report_dead_links.py index 1eb73127b..a6ee2a9d5 100644 --- a/src/catalogue/management/commands/report_dead_links.py +++ b/src/catalogue/management/commands/report_dead_links.py @@ -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. # -from __future__ import print_function, unicode_literals - 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 - 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 @@ -46,10 +43,10 @@ class Command(BaseCommand): if url: try: urlopen(url) - except (HTTPError, URLError, ValueError), e: + except (HTTPError, URLError, ValueError) as e: 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]))) diff --git a/src/catalogue/management/commands/savemedia.py b/src/catalogue/management/commands/savemedia.py deleted file mode 100755 index ab4da51d4..000000000 --- a/src/catalogue/management/commands/savemedia.py +++ /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) diff --git a/src/catalogue/management/commands/update_counters.py b/src/catalogue/management/commands/update_counters.py index 089dd0ff8..c79f61441 100644 --- a/src/catalogue/management/commands/update_counters.py +++ b/src/catalogue/management/commands/update_counters.py @@ -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. # -from __future__ import print_function, unicode_literals - from django.conf import settings from django.core.management.base import BaseCommand diff --git a/src/catalogue/management/commands/update_tag_description.py b/src/catalogue/management/commands/update_tag_description.py index d13b12005..bd005623c 100644 --- a/src/catalogue/management/commands/update_tag_description.py +++ b/src/catalogue/management/commands/update_tag_description.py @@ -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 - 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) diff --git a/src/catalogue/models/book.py b/src/catalogue/models/book.py index 923a60485..206ab733b 100644 --- a/src/catalogue/models/book.py +++ b/src/catalogue/models/book.py @@ -127,7 +127,7 @@ class Book(models.Model): verbose_name_plural = _('books') app_label = 'catalogue' - def __unicode__(self): + def __str__(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] - self.title = unicode(self.title) # ??? + self.title = str(self.title) # ??? try: author = self.authors().first().sort_key @@ -401,7 +401,7 @@ class Book(models.Model): index.index_tags() if commit: index.index.commit() - except Exception, e: + except Exception as e: index.index.rollback() raise e @@ -672,7 +672,7 @@ class Book(models.Model): 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) diff --git a/src/catalogue/models/bookmedia.py b/src/catalogue/models/bookmedia.py index 407c41969..957e982d1 100644 --- a/src/catalogue/models/bookmedia.py +++ b/src/catalogue/models/bookmedia.py @@ -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) - def __unicode__(self): + def __str__(self): 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 - 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()) diff --git a/src/catalogue/models/collection.py b/src/catalogue/models/collection.py index b765abefe..3c4b475a6 100644 --- a/src/catalogue/models/collection.py +++ b/src/catalogue/models/collection.py @@ -24,7 +24,7 @@ class Collection(models.Model): verbose_name_plural = _('collections') app_label = 'catalogue' - def __unicode__(self): + def __str__(self): return self.title def get_initial(self): diff --git a/src/catalogue/models/source.py b/src/catalogue/models/source.py index bcf5254bb..f36850ddd 100644 --- a/src/catalogue/models/source.py +++ b/src/catalogue/models/source.py @@ -17,7 +17,7 @@ class Source(models.Model): verbose_name_plural = _('sources') app_label = 'catalogue' - def __unicode__(self): + def __str__(self): return self.netloc def save(self, *args, **kwargs): diff --git a/src/catalogue/models/tag.py b/src/catalogue/models/tag.py index 31da2564b..1b4160121 100644 --- a/src/catalogue/models/tag.py +++ b/src/catalogue/models/tag.py @@ -43,7 +43,7 @@ class TagRelation(models.Model): 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: @@ -92,7 +92,7 @@ class Tag(models.Model): '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',) @@ -155,7 +155,7 @@ class Tag(models.Model): flush_ssi_includes([ '/katalog/%s.json' % lang for lang in languages]) - def __unicode__(self): + def __str__(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) - except KeyError: + except (AttributeError, KeyError): # TODO: shouldn't be KeyError here at all. 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: - 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()) diff --git a/src/catalogue/signals.py b/src/catalogue/signals.py index 28d84bece..d3d6ec340 100644 --- a/src/catalogue/signals.py +++ b/src/catalogue/signals.py @@ -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. # diff --git a/src/catalogue/tasks.py b/src/catalogue/tasks.py index 265897f52..a7b67aec9 100644 --- a/src/catalogue/tasks.py +++ b/src/catalogue/tasks.py @@ -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) - except Exception, e: - print "Exception during index: %s" % e + except Exception as e: + print("Exception during index: %s" % 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) - 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() diff --git a/src/catalogue/templatetags/catalogue_tags.py b/src/catalogue/templatetags/catalogue_tags.py index 263f12cfa..70676dd1a 100644 --- a/src/catalogue/templatetags/catalogue_tags.py +++ b/src/catalogue/templatetags/catalogue_tags.py @@ -3,7 +3,7 @@ # 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 @@ -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: - 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' % ( - 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: - title += u'Motyw %s' % unicode(self['theme']) + title += u'Motyw %s' % str(self['theme']) 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: - 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: - 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: - title += u'%s ' % unicode(self.get('kind', u'twórczość')) + title += u'%s ' % str(self.get('kind', u'twórczość')) 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: - title += flection.get_case(unicode(self['epoch']), u'dopełniacz') + title += flection.get_case(str(self['epoch']), u'dopełniacz') return capfirst(title) diff --git a/src/catalogue/test_utils.py b/src/catalogue/test_utils.py index 8c76b899d..7ed3536aa 100644 --- a/src/catalogue/test_utils.py +++ b/src/catalogue/test_utils.py @@ -58,25 +58,25 @@ class BookInfoStub(object): 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: - raise + raise AttributeError(e) 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 """ - slug = unicode(slugify(title)) + slug = str(slugify(title)) 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, diff --git a/src/catalogue/tests/test_bookmedia.py b/src/catalogue/tests/test_bookmedia.py index 263e48d9a..b744f963f 100644 --- a/src/catalogue/tests/test_bookmedia.py +++ b/src/catalogue/tests/test_bookmedia.py @@ -15,8 +15,8 @@ class BookMediaTests(WLTestCase): 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): @@ -50,7 +50,7 @@ class BookMediaTests(WLTestCase): 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?') @@ -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') - 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): """ @@ -81,7 +81,7 @@ class BookMediaTests(WLTestCase): 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): @@ -99,8 +99,8 @@ class BookMediaTests(WLTestCase): 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 = [ diff --git a/src/catalogue/tests/test_cover.py b/src/catalogue/tests/test_cover.py index 8c5d04718..59a05edc9 100644 --- a/src/catalogue/tests/test_cover.py +++ b/src/catalogue/tests/test_cover.py @@ -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 mock import patch +from unittest.mock import patch class CoverTests(WLTestCase): diff --git a/src/catalogue/tests/test_tags.py b/src/catalogue/tests/test_tags.py index d5aa72c49..771778246 100644 --- a/src/catalogue/tests/test_tags.py +++ b/src/catalogue/tests/test_tags.py @@ -29,7 +29,7 @@ class BooksByTagTests(WLTestCase): parts=[self.child_info.url], **info_args("Parent")) - self.book_file = ContentFile('') + self.book_file = ContentFile(b'') def test_nonexistent_tag(self): """ Looking for a non-existent tag should yield 404 """ @@ -95,8 +95,10 @@ class TagRelatedTagsTests(WLTestCase): Ala ma kota - """ % 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') @@ -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: ' + - 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']], @@ -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: ' + - unicode([(tag.name, tag.count) for tag in cats['epoch']])) + str([(tag.name, tag.count) for tag in cats['epoch']])) class CleanTagRelationTests(WLTestCase): @@ -187,7 +189,9 @@ class CleanTagRelationTests(WLTestCase): """ - 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): @@ -228,7 +232,9 @@ class TestIdenticalTag(WLTestCase): 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': @@ -237,9 +243,11 @@ class TestIdenticalTag(WLTestCase): 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'} - 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']) @@ -267,8 +275,10 @@ class BookTagsTests(WLTestCase): Ala ma kota - """ % 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 """ diff --git a/src/catalogue/translation.py b/src/catalogue/translation.py index 4946143db..aec44bc07 100644 --- a/src/catalogue/translation.py +++ b/src/catalogue/translation.py @@ -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. # diff --git a/src/catalogue/utils.py b/src/catalogue/utils.py index 380ec8ecd..ec938eabb 100644 --- a/src/catalogue/utils.py +++ b/src/catalogue/utils.py @@ -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.utils.encoding import force_unicode +from django.utils.encoding import force_text from reporting.utils import read_chunks @@ -27,14 +27,19 @@ if hasattr(random, 'SystemRandom'): 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): - 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): @@ -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. """ - s = force_unicode(s) + s = force_text(s) length = int(num) if length <= 0: return u'' diff --git a/src/catalogue/views.py b/src/catalogue/views.py index 1f2db19c6..5beb6dc01 100644 --- a/src/catalogue/views.py +++ b/src/catalogue/views.py @@ -171,10 +171,10 @@ def analyse_tags(request, tag_str): 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)) - 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)]))) diff --git a/src/chunks/models.py b/src/chunks/models.py index 37b9f600a..a9f6e7f88 100644 --- a/src/chunks/models.py +++ b/src/chunks/models.py @@ -23,7 +23,7 @@ class Chunk(models.Model): verbose_name = _('chunk') verbose_name_plural = _('chunks') - def __unicode__(self): + def __str__(self): 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') - def __unicode__(self): + def __str__(self): return self.key diff --git a/src/club/admin.py b/src/club/admin.py index bfa433006..76cc71ed0 100644 --- a/src/club/admin.py +++ b/src/club/admin.py @@ -20,6 +20,7 @@ class ScheduleAdmin(admin.ModelAdmin): 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) @@ -32,7 +33,9 @@ admin.site.register(models.Payment, PaymentAdmin) class MembershipAdmin(admin.ModelAdmin): - pass + list_display = ['user'] + raw_id_fields = ['user'] + search_fields = ['user__username', 'user__email'] admin.site.register(models.Membership, MembershipAdmin) diff --git a/src/club/forms.py b/src/club/forms.py index adf8959fc..c5d57819e 100644 --- a/src/club/forms.py +++ b/src/club/forms.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 from django import forms from . import models -from . import widgets from .payment_methods import method_by_slug diff --git a/src/club/models.py b/src/club/models.py index a8b4089c7..17771742c 100644 --- a/src/club/models.py +++ b/src/club/models.py @@ -31,7 +31,7 @@ class Plan(models.Model): 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: @@ -74,7 +74,7 @@ class Schedule(models.Model): verbose_name = _('schedule') verbose_name_plural = _('schedules') - def __unicode__(self): + def __str__(self): return self.key def save(self, *args, **kwargs): @@ -109,7 +109,7 @@ class Payment(models.Model): verbose_name = _('payment') verbose_name_plural = _('payments') - def __unicode__(self): + def __str__(self): return "%s %s" % (self.schedule, self.payed_at) @@ -122,8 +122,8 @@ class Membership(models.Model): 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): @@ -136,7 +136,7 @@ class ReminderEmail(models.Model): 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: diff --git a/src/contact/admin.py b/src/contact/admin.py index 41095092e..a05943381 100644 --- a/src/contact/admin.py +++ b/src/contact/admin.py @@ -157,11 +157,11 @@ def extract_view(request, form_tag, extract_type_slug): 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' - 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]) diff --git a/src/contact/models.py b/src/contact/models.py index 0ab8201f4..e44bd9bb0 100644 --- a/src/contact/models.py +++ b/src/contact/models.py @@ -2,7 +2,7 @@ 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 @@ -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: - value = smart_unicode(value).replace(u" ", unichr(160)) + value = smart_text(value).replace(u" ", unichr(160)) return value class Meta: @@ -28,11 +28,11 @@ class Contact(models.Model): 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): - 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() diff --git a/src/contact/views.py b/src/contact/views.py index 7ec05052a..0f8aad9a5 100644 --- a/src/contact/views.py +++ b/src/contact/views.py @@ -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 @@ -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) - 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: @@ -48,7 +47,7 @@ def form(request, form_tag, force_enabled=False): 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'], diff --git a/src/dictionary/models.py b/src/dictionary/models.py index 7df6ddc87..c8e086fb4 100644 --- a/src/dictionary/models.py +++ b/src/dictionary/models.py @@ -19,7 +19,7 @@ class Qualifier(models.Model): class Meta: ordering = ['qualifier'] - def __unicode__(self): + def __str__(self): return self.name or self.qualifier diff --git a/src/funding/management/commands/funding_notify.py b/src/funding/management/commands/funding_notify.py index 342c96377..f622374fe 100755 --- a/src/funding/management/commands/funding_notify.py +++ b/src/funding/management/commands/funding_notify.py @@ -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. # -from optparse import make_option 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.' + 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 @@ -24,7 +24,7 @@ class Command(BaseCommand): 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() @@ -35,5 +35,5 @@ class Command(BaseCommand): 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() diff --git a/src/funding/models.py b/src/funding/models.py index b126c7681..6ca02615f 100644 --- a/src/funding/models.py +++ b/src/funding/models.py @@ -3,7 +3,7 @@ # 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 @@ -48,7 +48,7 @@ class Offer(models.Model): 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): @@ -218,7 +218,7 @@ class Perk(models.Model): 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 "") @@ -248,8 +248,8 @@ class Funding(models.Model): """ 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]) @@ -318,8 +318,8 @@ class Spent(models.Model): 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) diff --git a/src/funding/utils.py b/src/funding/utils.py index 580833699..e93d8161d 100644 --- a/src/funding/utils.py +++ b/src/funding/utils.py @@ -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( - string.uppercase + - string.lowercase + + string.ascii_uppercase + + string.ascii_lowercase + u'ąćęłńóśźżĄĆĘŁŃÓŚŹŻ' + string.digits + ' ' + @@ -25,4 +25,4 @@ def replace_char(m): 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)) diff --git a/src/infopages/models.py b/src/infopages/models.py index c9c31e7b9..8999d99bd 100644 --- a/src/infopages/models.py +++ b/src/infopages/models.py @@ -20,7 +20,7 @@ class InfoPage(models.Model): verbose_name = _('info page') verbose_name_plural = _('info pages') - def __unicode__(self): + def __str__(self): return self.title @models.permalink diff --git a/src/isbn/forms.py b/src/isbn/forms.py index 2acc34a8e..47a72baf2 100644 --- a/src/isbn/forms.py +++ b/src/isbn/forms.py @@ -1,6 +1,6 @@ # -*- 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 _ diff --git a/src/isbn/management/commands/export_onix.py b/src/isbn/management/commands/export_onix.py index 19f3166f8..386dd1b99 100644 --- a/src/isbn/management/commands/export_onix.py +++ b/src/isbn/management/commands/export_onix.py @@ -113,7 +113,7 @@ class Command(BaseCommand): 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: diff --git a/src/isbn/management/commands/import_onix.py b/src/isbn/management/commands/import_onix.py index 796b7aadc..85d54eca7 100644 --- a/src/isbn/management/commands/import_onix.py +++ b/src/isbn/management/commands/import_onix.py @@ -30,7 +30,7 @@ def parse_date(date_str): 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)) @@ -44,14 +44,17 @@ def get_field(element, tags, allow_multiple=False): 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 - 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')] @@ -62,7 +65,7 @@ class Command(BaseCommand): 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'])) @@ -78,7 +81,7 @@ class Command(BaseCommand): 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: diff --git a/src/isbn/models.py b/src/isbn/models.py index a4fa28a4a..ca8dbee15 100644 --- a/src/isbn/models.py +++ b/src/isbn/models.py @@ -22,7 +22,7 @@ class ISBNPool(models.Model): next_suffix = models.IntegerField() purpose = models.CharField(max_length=4, choices=PURPOSE_CHOICES) - def __unicode__(self): + def __str__(self): return self.prefix @classmethod diff --git a/src/isbn/views.py b/src/isbn/views.py index 8cf7b5d34..8073c75a8 100644 --- a/src/isbn/views.py +++ b/src/isbn/views.py @@ -78,7 +78,7 @@ def get_format(record): 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') diff --git a/src/lesmianator/management/commands/lesmianator.py b/src/lesmianator/management/commands/lesmianator.py index a890f8640..bcad60189 100644 --- a/src/lesmianator/management/commands/lesmianator.py +++ b/src/lesmianator/management/commands/lesmianator.py @@ -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 -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 @@ -17,15 +15,18 @@ re_text = re.compile(r'\n{3,}(.*?)\n*-----\n', re.S).search 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() @@ -37,7 +38,7 @@ class Command(BaseCommand): 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 = [] @@ -59,22 +60,22 @@ class Command(BaseCommand): 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: - 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: - 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 = '' - 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) @@ -84,18 +85,18 @@ class Command(BaseCommand): 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: - print self.style.ERROR("No books found") + print(self.style.ERROR("No books found")) 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: - 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) diff --git a/src/lesmianator/models.py b/src/lesmianator/models.py index 684d1c4bf..0595810cc 100644 --- a/src/lesmianator/models.py +++ b/src/lesmianator/models.py @@ -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. # -import cPickle -from cPickle import PickleError +import pickle +from pickle import PickleError from datetime import datetime from random import randint -from StringIO import StringIO 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) - global_dictionary = cPickle.load(f) + global_dictionary = pickle.load(f) 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() - def __unicode__(self): + def __str__(self): 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'), ) - 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): @@ -124,7 +123,6 @@ class Continuations(models.Model): @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 @@ -158,7 +156,7 @@ class Continuations(models.Model): 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 @@ -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) - 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 diff --git a/src/libraries/models.py b/src/libraries/models.py index 588620f1e..0ad058610 100644 --- a/src/libraries/models.py +++ b/src/libraries/models.py @@ -16,7 +16,7 @@ class Catalog(models.Model): verbose_name = _('catalog') verbose_name_plural = _('catalogs') - def __unicode__(self): + def __str__(self): return self.name @models.permalink @@ -37,7 +37,7 @@ class Library(models.Model): verbose_name = _('library') verbose_name_plural = _('libraries') - def __unicode__(self): + def __str__(self): return self.name @models.permalink diff --git a/src/newsletter/models.py b/src/newsletter/models.py index 65fc43502..0cbf867be 100644 --- a/src/newsletter/models.py +++ b/src/newsletter/models.py @@ -16,7 +16,7 @@ class Subscription(Model): verbose_name = _('subscription') verbose_name_plural = _('subscriptions') - def __unicode__(self): + def __str__(self): return self.email def hashcode(self): diff --git a/src/oai/handlers.py b/src/oai/handlers.py index 0eeea8c0d..779406d70 100644 --- a/src/oai/handlers.py +++ b/src/oai/handlers.py @@ -116,7 +116,7 @@ class Catalogue(common.ResumptionOAIPMH): 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 diff --git a/src/opds/views.py b/src/opds/views.py index 004ee5b7d..1afbcda3e 100644 --- a/src/opds/views.py +++ b/src/opds/views.py @@ -3,7 +3,7 @@ # 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 @@ -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_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: - _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 = '' diff --git a/src/paypal/tests.py b/src/paypal/tests.py index a7badbe83..7de047af6 100644 --- a/src/paypal/tests.py +++ b/src/paypal/tests.py @@ -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 -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 diff --git a/src/paypal/views.py b/src/paypal/views.py index c78a19af3..c541ba80b 100644 --- a/src/paypal/views.py +++ b/src/paypal/views.py @@ -25,7 +25,7 @@ def paypal_form(request, app=False): 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() diff --git a/src/pdcounter/models.py b/src/pdcounter/models.py index 24866fee5..4454e5af0 100644 --- a/src/pdcounter/models.py +++ b/src/pdcounter/models.py @@ -28,7 +28,7 @@ class Author(models.Model): def category(self): return "author" - def __unicode__(self): + def __str__(self): return self.name def __repr__(self): @@ -71,7 +71,7 @@ class BookStub(models.Model): verbose_name = _('book stub') verbose_name_plural = _('book stubs') - def __unicode__(self): + def __str__(self): return self.title @permalink diff --git a/src/picture/migrations/0005_auto_20141022_1001.py b/src/picture/migrations/0005_auto_20141022_1001.py index 677877eae..7f8d346ec 100644 --- a/src/picture/migrations/0005_auto_20141022_1001.py +++ b/src/picture/migrations/0005_auto_20141022_1001.py @@ -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 - 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'], - })) + }) pic.html_file.save("%s.html" % pic.slug, ContentFile(html_text)) pic.save() diff --git a/src/picture/models.py b/src/picture/models.py index a3c098c79..4d8dac819 100644 --- a/src/picture/models.py +++ b/src/picture/models.py @@ -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 StringIO import StringIO +from io import BytesIO import jsonfield import itertools import logging @@ -123,7 +123,7 @@ class Picture(models.Model): return ret - def __unicode__(self): + def __str__(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): - image_file = File(open(image_file)) + image_file = File(open(image_file, 'rb')) 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() - 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)) @@ -282,7 +282,7 @@ class Picture(models.Model): 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)) @@ -372,6 +372,6 @@ class Picture(models.Model): index.index_tags() if commit: index.index.commit() - except Exception, e: + except Exception as e: index.index.rollback() raise e diff --git a/src/picture/tasks.py b/src/picture/tasks.py index cae7db7a6..e80b0fc77 100644 --- a/src/picture/tasks.py +++ b/src/picture/tasks.py @@ -14,10 +14,10 @@ def generate_picture_html(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'], - })) + }) 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) - except Exception, e: - print "Exception during index: %s" % e + except Exception as e: + print("Exception during index: %s" % e) print_exc() raise e diff --git a/src/picture/templatetags/picture_tags.py b/src/picture/templatetags/picture_tags.py index a766b44d9..973d862f9 100644 --- a/src/picture/templatetags/picture_tags.py +++ b/src/picture/templatetags/picture_tags.py @@ -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 '' - except Exception, e: + except Exception as e: logging.exception("Error creating a thumbnail for PictureArea") return '' diff --git a/src/picture/views.py b/src/picture/views.py index a43c09555..ecd5d66cf 100644 --- a/src/picture/views.py +++ b/src/picture/views.py @@ -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. # diff --git a/src/polls/models.py b/src/polls/models.py index bfc36394b..4b1882df2 100644 --- a/src/polls/models.py +++ b/src/polls/models.py @@ -26,7 +26,7 @@ class Poll(models.Model): 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): @@ -50,8 +50,8 @@ class PollItem(models.Model): 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): diff --git a/src/push/models.py b/src/push/models.py index 79e555173..bb84b992f 100644 --- a/src/push/models.py +++ b/src/push/models.py @@ -16,5 +16,5 @@ class Notification(models.Model): class Meta: ordering = ['-timestamp'] - def __unicode__(self): + def __str__(self): return '%s: %s' % (self.timestamp, self.title) diff --git a/src/reporting/utils.py b/src/reporting/utils.py index f5d4c3342..955f7d92d 100755 --- a/src/reporting/utils.py +++ b/src/reporting/utils.py @@ -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 """ - from StringIO import StringIO + from io import BytesIO 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) - 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: diff --git a/src/search/custom.py b/src/search/custom.py index da21e019e..a94468769 100644 --- a/src/search/custom.py +++ b/src/search/custom.py @@ -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. # -from sunburnt import sunburnt +from scorched import connection from lxml import etree -import urllib +from urllib.parse import urlencode import warnings -from sunburnt import search +from scorched import search 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 = [] - if isinstance(fields, basestring): + if isinstance(fields, (str, bytes)): fields = [fields] self.schema.check_fields(fields, {"stored": True}) self.fields.update(fields) @@ -42,13 +42,13 @@ class TermVectorOptions(search.Options): 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): - 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. " @@ -63,11 +63,11 @@ class CustomSolrConnection(sunburnt.SolrConnection): 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 -# monkey patching sunburnt SolrSearch +# monkey patching scorched SolrSearch 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) -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, - 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: @@ -97,8 +97,8 @@ class CustomSolrInterface(sunburnt.SolrInterface): 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: @@ -115,7 +115,7 @@ class CustomSolrInterface(sunburnt.SolrInterface): 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) @@ -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]") - 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): diff --git a/src/search/fields.py b/src/search/fields.py index e2cfb5463..c27cce166 100755 --- a/src/search/fields.py +++ b/src/search/fields.py @@ -4,7 +4,7 @@ # 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 @@ -21,7 +21,7 @@ class JQueryAutoCompleteWidget(forms.TextInput): 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 diff --git a/src/search/index.py b/src/search/index.py index d3377b103..f943a4d1d 100644 --- a/src/search/index.py +++ b/src/search/index.py @@ -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 sunburnt -import custom +import scorched +from . import custom import operator import logging from wolnelektury.utils import makedirs @@ -129,7 +129,7 @@ class Index(SolrIndex): """ 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 @@ -324,9 +324,9 @@ class Index(SolrIndex): elif type_indicator == dcparser.as_person: p = getattr(book_info, field.name) if isinstance(p, dcparser.Person): - persons = unicode(p) + persons = str(p) 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) @@ -478,7 +478,7 @@ class Index(SolrIndex): 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() @@ -610,14 +610,14 @@ class SearchResult(object): result._book = book return result - def __unicode__(self): + def __str__(self): return u"" % \ (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): @@ -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(' ') - 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]) @@ -791,11 +791,11 @@ class PictureResult(object): self._hits.append(hit) - def __unicode__(self): + def __str__(self): return u"" % (self.picture_id, self._score) def __repr__(self): - return unicode(self) + return str(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(' ') - 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]) @@ -965,7 +965,7 @@ class Search(SolrIndex): 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) diff --git a/src/search/management/commands/reindex.py b/src/search/management/commands/reindex.py index da4574fe0..c2fe78e94 100755 --- a/src/search/management/commands/reindex.py +++ b/src/search/management/commands/reindex.py @@ -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. # @@ -7,8 +6,6 @@ import traceback 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. @@ -44,26 +41,31 @@ def query_yes_no(question, default="yes"): 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']: - if args: + if opts['args']: books = [] - for a in args: + for a in opts['args']: 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: - print b.slug + print(b.slug) idx.index_book(b) idx.index.commit() books.pop(0) @@ -98,6 +100,6 @@ class Command(BaseCommand): if not retry: break - print 'Reindexing tags.' + print('Reindexing tags.') idx.index_tags() idx.index.commit() diff --git a/src/search/management/commands/reindex_pictures.py b/src/search/management/commands/reindex_pictures.py index bb6b50fcc..8505189a1 100644 --- a/src/search/management/commands/reindex_pictures.py +++ b/src/search/management/commands/reindex_pictures.py @@ -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. # @@ -7,8 +6,6 @@ import traceback 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. @@ -44,21 +41,21 @@ def query_yes_no(question, default="yes"): 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() - if args: + if opts['args']: pictures = [] - for a in args: + for a in opts['args']: 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] - print p.slug + print(p.slug) idx.index_picture(p) idx.index.commit() pictures.pop(0) diff --git a/src/search/management/commands/snippets.py b/src/search/management/commands/snippets.py index af8014385..62512c94b 100755 --- a/src/search/management/commands/snippets.py +++ b/src/search/management/commands/snippets.py @@ -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. # -from django.core.management.base import BaseCommand - 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.' - args = '' 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: - print "error in snippets %s" % bkid + print("error in snippets %s" % bkid) diff --git a/src/search/mock_search.py b/src/search/mock_search.py index 344e79f72..6c430400e 100644 --- a/src/search/mock_search.py +++ b/src/search/mock_search.py @@ -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. # -from mock import Mock +from unittest.mock import Mock from catalogue.models import Book, Tag from random import randint, choice diff --git a/src/search/templatetags/search_tags.py b/src/search/templatetags/search_tags.py index d0cbb5c10..0975c2a28 100644 --- a/src/search/templatetags/search_tags.py +++ b/src/search/templatetags/search_tags.py @@ -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. - 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)) diff --git a/src/social/models.py b/src/social/models.py index 979c0d4cd..b9417bae5 100644 --- a/src/social/models.py +++ b/src/social/models.py @@ -37,7 +37,7 @@ class Cite(models.Model): 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): diff --git a/src/social/templatetags/social_tags.py b/src/social/templatetags/social_tags.py index ce61985d2..898d3ba4a 100755 --- a/src/social/templatetags/social_tags.py +++ b/src/social/templatetags/social_tags.py @@ -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 lazy(get_value, unicode)() + return lazy(get_value, str)() diff --git a/src/sortify.py b/src/sortify.py index 61cb9d892..941f50f1f 100644 --- a/src/sortify.py +++ b/src/sortify.py @@ -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) diff --git a/src/sponsors/models.py b/src/sponsors/models.py index 89b06ff2c..8b5055239 100644 --- a/src/sponsors/models.py +++ b/src/sponsors/models.py @@ -4,7 +4,7 @@ # 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 @@ -24,7 +24,7 @@ class Sponsor(models.Model): 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): @@ -75,7 +75,7 @@ class SponsorPage(models.Model): (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: @@ -88,7 +88,7 @@ class SponsorPage(models.Model): 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() @@ -103,5 +103,5 @@ class SponsorPage(models.Model): def flush_includes(self): flush_ssi_includes(['/sponsors/page/%s.html' % self.name]) - def __unicode__(self): + def __str__(self): return self.name diff --git a/src/sponsors/widgets.py b/src/sponsors/widgets.py index 764154946..06bc3041a 100644 --- a/src/sponsors/widgets.py +++ b/src/sponsors/widgets.py @@ -23,7 +23,7 @@ class SponsorPageWidget(forms.Textarea): 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'