[#6728] Perform trac ticket import directly, not over http

Signed-off-by: Tim Van Steenburgh <tvansteenburgh@gmail.com>
diff --git a/Allura/allura/scripts/trac_export.py b/Allura/allura/scripts/trac_export.py
index f54e8bc..b269544 100644
--- a/Allura/allura/scripts/trac_export.py
+++ b/Allura/allura/scripts/trac_export.py
@@ -273,18 +273,24 @@
         return json.JSONEncoder.default(self, obj)
 
 
-def main():
-    options, args = parse_options()
-    ex = TracExport(args[0], start_id=options.start_id,
-            verbose=options.verbose, do_attachments=options.do_attachments)
-    # Implement iterator sequence limiting using islice()
-    doc = [t for t in islice(ex, options.limit)]
+def export(url, start_id=1, verbose=False, do_attachments=True,
+        only_tickets=False, limit=None):
+    ex = TracExport(url, start_id=start_id,
+            verbose=verbose, do_attachments=do_attachments)
 
-    if not options.only_tickets:
+    doc = [t for t in islice(ex, limit)]
+
+    if not only_tickets:
         doc = {
             'class': 'PROJECT',
             'trackers': {'default': {'artifacts': doc}}
         }
+    return doc
+
+
+def main():
+    options, args = parse_options()
+    doc = export(args[0], **vars(options))
 
     out_file = sys.stdout
     if options.out_filename:
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
index 298b09e..73f9352 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -36,15 +36,9 @@
     @patch('forgeimporters.trac.tickets.session')
     @patch('forgeimporters.trac.tickets.g')
     @patch('forgeimporters.trac.tickets.AuditLog')
-    @patch('forgeimporters.trac.tickets.import_tracker')
-    @patch('forgeimporters.trac.tickets.AlluraImportApiClient')
-    @patch('forgeimporters.trac.tickets.datetime')
-    @patch('forgeimporters.trac.tickets.ApiTicket')
-    @patch('forgeimporters.trac.tickets.TracExport')
-    def test_import_tool(self, TracExport, ApiTicket, dt, ApiClient, import_tracker, AuditLog, g, session):
-        from datetime import datetime, timedelta
-        now = datetime.utcnow()
-        dt.utcnow.return_value = now
+    @patch('forgeimporters.trac.tickets.ImportSupport')
+    @patch('forgeimporters.trac.tickets.export')
+    def test_import_tool(self, export, ImportSupport, AuditLog, g, session):
         user_map = {"orig_user":"new_user"}
         importer = TracTicketImporter()
         app = Mock(name='ForgeTrackerApp')
@@ -54,13 +48,13 @@
         project = Mock(name='Project', shortname='myproject')
         project.install_app.return_value = app
         user = Mock(name='User', _id='id')
-        with patch.dict('forgeimporters.trac.tickets.config', {'base_url': 'foo'}):
-            res = importer.import_tool(project, user,
-                    mount_point='bugs',
-                    mount_label='Bugs',
-                    trac_url='http://example.com/trac/url',
-                    user_map=json.dumps(user_map),
-                    )
+        export.return_value = []
+        res = importer.import_tool(project, user,
+                mount_point='bugs',
+                mount_label='Bugs',
+                trac_url='http://example.com/trac/url',
+                user_map=json.dumps(user_map),
+                )
         self.assertEqual(res, app)
         project.install_app.assert_called_once_with(
                 'Tickets', mount_point='bugs', mount_label='Bugs',
@@ -70,19 +64,14 @@
                         'source': 'Trac',
                         'trac_url': 'http://example.com/trac/url/',
                     })
-        TracExport.return_value = []
-        TracExport.assert_called_once_with('http://example.com/trac/url/')
-        ApiTicket.assert_called_once_with(
-                user_id=user._id,
-                capabilities={"import": ["Projects", "myproject"]},
-                expires=now + timedelta(minutes=60))
-        api_client = ApiClient.return_value
-        import_tracker.assert_called_once_with(
-                api_client, 'myproject', 'bugs', {
+        export.assert_called_once_with('http://example.com/trac/url/')
+        ImportSupport.return_value.perform_import.assert_called_once_with(
+                json.dumps(export.return_value),
+                json.dumps({
                     "user_map": user_map,
                     "usernames_match": False,
-                }, '[]',
-                validate=False)
+                    }),
+                )
         AuditLog.log.assert_called_once_with(
                 'import tool bugs from http://example.com/trac/url/',
                 project=project, user=user, url='foo')
@@ -90,14 +79,14 @@
 
     @patch('forgeimporters.trac.tickets.session')
     @patch('forgeimporters.trac.tickets.h')
