Python 3
authorRadek Czajka <rczajka@rczajka.pl>
Mon, 11 Mar 2019 09:27:59 +0000 (10:27 +0100)
committerRadek Czajka <rczajka@rczajka.pl>
Mon, 11 Mar 2019 09:28:14 +0000 (10:28 +0100)
32 files changed:
src/catalogue/ebook_utils.py
src/catalogue/management/__init__.py
src/catalogue/management/commands/__init__.py
src/catalogue/management/commands/fixdc.py
src/catalogue/management/commands/import_wl.py
src/catalogue/management/commands/insert_isbn.py
src/catalogue/management/commands/mark_final.py
src/catalogue/management/commands/merge_books.py
src/catalogue/management/commands/prune_audience.py
src/catalogue/models/__init__.py
src/catalogue/models/book.py
src/catalogue/models/chunk.py
src/catalogue/models/image.py
src/catalogue/models/project.py
src/catalogue/test_utils.py
src/catalogue/views.py
src/catalogue/xml_tools.py
src/cover/forms.py
src/cover/management/commands/refresh_covers.py
src/cover/models.py
src/cover/utils.py
src/dvcs/models.py
src/dvcs/storage.py
src/email_mangler/templatetags/email.py
src/fileupload/views.py
src/toolbar/admin.py
src/toolbar/management/commands/fixbuttons.py
src/toolbar/models.py
src/wiki/helpers.py
src/wiki/models.py
src/wiki/views.py
src/wiki_img/views.py

index dae2e76..a61d18f 100644 (file)
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
 # -*- 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
 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):
         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'))
 
                     ).materialize(publishable=self.publishable
                     ).encode('utf-8'))
 
index e537f9e..c629abf 100644 (file)
@@ -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:
     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:
         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
 
             self.counters['Bad XML'] += 1
             return
 
@@ -82,7 +82,7 @@ class XmlUpdater(object):
 
         if not dry_run:
             new_head = chunk.commit(
 
         if not dry_run:
             new_head = chunk.commit(
-                etree.tostring(tree, encoding=unicode),
+                etree.tostring(tree, encoding='unicode'),
                 author=user,
                 description=self.commit_desc
             )
                 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:
                 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):
         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()):
     def print_results(self):
         """Prints the counters."""
         for item in sorted(self.counters.items()):
