Librarian in regular requirements.
[redakcja.git] / apps / catalogue / management / commands / assign_from_redmine.py
1 # -*- coding: utf-8 -*-
2
3 import csv
4 from optparse import make_option
5 import re
6 import sys
7 import urllib
8 import urllib2
9
10 from django.contrib.auth.models import User
11 from django.core.management.base import BaseCommand
12 from django.core.management.color import color_style
13 from django.db import transaction
14
15 from slugify import slugify
16 from catalogue.models import Chunk
17
18
19 REDMINE_CSV = 'http://redmine.nowoczesnapolska.org.pl/projects/wl-publikacje/issues.csv'
20 REDAKCJA_URL = 'http://redakcja.wolnelektury.pl/documents/'
21
22
23 class Command(BaseCommand):
24     option_list = BaseCommand.option_list + (
25         make_option('-r', '--redakcja', dest='redakcja', metavar='URL',
26             help='Base URL of Redakcja documents',
27             default=REDAKCJA_URL),
28         make_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
29             help='Less output'),
30         make_option('-f', '--force', action='store_true', dest='force', default=False,
31             help='Force assignment overwrite'),
32     )
33     help = 'Imports ticket assignments from Redmine.'
34     args = '[redmine-csv-url]'
35
36     def handle(self, *redmine_csv, **options):
37
38         self.style = color_style()
39
40         redakcja = options.get('redakcja')
41         verbose = options.get('verbose')
42         force = options.get('force')
43
44         if not redmine_csv:
45             if verbose:
46                 print "Using default Redmine CSV URL:", REDMINE_CSV
47             redmine_csv = REDMINE_CSV
48
49         # Start transaction management.
50         transaction.enter_transaction_management()
51
52         redakcja_link = re.compile(re.escape(redakcja) + r'([-_.:?&%/a-zA-Z0-9]*)')
53
54         all_tickets = 0
55         all_chunks = 0
56         done_tickets = 0
57         done_chunks = 0
58         empty_users = 0
59         unknown_users = {}
60         unknown_books = []
61         forced = []
62
63         if verbose:
64             print 'Downloading CSV file'
65         for r in csv.reader(urllib2.urlopen(redmine_csv)):
66             if r[0] == '#':
67                 continue
68             all_tickets += 1
69
70             username = r[6]
71             if not username:
72                 if verbose:
73                     print "Empty user, skipping"
74                 empty_users += 1
75                 continue
76
77             first_name, last_name = unicode(username, 'utf-8').rsplit(u' ', 1)
78             try:
79                 user = User.objects.get(first_name=first_name, last_name=last_name)
80             except User.DoesNotExist:
81                 print self.style.ERROR('Unknown user: ' + username)
82                 unknown_users.setdefault(username, 0)
83                 unknown_users[username] += 1
84                 continue
85
86             ticket_done = False
87             for fname in redakcja_link.findall(r[-1]):
88                 fname = unicode(urllib.unquote(fname), 'utf-8', 'ignore')
89                 if fname.endswith('.xml'):
90                     fname = fname[:-4]
91                 fname = fname.replace(' ', '_')
92                 fname = slugify(fname)
93
94                 chunks = Chunk.objects.filter(book__slug=fname)
95                 if not chunks:
96                     print self.style.ERROR('Unknown book: ' + fname)
97                     unknown_books.append(fname)
98                     continue
99                 all_chunks += chunks.count()
100
101                 for chunk in chunks:
102                     if chunk.user:
103                         if chunk.user == user:
104                             continue
105                         else:
106                             forced.append((chunk, chunk.user, user))
107                             if force:
108                                 print self.style.WARNING(
109                                     '%s assigned to %s, forcing change to %s.' %
110                                     (chunk.pretty_name(), chunk.user, user))
111                             else:
112                                 print self.style.WARNING(
113                                     '%s assigned to %s not to %s, skipping.' %
114                                     (chunk.pretty_name(), chunk.user, user))
115                                 continue
116                     chunk.user = user
117                     chunk.save()
118                     ticket_done = True
119                     done_chunks += 1
120
121             if ticket_done:
122                 done_tickets += 1
123
124
125         # Print results
126         print
127         print "Results:"
128         print "Assignments imported from %d/%d tickets to %d/%d relevalt chunks." % (
129                 done_tickets, all_tickets, done_chunks, all_chunks)
130         if empty_users:
131             print "%d tickets were unassigned." % empty_users
132         if forced:
133             print "%d assignments conficts (%s):" % (
134                 len(forced), "changed" if force else "left")
135             for chunk, orig, user in forced:
136                 print "  %s: \t%s \t->  %s" % (
137                     chunk.pretty_name(), orig.username, user.username)
138         if unknown_books:
139             print "%d unknown books:" % len(unknown_books)
140             for fname in unknown_books:
141                 print "  %s" % fname
142         if unknown_users:
143             print "%d unknown users:" % len(unknown_users)
144             for name in unknown_users:
145                 print "  %s (%d tickets)" % (name, unknown_users[name])
146         print
147
148
149         transaction.commit()
150         transaction.leave_transaction_management()
151