refactor catalogue to separate app,
[redakcja.git] / apps / catalogue / management / commands / assign_from_redmine.py
diff --git a/apps/catalogue/management/commands/assign_from_redmine.py b/apps/catalogue/management/commands/assign_from_redmine.py
new file mode 100755 (executable)
index 0000000..9f7b12d
--- /dev/null
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+
+import csv
+from optparse import make_option
+import re
+import sys
+import urllib
+import urllib2
+
+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 Chunk
+
+
+REDMINE_CSV = 'http://redmine.nowoczesnapolska.org.pl/projects/wl-publikacje/issues.csv'
+REDAKCJA_URL = 'http://redakcja.wolnelektury.pl/documents/'
+
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('-r', '--redakcja', dest='redakcja', metavar='URL',
+            help='Base URL of Redakcja documents',
+            default=REDAKCJA_URL),
+        make_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
+            help='Less output'),
+        make_option('-f', '--force', action='store_true', dest='force', default=False,
+            help='Force assignment overwrite'),
+    )
+    help = 'Imports ticket assignments from Redmine.'
+    args = '[redmine-csv-url]'
+
+    def handle(self, *redmine_csv, **options):
+
+        self.style = color_style()
+
+        redakcja = options.get('redakcja')
+        verbose = options.get('verbose')
+        force = options.get('force')
+
+        if not redmine_csv:
+            if verbose:
+                print "Using default Redmine CSV URL:", REDMINE_CSV
+            redmine_csv = REDMINE_CSV
+
+        # Start transaction management.
+        transaction.commit_unless_managed()
+        transaction.enter_transaction_management()
+        transaction.managed(True)
+
+        redakcja_link = re.compile(re.escape(redakcja) + r'([-_.:?&%/a-zA-Z0-9]*)')
+
+        all_tickets = 0
+        all_chunks = 0
+        done_tickets = 0
+        done_chunks = 0
+        empty_users = 0
+        unknown_users = {}
+        unknown_books = []
+        forced = []
+
+        if verbose:
+            print 'Downloading CSV file'
+        for r in csv.reader(urllib2.urlopen(redmine_csv)):
+            if r[0] == '#':
+                continue
+            all_tickets += 1
+
+            username = r[6]
+            if not username:
+                if verbose:
+                    print "Empty user, skipping"
+                empty_users += 1
+                continue
+
+            first_name, last_name = unicode(username, 'utf-8').rsplit(u' ', 1)
+            try:
+                user = User.objects.get(first_name=first_name, last_name=last_name)
+            except User.DoesNotExist:
+                print self.style.ERROR('Unknown user: ' + username)
+                unknown_users.setdefault(username, 0)
+                unknown_users[username] += 1
+                continue
+
+            ticket_done = False
+            for fname in redakcja_link.findall(r[-1]):
+                fname = unicode(urllib.unquote(fname), 'utf-8', 'ignore')
+                if fname.endswith('.xml'):
+                    fname = fname[:-4]
+                fname = fname.replace(' ', '_')
+                fname = slughifi(fname)
+
+                chunks = Chunk.objects.filter(book__slug=fname)
+                if not chunks:
+                    print self.style.ERROR('Unknown book: ' + fname)
+                    unknown_books.append(fname)
+                    continue
+                all_chunks += chunks.count()
+
+                for chunk in chunks:
+                    if chunk.user:
+                        if chunk.user == user:
+                            continue
+                        else:
+                            forced.append((chunk, chunk.user, user))
+                            if force:
+                                print self.style.WARNING(
+                                    '%s assigned to %s, forcing change to %s.' %
+                                    (chunk.pretty_name(), chunk.user, user))
+                            else:
+                                print self.style.WARNING(
+                                    '%s assigned to %s not to %s, skipping.' %
+                                    (chunk.pretty_name(), chunk.user, user))
+                                continue
+                    chunk.user = user
+                    chunk.save()
+                    ticket_done = True
+                    done_chunks += 1
+
+            if ticket_done:
+                done_tickets += 1
+
+
+        # Print results
+        print
+        print "Results:"
+        print "Assignments imported from %d/%d tickets to %d/%d relevalt chunks." % (
+                done_tickets, all_tickets, done_chunks, all_chunks)
+        if empty_users:
+            print "%d tickets were unassigned." % empty_users
+        if forced:
+            print "%d assignments conficts (%s):" % (
+                len(forced), "changed" if force else "left")
+            for chunk, orig, user in forced:
+                print "  %s: \t%s \t->  %s" % (
+                    chunk.pretty_name(), orig.username, user.username)
+        if unknown_books:
+            print "%d unknown books:" % len(unknown_books)
+            for fname in unknown_books:
+                print "  %s" % fname
+        if unknown_users:
+            print "%d unknown users:" % len(unknown_users)
+            for name in unknown_users:
+                print "  %s (%d tickets)" % (name, unknown_users[name])
+        print
+
+
+        transaction.commit()
+        transaction.leave_transaction_management()
+