-            print "%s: %d" % item
+            print("%s: %d" % item)
index e6f146f..bede078 100644 (file)
@@ -4,7 +4,6 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import sys
 # 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
 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.
     """
 
     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]..."
 
     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')
     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:
         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
             sys.exit(1)
 
         books = Book.objects.filter(slug__in=args) if args else None
index 3f997d0..0401e65 100644 (file)
@@ -18,7 +18,7 @@ class FixDC(XmlUpdater):
         try:
             WLURI.strict(elem.text)
         except ValidationError:
         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)
                                 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:
                 # 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.tag, elem.text, correct_field
-                    )
+                    ))
             elem.text = correct_field
             return True
     for field in BookInfo.FIELDS:
             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:
         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
                         current_about, correct_about
-                    )
+                    ))
             elem.set(attr_name, correct_about)
             return True
 
             elem.set(attr_name, correct_about)
             return True
 
index 45c9e33..754474b 100644 (file)
@@ -2,8 +2,7 @@
 
 from collections import defaultdict
 import json
 
 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
 
 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):
 
 
 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.'
 
     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()
     def handle(self, *args, **options):
 
         self.style = color_style()
@@ -34,11 +33,11 @@ class Command(BaseCommand):
         transaction.enter_transaction_management()
 
         if verbose:
         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:
         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)
             text = b.materialize().encode('utf-8')
             try:
                 info = BookInfo.from_bytes(text)
@@ -55,33 +54,33 @@ class Command(BaseCommand):
         }
 
         if verbose:
         }
 
         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:
             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 = '*'
                 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
             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()
 
 
         transaction.commit()
index 7548cb1..e342c90 100644 (file)
@@ -8,8 +8,6 @@ import csv
 import sys
 from django.contrib.auth.models import User
 from lxml import etree
 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
 
 from collections import defaultdict
 from django.core.management import BaseCommand
 
@@ -42,24 +40,20 @@ def url_for_format(slug, format):
 
 
 class Command(BaseCommand):
 
 
 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'
 
     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:
     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')
             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():
         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
             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:
             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:
                 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(
                     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'
             )
                 author=user,
                 description='automatyczne dodanie isbn'
             )
-            print 'committed %s' % slug
+            print('committed %s' % slug)
             if old_head.publishable:
                 new_head.set_publishable(True)
             else:
             if old_head.publishable:
                 new_head.set_publishable(True)
             else:
-                print 'Warning: %s not publishable' % slug
+                print('Warning: %s not publishable' % slug)
index cdfaab9..11d63a3 100644 (file)
@@ -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
 # 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):
 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'
 
     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:
     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:
             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(
             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
                 )
                     tags=[Chunk.tag_model.objects.get(slug='editor-proofreading')],
                     publishable=True
                 )
-            print 'committed %s' % book.slug
+            print('committed %s' % book.slug)
index 82bd622..f6b37bb 100644 (file)
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-
-from optparse import make_option
 import sys
 
 from django.contrib.auth.models import User
 import sys
 
 from django.contrib.auth.models import User
@@ -24,23 +21,28 @@ def common_prefix(texts):
 
 
 class Command(BaseCommand):
 
 
 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]...'
 
     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
 
     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 = []
     
         def read_slug(slug):
             res = []
-            res.append((re.compile(ur'__?(przedmowa)$'), -1))
-            res.append((re.compile(ur'__?(cz(esc)?|ksiega|rozdzial)__?(?P<n>\d*)$'), None))
-            res.append((re.compile(ur'__?(rozdzialy__?)?(?P<n>\d*)-'), None))
+            res.append((re.compile(r'__?(przedmowa)$'), -1))
+            res.append((re.compile(r'__?(cz(esc)?|ksiega|rozdzial)__?(?P<n>\d*)$'), None))
+            res.append((re.compile(r'__?(rozdzialy__?)?(?P<n>\d*)-'), None))
         
             for r, default in res:
                 m = r.search(slug)
         
             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)
                 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)
                 '--dry-run ' if dry_run else '',
                 '--force ' if force else '',
                 quote(title), slug,
                 " \\\n    ".join(merge_slugs)
-                )
+                ))
     
         if conflicting_slugs:
             if force:
     
         if conflicting_slugs:
             if force:
-                print self.style.NOTICE('# These books will be archived:')
+                print(self.style.NOTICE('# These books will be archived:'))
             else:
             else:
-                print self.style.ERROR('# ERROR: Conflicting slugs:')
+                print(self.style.ERROR('# ERROR: Conflicting slugs:'))
             for slug in conflicting_slugs:
             for slug in conflicting_slugs:
-                print '#', slug
+                print('#', slug)
 
 
     def handle(self, *slugs, **options):
 
 
     def handle(self, *slugs, **options):
@@ -116,13 +118,13 @@ class Command(BaseCommand):
 
         if guess:
             if slugs:
 
         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:
                 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.
             return
 
         # Start transaction management.
@@ -147,13 +149,13 @@ class Command(BaseCommand):
 
 
         if dry_run and verbose:
 
 
         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:
 
         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 = []
 
         for i, book in enumerate(books):
             chunk_titles = []
@@ -172,12 +174,12 @@ class Command(BaseCommand):
                 chunk_slugs.append(new_chunk_slug)
 
                 if verbose:
                 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,
                         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:
 
             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()
                         # 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:
                     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:
                         return
 
                 if i:
index 114a26f..2f661c2 100644 (file)
@@ -7,7 +7,6 @@
 import sys
 from django.contrib.auth.models import User
 from lxml import etree
 import sys
 from django.contrib.auth.models import User
 from lxml import etree
-from optparse import make_option
 
 from django.core.management import BaseCommand
 
 
 from django.core.management import BaseCommand
 
@@ -16,24 +15,20 @@ from librarian import DCNS
 
 
 class Command(BaseCommand):
 
 
 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'
 
     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:
     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()]
             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
         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:
             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(
                 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
             )
                 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:
             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', ' ')))
index d0015c7..0b9c4d1 100755 (executable)
@@ -17,5 +17,5 @@ class User(AuthUser):
     class Meta:
         proxy = True
 
     class Meta:
         proxy = True
 
-    def __unicode__(self):
+    def __str__(self):
         return "%s %s" % (self.first_name, self.last_name)
         return "%s %s" % (self.first_name, self.last_name)
index 0ed8958..229b3d0 100755 (executable)
@@ -66,13 +66,13 @@ class Book(models.Model):
     def __len__(self):
         return self.chunk_set.count()
 
     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
 
         """
             Necessary so that __len__ isn't used for bool evaluation.
         """
         return True
 
-    def __unicode__(self):
+    def __str__(self):
         return self.title
 
     @models.permalink
         return self.title
 
     @models.permalink
@@ -266,12 +266,12 @@ class Book(models.Model):
 
         try:
             bi = self.wldocument(changes=changes, strict=True).book_info
 
         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 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
 
         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()
     def publishable_error(self):
         try:
             return self.assert_publishable()
-        except AssertionError, e:
+        except AssertionError as e:
             return e
         else:
             return None
             return e
         else:
             return None
index ade5cde..4182d08 100755 (executable)
@@ -43,7 +43,7 @@ class Chunk(dvcs_models.Document):
     # Representing
     # ============
 
     # Representing
     # ============
 
-    def __unicode__(self):
+    def __str__(self):
         return "%d:%d: %s" % (self.book_id, self.number, self.title)
 
     @models.permalink
         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
         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
 
             title += " (%d/%d)" % (self.number, book_length)
         return title
 
index 34f61f9..8cadcec 100755 (executable)
@@ -38,7 +38,7 @@ class Image(dvcs_models.Document):
     # Representing
     # ============
 
     # Representing
     # ============
 
-    def __unicode__(self):
+    def __str__(self):
         return self.title
 
     @models.permalink
         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)
             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.'))
             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()
             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()
     def publishable_error(self):
         try:
             return self.assert_publishable()
-        except AssertionError, e:
+        except AssertionError as e:
             return e
         else:
             return None
             return e
         else:
             return None
index eb95102..d7cc0df 100755 (executable)
@@ -19,5 +19,5 @@ class Project(models.Model):
         verbose_name = _('project')
         verbose_name_plural = _('projects')
 
         verbose_name = _('project')
         verbose_name_plural = _('projects')
 
-    def __unicode__(self):
+    def __str__(self):
         return self.name
         return self.name
index 2b08545..cacc9c8 100644 (file)
@@ -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:
 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()
index 5551a5c..1497be0 100644 (file)
@@ -3,9 +3,7 @@ from collections import defaultdict
 from datetime import datetime, date, timedelta
 import logging
 import os
 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
 
 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)
     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
             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)
     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
             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))
     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
             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)
     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
             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)
     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
             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))
     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
             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'))
         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())
         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'))
         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())
         return http.HttpResponse(e)
     else:
         return http.HttpResponseRedirect(image.get_absolute_url())
index 242714b..ff7b031 100644 (file)
@@ -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'
     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):
 
 
 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'
         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):
 
 
 def split_xml(text):
@@ -124,13 +124,13 @@ def split_xml(text):
                 del parent[0]
             element, parent = parent, parent.getparent()
         chunks[:0] = [[name,
                 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',
             ]]
 
         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:]:
         ]]
 
     for ch in chunks[1:]:
index e6f7868..e5bc400 100755 (executable)
@@ -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.
 #
 # 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
 
 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()
             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
         else:
             width, height = PILImage.open(uploaded_file.file).size
         min_width, min_height = settings.MIN_COVER_SIZE
index cc0ef31..0e998c9 100644 (file)
@@ -4,8 +4,6 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import urllib2 as urllib
 # 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
 
 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):
 
 
 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:
 
     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)
             try:
                 flickr_data = get_flickr_data(image.source_url)
-                print flickr_data
+                print(flickr_data)
             except FlickrError as e:
             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:
             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:
                         continue
                 try:
                     t = URLOpener().open(flickr_url).read()
                 except urllib.URLError:
-                    print 'Broken download url'
+                    print('Broken download url')
                 except IOError:
                 except IOError:
-                    print 'Connection failed'
+                    print('Connection failed')
                 else:
                     image.download_url = flickr_url
                     image.file.save(image.file.name, ContentFile(t))
                 else:
                     image.download_url = flickr_url
                     image.file.save(image.file.name, ContentFile(t))
index d83dad3..ea69f65 100644 (file)
@@ -34,7 +34,7 @@ class Image(models.Model):
         verbose_name = _('cover image')
         verbose_name_plural = _('cover images')
 
         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
         return u"%s - %s" % (self.author, self.title)
 
     @models.permalink
index 51aee19..75bf96d 100755 (executable)
@@ -5,7 +5,7 @@
 #
 import json
 import re
 #
 import json
 import re
-from urllib import FancyURLopener
+from urllib.request import FancyURLopener
 
 from django.contrib.sites.models import Site
 
 
 from django.contrib.sites.models import Site
 
index 270f24e..d6b0cc6 100644 (file)
@@ -30,7 +30,7 @@ class Tag(models.Model):
         abstract = True
         ordering = ['ordering']
 
         abstract = True
         ordering = ['ordering']
 
-    def __unicode__(self):
+    def __str__(self):
         return self.name
 
     @classmethod
         return self.name
 
     @classmethod
@@ -93,7 +93,7 @@ class Change(models.Model):
         ordering = ('created_at',)
         unique_together = ['tree', 'revision']
 
         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):
         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()
         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, 
 
     def merge_with(self, other, author=None, 
             author_name=None, author_email=None, 
@@ -225,11 +225,9 @@ class DocumentMeta(ModelBase):
         return model
 
 
         return model
 
 
-class Document(models.Model):
+class Document(models.Model, metaclass=DocumentMeta):
     """File in repository. Subclass it to use version control in your app."""
 
     """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')
 
     # default repository path
     REPO_PATH = os.path.join(settings.MEDIA_ROOT, 'dvcs')
 
