some book merging automation
[redakcja.git] / apps / catalogue / management / commands / merge_books.py
1 # -*- coding: utf-8 -*-
2
3 from optparse import make_option
4 import sys
5
6 from django.contrib.auth.models import User
7 from django.core.management.base import BaseCommand
8 from django.core.management.color import color_style
9 from django.db import transaction
10
11 from slughifi import slughifi
12 from catalogue.models import Book
13
14
15 def common_prefix(texts):
16     common = []
17
18     min_len = min(len(text) for text in texts)
19     for i in range(min_len):
20         chars = list(set([text[i] for text in texts]))
21         if len(chars) > 1:
22             break
23         common.append(chars[0])
24     return "".join(common)
25
26
27 def print_guess(dry_run=True):
28     from collections import defaultdict
29     from pipes import quote
30     import re
31
32     def read_slug(slug):
33         res = []
34         res.append((re.compile(ur'__?(przedmowa)$'), -1))
35         res.append((re.compile(ur'__?(cz(esc)?|ksiega|rozdzial)__?(?P<n>\d*)$'), None))
36         res.append((re.compile(ur'__?(rozdzialy__?)?(?P<n>\d*)-'), None))
37     
38         for r, default in res:
39             m = r.search(slug)
40             if m:
41                 start = m.start()
42                 try:
43                     return int(m.group('n')), slug[:start]
44                 except IndexError:
45                     return default, slug[:start]
46         return None, slug
47
48     def file_to_title(fname):
49         """ Returns a title-like version of a filename. """
50         parts = (p.replace('_', ' ').title() for p in fname.split('__'))
51         return ' / '.join(parts)
52
53     merges = defaultdict(list)
54     for b in Book.objects.all():
55         n, ns = read_slug(b.slug)
56         if n is not None:
57             merges[ns].append((n, b))
58
59     for slug in sorted(merges.keys()):
60         merge_list = sorted(merges[slug])
61         if len(merge_list) < 2:
62             continue
63
64         title = file_to_title(slug)
65         print "./manage.py merge_books %s--title=%s --slug=%s \\\n    %s\n" % (
66             '--dry-run ' if dry_run else '',
67             quote(title), slug,
68             " \\\n    ".join(b.slug for i, b in merge_list)
69             )
70
71
72 class Command(BaseCommand):
73     option_list = BaseCommand.option_list + (
74         make_option('-s', '--slug', dest='new_slug', metavar='SLUG',
75             help='New slug of the merged book (defaults to common part of all slugs).'),
76         make_option('-t', '--title', dest='new_title', metavar='TITLE',
77             help='New title of the merged book (defaults to common part of all titles).'),
78         make_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
79             help='Less output'),
80         make_option('-g', '--guess', action='store_true', dest='guess', default=False,
81             help='Try to guess what merges are needed (but do not apply them).'),
82         make_option('-d', '--dry-run', action='store_true', dest='dry_run', default=False,
83             help='Dry run: do not actually change anything.'),
84     )
85     help = 'Merges multiple books into one.'
86     args = '[slug]...'
87
88     def handle(self, *slugs, **options):
89
90         self.style = color_style()
91
92         guess = options.get('guess')
93         dry_run = options.get('dry_run')
94         new_slug = options.get('new_slug')
95         new_title = options.get('new_title')
96         verbose = options.get('verbose')
97
98         if guess:
99             if slugs:
100                 print "Please specify either slugs, or --guess."
101                 return
102             else:
103                 print_guess(dry_run)
104                 return
105         if not slugs:
106             print "Please specify some book slugs"
107             return
108
109
110         # Start transaction management.
111         transaction.commit_unless_managed()
112         transaction.enter_transaction_management()
113         transaction.managed(True)
114
115
116         books = [Book.objects.get(slug=slug) for slug in slugs]
117         common_slug = common_prefix(slugs)
118         common_title = common_prefix([b.title for b in books])
119
120         if not new_title:
121             new_title = common_title
122         elif common_title.startswith(new_title):
123             common_title = new_title
124
125         if not new_slug:
126             new_slug = common_slug
127         elif common_slug.startswith(new_slug):
128             common_slug = new_slug
129
130         if dry_run and verbose:
131             print self.style.NOTICE('DRY RUN: nothing will be changed.')
132             print
133
134         if verbose:
135             print "New title:", self.style.NOTICE(new_title)
136             print "New slug:", self.style.NOTICE(new_slug)
137             print
138
139         for i, book in enumerate(books):
140             chunk_titles = []
141             chunk_slugs = []
142
143             book_title = book.title[len(common_title):].replace(' / ', ' ').lstrip()
144             book_slug = book.slug[len(common_slug):].replace('__', '_').lstrip('-_')
145             for j, chunk in enumerate(book):
146                 if j:
147                     new_chunk_title = book_title + '_%d' % j
148                     new_chunk_slug = book_slug + '_%d' % j
149                 else:
150                     new_chunk_title, new_chunk_slug = book_title, book_slug
151
152                 chunk_titles.append(new_chunk_title)
153                 chunk_slugs.append(new_chunk_slug)
154
155                 if verbose:
156                     print "title: %s // %s  -->\n       %s // %s\nslug: %s / %s  -->\n      %s / %s" % (
157                         book.title, chunk.comment,
158                         new_title, new_chunk_title,
159                         book.slug, chunk.slug,
160                         new_slug, new_chunk_slug)
161                     print
162
163             if not dry_run:
164                 if i:
165                     books[0].append(books[i], slugs=chunk_slugs, titles=chunk_titles)
166                 else:
167                     book.title = new_title
168                     book.slug = new_slug
169                     book.save()
170                     for j, chunk in enumerate(book):
171                         chunk.comment = chunk_titles[j]
172                         chunk.slug = chunk_slugs[j]
173                         chunk.save()
174
175
176         transaction.commit()
177         transaction.leave_transaction_management()
178