From: Radek Czajka Date: Mon, 11 Mar 2019 09:27:59 +0000 (+0100) Subject: Python 3 X-Git-Url: https://git.mdrn.pl/redakcja.git/commitdiff_plain/3204e4303148302d278036eebcfc8cb105cc97d7?ds=inline;hp=0d32008f4ab55669ec5eb13ed1af3c2741092ef5 Python 3 --- diff --git a/src/catalogue/ebook_utils.py b/src/catalogue/ebook_utils.py index dae2e769..a61d18fb 100644 --- a/src/catalogue/ebook_utils.py +++ b/src/catalogue/ebook_utils.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from StringIO import StringIO +from io import BytesIO from catalogue.models import Book from librarian import DocProvider from django.http import HttpResponse @@ -12,7 +12,7 @@ class RedakcjaDocProvider(DocProvider): self.publishable = publishable def by_slug(self, slug): - return StringIO(Book.objects.get(dc_slug=slug + return BytesIO(Book.objects.get(dc_slug=slug ).materialize(publishable=self.publishable ).encode('utf-8')) diff --git a/src/catalogue/management/__init__.py b/src/catalogue/management/__init__.py index e537f9e5..c629abfc 100644 --- a/src/catalogue/management/__init__.py +++ b/src/catalogue/management/__init__.py @@ -54,14 +54,14 @@ class XmlUpdater(object): def fix_chunk(self, chunk, user, verbose=0, dry_run=False): """Runs the update for a single chunk.""" if verbose >= 2: - print chunk.get_absolute_url() + print(chunk.get_absolute_url()) old_head = chunk.head src = old_head.materialize() try: tree = etree.fromstring(src) except: if verbose: - print "%s: invalid XML" % chunk.get_absolute_url() + print("%s: invalid XML" % chunk.get_absolute_url()) self.counters['Bad XML'] += 1 return @@ -82,7 +82,7 @@ class XmlUpdater(object): if not dry_run: new_head = chunk.commit( - etree.tostring(tree, encoding=unicode), + etree.tostring(tree, encoding='unicode'), author=user, description=self.commit_desc ) @@ -90,7 +90,7 @@ class XmlUpdater(object): if old_head.publishable: new_head.set_publishable(True) if verbose >= 2: - print "done" + print("done") self.counters['Updated chunks'] += 1 def run(self, user, verbose=0, dry_run=False, books=None): @@ -113,4 +113,4 @@ class XmlUpdater(object): def print_results(self): """Prints the counters.""" for item in sorted(self.counters.items()): - print "%s: %d" % item + print("%s: %d" % item) diff --git a/src/catalogue/management/commands/__init__.py b/src/catalogue/management/commands/__init__.py index e6f146f8..bede0788 100644 --- a/src/catalogue/management/commands/__init__.py +++ b/src/catalogue/management/commands/__init__.py @@ -4,7 +4,6 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # import sys -from optparse import make_option from django.contrib.auth.models import User from django.core.management.base import BaseCommand from catalogue.models import Book @@ -15,16 +14,19 @@ class XmlUpdaterCommand(BaseCommand): In a subclass, provide an XmlUpdater class in the `updater' attribute. """ - option_list = BaseCommand.option_list + ( - make_option('-q', '--quiet', action='store_false', dest='verbose', - default=True, help='Less output'), - make_option('-d', '--dry-run', action='store_true', dest='dry_run', - default=False, help="Don't actually touch anything"), - make_option('-u', '--username', dest='username', metavar='USER', - help='Assign commits to this user (required, preferably yourself).'), - ) args = "[slug]..." + def add_arguments(self, parser): + parser.add_argument( + '-q', '--quiet', action='store_false', dest='verbose', + default=True, help='Less output') + parser.add_argument( + '-d', '--dry-run', action='store_true', dest='dry_run', + default=False, help="Don't actually touch anything") + parser.add_argument( + '-u', '--username', dest='username', metavar='USER', + help='Assign commits to this user (required, preferably yourself).') + def handle(self, *args, **options): verbose = options.get('verbose') dry_run = options.get('dry_run') @@ -33,7 +35,7 @@ class XmlUpdaterCommand(BaseCommand): if username: user = User.objects.get(username=username) else: - print 'Please provide a username.' + print('Please provide a username.') sys.exit(1) books = Book.objects.filter(slug__in=args) if args else None diff --git a/src/catalogue/management/commands/fixdc.py b/src/catalogue/management/commands/fixdc.py index 3f997d0c..0401e65e 100644 --- a/src/catalogue/management/commands/fixdc.py +++ b/src/catalogue/management/commands/fixdc.py @@ -18,7 +18,7 @@ class FixDC(XmlUpdater): try: WLURI.strict(elem.text) except ValidationError: - correct_field = unicode(WLURI.from_slug( + correct_field = str(WLURI.from_slug( WLURI(elem.text.strip()).slug)) try: WLURI.strict(correct_field) @@ -26,9 +26,9 @@ class FixDC(XmlUpdater): # Can't make a valid WLURI out of it, leave as is. return False if verbose: - print "Changing %s from %s to %s" % ( + print("Changing %s from %s to %s" % ( elem.tag, elem.text, correct_field - ) + )) elem.text = correct_field return True for field in BookInfo.FIELDS: @@ -42,9 +42,9 @@ class FixDC(XmlUpdater): current_about = elem.get(attr_name) if current_about != correct_about: if verbose: - print "Changing rdf:about from %s to %s" % ( + print("Changing rdf:about from %s to %s" % ( current_about, correct_about - ) + )) elem.set(attr_name, correct_about) return True diff --git a/src/catalogue/management/commands/import_wl.py b/src/catalogue/management/commands/import_wl.py index 45c9e331..754474be 100644 --- a/src/catalogue/management/commands/import_wl.py +++ b/src/catalogue/management/commands/import_wl.py @@ -2,8 +2,7 @@ from collections import defaultdict import json -from optparse import make_option -import urllib2 +from urllib.request import urlopen from django.core.management.base import BaseCommand from django.core.management.color import color_style @@ -18,12 +17,12 @@ WL_API = 'http://www.wolnelektury.pl/api/books/' class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option('-q', '--quiet', action='store_false', dest='verbose', default=True, - help='Less output'), - ) help = 'Imports XML files from WL.' + def add_arguments(self, parser): + parser.add_argument('-q', '--quiet', action='store_false', dest='verbose', default=True, + help='Less output') + def handle(self, *args, **options): self.style = color_style() @@ -34,11 +33,11 @@ class Command(BaseCommand): transaction.enter_transaction_management() if verbose: - print 'Reading currently managed files (skipping hidden ones).' + print('Reading currently managed files (skipping hidden ones).') slugs = defaultdict(list) for b in Book.objects.exclude(slug__startswith='.').all(): if verbose: - print b.slug + print(b.slug) text = b.materialize().encode('utf-8') try: info = BookInfo.from_bytes(text) @@ -55,33 +54,33 @@ class Command(BaseCommand): } if verbose: - print 'Opening books list' - for book in json.load(urllib2.urlopen(WL_API)): - book_detail = json.load(urllib2.urlopen(book['href'])) - xml_text = urllib2.urlopen(book_detail['xml']).read() + print('Opening books list') + for book in json.load(urlopen(WL_API)): + book_detail = json.load(urlopen(book['href'])) + xml_text = urlopen(book_detail['xml']).read() info = BookInfo.from_bytes(xml_text) previous_books = slugs.get(info.slug) if previous_books: if len(previous_books) > 1: - print self.style.ERROR("There is more than one book " - "with slug %s:"), + print(self.style.ERROR("There is more than one book " + "with slug %s:") % info.slug) previous_book = previous_books[0] comm = previous_book.slug else: previous_book = None comm = '*' - print book_count, info.slug , '-->', comm + print(book_count, info.slug , '-->', comm) Book.import_xml_text(xml_text, title=info.title[:255], slug=info.slug[:128], previous_book=previous_book, commit_args=commit_args) book_count += 1 # Print results - print - print "Results:" - print "Imported %d books from WL:" % ( - book_count, ) - print + print() + print("Results:") + print("Imported %d books from WL:" % ( + book_count, )) + print() transaction.commit() diff --git a/src/catalogue/management/commands/insert_isbn.py b/src/catalogue/management/commands/insert_isbn.py index 7548cb1f..e342c90d 100644 --- a/src/catalogue/management/commands/insert_isbn.py +++ b/src/catalogue/management/commands/insert_isbn.py @@ -8,8 +8,6 @@ import csv import sys from django.contrib.auth.models import User from lxml import etree -from optparse import make_option - from collections import defaultdict from django.core.management import BaseCommand @@ -42,24 +40,20 @@ def url_for_format(slug, format): class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - # make_option('-q', '--quiet', action='store_false', dest='verbose', - # default=True, help='Less output'), - # make_option('-d', '--dry-run', action='store_true', dest='dry_run', - # default=False, help="Don't actually touch anything"), - make_option( - '-u', '--username', dest='username', metavar='USER', - help='Assign commits to this user (required, preferably yourself).'), - ) args = 'csv_file' + def add_arguments(self, parser): + self.add_argument( + '-u', '--username', dest='username', metavar='USER', + help='Assign commits to this user (required, preferably yourself).') + def handle(self, csv_file, **options): username = options.get('username') if username: user = User.objects.get(username=username) else: - print 'Please provide a username.' + print('Please provide a username.') sys.exit(1) csvfile = open(csv_file, 'rb') @@ -69,7 +63,7 @@ class Command(BaseCommand): csvfile.close() for slug, isbn_list in isbn_lists.iteritems(): - print 'processing %s' % slug + print('processing %s' % slug) book = Book.objects.get(dc_slug=slug) chunk = book.chunk_set.first() old_head = chunk.head @@ -77,7 +71,7 @@ class Command(BaseCommand): tree = etree.fromstring(src) isbn_node = tree.find('.//' + DCNS("relation.hasFormat")) if isbn_node is not None: - print '%s already contains ISBN metadata, skipping' % slug + print('%s already contains ISBN metadata, skipping' % slug) continue desc = tree.find(".//" + RDFNS("Description")) for format, isbn in isbn_list: @@ -92,12 +86,12 @@ class Command(BaseCommand): element.tail = '\n' desc.append(element) new_head = chunk.commit( - etree.tostring(tree, encoding=unicode), + etree.tostring(tree, encoding='unicode'), author=user, description='automatyczne dodanie isbn' ) - print 'committed %s' % slug + print('committed %s' % slug) if old_head.publishable: new_head.set_publishable(True) else: - print 'Warning: %s not publishable' % slug + print('Warning: %s not publishable' % slug) diff --git a/src/catalogue/management/commands/mark_final.py b/src/catalogue/management/commands/mark_final.py index cdfaab93..11d63a3c 100644 --- a/src/catalogue/management/commands/mark_final.py +++ b/src/catalogue/management/commands/mark_final.py @@ -1,43 +1,35 @@ -# -*- coding: utf-8 -*- -# # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # import sys from django.contrib.auth.models import User -from optparse import make_option - from django.core.management import BaseCommand from catalogue.models import Book, Chunk class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - # make_option('-q', '--quiet', action='store_false', dest='verbose', - # default=True, help='Less output'), - # make_option('-d', '--dry-run', action='store_true', dest='dry_run', - # default=False, help="Don't actually touch anything"), - make_option( - '-u', '--username', dest='username', metavar='USER', - help='Assign commits to this user (required).'), - ) args = 'slug_file' + def add_arguments(self, parser): + self.add_argument( + '-u', '--username', dest='username', metavar='USER', + help='Assign commits to this user (required).') + def handle(self, slug_file, **options): username = options.get('username') if username: user = User.objects.get(username=username) else: - print 'Please provide a username.' + print('Please provide a username.') sys.exit(1) slugs = [line.strip() for line in open(slug_file)] books = Book.objects.filter(slug__in=slugs) for book in books: - print 'processing %s' % book.slug + print('processing %s' % book.slug) for chunk in book.chunk_set.all(): src = chunk.head.materialize() chunk.commit( @@ -47,4 +39,4 @@ class Command(BaseCommand): tags=[Chunk.tag_model.objects.get(slug='editor-proofreading')], publishable=True ) - print 'committed %s' % book.slug + print('committed %s' % book.slug) diff --git a/src/catalogue/management/commands/merge_books.py b/src/catalogue/management/commands/merge_books.py index 82bd622d..f6b37bbb 100644 --- a/src/catalogue/management/commands/merge_books.py +++ b/src/catalogue/management/commands/merge_books.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - -from optparse import make_option import sys from django.contrib.auth.models import User @@ -24,23 +21,28 @@ def common_prefix(texts): class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option('-s', '--slug', dest='new_slug', metavar='SLUG', - help='New slug of the merged book (defaults to common part of all slugs).'), - make_option('-t', '--title', dest='new_title', metavar='TITLE', - help='New title of the merged book (defaults to common part of all titles).'), - make_option('-q', '--quiet', action='store_false', dest='verbose', default=True, - help='Less output'), - make_option('-g', '--guess', action='store_true', dest='guess', default=False, - help='Try to guess what merges are needed (but do not apply them).'), - make_option('-d', '--dry-run', action='store_true', dest='dry_run', default=False, - help='Dry run: do not actually change anything.'), - make_option('-f', '--force', action='store_true', dest='force', default=False, - help='On slug conflict, hide the original book to archive.'), - ) help = 'Merges multiple books into one.' args = '[slug]...' + def add_arguments(self, parser): + self.add_argument( + '-s', '--slug', dest='new_slug', metavar='SLUG', + help='New slug of the merged book (defaults to common part of all slugs).') + self.add_argument( + '-t', '--title', dest='new_title', metavar='TITLE', + help='New title of the merged book (defaults to common part of all titles).') + self.add_argument( + '-q', '--quiet', action='store_false', dest='verbose', default=True, + help='Less output') + self.add_argument( + '-g', '--guess', action='store_true', dest='guess', default=False, + help='Try to guess what merges are needed (but do not apply them).') + self.add_argument( + '-d', '--dry-run', action='store_true', dest='dry_run', default=False, + help='Dry run: do not actually change anything.') + self.add_argument( + '-f', '--force', action='store_true', dest='force', default=False, + help='On slug conflict, hide the original book to archive.') def print_guess(self, dry_run=True, force=False): from collections import defaultdict @@ -49,9 +51,9 @@ class Command(BaseCommand): def read_slug(slug): res = [] - res.append((re.compile(ur'__?(przedmowa)$'), -1)) - res.append((re.compile(ur'__?(cz(esc)?|ksiega|rozdzial)__?(?P\d*)$'), None)) - res.append((re.compile(ur'__?(rozdzialy__?)?(?P\d*)-'), None)) + res.append((re.compile(r'__?(przedmowa)$'), -1)) + res.append((re.compile(r'__?(cz(esc)?|ksiega|rozdzial)__?(?P\d*)$'), None)) + res.append((re.compile(r'__?(rozdzialy__?)?(?P\d*)-'), None)) for r, default in res: m = r.search(slug) @@ -87,20 +89,20 @@ class Command(BaseCommand): conflicting_slugs.append(slug) title = file_to_title(slug) - print "./manage.py merge_books %s%s--title=%s --slug=%s \\\n %s\n" % ( + print("./manage.py merge_books %s%s--title=%s --slug=%s \\\n %s\n" % ( '--dry-run ' if dry_run else '', '--force ' if force else '', quote(title), slug, " \\\n ".join(merge_slugs) - ) + )) if conflicting_slugs: if force: - print self.style.NOTICE('# These books will be archived:') + print(self.style.NOTICE('# These books will be archived:')) else: - print self.style.ERROR('# ERROR: Conflicting slugs:') + print(self.style.ERROR('# ERROR: Conflicting slugs:')) for slug in conflicting_slugs: - print '#', slug + print('#', slug) def handle(self, *slugs, **options): @@ -116,13 +118,13 @@ class Command(BaseCommand): if guess: if slugs: - print "Please specify either slugs, or --guess." + print("Please specify either slugs, or --guess.") return else: self.print_guess(dry_run, force) return if not slugs: - print "Please specify some book slugs" + print("Please specify some book slugs") return # Start transaction management. @@ -147,13 +149,13 @@ class Command(BaseCommand): if dry_run and verbose: - print self.style.NOTICE('DRY RUN: nothing will be changed.') - print + print(self.style.NOTICE('DRY RUN: nothing will be changed.')) + print() if verbose: - print "New title:", self.style.NOTICE(new_title) - print "New slug:", self.style.NOTICE(new_slug) - print + print("New title:", self.style.NOTICE(new_title)) + print("New slug:", self.style.NOTICE(new_slug)) + print() for i, book in enumerate(books): chunk_titles = [] @@ -172,12 +174,12 @@ class Command(BaseCommand): chunk_slugs.append(new_chunk_slug) if verbose: - print "title: %s // %s -->\n %s // %s\nslug: %s / %s -->\n %s / %s" % ( + print("title: %s // %s -->\n %s // %s\nslug: %s / %s -->\n %s / %s" % ( book.title, chunk.title, new_title, new_chunk_title, book.slug, chunk.slug, - new_slug, new_chunk_slug) - print + new_slug, new_chunk_slug)) + print() if not dry_run: try: @@ -193,9 +195,9 @@ class Command(BaseCommand): # FIXME: there still may be a conflict conflict.slug = '.' + conflict.slug conflict.save() - print self.style.NOTICE('Book with slug "%s" moved to "%s".' % (new_slug, conflict.slug)) + print(self.style.NOTICE('Book with slug "%s" moved to "%s".' % (new_slug, conflict.slug))) else: - print self.style.ERROR('ERROR: Book with slug "%s" exists.' % new_slug) + print(self.style.ERROR('ERROR: Book with slug "%s" exists.' % new_slug)) return if i: diff --git a/src/catalogue/management/commands/prune_audience.py b/src/catalogue/management/commands/prune_audience.py index 114a26f9..2f661c2b 100644 --- a/src/catalogue/management/commands/prune_audience.py +++ b/src/catalogue/management/commands/prune_audience.py @@ -7,7 +7,6 @@ import sys from django.contrib.auth.models import User from lxml import etree -from optparse import make_option from django.core.management import BaseCommand @@ -16,24 +15,20 @@ from librarian import DCNS class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - # make_option('-q', '--quiet', action='store_false', dest='verbose', - # default=True, help='Less output'), - # make_option('-d', '--dry-run', action='store_true', dest='dry_run', - # default=False, help="Don't actually touch anything"), - make_option( - '-u', '--username', dest='username', metavar='USER', - help='Assign commits to this user (required, preferably yourself).'), - ) args = 'exclude_file' + def add_arguments(self, parser): + parser.add_argument( + '-u', '--username', dest='username', metavar='USER', + help='Assign commits to this user (required, preferably yourself).') + def handle(self, exclude_file, **options): username = options.get('username') if username: user = User.objects.get(username=username) else: - print 'Please provide a username.' + print('Please provide a username.') sys.exit(1) excluded_slugs = [line.strip() for line in open(exclude_file, 'rb') if line.strip()] @@ -42,26 +37,26 @@ class Command(BaseCommand): for book in books: if not book.is_published(): continue - print 'processing %s' % book.slug + print('processing %s' % book.slug) chunk = book.chunk_set.first() old_head = chunk.head src = old_head.materialize() tree = etree.fromstring(src) audience_nodes = tree.findall('.//' + DCNS("audience")) if not audience_nodes: - print '%s has no audience, skipping' % book.slug + print('%s has no audience, skipping' % book.slug) continue for node in audience_nodes: node.getparent().remove(node) chunk.commit( - etree.tostring(tree, encoding=unicode), + etree.tostring(tree, encoding='unicode'), author=user, description='automatyczne skasowanie audience', publishable=old_head.publishable ) - print 'committed %s' % book.slug + print('committed %s' % book.slug) if not old_head.publishable: - print 'Warning: %s not publishable, last head: %s, %s' % ( - book.slug, old_head.author.username, old_head.description[:40].replace('\n', ' ')) + print('Warning: %s not publishable, last head: %s, %s' % ( + book.slug, old_head.author.username, old_head.description[:40].replace('\n', ' '))) diff --git a/src/catalogue/models/__init__.py b/src/catalogue/models/__init__.py index d0015c79..0b9c4d17 100755 --- a/src/catalogue/models/__init__.py +++ b/src/catalogue/models/__init__.py @@ -17,5 +17,5 @@ class User(AuthUser): class Meta: proxy = True - def __unicode__(self): + def __str__(self): return "%s %s" % (self.first_name, self.last_name) diff --git a/src/catalogue/models/book.py b/src/catalogue/models/book.py index 0ed8958c..229b3d0b 100755 --- a/src/catalogue/models/book.py +++ b/src/catalogue/models/book.py @@ -66,13 +66,13 @@ class Book(models.Model): def __len__(self): return self.chunk_set.count() - def __nonzero__(self): + def __bool__(self): """ Necessary so that __len__ isn't used for bool evaluation. """ return True - def __unicode__(self): + def __str__(self): return self.title @models.permalink @@ -266,12 +266,12 @@ class Book(models.Model): try: bi = self.wldocument(changes=changes, strict=True).book_info - except ParseError, e: - raise AssertionError(_('Invalid XML') + ': ' + unicode(e)) + except ParseError as e: + raise AssertionError(_('Invalid XML') + ': ' + str(e)) except NoDublinCore: raise AssertionError(_('No Dublin Core found.')) - except ValidationError, e: - raise AssertionError(_('Invalid Dublin Core') + ': ' + unicode(e)) + except ValidationError as e: + raise AssertionError(_('Invalid Dublin Core') + ': ' + str(e)) valid_about = self.correct_about() assert bi.about == valid_about, _("rdf:about is not") + " " + valid_about @@ -279,7 +279,7 @@ class Book(models.Model): def publishable_error(self): try: return self.assert_publishable() - except AssertionError, e: + except AssertionError as e: return e else: return None diff --git a/src/catalogue/models/chunk.py b/src/catalogue/models/chunk.py index ade5cde1..4182d08f 100755 --- a/src/catalogue/models/chunk.py +++ b/src/catalogue/models/chunk.py @@ -43,7 +43,7 @@ class Chunk(dvcs_models.Document): # Representing # ============ - def __unicode__(self): + def __str__(self): return "%d:%d: %s" % (self.book_id, self.number, self.title) @models.permalink @@ -54,7 +54,7 @@ class Chunk(dvcs_models.Document): title = self.book.title if self.title: title += ", %s" % self.title - if book_length > 1: + if book_length and book_length > 1: title += " (%d/%d)" % (self.number, book_length) return title diff --git a/src/catalogue/models/image.py b/src/catalogue/models/image.py index 34f61f94..8cadcec2 100755 --- a/src/catalogue/models/image.py +++ b/src/catalogue/models/image.py @@ -38,7 +38,7 @@ class Image(dvcs_models.Document): # Representing # ============ - def __unicode__(self): + def __str__(self): return self.title @models.permalink @@ -81,11 +81,11 @@ class Image(dvcs_models.Document): picture = WLPicture.from_bytes( picture_xml.encode('utf-8'), image_store=SelfImageStore) - except ParseError, e: + except ParseError as e: raise AssertionError(_('Invalid XML') + ': ' + str(e)) except NoDublinCore: raise AssertionError(_('No Dublin Core found.')) - except ValidationError, e: + except ValidationError as e: raise AssertionError(_('Invalid Dublin Core') + ': ' + str(e)) valid_about = self.correct_about() @@ -95,7 +95,7 @@ class Image(dvcs_models.Document): def publishable_error(self): try: return self.assert_publishable() - except AssertionError, e: + except AssertionError as e: return e else: return None diff --git a/src/catalogue/models/project.py b/src/catalogue/models/project.py index eb951021..d7cc0df9 100755 --- a/src/catalogue/models/project.py +++ b/src/catalogue/models/project.py @@ -19,5 +19,5 @@ class Project(models.Model): verbose_name = _('project') verbose_name_plural = _('projects') - def __unicode__(self): + def __str__(self): return self.name diff --git a/src/catalogue/test_utils.py b/src/catalogue/test_utils.py index 2b085450..cacc9c8f 100644 --- a/src/catalogue/test_utils.py +++ b/src/catalogue/test_utils.py @@ -11,4 +11,4 @@ from os.path import abspath, dirname, join def get_fixture(path): f_path = join(dirname(abspath(__file__)), 'tests/files', path) with open(f_path) as f: - return unicode(f.read(), 'utf-8') + return f.read() diff --git a/src/catalogue/views.py b/src/catalogue/views.py index 5551a5cf..1497be08 100644 --- a/src/catalogue/views.py +++ b/src/catalogue/views.py @@ -3,9 +3,7 @@ from collections import defaultdict from datetime import datetime, date, timedelta import logging import os -from StringIO import StringIO -from urllib import unquote -from urlparse import urlsplit, urlunsplit +from urllib.parse import unquote, urlsplit, urlunsplit from django.conf import settings from django.contrib import auth @@ -470,7 +468,7 @@ def chunk_mass_edit(request): if stage: try: stage = Chunk.tag_model.objects.get(slug=stage) - except Chunk.DoesNotExist, e: + except Chunk.DoesNotExist as e: stage = None for c in chunks: c.stage = stage @@ -481,7 +479,7 @@ def chunk_mass_edit(request): if username: try: user = User.objects.get(username=username) - except User.DoesNotExist, e: + except User.DoesNotExist as e: user = None for c in chunks: c.user = user @@ -490,7 +488,7 @@ def chunk_mass_edit(request): if project_id: try: project = Project.objects.get(pk=int(project_id)) - except (Project.DoesNotExist, ValueError), e: + except (Project.DoesNotExist, ValueError) as e: project = None for c in chunks: book = c.book @@ -513,7 +511,7 @@ def image_mass_edit(request): if stage: try: stage = Image.tag_model.objects.get(slug=stage) - except Image.DoesNotExist, e: + except Image.DoesNotExist as e: stage = None for c in images: c.stage = stage @@ -524,7 +522,7 @@ def image_mass_edit(request): if username: try: user = User.objects.get(username=username) - except User.DoesNotExist, e: + except User.DoesNotExist as e: user = None for c in images: c.user = user @@ -533,7 +531,7 @@ def image_mass_edit(request): if project_id: try: project = Project.objects.get(pk=int(project_id)) - except (Project.DoesNotExist, ValueError), e: + except (Project.DoesNotExist, ValueError) as e: project = None for c in images: c.project = project @@ -584,7 +582,7 @@ def publish(request, slug): book.publish(request.user, host=protocol + request.get_host(), days=days, beta=beta) except NotAuthorizedError: return http.HttpResponseRedirect(reverse('apiclient_oauth' if not beta else 'apiclient_beta_oauth')) - except BaseException, e: + except BaseException as e: return http.HttpResponse(repr(e)) else: return http.HttpResponseRedirect(book.get_absolute_url()) @@ -601,7 +599,7 @@ def publish_image(request, slug): image.publish(request.user) except NotAuthorizedError: return http.HttpResponseRedirect(reverse('apiclient_oauth')) - except BaseException, e: + except BaseException as e: return http.HttpResponse(e) else: return http.HttpResponseRedirect(image.get_absolute_url()) diff --git a/src/catalogue/xml_tools.py b/src/catalogue/xml_tools.py index 242714b6..ff7b0310 100644 --- a/src/catalogue/xml_tools.py +++ b/src/catalogue/xml_tools.py @@ -60,7 +60,7 @@ def add_trim_begin(text): master.insert(0, trim_tag) trim_tag.tail = '\n\n\n' + (master.text or '') master.text = '\n' - return unicode(etree.tostring(e, encoding="utf-8"), 'utf-8') + return str(etree.tostring(e, encoding="utf-8"), 'utf-8') def add_trim_end(text): @@ -79,7 +79,7 @@ def add_trim_end(text): prev.tail = (prev.tail or '') + '\n\n\n' else: master.text = (master.text or '') + '\n\n\n' - return unicode(etree.tostring(e, encoding="utf-8"), 'utf-8') + return str(etree.tostring(e, encoding="utf-8"), 'utf-8') def split_xml(text): @@ -124,13 +124,13 @@ def split_xml(text): del parent[0] element, parent = parent, parent.getparent() chunks[:0] = [[name, - unicode(etree.tostring(copied, encoding='utf-8'), 'utf-8') + str(etree.tostring(copied, encoding='utf-8'), 'utf-8') ]] parts = src.findall('.//naglowek_rozdzial') chunks[:0] = [[u'początek', - unicode(etree.tostring(src, encoding='utf-8'), 'utf-8') + str(etree.tostring(src, encoding='utf-8'), 'utf-8') ]] for ch in chunks[1:]: diff --git a/src/cover/forms.py b/src/cover/forms.py index e6f7868e..e5bc400d 100755 --- a/src/cover/forms.py +++ b/src/cover/forms.py @@ -3,7 +3,7 @@ # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # -from StringIO import StringIO +from io import BytesIO from django import forms from django.conf import settings @@ -55,7 +55,7 @@ class ImageAddForm(forms.ModelForm): raise forms.ValidationError(ugettext('No image specified')) if download_url: image_data = URLOpener().open(download_url).read() - width, height = PILImage.open(StringIO(image_data)).size + width, height = PILImage.open(BytesIO(image_data)).size else: width, height = PILImage.open(uploaded_file.file).size min_width, min_height = settings.MIN_COVER_SIZE diff --git a/src/cover/management/commands/refresh_covers.py b/src/cover/management/commands/refresh_covers.py index cc0ef31c..0e998c99 100644 --- a/src/cover/management/commands/refresh_covers.py +++ b/src/cover/management/commands/refresh_covers.py @@ -4,8 +4,6 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # import urllib2 as urllib -from optparse import make_option - from django.core.files.base import ContentFile from django.core.management import BaseCommand @@ -14,34 +12,33 @@ from cover.utils import get_flickr_data, URLOpener, FlickrError class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option('--from', dest='from_id', type=int, default=1), - ) + def add_arguments(self, parser): + parser.add_argument('--from', dest='from_id', type=int, default=1) def handle(self, *args, **options): from_id = options.get('from_id', 1) images = Image.objects.filter(id__gte=from_id).exclude(book=None).order_by('id') images = images.filter(source_url__contains='flickr.com').exclude(download_url__endswith='_o.jpg') for image in images: - print image.id + print(image.id) try: flickr_data = get_flickr_data(image.source_url) - print flickr_data + print(flickr_data) except FlickrError as e: - print 'Flickr analysis failed: %s' % e + print('Flickr analysis failed: %s' % e) else: flickr_url = flickr_data['download_url'] if flickr_url != image.download_url: same_url = Image.objects.filter(download_url=flickr_url) if same_url: - print 'Download url already present in image %s' % same_url.get().id + print('Download url already present in image %s' % same_url.get().id) continue try: t = URLOpener().open(flickr_url).read() except urllib.URLError: - print 'Broken download url' + print('Broken download url') except IOError: - print 'Connection failed' + print('Connection failed') else: image.download_url = flickr_url image.file.save(image.file.name, ContentFile(t)) diff --git a/src/cover/models.py b/src/cover/models.py index d83dad39..ea69f654 100644 --- a/src/cover/models.py +++ b/src/cover/models.py @@ -34,7 +34,7 @@ class Image(models.Model): verbose_name = _('cover image') verbose_name_plural = _('cover images') - def __unicode__(self): + def __str__(self): return u"%s - %s" % (self.author, self.title) @models.permalink diff --git a/src/cover/utils.py b/src/cover/utils.py index 51aee190..75bf96d2 100755 --- a/src/cover/utils.py +++ b/src/cover/utils.py @@ -5,7 +5,7 @@ # import json import re -from urllib import FancyURLopener +from urllib.request import FancyURLopener from django.contrib.sites.models import Site diff --git a/src/dvcs/models.py b/src/dvcs/models.py index 270f24eb..d6b0cc68 100644 --- a/src/dvcs/models.py +++ b/src/dvcs/models.py @@ -30,7 +30,7 @@ class Tag(models.Model): abstract = True ordering = ['ordering'] - def __unicode__(self): + def __str__(self): return self.name @classmethod @@ -93,7 +93,7 @@ class Change(models.Model): ordering = ('created_at',) unique_together = ['tree', 'revision'] - def __unicode__(self): + def __str__(self): return u"Id: %r, Tree %r, Parent %r, Data: %s" % (self.id, self.tree_id, self.parent_id, self.data) def author_str(self): @@ -124,7 +124,7 @@ class Change(models.Model): f = self.data.storage.open(self.data) text = f.read() f.close() - return unicode(text, 'utf-8') + return str(text, 'utf-8') def merge_with(self, other, author=None, author_name=None, author_email=None, @@ -225,11 +225,9 @@ class DocumentMeta(ModelBase): return model -class Document(models.Model): +class Document(models.Model, metaclass=DocumentMeta): """File in repository. Subclass it to use version control in your app.""" - __metaclass__ = DocumentMeta - # default repository path REPO_PATH = os.path.join(settings.MEDIA_ROOT, 'dvcs') @@ -238,7 +236,7 @@ class Document(models.Model): class Meta: abstract = True - def __unicode__(self): + def __str__(self): return u"{0}, HEAD: {1}".format(self.id, self.head_id) def materialize(self, change=None): @@ -256,12 +254,12 @@ class Document(models.Model): This will automatically merge the commit into the main branch, if parent is not document's head. - :param unicode text: new version of the document + :param str text: new version of the document :param parent: parent revision (head, if not specified) :type parent: Change or None :param User author: the commiter - :param unicode author_name: commiter name (if ``author`` not specified) - :param unicode author_email: commiter e-mail (if ``author`` not specified) + :param str author_name: commiter name (if ``author`` not specified) + :param str author_email: commiter e-mail (if ``author`` not specified) :param Tag[] tags: list of tags to apply to the new commit :param bool publishable: set new commit as ready to publish :returns: new head diff --git a/src/dvcs/storage.py b/src/dvcs/storage.py index 91f78e60..a57d759f 100755 --- a/src/dvcs/storage.py +++ b/src/dvcs/storage.py @@ -3,6 +3,7 @@ from zlib import compress, decompress from django.core.files.base import ContentFile, File from django.core.files.storage import FileSystemStorage from django.utils.deconstruct import deconstructible +from django.utils.encoding import force_bytes @deconstructible @@ -15,6 +16,7 @@ class GzipFileSystemStorage(FileSystemStorage): return ContentFile(decompress(text)) def _save(self, name, content): - content = ContentFile(compress(content.read())) + data = force_bytes(content.read()) + content = ContentFile(compress(data)) return super(GzipFileSystemStorage, self)._save(name, content) diff --git a/src/email_mangler/templatetags/email.py b/src/email_mangler/templatetags/email.py index 376117a8..40ad72f5 100755 --- a/src/email_mangler/templatetags/email.py +++ b/src/email_mangler/templatetags/email.py @@ -1,3 +1,4 @@ +import codecs from django.utils.html import escape from django.utils.safestring import mark_safe from django.utils.translation import ugettext as _ @@ -19,7 +20,7 @@ def email_link(email): mangled = "%s %s %s" % (name, at, (' %s ' % dot).join(domain.split('.'))) return mark_safe("%(mangled)s" % { - 'name': name.encode('rot13'), - 'domain': domain.encode('rot13'), + 'name': codecs.encode(name, 'rot13'), + 'domain': codecs.encode(domain, 'rot13'), 'mangled': mangled, }) diff --git a/src/fileupload/views.py b/src/fileupload/views.py index a2025fe7..9993ddb3 100644 --- a/src/fileupload/views.py +++ b/src/fileupload/views.py @@ -1,6 +1,6 @@ import json import os -from urllib import quote +from urllib.parse import quote from django.conf import settings from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404 from django.utils.decorators import method_decorator diff --git a/src/toolbar/admin.py b/src/toolbar/admin.py index 654480ca..9d3c68d9 100644 --- a/src/toolbar/admin.py +++ b/src/toolbar/admin.py @@ -15,7 +15,7 @@ class ButtonAdminForm(forms.ModelForm): value = self.cleaned_data['params'] try: return json.dumps(json.loads(value)) - except ValueError, e: + except ValueError as e: raise forms.ValidationError(e) diff --git a/src/toolbar/management/commands/fixbuttons.py b/src/toolbar/management/commands/fixbuttons.py index de48ceda..7fed15a7 100644 --- a/src/toolbar/management/commands/fixbuttons.py +++ b/src/toolbar/management/commands/fixbuttons.py @@ -15,29 +15,29 @@ class Command(NoArgsCommand): def handle_noargs(self, **options): buttons = Button.objects.all() - print "Validating parameters... " + print("Validating parameters... ") for b in buttons: params = b.params try: v = json.loads(b.params) - except ValueError, e: - print 'Trying to fix button "%s" ...' % b.slug + except ValueError as e: + print('Trying to fix button "%s" ...' % b.slug) if params[0] == u'(': params = params[1:] if params[-1] == u')': params = params[:-1] try: v = son.loads(re.sub(u'([\\w-]+)\\s*:', u'"\\1": ', params).encode('utf-8')) - except ValueError, e: - print "Unable to fix '%s' " % b.params - print "Try to fix this button manually and rerun the script." + except ValueError as e: + print("Unable to fix '%s' " % b.params) + print("Try to fix this button manually and rerun the script.") return False # resave b.params = json.dumps(v) b.save() - print "Merge duplicate buttons (if any)..." + print("Merge duplicate buttons (if any)...") hash = {} for b in buttons: if b.slug not in hash: @@ -45,13 +45,13 @@ class Command(NoArgsCommand): continue # duplicate button - print "Found duplicate of '%s'" % b.slug + print("Found duplicate of '%s'" % b.slug) a = hash[b.slug] remove_duplicate = True if a.params != b.params: - print "Conflicting params for duplicate of '%s'." % b.slug - print "Groups will be joined, but won't remove duplicates." + print("Conflicting params for duplicate of '%s'." % b.slug) + print("Groups will be joined, but won't remove duplicates.") remove_duplicate = False for g in b.group.all(): @@ -63,11 +63,11 @@ class Command(NoArgsCommand): if remove_duplicate: b.delete() - print "Searching for empty groups and orphaned buttons:" + print("Searching for empty groups and orphaned buttons:") for g in ButtonGroup.objects.all(): if len(g.button_set.all()) == 0: - print "Empty group: '%s'" % g.slug + print("Empty group: '%s'" % g.slug) for b in Button.objects.all(): if len(b.group.all()) == 0: - print "orphan: '%s'" % b.slug + print("orphan: '%s'" % b.slug) diff --git a/src/toolbar/models.py b/src/toolbar/models.py index a23e3463..fbaa396e 100644 --- a/src/toolbar/models.py +++ b/src/toolbar/models.py @@ -16,7 +16,7 @@ class ButtonGroup(models.Model): ordering = ('position', 'name',) verbose_name, verbose_name_plural = _('button group'), _('button groups') - def __unicode__(self): + def __str__(self): return self.name def to_dict(self, with_buttons=False): @@ -63,7 +63,7 @@ class Button(models.Model): 'scriptlet_id': self.scriptlet_id, } - def __unicode__(self): + def __str__(self): return self.label @@ -71,5 +71,5 @@ class Scriptlet(models.Model): name = models.CharField(max_length=64, primary_key=True) code = models.TextField() - def __unicode__(self): + def __str__(self): return _(u'javascript') + u':' + self.name diff --git a/src/wiki/helpers.py b/src/wiki/helpers.py index 877a9d0e..d62c5459 100644 --- a/src/wiki/helpers.py +++ b/src/wiki/helpers.py @@ -10,7 +10,7 @@ class ExtendedEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, Promise): - return unicode(obj) + return str(obj) if isinstance(obj, datetime): return datetime.ctime(obj) + " " + (datetime.tzname(obj) or 'GMT') diff --git a/src/wiki/models.py b/src/wiki/models.py index c539908d..fb203924 100644 --- a/src/wiki/models.py +++ b/src/wiki/models.py @@ -18,7 +18,7 @@ class Theme(models.Model): verbose_name = _('theme') verbose_name_plural = _('themes') - def __unicode__(self): + def __str__(self): return self.name def __repr__(self): diff --git a/src/wiki/views.py b/src/wiki/views.py index e1ef6aed..5dc770f7 100644 --- a/src/wiki/views.py +++ b/src/wiki/views.py @@ -3,7 +3,7 @@ from datetime import datetime import os import logging from time import mktime -import urllib +from urllib.parse import quote from django.conf import settings from django.core.urlresolvers import reverse @@ -11,14 +11,13 @@ from django import http from django.http import Http404, HttpResponseForbidden from django.middleware.gzip import GZipMiddleware from django.utils.decorators import decorator_from_middleware -from django.utils.encoding import smart_unicode from django.utils.formats import localize from django.utils.translation import ugettext as _ from django.views.decorators.http import require_POST, require_GET from django.shortcuts import get_object_or_404, render from catalogue.models import Book, Chunk -import nice_diff +from . import nice_diff from wiki import forms from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError, ajax_require_permission) @@ -203,22 +202,22 @@ def revert(request, chunk_id): def gallery(request, directory): try: base_url = ''.join(( - smart_unicode(settings.MEDIA_URL), - smart_unicode(settings.IMAGE_DIR), - smart_unicode(directory))) + settings.MEDIA_URL, + settings.IMAGE_DIR, + directory)) - base_dir = os.path.join( - smart_unicode(settings.MEDIA_ROOT), - smart_unicode(settings.IMAGE_DIR), - smart_unicode(directory)) + base_dir = os.path.join(( + settings.MEDIA_ROOT, + settings.IMAGE_DIR, + directory)) def map_to_url(filename): - return urllib.quote(("%s/%s" % (base_url, smart_unicode(filename))).encode('utf-8')) + return quote(("%s/%s" % (base_url, filename))) def is_image(filename): return os.path.splitext(filename)[1].lower() in (u'.jpg', u'.jpeg', u'.png') - images = [map_to_url(f) for f in map(smart_unicode, os.listdir(base_dir)) if is_image(f)] + images = [map_to_url(f) for f in os.listdir(base_dir) if is_image(f)] images.sort() books = Book.objects.filter(gallery=directory) @@ -281,7 +280,7 @@ def history(request, chunk_id): "author": change.author_str(), "date": localize(change.created_at), "publishable": _("Publishable") + "\n" if change.publishable else "", - "tag": ',\n'.join(unicode(tag) for tag in change.tags.all()), + "tag": ',\n'.join(str(tag) for tag in change.tags.all()), "published": _("Published") + ": " + \ localize(change.publish_log.order_by('-book_record__timestamp')[0].book_record.timestamp) \ if change.publish_log.exists() else "", diff --git a/src/wiki_img/views.py b/src/wiki_img/views.py index 2b8dd67e..e5bbf410 100644 --- a/src/wiki_img/views.py +++ b/src/wiki_img/views.py @@ -129,7 +129,7 @@ def history(request, object_id): "author": change.author_str(), "date": localize(change.created_at), "publishable": _("Publishable") + "\n" if change.publishable else "", - "tag": ',\n'.join(unicode(tag) for tag in change.tags.all()), + "tag": ',\n'.join(str(tag) for tag in change.tags.all()), }) return JSONResponse(changes)