@@ -238,7 +236,7 @@ class Document(models.Model):
     class Meta:
         abstract = True
 
     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):
         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.
 
         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 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
         :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
index 91f78e6..a57d759 100755 (executable)
@@ -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.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
 
 
 @deconstructible
@@ -15,6 +16,7 @@ class GzipFileSystemStorage(FileSystemStorage):
         return ContentFile(decompress(text))
 
     def _save(self, name, content):
         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)
 
         return super(GzipFileSystemStorage, self)._save(name, content)
index 376117a..40ad72f 100755 (executable)
@@ -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 _
 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("<a class='mangled' data-addr1='%(name)s' "
         "data-addr2='%(domain)s'>%(mangled)s</a>" % {
     mangled = "%s %s %s" % (name, at, (' %s ' % dot).join(domain.split('.')))
     return mark_safe("<a class='mangled' data-addr1='%(name)s' "
         "data-addr2='%(domain)s'>%(mangled)s</a>" % {
-            'name': name.encode('rot13'),
-            'domain': domain.encode('rot13'),
+            'name': codecs.encode(name, 'rot13'),
+            'domain': codecs.encode(domain, 'rot13'),
             'mangled': mangled,
         })
             'mangled': mangled,
         })
index a2025fe..9993ddb 100644 (file)
@@ -1,6 +1,6 @@
 import json
 import os
 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
 from django.conf import settings
 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404
 from django.utils.decorators import method_decorator
