editor: minor fix
[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 slughifi import slughifi
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.commit_unless_managed()
51         transaction.enter_transaction_management()
52         transaction.managed(True)
53
54         redakcja_link = re.compile(re.escape(redakcja) + r'([-_.:?&%/a-zA-Z0-9]*)')
55
56         all_tickets = 0
57         all_chunks = 0
58         done_tickets = 0
59         done_chunks = 0
60         empty_users = 0
61         unknown_users = {}
62         unknown_books = []
63         forced = []
64
65         if verbose:
66             print 'Downloading CSV file'
67         for r in csv.reader(urllib2.urlopen(redmine_csv)):
68             if r[0] == '#':
69                 continue
70             all_tickets += 1
71
72             username = r[6]
73             if not username:
74                 if verbose:
75                     print "Empty user, skipping"
76                 empty_users += 1
77                 continue
78
79             first_name, last_name = unicode(username, 'utf-8').rsplit(u' ', 1)
80             try:
81                 user = User.objects.get(first_name=first_name, last_name=last_name)
82             except User.DoesNotExist:
83                 print self.style.ERROR('Unknown user: ' + username)
84                 unknown_users.setdefault(username, 0)
85                 unknown_users[username] += 1
86                 continue
87
88             ticket_done = False
89             for fname in redakcja_link.findall(r[-1]):
90                 fname = unicode(urllib.unquote(fname), 'utf-8', 'ignore')
91                 if fname.endswith('.xml'):
92                     fname = fname[:-4]
93                 fname = fname.replace(' ', '_')
94                 fname = slughifi(fname)
95
96                 chunks = Chunk.objects.filter(book__slug=fname)
97                 if not chunks:
98                     print self.style.ERROR('Unknown book: ' + fname)
99                     unknown_books.append(fname)
100                     continue
101                 all_chunks += chunks.count()
102
103                 for chunk in chunks:
104                     if chunk.user:
105                         if chunk.user == user:
106                             continue
107                         else:
108                             forced.append((chunk, chunk.user, user))
109                             if force:
110                                 print self.style.WARNING(
111                                     '%s assigned to %s, forcing change to %s.' %
112                                     (chunk.pretty_name(), chunk.user, user))
113                             else:
114                                 print self.style.WARNING(
115                                     '%s assigned to %s not to %s, skipping.' %
116                                     (chunk.pretty_name(), chunk.user, user))
117                                 continue
118                     chunk.user = user
119                     chunk.save()
120                     ticket_done = True
121                     done_chunks += 1
122
123             if ticket_done:
124                 done_tickets += 1
125
126
127         # Print results
128         print
129         print "Results:"
130         print "Assignments imported from %d/%d tickets to %d/%d relevalt chunks." % (
131                 done_tickets, all_tickets, done_chunks, all_chunks)
132         if empty_users:
133             print "%d tickets were unassigned." % empty_users
134         if forced:
135             print "%d assignments conficts (%s):" % (
136                 len(forced), "changed" if force else "left")
137             for chunk, orig, user in forced:
138                 print "  %s: \t%s \t->  %s" % (
139                     chunk.pretty_name(), orig.username, user.username)
140         if unknown_books:
141             print "%d unknown books:" % len(unknown_books)
142             for fname in unknown_books:
143                 print "  %s" % fname
144         if unknown_users:
145             print "%d unknown users:" % len(unknown_users)
146             for name in unknown_users:
147                 print "  %s (%d tickets)" % (name, unknown_users[name])
148         print
149
150
151         transaction.commit()
152         transaction.leave_transaction_management()
153