You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2013/08/07 18:48:03 UTC
[01/11] git commit: [#6480] TracExport bug fixes
Updated Branches:
refs/heads/master e3663fb93 -> 04cd1ed28
[#6480] TracExport bug fixes
- Pass in options explicitly instead of attempting to read from a
non-existent global object.
- Improve logging.
- Fix infinite loop bug.
Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/35cc655a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/35cc655a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/35cc655a
Branch: refs/heads/master
Commit: 35cc655a2913532a6471488d82a6452365e08e4d
Parents: 280aff0
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Aug 6 18:50:46 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:23 2013 +0000
----------------------------------------------------------------------
Allura/allura/scripts/trac_export.py | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/35cc655a/Allura/allura/scripts/trac_export.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/trac_export.py b/Allura/allura/scripts/trac_export.py
index aeb14ea..bce548e 100644
--- a/Allura/allura/scripts/trac_export.py
+++ b/Allura/allura/scripts/trac_export.py
@@ -17,7 +17,7 @@
# specific language governing permissions and limitations
# under the License.
-
+import logging
import sys
import csv
import urlparse
@@ -35,6 +35,8 @@ from BeautifulSoup import BeautifulSoup, NavigableString
import dateutil.parser
import pytz
+log = logging.getLogger(__name__)
+
def parse_options():
optparser = OptionParser(usage=''' %prog <Trac URL>
@@ -66,7 +68,7 @@ class TracExport(object):
'owner': 'assigned_to',
}
- def __init__(self, base_url, start_id=1):
+ def __init__(self, base_url, start_id=1, verbose=False, do_attachments=True):
"""start_id - start with at least that ticket number (actual returned
ticket may have higher id if we don't have access to exact
one).
@@ -78,6 +80,9 @@ class TracExport(object):
self.ticket_map = {}
self.start_id = start_id
self.page = (start_id - 1) / self.PAGE_SIZE + 1
+ self.verbose = verbose
+ self.do_attachments = do_attachments
+ self.exhausted = False
self.ticket_queue = self.next_ticket_ids()
def remap_fields(self, dict):
@@ -98,9 +103,9 @@ class TracExport(object):
glue = '&' if '?' in suburl else '?'
return url + glue + 'format=' + type
- @staticmethod
- def log_url(url):
- if options.verbose:
+ def log_url(self, url):
+ log.info(url)
+ if self.verbose:
print >>sys.stderr, url
@classmethod
@@ -198,7 +203,7 @@ class TracExport(object):
'''
t = self.parse_ticket_body(id)
t['comments'] = self.parse_ticket_comments(id)
- if options.do_attachments:
+ if self.do_attachments:
atts = self.parse_ticket_attachments(id)
if atts:
t['attachments'] = atts
@@ -230,6 +235,9 @@ class TracExport(object):
res.append((id, extra))
self.page += 1
+ if len(res) < self.PAGE_SIZE:
+ self.exhausted = True
+
return res
def __iter__(self):
@@ -238,7 +246,7 @@ class TracExport(object):
def next(self):
while True:
# queue empty, try to fetch more
- if len(self.ticket_queue) == 0:
+ if len(self.ticket_queue) == 0 and not self.exhausted:
self.ticket_queue = self.next_ticket_ids()
# there aren't any more, we're really done
if len(self.ticket_queue) == 0:
@@ -258,7 +266,8 @@ class DateJSONEncoder(json.JSONEncoder):
def main():
options, args = parse_options()
- ex = TracExport(args[0], start_id=options.start_id)
+ 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)]
[07/11] git commit: [#6480] Initialize classes to prevent exc if no
href
Posted by br...@apache.org.
[#6480] Initialize classes to prevent exc if no href
Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/f398a06e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/f398a06e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/f398a06e
Branch: refs/heads/master
Commit: f398a06e1ca979922c366d30ad17060b53eb5988
Parents: 7553977
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Aug 6 19:23:16 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000
----------------------------------------------------------------------
Allura/allura/lib/markdown_extensions.py | 1 +
1 file changed, 1 insertion(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/f398a06e/Allura/allura/lib/markdown_extensions.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/markdown_extensions.py b/Allura/allura/lib/markdown_extensions.py
index 4879bf4..6e1004d 100644
--- a/Allura/allura/lib/markdown_extensions.py
+++ b/Allura/allura/lib/markdown_extensions.py
@@ -99,6 +99,7 @@ class ForgeLinkPattern(markdown.inlinepatterns.LinkPattern):
except IndexError:
title = None
+ classes = ''
if href:
if href == 'TOC':
return '[TOC]' # skip TOC
[02/11] git commit: [#6480] Remove whitespace from shortname;
use autofocus
Posted by br...@apache.org.
[#6480] Remove whitespace from shortname; use autofocus
Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/280aff07
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/280aff07
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/280aff07
Branch: refs/heads/master
Commit: 280aff074794e8d7bd2a1924a93d3f6f6fdfbb32
Parents: 63fdf19
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Mon Aug 5 19:30:50 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:23 2013 +0000
----------------------------------------------------------------------
ForgeImporters/forgeimporters/google/templates/project.html | 2 +-
ForgeImporters/forgeimporters/templates/project_base.html | 4 ++--
ForgeImporters/forgeimporters/trac/templates/project.html | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/280aff07/ForgeImporters/forgeimporters/google/templates/project.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/templates/project.html b/ForgeImporters/forgeimporters/google/templates/project.html
index 172dcf5..a2887e7 100644
--- a/ForgeImporters/forgeimporters/google/templates/project.html
+++ b/ForgeImporters/forgeimporters/google/templates/project.html
@@ -23,7 +23,7 @@
<label>Google Project Name</label>
</div>
<div class="grid-10">
- <input id="project_name" name="project_name" value="{{c.form_values['project_name']}}"/>
+ <input id="project_name" name="project_name" value="{{c.form_values['project_name']}}" autofocus/>
<div id="project_name_error" class="error{% if not c.form_errors['project_name'] %} hidden{% endif %}">
{{c.form_errors['project_name']}}
</div>
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/280aff07/ForgeImporters/forgeimporters/templates/project_base.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/templates/project_base.html b/ForgeImporters/forgeimporters/templates/project_base.html
index d4db30e..6f1b683 100644
--- a/ForgeImporters/forgeimporters/templates/project_base.html
+++ b/ForgeImporters/forgeimporters/templates/project_base.html
@@ -47,7 +47,7 @@
function suggest_name() {
var $project_shortname = $('#project_shortname');
if (!manual) {
- $project_shortname.val($('#project_name').val());
+ $project_shortname.val($('#project_name').val().replace(/\s/g, '').toLowerCase());
}
$project_shortname.trigger('change');
}
@@ -72,7 +72,7 @@
}
$(function() {
- $('#project_name').focus().bind('change keyup', suggest_name);
+ $('#project_name').bind('change keyup', suggest_name);
$('#project_shortname').bind('change keyup', function(event) {
if (event.type == 'keyup') {
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/280aff07/ForgeImporters/forgeimporters/trac/templates/project.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/templates/project.html b/ForgeImporters/forgeimporters/trac/templates/project.html
index abcfb48..869e290 100644
--- a/ForgeImporters/forgeimporters/trac/templates/project.html
+++ b/ForgeImporters/forgeimporters/trac/templates/project.html
@@ -23,7 +23,7 @@
<label>Trac URL</label>
</div>
<div class="grid-10">
- <input id="trac_url" name="trac_url" value="{{c.form_values['trac_url']}}"/>
+ <input id="trac_url" name="trac_url" value="{{c.form_values['trac_url']}}" autofocus/>
<div id="trac_ur_errorl" class="error{% if not c.form_errors['trac_url'] %} hidden{% endif %}">
{{c.form_errors['trac_url']}}
</div>
[06/11] git commit: [#6480] Importer bug fixes
Posted by br...@apache.org.
[#6480] Importer bug fixes
- Normalize trac urls
- Flush new app configs and related objects before importing artifacts
- Add options needed for WikiExporter
Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/75539774
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/75539774
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/75539774
Branch: refs/heads/master
Commit: 755397743b8956fe2342eede82d4163b6fd92a3d
Parents: 35cc655
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Aug 6 18:57:37 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000
----------------------------------------------------------------------
ForgeImporters/forgeimporters/base.py | 4 +++-
ForgeImporters/forgeimporters/google/code.py | 1 +
ForgeImporters/forgeimporters/trac/tickets.py | 9 +++++++--
ForgeImporters/forgeimporters/trac/wiki.py | 10 +++++++++-
ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py | 4 ++++
5 files changed, 24 insertions(+), 4 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/75539774/ForgeImporters/forgeimporters/base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index 5652474..7ad720c 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -52,7 +52,9 @@ class ProjectImportForm(schema.Schema):
@task
def import_tool(importer_name, project_name, mount_point=None, mount_label=None, **kw):
importer = ToolImporter.by_name(importer_name)
- importer.import_tool(c.project, mount_point, mount_label, **kw)
+ importer.import_tool(project=c.project, user=c.user,
+ mount_point=mount_point,
+ mount_label=mount_label, **kw)
class ProjectImporter(BaseController):
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/75539774/ForgeImporters/forgeimporters/google/code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/code.py b/ForgeImporters/forgeimporters/google/code.py
index ef7f800..4457b55 100644
--- a/ForgeImporters/forgeimporters/google/code.py
+++ b/ForgeImporters/forgeimporters/google/code.py
@@ -101,6 +101,7 @@ class GoogleRepoImporter(ToolImporter):
tool_description = 'Import your SVN, Git, or Hg repo from Google Code'
def import_tool(self, project, project_name, mount_point=None, mount_label=None):
+ def import_tool(self, project, project_name, mount_point=None, mount_label=None, **kw):
""" Import a Google Code repo into a new SVN, Git, or Hg Allura tool.
"""
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/75539774/ForgeImporters/forgeimporters/trac/tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index 969dfd2..0f13649 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -24,6 +24,7 @@ import json
import formencode as fe
from formencode import validators as fev
+from ming.orm import session
from pylons import tmpl_context as c
from pylons import app_globals as g
from tg import (
@@ -84,21 +85,25 @@ class TracTicketImporter(ToolImporter):
tool_description = 'Import your tickets from Trac'
def import_tool(self, project=None, mount_point=None, mount_label=None,
- trac_url=None, user=None):
+ trac_url=None, user=None, **kw):
""" Import Trac tickets into a new Allura Tracker tool.
"""
+ trac_url = trac_url.rstrip('/') + '/'
mount_point = mount_point or 'tickets'
app = project.install_app(
'Tickets',
mount_point=mount_point,
mount_label=mount_label or 'Tickets',
)
- export = TracExport(trac_url)
+ session(app.config).flush(app.config)
+ session(app.globals).flush(app.globals)
+ 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, False)
import_tracker(cli, project.shortname, mount_point, {},
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/75539774/ForgeImporters/forgeimporters/trac/wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/wiki.py b/ForgeImporters/forgeimporters/trac/wiki.py
index 2417863..e7ead86 100644
--- a/ForgeImporters/forgeimporters/trac/wiki.py
+++ b/ForgeImporters/forgeimporters/trac/wiki.py
@@ -25,6 +25,7 @@ import tempfile
import formencode as fe
from formencode import validators as fev
+from ming.orm import session
from pylons import tmpl_context as c
from pylons import app_globals as g
from tg import (
@@ -83,25 +84,32 @@ class TracWikiImporter(ToolImporter):
tool_description = 'Import your wiki from Trac'
def import_tool(self, project=None, mount_point=None, mount_label=None,
- trac_url=None, user=None):
+ trac_url=None, user=None, **kw):
""" Import Trac wiki into a new Allura Wiki tool.
"""
+ trac_url = trac_url.rstrip('/') + '/'
mount_point = mount_point or 'wiki'
app = project.install_app(
'Wiki',
mount_point=mount_point,
mount_label=mount_label or 'Wiki',
)
+ session(app.config).flush(app.config)
api_ticket = ApiTicket(user_id=user._id,
capabilities={"import": ["Projects", project.shortname]},
expires=datetime.utcnow() + timedelta(minutes=60))
+ session(api_ticket).flush(api_ticket)
options = argparse.Namespace()
options.api_key = api_ticket.api_key
options.secret_key = api_ticket.secret_key
options.project = project.shortname
options.wiki = mount_point
options.base_url = config['base_url']
+ options.verbose = False
+ options.converter = 'html2text'
+ options.import_opts = []
+ options.user_map_file = None
with tempfile.NamedTemporaryFile() as f:
WikiExporter(trac_url, options).export(f)
f.flush()
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/75539774/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py b/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py
index 0038dd9..7f111d7 100644
--- a/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py
+++ b/ForgeWiki/forgewiki/scripts/wiki_from_trac/extractors.py
@@ -15,6 +15,7 @@
# specific language governing permissions and limitations
# under the License.
+import logging
import re
import sys
import json
@@ -36,6 +37,8 @@ except:
from BeautifulSoup import BeautifulSoup
+log = logging.getLogger(__name__)
+
class WikiExporter(object):
@@ -114,6 +117,7 @@ class WikiExporter(object):
out.write('\n')
def log(self, msg):
+ log.info(msg)
if self.options.verbose:
print >>sys.stderr, msg
[08/11] git commit: [#6480] Add tests for Trac ticket importer
Posted by br...@apache.org.
[#6480] Add tests for Trac ticket importer
Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/8a365f74
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/8a365f74
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/8a365f74
Branch: refs/heads/master
Commit: 8a365f74d9e8921e13a597fe34032b373ee49257
Parents: 1d5708d
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Aug 6 21:12:10 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000
----------------------------------------------------------------------
.../forgeimporters/google/tests/__init__.py | 17 ++++
.../forgeimporters/trac/tests/__init__.py | 17 ++++
.../forgeimporters/trac/tests/test_tickets.py | 100 +++++++++++++++++++
.../forgeimporters/trac/tests/test_wiki.py | 17 ++++
4 files changed, 151 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8a365f74/ForgeImporters/forgeimporters/google/tests/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tests/__init__.py b/ForgeImporters/forgeimporters/google/tests/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeImporters/forgeimporters/google/tests/__init__.py
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8a365f74/ForgeImporters/forgeimporters/trac/tests/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/__init__.py b/ForgeImporters/forgeimporters/trac/tests/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/tests/__init__.py
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8a365f74/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
new file mode 100644
index 0000000..8b102a9
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -0,0 +1,100 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from unittest import TestCase
+from mock import Mock, patch
+
+from allura.tests import TestController
+from allura.tests.decorators import with_tracker
+
+from forgeimporters.trac.tickets import (
+ TracTicketImporter,
+ TracTicketImportController,
+ )
+
+
+class TestTracTicketImporter(TestCase):
+ @patch('forgeimporters.trac.tickets.session')
+ @patch('forgeimporters.trac.tickets.g')
+ @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, g, session):
+ from datetime import datetime, timedelta
+ now = datetime.utcnow()
+ dt.utcnow.return_value = now
+
+ importer = TracTicketImporter()
+ app = Mock(name='ForgeTrackerApp')
+ project = Mock(name='Project', shortname='myproject')
+ project.install_app.return_value = app
+ user = Mock(name='User', _id='id')
+ res = importer.import_tool(project=project,
+ mount_point='bugs',
+ mount_label='Bugs',
+ trac_url='http://example.com/trac/url',
+ user=user)
+ self.assertEqual(res, app)
+ project.install_app.assert_called_once_with(
+ 'Tickets', mount_point='bugs', mount_label='Bugs')
+ 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', {}, '[]',
+ validate=False)
+ g.post_event.assert_called_once_with('project_updated')
+
+
+class TestTracTicketImportController(TestController, TestCase):
+ def setUp(self):
+ """Mount Trac import controller on the Tracker admin controller"""
+ super(TestTracTicketImportController, self).setUp()
+ from forgetracker.tracker_main import TrackerAdminController
+ TrackerAdminController._importer = TracTicketImportController()
+
+ @with_tracker
+ def test_index(self):
+ r = self.app.get('/p/test/admin/bugs/_importer/')
+ self.assertIsNotNone(r.html.find(attrs=dict(name="trac_url")))
+ self.assertIsNotNone(r.html.find(attrs=dict(name="mount_label")))
+ self.assertIsNotNone(r.html.find(attrs=dict(name="mount_point")))
+
+ @with_tracker
+ @patch('forgeimporters.trac.tickets.TracTicketImporter')
+ def test_create(self, importer):
+ from allura import model as M
+ importer.import_tool.return_value = Mock()
+ importer.import_tool.return_value.url.return_value = '/p/test/mymount'
+ params = dict(trac_url='http://example.com/trac/url',
+ mount_label='mylabel',
+ mount_point='mymount',
+ )
+ r = self.app.post('/p/test/admin/bugs/_importer/create', params,
+ status=302)
+ project = M.Project.query.get(shortname='test')
+ self.assertEqual(r.location, 'http://localhost/p/test/mymount')
+ self.assertEqual(project._id, importer.import_tool.call_args[0][0]._id)
+ self.assertEqual(u'mymount', importer.import_tool.call_args[1]['mount_point'])
+ self.assertEqual(u'mylabel', importer.import_tool.call_args[1]['mount_label'])
+ self.assertEqual(u'http://example.com/trac/url', importer.import_tool.call_args[1]['trac_url'])
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8a365f74/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
[03/11] git commit: [#6480] Add trac project and wiki importers;
refactor bases
Posted by br...@apache.org.
[#6480] Add trac project and wiki importers; refactor bases
Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/63fdf19f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/63fdf19f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/63fdf19f
Branch: refs/heads/master
Commit: 63fdf19fda232d6b0774ee5c428f3fed1b80cd21
Parents: 512ee4b
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Fri Aug 2 22:01:08 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:23 2013 +0000
----------------------------------------------------------------------
ForgeImporters/forgeimporters/base.py | 96 +++++++++++++++-
ForgeImporters/forgeimporters/google/project.py | 66 +++--------
ForgeImporters/forgeimporters/google/tasks.py | 6 -
.../google/templates/project.html | 62 -----------
.../forgeimporters/templates/project_base.html | 89 ++++++++++++++-
ForgeImporters/forgeimporters/trac/project.py | 63 +++++++++++
.../forgeimporters/trac/templates/project.html | 32 ++++++
.../trac/templates/wiki/index.html | 42 +++++++
ForgeImporters/forgeimporters/trac/tickets.py | 2 +-
ForgeImporters/forgeimporters/trac/wiki.py | 110 +++++++++++++++++++
ForgeImporters/setup.py | 3 +
11 files changed, 445 insertions(+), 126 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index 034a580..5652474 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -15,16 +15,46 @@
# specific language governing permissions and limitations
# under the License.
+import logging
+
from pkg_resources import iter_entry_points
-from tg import expose
+from tg import expose, validate, flash, redirect, config
+from tg.decorators import with_trailing_slash
+from pylons import tmpl_context as c
+from formencode import validators as fev, schema
+
+from allura.lib.decorators import require_post
+from allura.lib.decorators import task
+from allura.lib.security import require_access
+from allura.lib.widgets.forms import NeighborhoodProjectShortNameValidator
+from allura.lib import exceptions
+
from paste.deploy.converters import aslist
-from formencode import validators as fev
from ming.utils import LazyProperty
from allura.controllers import BaseController
+log = logging.getLogger(__name__)
+
+
+class ProjectImportForm(schema.Schema):
+ def __init__(self, source):
+ super(ProjectImportForm, self).__init__()
+ self.add_field('tools', ToolsValidator(source))
+
+ neighborhood = fev.PlainText(not_empty=True)
+ project_name = fev.UnicodeString(not_empty=True, max=40)
+ project_shortname = NeighborhoodProjectShortNameValidator()
+
+
+@task
+def import_tool(importer_name, project_name, mount_point=None, mount_label=None, **kw):
+ importer = ToolImporter.by_name(importer_name)
+ importer.import_tool(c.project, mount_point, mount_label, **kw)
+
+
class ProjectImporter(BaseController):
"""
Base class for project importers.
@@ -33,6 +63,14 @@ class ProjectImporter(BaseController):
:meth:`process()` views described below.
"""
source = None
+ process_validator = None
+ index_template = None
+
+ def __init__(self, neighborhood, *a, **kw):
+ self.neighborhood = neighborhood
+
+ def _check_security(self):
+ require_access(self.neighborhood, 'register')
@LazyProperty
def tool_importers(self):
@@ -47,6 +85,8 @@ class ProjectImporter(BaseController):
tools[ep.name] = epv()
return tools
+ @with_trailing_slash
+ @expose()
def index(self, **kw):
"""
Override and expose this view to present the project import form.
@@ -58,9 +98,12 @@ class ProjectImporter(BaseController):
This will list the available tool importers. Other project fields
(e.g., project_name) should go in the project_fields block.
"""
- raise NotImplemented
+ return {'importer': self, 'tg_template': self.index_template}
- def process(self, tools=None, **kw):
+ @require_post()
+ @expose()
+ @validate(process_validator, error_handler=index)
+ def process(self, **kw):
"""
Override and expose this to handle a project import.
@@ -68,7 +111,50 @@ class ProjectImporter(BaseController):
tools installed and redirect to the new project, presumably with a
message indicating that some data will not be available immediately.
"""
- raise NotImplemented
+ try:
+ c.project = self.neighborhood.register_project(kw['project_shortname'],
+ project_name=kw['project_name'])
+ except exceptions.ProjectOverlimitError:
+ flash("You have exceeded the maximum number of projects you are allowed to create", 'error')
+ redirect('.')
+ except exceptions.ProjectRatelimitError:
+ flash("Project creation rate limit exceeded. Please try again later.", 'error')
+ redirect('.')
+ except Exception:
+ log.error('error registering project: %s', kw['project_shortname'], exc_info=True)
+ flash('Internal Error. Please try again later.', 'error')
+ redirect('.')
+
+ self.after_project_create(c.project, **kw)
+ for importer_name in kw['tools']:
+ import_tool.post(importer_name, **kw)
+
+ flash('Welcome to the %s Project System! '
+ 'Your project data will be imported and should show up here shortly.' % config['site_name'])
+ redirect(c.project.script_name + 'admin/overview')
+
+ @expose('json:')
+ @validate(process_validator)
+ def check_names(self, **kw):
+ """
+ Ajax form validation.
+
+ """
+ return c.form_errors
+
+ def after_project_create(self, project, **kw):
+ """
+ Called after project is created.
+
+ Useful for doing extra processing on the project before individual
+ tool imports happen.
+
+ :param project: The newly created project.
+ :param \*\*kw: The keyword arguments that were posted to the controller
+ method that created the project.
+
+ """
+ pass
class ToolImporter(object):
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/google/project.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/project.py b/ForgeImporters/forgeimporters/google/project.py
index 7416258..dda41ea 100644
--- a/ForgeImporters/forgeimporters/google/project.py
+++ b/ForgeImporters/forgeimporters/google/project.py
@@ -17,16 +17,12 @@
import logging
-from tg import expose, validate, flash, redirect, config
+from formencode import validators as fev
+
+from tg import expose, validate
from tg.decorators import with_trailing_slash
-from pylons import tmpl_context as c
-from formencode import validators as fev, schema
from allura.lib.decorators import require_post
-from allura.lib.widgets.forms import NeighborhoodProjectShortNameValidator
-from allura.lib.security import require_access
-from allura.lib import helpers as h
-from allura.lib import exceptions
from .. import base
from . import tasks
@@ -35,15 +31,12 @@ from . import tasks
log = logging.getLogger(__name__)
-class GoogleCodeProjectForm(schema.Schema):
- neighborhood = fev.PlainText(not_empty=True)
+class GoogleCodeProjectForm(base.ProjectImportForm):
project_name = fev.Regex(r'^[a-z0-9][a-z0-9-]{,61}$',
not_empty=True,
messages={
'invalid': 'Please use only letters, numbers, and dashes.',
})
- project_shortname = NeighborhoodProjectShortNameValidator()
- tools = base.ToolsValidator('Google Code')
class GoogleCodeProjectImporter(base.ProjectImporter):
@@ -55,50 +48,25 @@ class GoogleCodeProjectImporter(base.ProjectImporter):
import.
"""
source = 'Google Code'
+ process_validator = GoogleCodeProjectForm(source)
+ index_template = 'jinja:forgeimporters.google:templates/project.html'
- def __init__(self, neighborhood, *a, **kw):
- super(GoogleCodeProjectImporter, self).__init__(*a, **kw)
- self.neighborhood = neighborhood
-
- def _check_security(self):
- require_access(self.neighborhood, 'register')
+ def after_project_create(self, project, **kw):
+ project.set_tool_data('google-code', project_name=project.name)
+ tasks.import_project_info.post()
@with_trailing_slash
- @expose('jinja:forgeimporters.google:templates/project.html')
+ @expose(index_template)
def index(self, **kw):
- return {'importer': self}
+ return super(self.__class__, self).index(**kw)
@require_post()
@expose()
- @validate(GoogleCodeProjectForm(), error_handler=index)
- def process(self, project_name=None, project_shortname=None, tools=None, **kw):
- project_name = h.really_unicode(project_name).encode('utf-8')
- project_shortname = h.really_unicode(project_shortname).encode('utf-8').lower()
-
- try:
- c.project = self.neighborhood.register_project(project_shortname,
- project_name=project_name)
- except exceptions.ProjectOverlimitError:
- flash("You have exceeded the maximum number of projects you are allowed to create", 'error')
- redirect('.')
- except exceptions.ProjectRatelimitError:
- flash("Project creation rate limit exceeded. Please try again later.", 'error')
- redirect('.')
- except Exception as e:
- log.error('error registering project: %s', project_shortname, exc_info=True)
- flash('Internal Error. Please try again later.', 'error')
- redirect('.')
-
- c.project.set_tool_data('google-code', project_name=project_name)
- tasks.import_project_info.post(project_name)
- for importer_name in tools:
- tasks.import_tool.post(importer_name, project_name)
-
- flash('Welcome to the %s Project System! '
- 'Your project data will be imported and should show up here shortly.' % config['site_name'])
- redirect(c.project.script_name + 'admin/overview')
+ @validate(process_validator, error_handler=index)
+ def process(self, **kw):
+ return super(self.__class__, self).process(**kw)
@expose('json:')
- @validate(GoogleCodeProjectForm())
- def check_names(self, project_name=None, project_shortname=None, tools=None, **kw):
- return c.form_errors
+ @validate(process_validator)
+ def check_names(self, **kw):
+ return super(self.__class__, self).check_names(**kw)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/google/tasks.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tasks.py b/ForgeImporters/forgeimporters/google/tasks.py
index 3e6e74d..968d9a9 100644
--- a/ForgeImporters/forgeimporters/google/tasks.py
+++ b/ForgeImporters/forgeimporters/google/tasks.py
@@ -23,7 +23,6 @@ from ming.orm import ThreadLocalORMSession
from allura.lib.decorators import task
from . import GoogleCodeProjectExtractor
-from ..base import ToolImporter
@task
@@ -34,8 +33,3 @@ def import_project_info(project_name):
extractor.get_license()
ThreadLocalORMSession.flush_all()
g.post_event('project_updated')
-
-@task
-def import_tool(importer_name, project_name, mount_point=None, mount_label=None):
- importer = ToolImporter.by_name(importer_name)
- importer.import_tool(c.project, project_name, mount_point, mount_label)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/google/templates/project.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/templates/project.html b/ForgeImporters/forgeimporters/google/templates/project.html
index 2cda0d0..172dcf5 100644
--- a/ForgeImporters/forgeimporters/google/templates/project.html
+++ b/ForgeImporters/forgeimporters/google/templates/project.html
@@ -18,68 +18,6 @@
-#}
{% extends 'forgeimporters:templates/project_base.html' %}
-{% block extra_css %}
- {{ super() }}
- <style type="text/css">
- #project-import-form #project-fields input {
- width: 88%;
- }
-
- .hidden { display: none; }
- </style>
-{% endblock %}
-
-{% block extra_js %}
- {{ super() }}
- <script type="text/javascript">
- var timers = {};
- function delay(callback, ms) {
- clearTimeout(timers[callback]);
- timers[callback] = setTimeout(callback, ms);
- }
-
- var manual = false;
- function suggest_name() {
- var $project_shortname = $('#project_shortname');
- if (!manual) {
- $project_shortname.val($('#project_name').val());
- }
- $project_shortname.trigger('change');
- }
-
- function check_names() {
- var data = {
- 'neighborhood': $('#neighborhood').val(),
- 'project_name': $('#project_name').val(),
- 'project_shortname': $('#project_shortname').val()
- };
- $.getJSON('check_names', data, function(result) {
- $('#project_name_error').addClass('hidden');
- $('#project_shortname_error').addClass('hidden');
- for(var field in result) {
- $('#'+field+'_error').text(result[field]).removeClass('hidden');
- }
- });
- }
-
- function update_url() {
- $('#url-fragment').text($('#project_shortname').val());
- }
-
- $(function() {
- $('#project_name').focus().bind('change keyup', suggest_name);
-
- $('#project_shortname').bind('change keyup', function(event) {
- if (event.type == 'keyup') {
- manual = true;
- }
- update_url();
- delay(check_names, 500);
- });
- });
- </script>
-{% endblock %}
-
{% block project_fields %}
<div class="grid-6">
<label>Google Project Name</label>
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/templates/project_base.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/templates/project_base.html b/ForgeImporters/forgeimporters/templates/project_base.html
index fc654f2..d4db30e 100644
--- a/ForgeImporters/forgeimporters/templates/project_base.html
+++ b/ForgeImporters/forgeimporters/templates/project_base.html
@@ -23,13 +23,97 @@
{% block title %}{{importer.source}} Project Importer{% endblock %}
{% block header %}{{importer.source}} Project Importer{% endblock %}
-{% block content %}
+{% block extra_css %}
+ {{ super() }}
+ <style type="text/css">
+ #project-import-form #project-fields input {
+ width: 88%;
+ }
+
+ .hidden { display: none; }
+ </style>
+{% endblock %}
+
+{% block extra_js %}
+ {{ super() }}
+ <script type="text/javascript">
+ var timers = {};
+ function delay(callback, ms) {
+ clearTimeout(timers[callback]);
+ timers[callback] = setTimeout(callback, ms);
+ }
+
+ var manual = false;
+ function suggest_name() {
+ var $project_shortname = $('#project_shortname');
+ if (!manual) {
+ $project_shortname.val($('#project_name').val());
+ }
+ $project_shortname.trigger('change');
+ }
+
+ function check_names() {
+ var data = {
+ 'neighborhood': $('#neighborhood').val(),
+ 'project_name': $('#project_name').val(),
+ 'project_shortname': $('#project_shortname').val()
+ };
+ $.getJSON('check_names', data, function(result) {
+ $('#project_name_error').addClass('hidden');
+ $('#project_shortname_error').addClass('hidden');
+ for(var field in result) {
+ $('#'+field+'_error').text(result[field]).removeClass('hidden');
+ }
+ });
+ }
+
+ function update_url() {
+ $('#url-fragment').text($('#project_shortname').val());
+ }
+ $(function() {
+ $('#project_name').focus().bind('change keyup', suggest_name);
+
+ $('#project_shortname').bind('change keyup', function(event) {
+ if (event.type == 'keyup') {
+ manual = true;
+ }
+ update_url();
+ delay(check_names, 500);
+ });
+ });
+ </script>
+{% endblock %}
+
+{% block content %}
<form id="project-import-form" method="POST" action="process">
<input type="hidden" id="neighborhood" name="neighborhood" value="{{importer.neighborhood.name}}"/>
<fieldset id="project-fields">
- {% block project_fields %}{% endblock %}
+ {% block project_fields %}
+ <div class="grid-6" style="clear:left">
+ <label>Project Name</label>
+ </div>
+ <div class="grid-10">
+ <input id="project_name" name="project_name" value="{{c.form_values['project_name']}}"/>
+ <div id="project_name_error" class="error{% if not c.form_errors['project_name'] %} hidden{% endif %}">
+ {{c.form_errors['project_name']}}
+ </div>
+ </div>
+
+ <div class="grid-6" style="clear:left">
+ <label>URL Name</label>
+ </div>
+ <div class="grid-10">
+ <input id="project_shortname" name="project_shortname" value="{{c.form_values['project_shortname']}}"/>
+ <div id="project_shortname_error" class="error{% if not c.form_errors['project_shortname'] %} hidden{% endif %}">
+ {{c.form_errors['project_shortname']}}
+ </div>
+ <div id="project-url">
+ http://{{request.environ['HTTP_HOST']}}{{importer.neighborhood.url()}}<span id="url-fragment">{{c.form_values['project_shortname']}}</span>
+ </div>
+ </div>
+ {% endblock %}
</fieldset>
<fieldset id="tool-fields">
@@ -50,5 +134,4 @@
<input type="submit" value="Import"/>
</form>
-
{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/trac/project.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/project.py b/ForgeImporters/forgeimporters/trac/project.py
new file mode 100644
index 0000000..66e8326
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/project.py
@@ -0,0 +1,63 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+
+from formencode import validators as fev
+
+from tg import expose, validate
+from tg.decorators import with_trailing_slash
+
+from allura.lib.decorators import require_post
+
+from .. import base
+
+
+log = logging.getLogger(__name__)
+
+
+class TracProjectForm(base.ProjectImportForm):
+ trac_url = fev.URL(not_empty=True)
+
+
+class TracProjectImporter(base.ProjectImporter):
+ """
+ Project importer for Trac.
+
+ """
+ source = 'Trac'
+ process_validator = TracProjectForm(source)
+ index_template = 'jinja:forgeimporters.trac:templates/project.html'
+
+ def after_project_create(self, project, **kw):
+ project.set_tool_data('trac', url=kw['trac_url'])
+
+ @with_trailing_slash
+ @expose(index_template)
+ def index(self, **kw):
+ return super(self.__class__, self).index(**kw)
+
+ @require_post()
+ @expose()
+ @validate(process_validator, error_handler=index)
+ def process(self, **kw):
+ return super(self.__class__, self).process(**kw)
+
+ @expose('json:')
+ @validate(process_validator)
+ def check_names(self, **kw):
+ return super(self.__class__, self).check_names(**kw)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/trac/templates/project.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/templates/project.html b/ForgeImporters/forgeimporters/trac/templates/project.html
new file mode 100644
index 0000000..abcfb48
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/templates/project.html
@@ -0,0 +1,32 @@
+{#-
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-#}
+{% extends 'forgeimporters:templates/project_base.html' %}
+
+{% block project_fields %}
+ <div class="grid-6">
+ <label>Trac URL</label>
+ </div>
+ <div class="grid-10">
+ <input id="trac_url" name="trac_url" value="{{c.form_values['trac_url']}}"/>
+ <div id="trac_ur_errorl" class="error{% if not c.form_errors['trac_url'] %} hidden{% endif %}">
+ {{c.form_errors['trac_url']}}
+ </div>
+ </div>
+ {{ super() }}
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/trac/templates/wiki/index.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/templates/wiki/index.html b/ForgeImporters/forgeimporters/trac/templates/wiki/index.html
new file mode 100644
index 0000000..6083b9c
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/templates/wiki/index.html
@@ -0,0 +1,42 @@
+{#-
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-#}
+{% extends g.theme.master %}
+
+{% block title %}
+{{c.project.name}} / Import Trac Wiki
+{% endblock %}
+
+{% block header %}
+Import wiki from Trac
+{% endblock %}
+
+{% block content %}
+<form action="create" method="post" class="pad">
+ <label for="trac_url">URL of the Trac instance</label>
+ <input name="trac_url" />
+
+ <label for="mount_label">Label</label>
+ <input name="mount_label" value="Source" />
+
+ <label for="mount_point">Mount Point</label>
+ <input name="mount_point" value="source" />
+
+ <input type="submit" />
+</form>
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/trac/tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index cc31741..969dfd2 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -48,7 +48,7 @@ from allura.scripts.trac_export import (
from forgeimporters.base import ToolImporter
from forgetracker.tracker_main import ForgeTrackerApp
-from forgetracker.script.import_tracker import import_tracker
+from forgetracker.scripts.import_tracker import import_tracker
class TracTicketImportSchema(fe.Schema):
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/forgeimporters/trac/wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/wiki.py b/ForgeImporters/forgeimporters/trac/wiki.py
new file mode 100644
index 0000000..2417863
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/wiki.py
@@ -0,0 +1,110 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import argparse
+from datetime import (
+ datetime,
+ timedelta,
+ )
+import tempfile
+
+import formencode as fe
+from formencode import validators as fev
+
+from pylons import tmpl_context as c
+from pylons import app_globals as g
+from tg import (
+ config,
+ expose,
+ redirect,
+ validate,
+ )
+from tg.decorators import (
+ with_trailing_slash,
+ without_trailing_slash,
+ )
+
+from allura.controllers import BaseController
+from allura.lib.decorators import require_post
+from allura.model import ApiTicket
+
+from forgeimporters.base import ToolImporter
+
+from forgewiki.scripts.wiki_from_trac.extractors import WikiExporter
+from forgewiki.scripts.wiki_from_trac.loaders import load_data
+from forgewiki.scripts.wiki_from_trac.wiki_from_trac import WikiFromTrac
+from forgewiki.wiki_main import ForgeWikiApp
+
+
+class TracWikiImportSchema(fe.Schema):
+ trac_url = fev.URL(not_empty=True)
+ mount_point = fev.UnicodeString()
+ mount_label = fev.UnicodeString()
+
+
+class TracWikiImportController(BaseController):
+ @with_trailing_slash
+ @expose('jinja:forgeimporters.trac:templates/wiki/index.html')
+ def index(self, **kw):
+ return {}
+
+ @without_trailing_slash
+ @expose()
+ @require_post()
+ @validate(TracWikiImportSchema(), error_handler=index)
+ def create(self, trac_url, mount_point, mount_label, **kw):
+ app = TracWikiImporter.import_tool(c.project,
+ mount_point=mount_point,
+ mount_label=mount_label,
+ trac_url=trac_url,
+ user=c.user)
+ redirect(app.url())
+
+
+class TracWikiImporter(ToolImporter):
+ target_app = ForgeWikiApp
+ source = 'Trac'
+ controller = TracWikiImportController
+ tool_label = 'Trac Wiki Importer'
+ tool_description = 'Import your wiki from Trac'
+
+ def import_tool(self, project=None, mount_point=None, mount_label=None,
+ trac_url=None, user=None):
+ """ Import Trac wiki into a new Allura Wiki tool.
+
+ """
+ mount_point = mount_point or 'wiki'
+ app = project.install_app(
+ 'Wiki',
+ mount_point=mount_point,
+ mount_label=mount_label or 'Wiki',
+ )
+ api_ticket = ApiTicket(user_id=user._id,
+ capabilities={"import": ["Projects", project.shortname]},
+ expires=datetime.utcnow() + timedelta(minutes=60))
+ options = argparse.Namespace()
+ options.api_key = api_ticket.api_key
+ options.secret_key = api_ticket.secret_key
+ options.project = project.shortname
+ options.wiki = mount_point
+ options.base_url = config['base_url']
+ with tempfile.NamedTemporaryFile() as f:
+ WikiExporter(trac_url, options).export(f)
+ f.flush()
+ load_data(f.name, WikiFromTrac.parser(), options)
+ g.post_event('project_updated')
+ return app
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63fdf19f/ForgeImporters/setup.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/setup.py b/ForgeImporters/setup.py
index 45a08eb..c4776ba 100644
--- a/ForgeImporters/setup.py
+++ b/ForgeImporters/setup.py
@@ -35,8 +35,11 @@ setup(name='ForgeImporters',
# -*- Entry points: -*-
[allura.project_importers]
google-code = forgeimporters.google.project:GoogleCodeProjectImporter
+ trac = forgeimporters.trac.project:TracProjectImporter
[allura.importers]
google-code-tracker = forgeimporters.google.tracker:GoogleCodeTrackerImporter
google-code-repo = forgeimporters.google.code:GoogleRepoImporter
+ trac-tickets = forgeimporters.trac.tickets:TracTicketImporter
+ trac-wiki = forgeimporters.trac.wiki:TracWikiImporter
""",)
[10/11] git commit: [#6480] Fix tests to match refactors
Posted by br...@apache.org.
[#6480] Fix tests to match refactors
Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/1d5708d0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/1d5708d0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/1d5708d0
Branch: refs/heads/master
Commit: 1d5708d07dbac80cad787c99c5d4595559c52cd6
Parents: f398a06
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Aug 6 19:38:52 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000
----------------------------------------------------------------------
.../forgeimporters/tests/google/test_tasks.py | 9 ---------
ForgeImporters/forgeimporters/tests/test_base.py | 14 +++++++++++++-
2 files changed, 13 insertions(+), 10 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/1d5708d0/ForgeImporters/forgeimporters/tests/google/test_tasks.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tasks.py b/ForgeImporters/forgeimporters/tests/google/test_tasks.py
index 23da83f..dc7d936 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tasks.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tasks.py
@@ -31,12 +31,3 @@ def test_import_project_info(c, session, gpe):
gpe.return_value.get_icon.assert_called_once_with()
gpe.return_value.get_license.assert_called_once_with()
session.flush_all.assert_called_once_with()
-
-
-@mock.patch.object(tasks.ToolImporter, 'by_name')
-@mock.patch.object(tasks, 'c')
-def test_import_tool(c, by_name):
- c.project = mock.Mock(name='project')
- tasks.import_tool('importer_name', 'project_name', 'mount_point', 'mount_label')
- by_name.assert_called_once_with('importer_name')
- by_name.return_value.import_tool.assert_called_once_with(c.project, 'project_name', 'mount_point', 'mount_label')
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/1d5708d0/ForgeImporters/forgeimporters/tests/test_base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/test_base.py b/ForgeImporters/forgeimporters/tests/test_base.py
index 1558db4..18b83ac 100644
--- a/ForgeImporters/forgeimporters/tests/test_base.py
+++ b/ForgeImporters/forgeimporters/tests/test_base.py
@@ -23,6 +23,18 @@ import mock
from .. import base
+@mock.patch.object(base.ToolImporter, 'by_name')
+@mock.patch.object(base, 'c')
+def test_import_tool(c, by_name):
+ c.project = mock.Mock(name='project')
+ c.user = mock.Mock(name='user')
+ base.import_tool('importer_name', 'project_name', 'mount_point', 'mount_label')
+ by_name.assert_called_once_with('importer_name')
+ by_name.return_value.import_tool.assert_called_once_with(c.project,
+ 'project_name', user=c.user, mount_point='mount_point',
+ mount_label='mount_label')
+
+
def ep(name, source=None, importer=None, **kw):
mep = mock.Mock(name='mock_ep', **kw)
mep.name = name
@@ -39,7 +51,7 @@ class TestProjectImporter(TestCase):
@mock.patch.object(base, 'iter_entry_points')
def test_tool_importers(self, iep):
eps = iep.return_value = [ep('ep1', 'foo'), ep('ep2', 'bar'), ep('ep3', 'foo')]
- pi = base.ProjectImporter()
+ pi = base.ProjectImporter(mock.Mock(name='neighborhood'))
pi.source = 'foo'
self.assertEqual(pi.tool_importers, {'ep1': eps[0].lv, 'ep3': eps[2].lv})
iep.assert_called_once_with('allura.importers')
[11/11] git commit: [#6480] Moved Trac wiki importer to separate
package
Posted by br...@apache.org.
[#6480] Moved Trac wiki importer to separate package
Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/04cd1ed2
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/04cd1ed2
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/04cd1ed2
Branch: refs/heads/master
Commit: 04cd1ed28c73fd25791d4fa6a0ad32a5fec2f157
Parents: 85c29b9
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Wed Aug 7 14:27:44 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000
----------------------------------------------------------------------
ForgeImporters/forgeimporters/google/project.py | 2 +-
.../trac/templates/wiki/index.html | 42 -------
.../forgeimporters/trac/tests/test_wiki.py | 104 -----------------
ForgeImporters/forgeimporters/trac/wiki.py | 117 -------------------
ForgeImporters/setup.py | 1 -
5 files changed, 1 insertion(+), 265 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/04cd1ed2/ForgeImporters/forgeimporters/google/project.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/project.py b/ForgeImporters/forgeimporters/google/project.py
index dda41ea..e69ac9e 100644
--- a/ForgeImporters/forgeimporters/google/project.py
+++ b/ForgeImporters/forgeimporters/google/project.py
@@ -53,7 +53,7 @@ class GoogleCodeProjectImporter(base.ProjectImporter):
def after_project_create(self, project, **kw):
project.set_tool_data('google-code', project_name=project.name)
- tasks.import_project_info.post()
+ tasks.import_project_info.post(project.name)
@with_trailing_slash
@expose(index_template)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/04cd1ed2/ForgeImporters/forgeimporters/trac/templates/wiki/index.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/templates/wiki/index.html b/ForgeImporters/forgeimporters/trac/templates/wiki/index.html
deleted file mode 100644
index 6083b9c..0000000
--- a/ForgeImporters/forgeimporters/trac/templates/wiki/index.html
+++ /dev/null
@@ -1,42 +0,0 @@
-{#-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
--#}
-{% extends g.theme.master %}
-
-{% block title %}
-{{c.project.name}} / Import Trac Wiki
-{% endblock %}
-
-{% block header %}
-Import wiki from Trac
-{% endblock %}
-
-{% block content %}
-<form action="create" method="post" class="pad">
- <label for="trac_url">URL of the Trac instance</label>
- <input name="trac_url" />
-
- <label for="mount_label">Label</label>
- <input name="mount_label" value="Source" />
-
- <label for="mount_point">Mount Point</label>
- <input name="mount_point" value="source" />
-
- <input type="submit" />
-</form>
-{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/04cd1ed2/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
deleted file mode 100644
index 738e49b..0000000
--- a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from unittest import TestCase
-from mock import Mock, patch
-
-from allura.tests import TestController
-from allura.tests.decorators import with_wiki
-
-from forgeimporters.trac.wiki import (
- TracWikiImporter,
- TracWikiImportController,
- )
-
-
-class TestWikiTicketImporter(TestCase):
- @patch('forgeimporters.trac.wiki.session')
- @patch('forgeimporters.trac.wiki.tempfile.NamedTemporaryFile')
- @patch('forgeimporters.trac.wiki.g')
- @patch('forgeimporters.trac.wiki.WikiFromTrac')
- @patch('forgeimporters.trac.wiki.load_data')
- @patch('forgeimporters.trac.wiki.argparse.Namespace')
- @patch('forgeimporters.trac.wiki.WikiExporter')
- @patch('forgeimporters.trac.wiki.ApiTicket')
- @patch('forgeimporters.trac.wiki.datetime')
- def test_import_tool(self, dt, ApiTicket, WikiExporter, Namespace,
- load_data, WikiFromTrac, g, NamedTemporaryFile, session):
- from datetime import datetime, timedelta
- now = datetime.utcnow()
- dt.utcnow.return_value = now
- export_file = NamedTemporaryFile.return_value.__enter__.return_value
- export_file.name = '/my/file'
-
- importer = TracWikiImporter()
- app = Mock(name='ForgeWikiApp')
- project = Mock(name='Project', shortname='myproject')
- project.install_app.return_value = app
- user = Mock(name='User', _id='id')
- res = importer.import_tool(project, user,
- mount_point='pages',
- mount_label='Pages',
- trac_url='http://example.com/trac/url')
- self.assertEqual(res, app)
- project.install_app.assert_called_once_with(
- 'Wiki', mount_point='pages', mount_label='Pages')
- ApiTicket.assert_called_once_with(
- user_id=user._id,
- capabilities={"import": ["Projects", "myproject"]},
- expires=now + timedelta(minutes=60))
- WikiExporter.assert_called_once_with('http://example.com/trac/url/',
- Namespace.return_value)
- WikiExporter.return_value.export.assert_called_once_with(export_file)
- load_data.assert_called_once_with('/my/file',
- WikiFromTrac.parser.return_value, Namespace.return_value)
- g.post_event.assert_called_once_with('project_updated')
-
-
-class TestTracWikiImportController(TestController, TestCase):
- def setUp(self):
- """Mount Trac import controller on the Wiki admin controller"""
- super(self.__class__, self).setUp()
- from forgewiki.wiki_main import WikiAdminController
- WikiAdminController._importer = TracWikiImportController()
-
- @with_wiki
- def test_index(self):
- r = self.app.get('/p/test/admin/wiki/_importer/')
- self.assertIsNotNone(r.html.find(attrs=dict(name="trac_url")))
- self.assertIsNotNone(r.html.find(attrs=dict(name="mount_label")))
- self.assertIsNotNone(r.html.find(attrs=dict(name="mount_point")))
-
- @with_wiki
- @patch('forgeimporters.trac.wiki.TracWikiImporter')
- def test_create(self, importer):
- from allura import model as M
- importer = importer.return_value
- importer.import_tool.return_value = Mock()
- importer.import_tool.return_value.url.return_value = '/p/test/mymount'
- params = dict(trac_url='http://example.com/trac/url',
- mount_label='mylabel',
- mount_point='mymount',
- )
- r = self.app.post('/p/test/admin/wiki/_importer/create', params,
- status=302)
- project = M.Project.query.get(shortname='test')
- self.assertEqual(r.location, 'http://localhost/p/test/mymount')
- self.assertEqual(project._id, importer.import_tool.call_args[0][0]._id)
- self.assertEqual(u'mymount', importer.import_tool.call_args[1]['mount_point'])
- self.assertEqual(u'mylabel', importer.import_tool.call_args[1]['mount_label'])
- self.assertEqual(u'http://example.com/trac/url', importer.import_tool.call_args[1]['trac_url'])
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/04cd1ed2/ForgeImporters/forgeimporters/trac/wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/wiki.py b/ForgeImporters/forgeimporters/trac/wiki.py
deleted file mode 100644
index 300b476..0000000
--- a/ForgeImporters/forgeimporters/trac/wiki.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import argparse
-from datetime import (
- datetime,
- timedelta,
- )
-import tempfile
-
-import formencode as fe
-from formencode import validators as fev
-
-from ming.orm import session
-from pylons import tmpl_context as c
-from pylons import app_globals as g
-from tg import (
- config,
- expose,
- redirect,
- validate,
- )
-from tg.decorators import (
- with_trailing_slash,
- without_trailing_slash,
- )
-
-from allura.controllers import BaseController
-from allura.lib.decorators import require_post
-from allura.model import ApiTicket
-
-from forgeimporters.base import ToolImporter
-
-from forgewiki.scripts.wiki_from_trac.extractors import WikiExporter
-from forgewiki.scripts.wiki_from_trac.loaders import load_data
-from forgewiki.scripts.wiki_from_trac.wiki_from_trac import WikiFromTrac
-from forgewiki.wiki_main import ForgeWikiApp
-
-
-class TracWikiImportSchema(fe.Schema):
- trac_url = fev.URL(not_empty=True)
- mount_point = fev.UnicodeString()
- mount_label = fev.UnicodeString()
-
-
-class TracWikiImportController(BaseController):
- @with_trailing_slash
- @expose('jinja:forgeimporters.trac:templates/wiki/index.html')
- def index(self, **kw):
- return {}
-
- @without_trailing_slash
- @expose()
- @require_post()
- @validate(TracWikiImportSchema(), error_handler=index)
- def create(self, trac_url, mount_point, mount_label, **kw):
- app = TracWikiImporter().import_tool(c.project, c.user,
- mount_point=mount_point,
- mount_label=mount_label,
- trac_url=trac_url)
- redirect(app.url())
-
-
-class TracWikiImporter(ToolImporter):
- target_app = ForgeWikiApp
- source = 'Trac'
- controller = TracWikiImportController
- tool_label = 'Trac Wiki Importer'
- tool_description = 'Import your wiki from Trac'
-
- def import_tool(self, project, user, project_name=None, mount_point=None,
- mount_label=None, trac_url=None, **kw):
- """ Import Trac wiki into a new Allura Wiki tool.
-
- """
- trac_url = trac_url.rstrip('/') + '/'
- mount_point = mount_point or 'wiki'
- app = project.install_app(
- 'Wiki',
- mount_point=mount_point,
- mount_label=mount_label or 'Wiki',
- )
- session(app.config).flush(app.config)
- api_ticket = ApiTicket(user_id=user._id,
- capabilities={"import": ["Projects", project.shortname]},
- expires=datetime.utcnow() + timedelta(minutes=60))
- session(api_ticket).flush(api_ticket)
- options = argparse.Namespace()
- options.api_key = api_ticket.api_key
- options.secret_key = api_ticket.secret_key
- options.project = project.shortname
- options.wiki = mount_point
- options.base_url = config['base_url']
- options.verbose = False
- options.converter = 'html2text'
- options.import_opts = []
- options.user_map_file = None
- with tempfile.NamedTemporaryFile() as f:
- WikiExporter(trac_url, options).export(f)
- f.flush()
- load_data(f.name, WikiFromTrac.parser(), options)
- g.post_event('project_updated')
- return app
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/04cd1ed2/ForgeImporters/setup.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/setup.py b/ForgeImporters/setup.py
index c4776ba..b19fd17 100644
--- a/ForgeImporters/setup.py
+++ b/ForgeImporters/setup.py
@@ -41,5 +41,4 @@ setup(name='ForgeImporters',
google-code-tracker = forgeimporters.google.tracker:GoogleCodeTrackerImporter
google-code-repo = forgeimporters.google.code:GoogleRepoImporter
trac-tickets = forgeimporters.trac.tickets:TracTicketImporter
- trac-wiki = forgeimporters.trac.wiki:TracWikiImporter
""",)
[09/11] git commit: [#6480] Refactor import_tool() params
Posted by br...@apache.org.
[#6480] Refactor import_tool() params
- project and user are the only guaranteed params for every
importer, and should therefore be the only positional args
- All other args to import_tool, even project_name (which refers
to the name of the project being imported), should be passed
by keyword.
Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/85c29b92
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/85c29b92
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/85c29b92
Branch: refs/heads/master
Commit: 85c29b926350a1c6cb5f5178fcabc031a12c45ff
Parents: 2379488
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Wed Aug 7 13:32:14 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000
----------------------------------------------------------------------
ForgeImporters/forgeimporters/base.py | 10 +++++-----
ForgeImporters/forgeimporters/google/code.py | 6 +++---
ForgeImporters/forgeimporters/google/tests/test_code.py | 4 +++-
ForgeImporters/forgeimporters/google/tracker.py | 3 ++-
.../forgeimporters/tests/google/test_tracker.py | 5 +++--
ForgeImporters/forgeimporters/tests/test_base.py | 5 +++--
ForgeImporters/forgeimporters/trac/tests/test_tickets.py | 5 ++---
ForgeImporters/forgeimporters/trac/tests/test_wiki.py | 5 ++---
ForgeImporters/forgeimporters/trac/tickets.py | 9 ++++-----
ForgeImporters/forgeimporters/trac/wiki.py | 9 ++++-----
10 files changed, 31 insertions(+), 30 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index 7ad720c..3aea8c0 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -50,11 +50,10 @@ class ProjectImportForm(schema.Schema):
@task
-def import_tool(importer_name, project_name, mount_point=None, mount_label=None, **kw):
+def import_tool(importer_name, project_name=None, mount_point=None, mount_label=None, **kw):
importer = ToolImporter.by_name(importer_name)
- importer.import_tool(project=c.project, user=c.user,
- mount_point=mount_point,
- mount_label=mount_label, **kw)
+ importer.import_tool(c.project, c.user, project_name=project_name,
+ mount_point=mount_point, mount_label=mount_label, **kw)
class ProjectImporter(BaseController):
@@ -209,7 +208,8 @@ class ToolImporter(object):
importers[ep.name] = importer()
return importers
- def import_tool(self, project, project_name, mount_point=None, mount_label=None):
+ def import_tool(self, project, user, project_name=None,
+ mount_point=None, mount_label=None, **kw):
"""
Override this method to perform the tool import.
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/google/code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/code.py b/ForgeImporters/forgeimporters/google/code.py
index 4457b55..c88d42a 100644
--- a/ForgeImporters/forgeimporters/google/code.py
+++ b/ForgeImporters/forgeimporters/google/code.py
@@ -86,7 +86,7 @@ class GoogleRepoImportController(BaseController):
@require_post()
@validate(GoogleRepoImportSchema(), error_handler=index)
def create(self, gc_project_name, mount_point, mount_label, **kw):
- app = GoogleRepoImporter.import_tool(c.project,
+ app = GoogleRepoImporter().import_tool(c.project, c.user,
project_name=gc_project_name,
mount_point=mount_point,
mount_label=mount_label)
@@ -100,8 +100,8 @@ class GoogleRepoImporter(ToolImporter):
tool_label = 'Google Code Source Importer'
tool_description = 'Import your SVN, Git, or Hg repo from Google Code'
- def import_tool(self, project, project_name, mount_point=None, mount_label=None):
- def import_tool(self, project, project_name, mount_point=None, mount_label=None, **kw):
+ def import_tool(self, project, user, project_name=None, mount_point=None,
+ mount_label=None, **kw):
""" Import a Google Code repo into a new SVN, Git, or Hg Allura tool.
"""
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/google/tests/test_code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tests/test_code.py b/ForgeImporters/forgeimporters/google/tests/test_code.py
index fe6943b..13ec0c3 100644
--- a/ForgeImporters/forgeimporters/google/tests/test_code.py
+++ b/ForgeImporters/forgeimporters/google/tests/test_code.py
@@ -62,7 +62,8 @@ class TestGoogleRepoImporter(TestCase):
gcpe.return_value.get_repo_type.return_value = 'git'
get_repo_url.return_value = 'http://remote/clone/url/'
p = self._make_project(gc_proj_name='myproject')
- GoogleRepoImporter().import_tool(p, 'project_name')
+ GoogleRepoImporter().import_tool(p, Mock(name='c.user'),
+ project_name='project_name')
get_repo_url.assert_called_once_with('project_name', 'git')
p.install_app.assert_called_once_with('Git',
mount_point='code',
@@ -89,6 +90,7 @@ class TestGoogleRepoImportController(TestController, TestCase):
@patch('forgeimporters.google.code.GoogleRepoImporter')
def test_create(self, gri):
from allura import model as M
+ gri = gri.return_value
gri.import_tool.return_value = Mock()
gri.import_tool.return_value.url.return_value = '/p/{}/mymount'.format(test_project_with_repo)
params = dict(gc_project_name='poop',
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index 4d37a16..95f53e4 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -42,7 +42,8 @@ class GoogleCodeTrackerImporter(ToolImporter):
type='select',
)
- def import_tool(self, project, project_name, mount_point=None, mount_label=None):
+ def import_tool(self, project, user, project_name=None, mount_point=None,
+ mount_label=None, **kw):
c.app = project.install_app('tracker', mount_point, mount_label)
c.app.globals.open_status_names = ['New', 'Accepted', 'Started']
c.app.globals.closed_status_names = ['Fixed', 'Verified', 'Invalid', 'Duplicate', 'WontFix', 'Done']
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index d54ac90..e49f279 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -33,14 +33,15 @@ class TestTrackerImporter(TestCase):
importer.process_labels = mock.Mock()
importer.process_comments = mock.Mock()
importer.postprocess_custom_fields = mock.Mock()
- project = mock.Mock()
+ project, user = mock.Mock(), mock.Mock()
app = project.install_app.return_value
extractor = gdata.return_value
issues = extractor.iter_issues.return_value = [mock.Mock(), mock.Mock()]
tickets = TM.Ticket.new.side_effect = [mock.Mock(), mock.Mock()]
comments = extractor.iter_comments.side_effect = [mock.Mock(), mock.Mock()]
- importer.import_tool(project, 'project_name', 'mount_point', 'mount_label')
+ importer.import_tool(project, user, project_name='project_name',
+ mount_point='mount_point', mount_label='mount_label')
project.install_app.assert_called_once_with('tracker', 'mount_point', 'mount_label')
gdata.assert_called_once_with('project_name')
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/tests/test_base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/test_base.py b/ForgeImporters/forgeimporters/tests/test_base.py
index 18b83ac..68be24d 100644
--- a/ForgeImporters/forgeimporters/tests/test_base.py
+++ b/ForgeImporters/forgeimporters/tests/test_base.py
@@ -28,10 +28,11 @@ from .. import base
def test_import_tool(c, by_name):
c.project = mock.Mock(name='project')
c.user = mock.Mock(name='user')
- base.import_tool('importer_name', 'project_name', 'mount_point', 'mount_label')
+ base.import_tool('importer_name', project_name='project_name',
+ mount_point='mount_point', mount_label='mount_label')
by_name.assert_called_once_with('importer_name')
by_name.return_value.import_tool.assert_called_once_with(c.project,
- 'project_name', user=c.user, mount_point='mount_point',
+ c.user, project_name='project_name', mount_point='mount_point',
mount_label='mount_label')
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
index 2cce886..5f88eef 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -45,11 +45,10 @@ class TestTracTicketImporter(TestCase):
project = Mock(name='Project', shortname='myproject')
project.install_app.return_value = app
user = Mock(name='User', _id='id')
- res = importer.import_tool(project=project,
+ res = importer.import_tool(project, user,
mount_point='bugs',
mount_label='Bugs',
- trac_url='http://example.com/trac/url',
- user=user)
+ trac_url='http://example.com/trac/url')
self.assertEqual(res, app)
project.install_app.assert_called_once_with(
'Tickets', mount_point='bugs', mount_label='Bugs')
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
index 71cc8b5..738e49b 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
@@ -50,11 +50,10 @@ class TestWikiTicketImporter(TestCase):
project = Mock(name='Project', shortname='myproject')
project.install_app.return_value = app
user = Mock(name='User', _id='id')
- res = importer.import_tool(project=project,
+ res = importer.import_tool(project, user,
mount_point='pages',
mount_label='Pages',
- trac_url='http://example.com/trac/url',
- user=user)
+ trac_url='http://example.com/trac/url')
self.assertEqual(res, app)
project.install_app.assert_called_once_with(
'Wiki', mount_point='pages', mount_label='Pages')
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/trac/tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index 78d8d17..f7d50b4 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -69,11 +69,10 @@ class TracTicketImportController(BaseController):
@require_post()
@validate(TracTicketImportSchema(), error_handler=index)
def create(self, trac_url, mount_point, mount_label, **kw):
- app = TracTicketImporter().import_tool(c.project,
+ app = TracTicketImporter().import_tool(c.project, c.user,
mount_point=mount_point,
mount_label=mount_label,
- trac_url=trac_url,
- user=c.user)
+ trac_url=trac_url)
redirect(app.url())
@@ -84,8 +83,8 @@ class TracTicketImporter(ToolImporter):
tool_label = 'Trac Ticket Importer'
tool_description = 'Import your tickets from Trac'
- def import_tool(self, project=None, mount_point=None, mount_label=None,
- trac_url=None, user=None, **kw):
+ def import_tool(self, project, user, project_name=None, mount_point=None,
+ mount_label=None, trac_url=None, **kw):
""" Import Trac tickets into a new Allura Tracker tool.
"""
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/85c29b92/ForgeImporters/forgeimporters/trac/wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/wiki.py b/ForgeImporters/forgeimporters/trac/wiki.py
index 00f0d48..300b476 100644
--- a/ForgeImporters/forgeimporters/trac/wiki.py
+++ b/ForgeImporters/forgeimporters/trac/wiki.py
@@ -68,11 +68,10 @@ class TracWikiImportController(BaseController):
@require_post()
@validate(TracWikiImportSchema(), error_handler=index)
def create(self, trac_url, mount_point, mount_label, **kw):
- app = TracWikiImporter().import_tool(c.project,
+ app = TracWikiImporter().import_tool(c.project, c.user,
mount_point=mount_point,
mount_label=mount_label,
- trac_url=trac_url,
- user=c.user)
+ trac_url=trac_url)
redirect(app.url())
@@ -83,8 +82,8 @@ class TracWikiImporter(ToolImporter):
tool_label = 'Trac Wiki Importer'
tool_description = 'Import your wiki from Trac'
- def import_tool(self, project=None, mount_point=None, mount_label=None,
- trac_url=None, user=None, **kw):
+ def import_tool(self, project, user, project_name=None, mount_point=None,
+ mount_label=None, trac_url=None, **kw):
""" Import Trac wiki into a new Allura Wiki tool.
"""
[04/11] git commit: [#6480] Add trac ticket importer plugin
Posted by br...@apache.org.
[#6480] Add trac ticket importer plugin
Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/512ee4b1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/512ee4b1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/512ee4b1
Branch: refs/heads/master
Commit: 512ee4b11f6fe09be909d539ff51f5b14824954c
Parents: e3663fb
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Fri Aug 2 17:00:29 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:23 2013 +0000
----------------------------------------------------------------------
Allura/allura/scripts/trac_export.py | 280 +++++++++++++++++++
ForgeImporters/forgeimporters/trac/__init__.py | 17 ++
.../trac/templates/tickets/index.html | 42 +++
ForgeImporters/forgeimporters/trac/tickets.py | 107 +++++++
.../forgetracker/scripts/import_tracker.py | 18 +-
scripts/trac_export.py | 257 +----------------
6 files changed, 458 insertions(+), 263 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/512ee4b1/Allura/allura/scripts/trac_export.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/trac_export.py b/Allura/allura/scripts/trac_export.py
new file mode 100644
index 0000000..aeb14ea
--- /dev/null
+++ b/Allura/allura/scripts/trac_export.py
@@ -0,0 +1,280 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+import sys
+import csv
+import urlparse
+import urllib2
+import json
+import time
+import re
+from optparse import OptionParser
+from itertools import islice
+from datetime import datetime
+
+import feedparser
+from html2text import html2text
+from BeautifulSoup import BeautifulSoup, NavigableString
+import dateutil.parser
+import pytz
+
+
+def parse_options():
+ optparser = OptionParser(usage=''' %prog <Trac URL>
+
+Export ticket data from a Trac instance''')
+ optparser.add_option('-o', '--out-file', dest='out_filename', help='Write to file (default stdout)')
+ optparser.add_option('--no-attachments', dest='do_attachments', action='store_false', default=True, help='Export attachment info')
+ optparser.add_option('--only-tickets', dest='only_tickets', action='store_true', help='Export only ticket list')
+ optparser.add_option('--start', dest='start_id', type='int', default=1, help='Start with given ticket numer (or next accessible)')
+ optparser.add_option('--limit', dest='limit', type='int', default=None, help='Limit number of tickets')
+ optparser.add_option('-v', '--verbose', dest='verbose', action='store_true', help='Verbose operation')
+ options, args = optparser.parse_args()
+ if len(args) != 1:
+ optparser.error("Wrong number of arguments.")
+ return options, args
+
+
+class TracExport(object):
+
+ PAGE_SIZE = 100
+ TICKET_URL = 'ticket/%d'
+ QUERY_MAX_ID_URL = 'query?col=id&order=id&desc=1&max=2'
+ QUERY_BY_PAGE_URL = 'query?col=id&col=time&col=changetime&order=id&max=' + str(PAGE_SIZE)+ '&page=%d'
+ ATTACHMENT_LIST_URL = 'attachment/ticket/%d/'
+ ATTACHMENT_URL = 'raw-attachment/ticket/%d/%s'
+
+ FIELD_MAP = {
+ 'reporter': 'submitter',
+ 'owner': 'assigned_to',
+ }
+
+ def __init__(self, base_url, start_id=1):
+ """start_id - start with at least that ticket number (actual returned
+ ticket may have higher id if we don't have access to exact
+ one).
+ """
+ self.base_url = base_url.rstrip('/') + '/'
+ # Contains additional info for a ticket which cannot
+ # be get with single-ticket export (create/mod times is
+ # and example).
+ self.ticket_map = {}
+ self.start_id = start_id
+ self.page = (start_id - 1) / self.PAGE_SIZE + 1
+ self.ticket_queue = self.next_ticket_ids()
+
+ def remap_fields(self, dict):
+ "Remap fields to adhere to standard taxonomy."
+ out = {}
+ for k, v in dict.iteritems():
+ out[self.FIELD_MAP.get(k, k)] = v
+
+ out['id'] = int(out['id'])
+ if 'private' in out:
+ out['private'] = bool(int(out['private']))
+ return out
+
+ def full_url(self, suburl, type=None):
+ url = urlparse.urljoin(self.base_url, suburl)
+ if type is None:
+ return url
+ glue = '&' if '?' in suburl else '?'
+ return url + glue + 'format=' + type
+
+ @staticmethod
+ def log_url(url):
+ if options.verbose:
+ print >>sys.stderr, url
+
+ @classmethod
+ def trac2z_date(cls, s):
+ d = dateutil.parser.parse(s)
+ d = d.astimezone(pytz.UTC)
+ return d.strftime("%Y-%m-%dT%H:%M:%SZ")
+
+ @staticmethod
+ def match_pattern(regexp, string):
+ m = re.match(regexp, string)
+ assert m
+ return m.group(1)
+
+ def csvopen(self, url):
+ self.log_url(url)
+ f = urllib2.urlopen(url)
+ # Trac doesn't throw 403 error, just shows normal 200 HTML page
+ # telling that access denied. So, we'll emulate 403 ourselves.
+ # TODO: currently, any non-csv result treated as 403.
+ if not f.info()['Content-Type'].startswith('text/csv'):
+ raise urllib2.HTTPError(url, 403, 'Forbidden - emulated', f.info(), f)
+ return f
+
+ def parse_ticket_body(self, id):
+ # Use CSV export to get ticket fields
+ url = self.full_url(self.TICKET_URL % id, 'csv')
+ f = self.csvopen(url)
+ reader = csv.DictReader(f)
+ ticket_fields = reader.next()
+ ticket_fields['class'] = 'ARTIFACT'
+ return self.remap_fields(ticket_fields)
+
+ def parse_ticket_comments(self, id):
+ # Use RSS export to get ticket comments
+ url = self.full_url(self.TICKET_URL % id, 'rss')
+ self.log_url(url)
+ d = feedparser.parse(url)
+ res = []
+ for comment in d['entries']:
+ c = {}
+ c['submitter'] = comment.author
+ c['date'] = comment.updated_parsed
+ c['comment'] = html2text(comment.summary)
+ c['class'] = 'COMMENT'
+ res.append(c)
+ return res
+
+ def parse_ticket_attachments(self, id):
+ SIZE_PATTERN = r'(\d+) bytes'
+ TIMESTAMP_PATTERN = r'(.+) in Timeline'
+ # Scrape HTML to get ticket attachments
+ url = self.full_url(self.ATTACHMENT_LIST_URL % id)
+ self.log_url(url)
+ f = urllib2.urlopen(url)
+ soup = BeautifulSoup(f)
+ attach = soup.find('div', id='attachments')
+ list = []
+ while attach:
+ attach = attach.findNext('dt')
+ if not attach:
+ break
+ d = {}
+ d['filename'] = attach.a['href'].rsplit('/', 1)[1]
+ d['url'] = self.full_url(self.ATTACHMENT_URL % (id, d['filename']))
+ size_s = attach.span['title']
+ d['size'] = int(self.match_pattern(SIZE_PATTERN, size_s))
+ timestamp_s = attach.find('a', {'class': 'timeline'})['title']
+ d['date'] = self.trac2z_date(self.match_pattern(TIMESTAMP_PATTERN, timestamp_s))
+ d['by'] = attach.find(text=re.compile('added by')).nextSibling.renderContents()
+ d['description'] = ''
+ # Skip whitespace
+ while attach.nextSibling and type(attach.nextSibling) is NavigableString:
+ attach = attach.nextSibling
+ # if there's a description, there will be a <dd> element, other immediately next <dt>
+ if attach.nextSibling and attach.nextSibling.name == 'dd':
+ desc_el = attach.nextSibling
+ if desc_el:
+ # TODO: Convert to Allura link syntax as needed
+ d['description'] = ''.join(desc_el.findAll(text=True)).strip()
+ list.append(d)
+ return list
+
+ def get_max_ticket_id(self):
+ url = self.full_url(self.QUERY_MAX_ID_URL, 'csv')
+ f = self.csvopen(url)
+ reader = csv.DictReader(f)
+ fields = reader.next()
+ print fields
+ return int(fields['id'])
+
+ def get_ticket(self, id, extra={}):
+ '''Get ticket with given id
+ extra: extra fields to add to ticket (parsed elsewhere)
+ '''
+ t = self.parse_ticket_body(id)
+ t['comments'] = self.parse_ticket_comments(id)
+ if options.do_attachments:
+ atts = self.parse_ticket_attachments(id)
+ if atts:
+ t['attachments'] = atts
+ t.update(extra)
+ return t
+
+ def next_ticket_ids(self):
+ 'Go thru ticket list and collect available ticket ids.'
+ # We could just do CSV export, which by default dumps entire list
+ # Alas, for many busy servers with long ticket list, it will just
+ # time out. So, let's paginate it instead.
+ res = []
+
+ url = self.full_url(self.QUERY_BY_PAGE_URL % self.page, 'csv')
+ try:
+ f = self.csvopen(url)
+ except urllib2.HTTPError, e:
+ if 'emulated' in e.msg:
+ body = e.fp.read()
+ if 'beyond the number of pages in the query' in body or 'Log in with a SourceForge account' in body:
+ raise StopIteration
+ raise
+ reader = csv.reader(f)
+ cols = reader.next()
+ for r in reader:
+ if r and r[0].isdigit():
+ id = int(r[0])
+ extra = {'date': self.trac2z_date(r[1]), 'date_updated': self.trac2z_date(r[2])}
+ res.append((id, extra))
+ self.page += 1
+
+ return res
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ while True:
+ # queue empty, try to fetch more
+ if len(self.ticket_queue) == 0:
+ self.ticket_queue = self.next_ticket_ids()
+ # there aren't any more, we're really done
+ if len(self.ticket_queue) == 0:
+ raise StopIteration
+ id, extra = self.ticket_queue.pop(0)
+ if id >= self.start_id:
+ break
+ return self.get_ticket(id, extra)
+
+
+class DateJSONEncoder(json.JSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, time.struct_time):
+ return time.strftime('%Y-%m-%dT%H:%M:%SZ', obj)
+ return json.JSONEncoder.default(self, obj)
+
+
+def main():
+ options, args = parse_options()
+ ex = TracExport(args[0], start_id=options.start_id)
+ # Implement iterator sequence limiting using islice()
+ doc = [t for t in islice(ex, options.limit)]
+
+ if not options.only_tickets:
+ doc = {
+ 'class': 'PROJECT',
+ 'trackers': {'default': {'artifacts': doc}}
+ }
+
+ out_file = sys.stdout
+ if options.out_filename:
+ out_file = open(options.out_filename, 'w')
+ out_file.write(json.dumps(doc, cls=DateJSONEncoder, indent=2, sort_keys=True))
+ # It's bad habit not to terminate lines
+ out_file.write('\n')
+
+
+if __name__ == '__main__':
+ main()
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/512ee4b1/ForgeImporters/forgeimporters/trac/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/__init__.py b/ForgeImporters/forgeimporters/trac/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/__init__.py
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/512ee4b1/ForgeImporters/forgeimporters/trac/templates/tickets/index.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/templates/tickets/index.html b/ForgeImporters/forgeimporters/trac/templates/tickets/index.html
new file mode 100644
index 0000000..eaf9aac
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/templates/tickets/index.html
@@ -0,0 +1,42 @@
+{#-
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-#}
+{% extends g.theme.master %}
+
+{% block title %}
+{{c.project.name}} / Import Trac Tickets
+{% endblock %}
+
+{% block header %}
+Import tickets from Trac
+{% endblock %}
+
+{% block content %}
+<form action="create" method="post" class="pad">
+ <label for="trac_url">URL of the Trac instance</label>
+ <input name="trac_url" />
+
+ <label for="mount_label">Label</label>
+ <input name="mount_label" value="Source" />
+
+ <label for="mount_point">Mount Point</label>
+ <input name="mount_point" value="source" />
+
+ <input type="submit" />
+</form>
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/512ee4b1/ForgeImporters/forgeimporters/trac/tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
new file mode 100644
index 0000000..cc31741
--- /dev/null
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -0,0 +1,107 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from datetime import (
+ datetime,
+ timedelta,
+ )
+import json
+
+import formencode as fe
+from formencode import validators as fev
+
+from pylons import tmpl_context as c
+from pylons import app_globals as g
+from tg import (
+ config,
+ expose,
+ redirect,
+ validate,
+ )
+from tg.decorators import (
+ with_trailing_slash,
+ without_trailing_slash,
+ )
+
+from allura.controllers import BaseController
+from allura.lib.decorators import require_post
+from allura.lib.import_api import AlluraImportApiClient
+from allura.model import ApiTicket
+from allura.scripts.trac_export import (
+ TracExport,
+ DateJSONEncoder,
+ )
+
+from forgeimporters.base import ToolImporter
+from forgetracker.tracker_main import ForgeTrackerApp
+from forgetracker.script.import_tracker import import_tracker
+
+
+class TracTicketImportSchema(fe.Schema):
+ trac_url = fev.URL(not_empty=True)
+ mount_point = fev.UnicodeString()
+ mount_label = fev.UnicodeString()
+
+
+class TracTicketImportController(BaseController):
+ @with_trailing_slash
+ @expose('jinja:forgeimporters.trac:templates/tickets/index.html')
+ def index(self, **kw):
+ return {}
+
+ @without_trailing_slash
+ @expose()
+ @require_post()
+ @validate(TracTicketImportSchema(), error_handler=index)
+ def create(self, trac_url, mount_point, mount_label, **kw):
+ app = TracTicketImporter.import_tool(c.project,
+ mount_point=mount_point,
+ mount_label=mount_label,
+ trac_url=trac_url,
+ user=c.user)
+ redirect(app.url())
+
+
+class TracTicketImporter(ToolImporter):
+ target_app = ForgeTrackerApp
+ source = 'Trac'
+ controller = TracTicketImportController
+ tool_label = 'Trac Ticket Importer'
+ tool_description = 'Import your tickets from Trac'
+
+ def import_tool(self, project=None, mount_point=None, mount_label=None,
+ trac_url=None, user=None):
+ """ Import Trac tickets into a new Allura Tracker tool.
+
+ """
+ mount_point = mount_point or 'tickets'
+ app = project.install_app(
+ 'Tickets',
+ mount_point=mount_point,
+ mount_label=mount_label or 'Tickets',
+ )
+ export = 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))
+ cli = AlluraImportApiClient(config['base_url'], api_ticket.api_key,
+ api_ticket.secret_key, False)
+ import_tracker(cli, project.shortname, mount_point, {},
+ export_string, validate=False)
+ g.post_event('project_updated')
+ return app
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/512ee4b1/ForgeTracker/forgetracker/scripts/import_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/scripts/import_tracker.py b/ForgeTracker/forgetracker/scripts/import_tracker.py
index 506e771..32b4d1c 100644
--- a/ForgeTracker/forgetracker/scripts/import_tracker.py
+++ b/ForgeTracker/forgetracker/scripts/import_tracker.py
@@ -25,7 +25,8 @@ from allura.lib.import_api import AlluraImportApiClient
log = logging.getLogger(__name__)
-def import_tracker(cli, project, tool, import_options, options, doc_txt, validate=True, verbose=False):
+def import_tracker(cli, project, tool, import_options, doc_txt,
+ validate=True, verbose=False, cont=False):
url = '/rest/p/' + project + '/' + tool
if validate:
url += '/validate_import'
@@ -33,8 +34,8 @@ def import_tracker(cli, project, tool, import_options, options, doc_txt, validat
url += '/perform_import'
existing_map = {}
- if options.cont:
- existing_tickets = cli.call('/rest/p/' + options.project + '/' + options.tracker + '/')['tickets']
+ if cont:
+ existing_tickets = cli.call('/rest/p/' + project + '/' + tool + '/')['tickets']
for t in existing_tickets:
existing_map[t['ticket_num']] = t['summary']
@@ -46,12 +47,12 @@ def import_tracker(cli, project, tool, import_options, options, doc_txt, validat
else:
tickets_in = doc
- if options.verbose:
+ if verbose:
print "Processing %d tickets" % len(tickets_in)
for cnt, ticket_in in enumerate(tickets_in):
if ticket_in['id'] in existing_map:
- if options.verbose:
+ if verbose:
print 'Ticket id %d already exists, skipping' % ticket_in['id']
continue
doc_import={}
@@ -60,7 +61,7 @@ def import_tracker(cli, project, tool, import_options, options, doc_txt, validat
doc_import['trackers']['default']['artifacts'] = [ticket_in]
res = cli.call(url, doc=json.dumps(doc_import), options=json.dumps(import_options))
assert res['status'] and not res['errors']
- if options.validate:
+ if validate:
if res['warnings']:
print "Ticket id %s warnings: %s" % (ticket_in['id'], res['warnings'])
else:
@@ -93,9 +94,10 @@ class ImportTracker(ScriptTask):
import_options['user_map'] = user_map
cli = AlluraImportApiClient(options.base_url, options.api_key, options.secret_key, options.verbose)
doc_txt = open(options.file_data).read()
- import_tracker(cli, options.project, options.tracker, import_options, options, doc_txt,
+ import_tracker(cli, options.project, options.tracker, import_options, doc_txt,
validate=options.validate,
- verbose=options.verbose)
+ verbose=options.verbose,
+ cont=options.cont)
@classmethod
def parser(cls):
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/512ee4b1/scripts/trac_export.py
----------------------------------------------------------------------
diff --git a/scripts/trac_export.py b/scripts/trac_export.py
index 002a1e8..ac90b17 100755
--- a/scripts/trac_export.py
+++ b/scripts/trac_export.py
@@ -17,259 +17,6 @@
# specific language governing permissions and limitations
# under the License.
-
-import sys
-import csv
-import urlparse
-import urllib2
-import json
-import time
-import re
-from optparse import OptionParser
-from itertools import islice
-from datetime import datetime
-
-import feedparser
-from html2text import html2text
-from BeautifulSoup import BeautifulSoup, NavigableString
-import dateutil.parser
-import pytz
-
-
-def parse_options():
- optparser = OptionParser(usage=''' %prog <Trac URL>
-
-Export ticket data from a Trac instance''')
- optparser.add_option('-o', '--out-file', dest='out_filename', help='Write to file (default stdout)')
- optparser.add_option('--no-attachments', dest='do_attachments', action='store_false', default=True, help='Export attachment info')
- optparser.add_option('--only-tickets', dest='only_tickets', action='store_true', help='Export only ticket list')
- optparser.add_option('--start', dest='start_id', type='int', default=1, help='Start with given ticket numer (or next accessible)')
- optparser.add_option('--limit', dest='limit', type='int', default=None, help='Limit number of tickets')
- optparser.add_option('-v', '--verbose', dest='verbose', action='store_true', help='Verbose operation')
- options, args = optparser.parse_args()
- if len(args) != 1:
- optparser.error("Wrong number of arguments.")
- return options, args
-
-
-class TracExport(object):
-
- PAGE_SIZE = 100
- TICKET_URL = 'ticket/%d'
- QUERY_MAX_ID_URL = 'query?col=id&order=id&desc=1&max=2'
- QUERY_BY_PAGE_URL = 'query?col=id&col=time&col=changetime&order=id&max=' + str(PAGE_SIZE)+ '&page=%d'
- ATTACHMENT_LIST_URL = 'attachment/ticket/%d/'
- ATTACHMENT_URL = 'raw-attachment/ticket/%d/%s'
-
- FIELD_MAP = {
- 'reporter': 'submitter',
- 'owner': 'assigned_to',
- }
-
- def __init__(self, base_url, start_id=1):
- """start_id - start with at least that ticket number (actual returned
- ticket may have higher id if we don't have access to exact
- one).
- """
- self.base_url = base_url.rstrip('/') + '/'
- # Contains additional info for a ticket which cannot
- # be get with single-ticket export (create/mod times is
- # and example).
- self.ticket_map = {}
- self.start_id = start_id
- self.page = (start_id - 1) / self.PAGE_SIZE + 1
- self.ticket_queue = self.next_ticket_ids()
-
- def remap_fields(self, dict):
- "Remap fields to adhere to standard taxonomy."
- out = {}
- for k, v in dict.iteritems():
- out[self.FIELD_MAP.get(k, k)] = v
-
- out['id'] = int(out['id'])
- if 'private' in out:
- out['private'] = bool(int(out['private']))
- return out
-
- def full_url(self, suburl, type=None):
- url = urlparse.urljoin(self.base_url, suburl)
- if type is None:
- return url
- glue = '&' if '?' in suburl else '?'
- return url + glue + 'format=' + type
-
- @staticmethod
- def log_url(url):
- if options.verbose:
- print >>sys.stderr, url
-
- @classmethod
- def trac2z_date(cls, s):
- d = dateutil.parser.parse(s)
- d = d.astimezone(pytz.UTC)
- return d.strftime("%Y-%m-%dT%H:%M:%SZ")
-
- @staticmethod
- def match_pattern(regexp, string):
- m = re.match(regexp, string)
- assert m
- return m.group(1)
-
- def csvopen(self, url):
- self.log_url(url)
- f = urllib2.urlopen(url)
- # Trac doesn't throw 403 error, just shows normal 200 HTML page
- # telling that access denied. So, we'll emulate 403 ourselves.
- # TODO: currently, any non-csv result treated as 403.
- if not f.info()['Content-Type'].startswith('text/csv'):
- raise urllib2.HTTPError(url, 403, 'Forbidden - emulated', f.info(), f)
- return f
-
- def parse_ticket_body(self, id):
- # Use CSV export to get ticket fields
- url = self.full_url(self.TICKET_URL % id, 'csv')
- f = self.csvopen(url)
- reader = csv.DictReader(f)
- ticket_fields = reader.next()
- ticket_fields['class'] = 'ARTIFACT'
- return self.remap_fields(ticket_fields)
-
- def parse_ticket_comments(self, id):
- # Use RSS export to get ticket comments
- url = self.full_url(self.TICKET_URL % id, 'rss')
- self.log_url(url)
- d = feedparser.parse(url)
- res = []
- for comment in d['entries']:
- c = {}
- c['submitter'] = comment.author
- c['date'] = comment.updated_parsed
- c['comment'] = html2text(comment.summary)
- c['class'] = 'COMMENT'
- res.append(c)
- return res
-
- def parse_ticket_attachments(self, id):
- SIZE_PATTERN = r'(\d+) bytes'
- TIMESTAMP_PATTERN = r'(.+) in Timeline'
- # Scrape HTML to get ticket attachments
- url = self.full_url(self.ATTACHMENT_LIST_URL % id)
- self.log_url(url)
- f = urllib2.urlopen(url)
- soup = BeautifulSoup(f)
- attach = soup.find('div', id='attachments')
- list = []
- while attach:
- attach = attach.findNext('dt')
- if not attach:
- break
- d = {}
- d['filename'] = attach.a['href'].rsplit('/', 1)[1]
- d['url'] = self.full_url(self.ATTACHMENT_URL % (id, d['filename']))
- size_s = attach.span['title']
- d['size'] = int(self.match_pattern(SIZE_PATTERN, size_s))
- timestamp_s = attach.find('a', {'class': 'timeline'})['title']
- d['date'] = self.trac2z_date(self.match_pattern(TIMESTAMP_PATTERN, timestamp_s))
- d['by'] = attach.find(text=re.compile('added by')).nextSibling.renderContents()
- d['description'] = ''
- # Skip whitespace
- while attach.nextSibling and type(attach.nextSibling) is NavigableString:
- attach = attach.nextSibling
- # if there's a description, there will be a <dd> element, other immediately next <dt>
- if attach.nextSibling and attach.nextSibling.name == 'dd':
- desc_el = attach.nextSibling
- if desc_el:
- # TODO: Convert to Allura link syntax as needed
- d['description'] = ''.join(desc_el.findAll(text=True)).strip()
- list.append(d)
- return list
-
- def get_max_ticket_id(self):
- url = self.full_url(self.QUERY_MAX_ID_URL, 'csv')
- f = self.csvopen(url)
- reader = csv.DictReader(f)
- fields = reader.next()
- print fields
- return int(fields['id'])
-
- def get_ticket(self, id, extra={}):
- '''Get ticket with given id
- extra: extra fields to add to ticket (parsed elsewhere)
- '''
- t = self.parse_ticket_body(id)
- t['comments'] = self.parse_ticket_comments(id)
- if options.do_attachments:
- atts = self.parse_ticket_attachments(id)
- if atts:
- t['attachments'] = atts
- t.update(extra)
- return t
-
- def next_ticket_ids(self):
- 'Go thru ticket list and collect available ticket ids.'
- # We could just do CSV export, which by default dumps entire list
- # Alas, for many busy servers with long ticket list, it will just
- # time out. So, let's paginate it instead.
- res = []
-
- url = self.full_url(self.QUERY_BY_PAGE_URL % self.page, 'csv')
- try:
- f = self.csvopen(url)
- except urllib2.HTTPError, e:
- if 'emulated' in e.msg:
- body = e.fp.read()
- if 'beyond the number of pages in the query' in body or 'Log in with a SourceForge account' in body:
- raise StopIteration
- raise
- reader = csv.reader(f)
- cols = reader.next()
- for r in reader:
- if r and r[0].isdigit():
- id = int(r[0])
- extra = {'date': self.trac2z_date(r[1]), 'date_updated': self.trac2z_date(r[2])}
- res.append((id, extra))
- self.page += 1
-
- return res
-
- def __iter__(self):
- return self
-
- def next(self):
- while True:
- # queue empty, try to fetch more
- if len(self.ticket_queue) == 0:
- self.ticket_queue = self.next_ticket_ids()
- # there aren't any more, we're really done
- if len(self.ticket_queue) == 0:
- raise StopIteration
- id, extra = self.ticket_queue.pop(0)
- if id >= self.start_id:
- break
- return self.get_ticket(id, extra)
-
-
-class DateJSONEncoder(json.JSONEncoder):
- def default(self, obj):
- if isinstance(obj, time.struct_time):
- return time.strftime('%Y-%m-%dT%H:%M:%SZ', obj)
- return json.JSONEncoder.default(self, obj)
-
if __name__ == '__main__':
- options, args = parse_options()
- ex = TracExport(args[0], start_id=options.start_id)
- # Implement iterator sequence limiting using islice()
- doc = [t for t in islice(ex, options.limit)]
-
- if not options.only_tickets:
- doc = {
- 'class': 'PROJECT',
- 'trackers': {'default': {'artifacts': doc}}
- }
-
- out_file = sys.stdout
- if options.out_filename:
- out_file = open(options.out_filename, 'w')
- out_file.write(json.dumps(doc, cls=DateJSONEncoder, indent=2, sort_keys=True))
- # It's bad habit not to terminate lines
- out_file.write('\n')
+ from allura.scripts.trac_export import main
+ main()
[05/11] git commit: [#6480] Add tests for Trac wiki importer
Posted by br...@apache.org.
[#6480] Add tests for Trac wiki importer
Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/2379488a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/2379488a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/2379488a
Branch: refs/heads/master
Commit: 2379488ac978b40c027f28720c92a97d9d54a585
Parents: 8a365f7
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Aug 6 21:59:58 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Aug 7 16:47:24 2013 +0000
----------------------------------------------------------------------
.../forgeimporters/trac/tests/test_tickets.py | 1 +
.../forgeimporters/trac/tests/test_wiki.py | 88 ++++++++++++++++++++
ForgeImporters/forgeimporters/trac/tickets.py | 2 +-
ForgeImporters/forgeimporters/trac/wiki.py | 2 +-
4 files changed, 91 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2379488a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
index 8b102a9..2cce886 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -84,6 +84,7 @@ class TestTracTicketImportController(TestController, TestCase):
@patch('forgeimporters.trac.tickets.TracTicketImporter')
def test_create(self, importer):
from allura import model as M
+ importer = importer.return_value
importer.import_tool.return_value = Mock()
importer.import_tool.return_value.url.return_value = '/p/test/mymount'
params = dict(trac_url='http://example.com/trac/url',
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2379488a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
index 77505f1..71cc8b5 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_wiki.py
@@ -15,3 +15,91 @@
# specific language governing permissions and limitations
# under the License.
+from unittest import TestCase
+from mock import Mock, patch
+
+from allura.tests import TestController
+from allura.tests.decorators import with_wiki
+
+from forgeimporters.trac.wiki import (
+ TracWikiImporter,
+ TracWikiImportController,
+ )
+
+
+class TestWikiTicketImporter(TestCase):
+ @patch('forgeimporters.trac.wiki.session')
+ @patch('forgeimporters.trac.wiki.tempfile.NamedTemporaryFile')
+ @patch('forgeimporters.trac.wiki.g')
+ @patch('forgeimporters.trac.wiki.WikiFromTrac')
+ @patch('forgeimporters.trac.wiki.load_data')
+ @patch('forgeimporters.trac.wiki.argparse.Namespace')
+ @patch('forgeimporters.trac.wiki.WikiExporter')
+ @patch('forgeimporters.trac.wiki.ApiTicket')
+ @patch('forgeimporters.trac.wiki.datetime')
+ def test_import_tool(self, dt, ApiTicket, WikiExporter, Namespace,
+ load_data, WikiFromTrac, g, NamedTemporaryFile, session):
+ from datetime import datetime, timedelta
+ now = datetime.utcnow()
+ dt.utcnow.return_value = now
+ export_file = NamedTemporaryFile.return_value.__enter__.return_value
+ export_file.name = '/my/file'
+
+ importer = TracWikiImporter()
+ app = Mock(name='ForgeWikiApp')
+ project = Mock(name='Project', shortname='myproject')
+ project.install_app.return_value = app
+ user = Mock(name='User', _id='id')
+ res = importer.import_tool(project=project,
+ mount_point='pages',
+ mount_label='Pages',
+ trac_url='http://example.com/trac/url',
+ user=user)
+ self.assertEqual(res, app)
+ project.install_app.assert_called_once_with(
+ 'Wiki', mount_point='pages', mount_label='Pages')
+ ApiTicket.assert_called_once_with(
+ user_id=user._id,
+ capabilities={"import": ["Projects", "myproject"]},
+ expires=now + timedelta(minutes=60))
+ WikiExporter.assert_called_once_with('http://example.com/trac/url/',
+ Namespace.return_value)
+ WikiExporter.return_value.export.assert_called_once_with(export_file)
+ load_data.assert_called_once_with('/my/file',
+ WikiFromTrac.parser.return_value, Namespace.return_value)
+ g.post_event.assert_called_once_with('project_updated')
+
+
+class TestTracWikiImportController(TestController, TestCase):
+ def setUp(self):
+ """Mount Trac import controller on the Wiki admin controller"""
+ super(self.__class__, self).setUp()
+ from forgewiki.wiki_main import WikiAdminController
+ WikiAdminController._importer = TracWikiImportController()
+
+ @with_wiki
+ def test_index(self):
+ r = self.app.get('/p/test/admin/wiki/_importer/')
+ self.assertIsNotNone(r.html.find(attrs=dict(name="trac_url")))
+ self.assertIsNotNone(r.html.find(attrs=dict(name="mount_label")))
+ self.assertIsNotNone(r.html.find(attrs=dict(name="mount_point")))
+
+ @with_wiki
+ @patch('forgeimporters.trac.wiki.TracWikiImporter')
+ def test_create(self, importer):
+ from allura import model as M
+ importer = importer.return_value
+ importer.import_tool.return_value = Mock()
+ importer.import_tool.return_value.url.return_value = '/p/test/mymount'
+ params = dict(trac_url='http://example.com/trac/url',
+ mount_label='mylabel',
+ mount_point='mymount',
+ )
+ r = self.app.post('/p/test/admin/wiki/_importer/create', params,
+ status=302)
+ project = M.Project.query.get(shortname='test')
+ self.assertEqual(r.location, 'http://localhost/p/test/mymount')
+ self.assertEqual(project._id, importer.import_tool.call_args[0][0]._id)
+ self.assertEqual(u'mymount', importer.import_tool.call_args[1]['mount_point'])
+ self.assertEqual(u'mylabel', importer.import_tool.call_args[1]['mount_label'])
+ self.assertEqual(u'http://example.com/trac/url', importer.import_tool.call_args[1]['trac_url'])
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2379488a/ForgeImporters/forgeimporters/trac/tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index 0f13649..78d8d17 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -69,7 +69,7 @@ class TracTicketImportController(BaseController):
@require_post()
@validate(TracTicketImportSchema(), error_handler=index)
def create(self, trac_url, mount_point, mount_label, **kw):
- app = TracTicketImporter.import_tool(c.project,
+ app = TracTicketImporter().import_tool(c.project,
mount_point=mount_point,
mount_label=mount_label,
trac_url=trac_url,
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2379488a/ForgeImporters/forgeimporters/trac/wiki.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/wiki.py b/ForgeImporters/forgeimporters/trac/wiki.py
index e7ead86..00f0d48 100644
--- a/ForgeImporters/forgeimporters/trac/wiki.py
+++ b/ForgeImporters/forgeimporters/trac/wiki.py
@@ -68,7 +68,7 @@ class TracWikiImportController(BaseController):
@require_post()
@validate(TracWikiImportSchema(), error_handler=index)
def create(self, trac_url, mount_point, mount_label, **kw):
- app = TracWikiImporter.import_tool(c.project,
+ app = TracWikiImporter().import_tool(c.project,
mount_point=mount_point,
mount_label=mount_label,
trac_url=trac_url,