index 654480c..9d3c68d 100644 (file)
@@ -15,7 +15,7 @@ class ButtonAdminForm(forms.ModelForm):
         value = self.cleaned_data['params']
         try:
             return json.dumps(json.loads(value))
         value = self.cleaned_data['params']
         try:
             return json.dumps(json.loads(value))
-        except ValueError, e:
+        except ValueError as e:
             raise forms.ValidationError(e)
 
 
             raise forms.ValidationError(e)
 
 
index de48ced..7fed15a 100644 (file)
@@ -15,29 +15,29 @@ class Command(NoArgsCommand):
 
     def handle_noargs(self, **options):
         buttons = Button.objects.all()
 
     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)
         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'))
                 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()
 
                     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:
         hash = {}
         for b in buttons:
             if b.slug not in hash:
@@ -45,13 +45,13 @@ class Command(NoArgsCommand):
                 continue
 
             # duplicate button
                 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:
             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():
                 remove_duplicate = False
 
             for g in b.group.all():
@@ -63,11 +63,11 @@ class Command(NoArgsCommand):
             if remove_duplicate:
                 b.delete()
 
             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:
         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:
 
         for b in Button.objects.all():
             if len(b.group.all()) == 0:
-                print "orphan: '%s'" % b.slug
+                print("orphan: '%s'" % b.slug)
index a23e346..fbaa396 100644 (file)
@@ -16,7 +16,7 @@ class ButtonGroup(models.Model):
         ordering = ('position', 'name',)
         verbose_name, verbose_name_plural = _('button group'), _('button groups')
 
         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):
         return self.name
 
     def to_dict(self, with_buttons=False):