-    @patch('forgeimporters.trac.tickets.TracExport')
-    def test_import_tool_failure(self, TracExport, h, session):
+    @patch('forgeimporters.trac.tickets.export')
+    def test_import_tool_failure(self, export, h, session):
         importer = TracTicketImporter()
         app = Mock(name='ForgeTrackerApp')
         project = Mock(name='Project', shortname='myproject')
         project.install_app.return_value = app
         user = Mock(name='User', _id='id')
-        TracExport.side_effect = ValueError
+        export.side_effect = ValueError
 
         self.assertRaises(ValueError, importer.import_tool, project, user,
                 mount_point='bugs',
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index 6b2600a..cae010b 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -15,10 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-from datetime import (
-        datetime,
-        timedelta,
-        )
 import json
 
 from formencode import validators as fev
@@ -27,7 +23,6 @@
 from pylons import tmpl_context as c
 from pylons import app_globals as g
 from tg import (
-        config,
         expose,
         flash,
         redirect,
@@ -40,12 +35,11 @@
 
 from allura.controllers import BaseController
 from allura.lib.decorators import require_post, task
-from allura.lib.import_api import AlluraImportApiClient
 from allura.lib import validators as v
 from allura.lib import helpers as h
-from allura.model import ApiTicket, AuditLog
+from allura.model import AuditLog
 from allura.scripts.trac_export import (
-        TracExport,
+        export,
         DateJSONEncoder,
         )
 
@@ -55,7 +49,7 @@
         ImportErrorHandler,
         )
 from forgetracker.tracker_main import ForgeTrackerApp
-from forgetracker.scripts.import_tracker import import_tracker
+from forgetracker.import_support import ImportSupport
 
 
 @task(notifications_disabled=True)
@@ -131,19 +125,14 @@
         session(app.config).flush(app.config)
         session(app.globals).flush(app.globals)
         try:
-            export = [ticket for ticket in TracExport(trac_url)]
-            export_string = json.dumps(export, cls=DateJSONEncoder)
-            api_ticket = ApiTicket(user_id=user._id,
-                    capabilities={"import": ["Projects", project.shortname]},
-                    expires=datetime.utcnow() + timedelta(minutes=60))
-            session(api_ticket).flush(api_ticket)
-            cli = AlluraImportApiClient(config['base_url'], api_ticket.api_key,
-                    api_ticket.secret_key, verbose=True, retry=False)
-            import_tracker(cli, project.shortname, mount_point, {
-                        'user_map': json.loads(user_map) if user_map else {},
-                        'usernames_match': self.usernames_match(trac_url),
-                    },
-                    export_string, validate=False)
+            with h.push_config(c, app=app):
+                ImportSupport().perform_import(
+                        json.dumps(export(trac_url), cls=DateJSONEncoder),
+                        json.dumps({
+                            'user_map': json.loads(user_map) if user_map else {},
+                            'usernames_match': self.usernames_match(trac_url),
+                            }),
+                        )
             AuditLog.log(
                 'import tool %s from %s' % (
                         app.config.options.mount_point,
diff --git a/ForgeTracker/forgetracker/import_support.py b/ForgeTracker/forgetracker/import_support.py
index 381e711..1dc466c 100644
--- a/ForgeTracker/forgetracker/import_support.py
+++ b/ForgeTracker/forgetracker/import_support.py
@@ -24,12 +24,12 @@
 
 # Non-stdlib imports
 from pylons import tmpl_context as c
-from allura.lib import helpers as h
-
 from ming.orm.ormsession import ThreadLocalORMSession
 
 # Pyforge-specific imports
 from allura import model as M
+from allura.lib import helpers as h
+from allura.lib.plugin import ImportIdConverter
 
 # Local imports
 from forgetracker import model as TM
@@ -230,7 +230,7 @@
             app_config_id=c.app.config._id,
             custom_fields=dict(),
             ticket_num=ticket_num,
-            import_id=c.api_token.api_key)
+            import_id=ImportIdConverter.get().expand(ticket_dict['id'], c.app))
         ticket.update(remapped)
         return ticket
 
@@ -289,7 +289,6 @@
                     h.really_unicode(comment_dict['submitter']), text)
         comment = thread.post(text=text, timestamp=ts)
         comment.author_id = author_id
-        comment.import_id = c.api_token.api_key
 
     def make_attachment(self, org_ticket_id, ticket_id, att_dict):
         if att_dict['size'] > self.ATTACHMENT_SIZE_LIMIT:
@@ -375,8 +374,6 @@
             self.errors.append('Only single tracker import is supported')
             return self.errors, self.warnings
 
-        log.info('Import id: %s', c.api_token.api_key)
-
         artifacts = project_doc['trackers'][tracker_names[0]]['artifacts']
 
         if self.option('create_users'):