subject.curriculum.new in select list
[redakcja.git] / apps / catalogue / management / commands / merge_books.py
old mode 100755 (executable)
new mode 100644 (file)
index 4747591..d148266
@@ -1,14 +1,11 @@
 # -*- coding: utf-8 -*-
 
 from optparse import make_option
-import sys
 
-from django.contrib.auth.models import User
 from django.core.management.base import BaseCommand
 from django.core.management.color import color_style
 from django.db import transaction
 
-from slughifi import slughifi
 from catalogue.models import Book
 
 
@@ -24,75 +21,99 @@ def common_prefix(texts):
     return "".join(common)
 
 
-def print_guess(dry_run=True):
-    from collections import defaultdict
-    from pipes import quote
-    import re
-
-    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))
-    
-        for r, default in res:
-            m = r.search(slug)
-            if m:
-                start = m.start()
-                try:
-                    return int(m.group('n')), slug[:start]
-                except IndexError:
-                    return default, slug[:start]
-        return None, slug
-
-    def file_to_title(fname):
-        """ Returns a title-like version of a filename. """
-        parts = (p.replace('_', ' ').title() for p in fname.split('__'))
-        return ' / '.join(parts)
-
-    merges = defaultdict(list)
-    for b in Book.objects.all():
-        n, ns = read_slug(b.slug)
-        if n is not None:
-            merges[ns].append((n, b))
-
-    for slug in sorted(merges.keys()):
-        merge_list = sorted(merges[slug])
-        if len(merge_list) < 2:
-            continue
-
-        title = file_to_title(slug)
-        print "./manage.py merge_books %s--title=%s --slug=%s \\\n    %s\n" % (
-            '--dry-run ' if dry_run else '',
-            quote(title), slug,
-            " \\\n    ".join(b.slug for i, b in merge_list)
-            )
-
-
 class Command(BaseCommand):
     option_list = BaseCommand.option_list + (
-        make_option('-s', '--slug', dest='new_slug', metavar='SLUG',
+        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',
+        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,
+        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,
+        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,
+        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 handle(self, *slugs, **options):
+    def print_guess(self, dry_run=True, force=False):
+        from collections import defaultdict
+        from pipes import quote
+        import re
+
+        def read_slug(slug):
+            res = [
+                (re.compile(ur'__?(przedmowa)$'), -1),
+                (re.compile(ur'__?(cz(esc)?|ksiega|rozdzial)__?(?P<n>\d*)$'), None),
+                (re.compile(ur'__?(rozdzialy__?)?(?P<n>\d*)-'), None),
+            ]
+
+            for r, default in res:
+                m = r.search(slug)
+                if m:
+                    start = m.start()
+                    try:
+                        return int(m.group('n')), slug[:start]
+                    except IndexError:
+                        return default, slug[:start]
+            return None, slug
+
+        def file_to_title(fname):
+            """ Returns a title-like version of a filename. """
+            parts = (p.replace('_', ' ').title() for p in fname.split('__'))
+            return ' / '.join(parts)
+
+        merges = defaultdict(list)
+        slugs = []
+        for b in Book.objects.all():
+            slugs.append(b.slug)
+            n, ns = read_slug(b.slug)
+            if n is not None:
+                merges[ns].append((n, b))
+
+        conflicting_slugs = []
+        for slug in sorted(merges.keys()):
+            merge_list = sorted(merges[slug])
+            if len(merge_list) < 2:
+                continue
+
+            merge_slugs = [b.slug for i, b in merge_list]
+            if slug in slugs and slug not in merge_slugs:
+                conflicting_slugs.append(slug)
+
+            title = file_to_title(slug)
+            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:')
+            else:
+                print self.style.ERROR('# ERROR: Conflicting slugs:')
+            for slug in conflicting_slugs:
+                print '#', slug
 
+    def handle(self, *slugs, **options):
         self.style = color_style()
 
+        force = options.get('force')
         guess = options.get('guess')
         dry_run = options.get('dry_run')
-        new_slug = options.get('new_slug')
-        new_title = options.get('new_title')
+        new_slug = options.get('new_slug').decode('utf-8')
+        new_title = options.get('new_title').decode('utf-8')
         verbose = options.get('verbose')
 
         if guess:
@@ -100,19 +121,17 @@ class Command(BaseCommand):
                 print "Please specify either slugs, or --guess."
                 return
             else:
-                print_guess(dry_run)
+                self.print_guess(dry_run, force)
                 return
         if not slugs:
             print "Please specify some book slugs"
             return
 
-
         # Start transaction management.
         transaction.commit_unless_managed()
         transaction.enter_transaction_management()
         transaction.managed(True)
 
-
         books = [Book.objects.get(slug=slug) for slug in slugs]
         common_slug = common_prefix(slugs)
         common_title = common_prefix([b.title for b in books])
@@ -127,6 +146,9 @@ class Command(BaseCommand):
         elif common_slug.startswith(new_slug):
             common_slug = new_slug
 
+        if slugs[0] != new_slug and Book.objects.filter(slug=new_slug).exists():
+            self.style.ERROR('Book already exists, skipping!')
+
         if dry_run and verbose:
             print self.style.NOTICE('DRY RUN: nothing will be changed.')
             print
@@ -161,6 +183,24 @@ class Command(BaseCommand):
                     print
 
             if not dry_run:
+                try:
+                    conflict = Book.objects.get(slug=new_slug)
+                except Book.DoesNotExist:
+                    conflict = None
+                else:
+                    if conflict == books[0]:
+                        conflict = None
+
+                if conflict:
+                    if force:
+                        # 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))
+                    else:
+                        print self.style.ERROR('ERROR: Book with slug "%s" exists.' % new_slug)
+                        return
+
                 if i:
                     books[0].append(books[i], slugs=chunk_slugs, titles=chunk_titles)
                 else:
@@ -172,7 +212,5 @@ class Command(BaseCommand):
                         chunk.slug = chunk_slugs[j]
                         chunk.save()
 
-
         transaction.commit()
         transaction.leave_transaction_management()
-