@@ -63,7 +63,7 @@ class Button(models.Model):
             'scriptlet_id': self.scriptlet_id,
         }
 
             'scriptlet_id': self.scriptlet_id,
         }
 
-    def __unicode__(self):
+    def __str__(self):
         return self.label
 
 
         return self.label
 
 
@@ -71,5 +71,5 @@ class Scriptlet(models.Model):
     name = models.CharField(max_length=64, primary_key=True)
     code = models.TextField()
 
     name = models.CharField(max_length=64, primary_key=True)
     code = models.TextField()
 
-    def __unicode__(self):
+    def __str__(self):
         return _(u'javascript') + u':' + self.name
         return _(u'javascript') + u':' + self.name
index 877a9d0..d62c545 100644 (file)
@@ -10,7 +10,7 @@ class ExtendedEncoder(json.JSONEncoder):
 
     def default(self, obj):
         if isinstance(obj, Promise):
 
     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')
 
         if isinstance(obj, datetime):
             return datetime.ctime(obj) + " " + (datetime.tzname(obj) or 'GMT')
index c539908..fb20392 100644 (file)
@@ -18,7 +18,7 @@ class Theme(models.Model):
         verbose_name = _('theme')
         verbose_name_plural = _('themes')
 
         verbose_name = _('theme')
         verbose_name_plural = _('themes')
 
-    def __unicode__(self):
+    def __str__(self):
         return self.name
 
     def __repr__(self):
         return self.name
 
     def __repr__(self):
index e1ef6ae..5dc770f 100644 (file)
@@ -3,7 +3,7 @@ from datetime import datetime
 import os
 import logging
 from time import mktime
 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
 
 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.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
 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)
 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((
 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):
 
         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')
 
 
         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)
         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 "",
                 "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 "",
                 "published": _("Published") + ": " + \
                     localize(change.publish_log.order_by('-book_record__timestamp')[0].book_record.timestamp) \
                     if change.publish_log.exists() else "",
index 2b8dd67..e5bbf41 100644 (file)
@@ -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 "",
                 "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)
 
             })
     return JSONResponse(changes)