3 from django.contrib.auth.models import User
 
   4 from django.core.management.base import BaseCommand
 
   5 from django.core.management.color import color_style
 
   6 from django.db import transaction
 
   8 from catalogue.models import Book
 
  11 def common_prefix(texts):
 
  14     min_len = min(len(text) for text in texts)
 
  15     for i in range(min_len):
 
  16         chars = list(set([text[i] for text in texts]))
 
  19         common.append(chars[0])
 
  20     return "".join(common)
 
  23 class Command(BaseCommand):
 
  24     help = 'Merges multiple books into one.'
 
  27     def add_arguments(self, parser):
 
  29             '-s', '--slug', dest='new_slug', metavar='SLUG',
 
  30             help='New slug of the merged book (defaults to common part of all slugs).')
 
  32             '-t', '--title', dest='new_title', metavar='TITLE',
 
  33             help='New title of the merged book (defaults to common part of all titles).')
 
  35             '-q', '--quiet', action='store_false', dest='verbose', default=True,
 
  38             '-g', '--guess', action='store_true', dest='guess', default=False,
 
  39             help='Try to guess what merges are needed (but do not apply them).')
 
  41             '-d', '--dry-run', action='store_true', dest='dry_run', default=False,
 
  42             help='Dry run: do not actually change anything.')
 
  44             '-f', '--force', action='store_true', dest='force', default=False,
 
  45             help='On slug conflict, hide the original book to archive.')
 
  47     def print_guess(self, dry_run=True, force=False):
 
  48         from collections import defaultdict
 
  49         from pipes import quote
 
  54             res.append((re.compile(r'__?(przedmowa)$'), -1))
 
  55             res.append((re.compile(r'__?(cz(esc)?|ksiega|rozdzial)__?(?P<n>\d*)$'), None))
 
  56             res.append((re.compile(r'__?(rozdzialy__?)?(?P<n>\d*)-'), None))
 
  58             for r, default in res:
 
  63                         return int(m.group('n')), slug[:start]
 
  65                         return default, slug[:start]
 
  68         def file_to_title(fname):
 
  69             """ Returns a title-like version of a filename. """
 
  70             parts = (p.replace('_', ' ').title() for p in fname.split('__'))
 
  71             return ' / '.join(parts)
 
  73         merges = defaultdict(list)
 
  75         for b in Book.objects.all():
 
  77             n, ns = read_slug(b.slug)
 
  79                 merges[ns].append((n, b))
 
  81         conflicting_slugs = []
 
  82         for slug in sorted(merges.keys()):
 
  83             merge_list = sorted(merges[slug])
 
  84             if len(merge_list) < 2:
 
  87             merge_slugs = [b.slug for i, b in merge_list]
 
  88             if slug in slugs and slug not in merge_slugs:
 
  89                 conflicting_slugs.append(slug)
 
  91             title = file_to_title(slug)
 
  92             print("./manage.py merge_books %s%s--title=%s --slug=%s \\\n    %s\n" % (
 
  93                 '--dry-run ' if dry_run else '',
 
  94                 '--force ' if force else '',
 
  96                 " \\\n    ".join(merge_slugs)
 
 101                 print(self.style.NOTICE('# These books will be archived:'))
 
 103                 print(self.style.ERROR('# ERROR: Conflicting slugs:'))
 
 104             for slug in conflicting_slugs:
 
 108     def handle(self, *slugs, **options):
 
 110         self.style = color_style()
 
 112         force = options.get('force')
 
 113         guess = options.get('guess')
 
 114         dry_run = options.get('dry_run')
 
 115         new_slug = options.get('new_slug').decode('utf-8')
 
 116         new_title = options.get('new_title').decode('utf-8')
 
 117         verbose = options.get('verbose')
 
 121                 print("Please specify either slugs, or --guess.")
 
 124                 self.print_guess(dry_run, force)
 
 127             print("Please specify some book slugs")
 
 130         # Start transaction management.
 
 131         transaction.enter_transaction_management()
 
 133         books = [Book.objects.get(slug=slug) for slug in slugs]
 
 134         common_slug = common_prefix(slugs)
 
 135         common_title = common_prefix([b.title for b in books])
 
 138             new_title = common_title
 
 139         elif common_title.startswith(new_title):
 
 140             common_title = new_title
 
 143             new_slug = common_slug
 
 144         elif common_slug.startswith(new_slug):
 
 145             common_slug = new_slug
 
 147         if slugs[0] != new_slug and Book.objects.filter(slug=new_slug).exists():
 
 148             self.style.ERROR('Book already exists, skipping!')
 
 151         if dry_run and verbose:
 
 152             print(self.style.NOTICE('DRY RUN: nothing will be changed.'))
 
 156             print("New title:", self.style.NOTICE(new_title))
 
 157             print("New slug:", self.style.NOTICE(new_slug))
 
 160         for i, book in enumerate(books):
 
 164             book_title = book.title[len(common_title):].replace(' / ', ' ').lstrip()
 
 165             book_slug = book.slug[len(common_slug):].replace('__', '_').lstrip('-_')
 
 166             for j, chunk in enumerate(book):
 
 168                     new_chunk_title = book_title + '_%d' % j
 
 169                     new_chunk_slug = book_slug + '_%d' % j
 
 171                     new_chunk_title, new_chunk_slug = book_title, book_slug
 
 173                 chunk_titles.append(new_chunk_title)
 
 174                 chunk_slugs.append(new_chunk_slug)
 
 177                     print("title: %s // %s  -->\n       %s // %s\nslug: %s / %s  -->\n      %s / %s" % (
 
 178                         book.title, chunk.title,
 
 179                         new_title, new_chunk_title,
 
 180                         book.slug, chunk.slug,
 
 181                         new_slug, new_chunk_slug))
 
 186                     conflict = Book.objects.get(slug=new_slug)
 
 187                 except Book.DoesNotExist:
 
 190                     if conflict == books[0]:
 
 195                         # FIXME: there still may be a conflict
 
 196                         conflict.slug = '.' + conflict.slug
 
 198                         print(self.style.NOTICE('Book with slug "%s" moved to "%s".' % (new_slug, conflict.slug)))
 
 200                         print(self.style.ERROR('ERROR: Book with slug "%s" exists.' % new_slug))
 
 204                     books[0].append(books[i], slugs=chunk_slugs, titles=chunk_titles)
 
 206                     book.title = new_title
 
 209                     for j, chunk in enumerate(book):
 
 210                         chunk.title = chunk_titles[j]
 
 211                         chunk.slug = chunk_slugs[j]
 
 216         transaction.leave_transaction_management()