You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by jo...@apache.org on 2013/09/05 20:20:30 UTC
[05/50] git commit: [#6526] Clean up individual tool importers
[#6526] Clean up individual tool importers
- Make forms look (slightly) better
- Add validation
- Kick off import as task
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/8378ac03
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/8378ac03
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/8378ac03
Branch: refs/heads/cj/6596
Commit: 8378ac032f3d92ea77d2756336641f45a9c71931
Parents: ec9c7cf
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Wed Aug 21 20:53:33 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Tue Aug 27 17:24:58 2013 +0000
----------------------------------------------------------------------
Allura/allura/lib/validators.py | 29 +++++++
Allura/allura/model/project.py | 23 ++----
ForgeImporters/forgeimporters/base.py | 10 ++-
.../forgeimporters/google/__init__.py | 2 +-
ForgeImporters/forgeimporters/google/code.py | 62 +++++++++++++--
.../google/templates/code/index.html | 25 ++----
.../google/templates/tracker/index.html | 27 +++++++
.../forgeimporters/google/tests/test_code.py | 17 ++---
ForgeImporters/forgeimporters/google/tracker.py | 62 ++++++++++++++-
.../forgeimporters/templates/importer_base.html | 80 ++++++++++++++++++++
.../forgeimporters/tests/google/test_tracker.py | 34 ++++++++-
.../trac/templates/tickets/index.html | 41 ++++------
.../forgeimporters/trac/tests/test_tickets.py | 20 ++---
ForgeImporters/forgeimporters/trac/tickets.py | 40 +++++++---
14 files changed, 364 insertions(+), 108 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/Allura/allura/lib/validators.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/validators.py b/Allura/allura/lib/validators.py
index 9b44d53..fa82d32 100644
--- a/Allura/allura/lib/validators.py
+++ b/Allura/allura/lib/validators.py
@@ -72,6 +72,35 @@ class MaxBytesValidator(fev.FancyValidator):
def from_python(self, value, state):
return h.really_unicode(value or '')
+class MountPointValidator(fev.UnicodeString):
+ def __init__(self, app_class,
+ reserved_mount_points=('feed', 'index', 'icon', '_nav.json')):
+ super(self.__class__, self).__init__()
+ self.app_class = app_class
+ self.reserved_mount_points = reserved_mount_points
+
+ def _to_python(self, value, state):
+ from pylons import tmpl_context as c
+ project = state.project if hasattr(state, 'project') else c.project
+ mount_point, App = value, self.app_class
+ if not mount_point:
+ base_mount_point = mount_point = App.default_mount_point
+ for x in range(10):
+ if project.app_instance(mount_point) is None: break
+ mount_point = base_mount_point + '-%d' % x
+ if not App.relaxed_mount_points:
+ mount_point = mount_point.lower()
+ if not App.validate_mount_point(mount_point):
+ raise fe.Invalid('Mount point "%s" is invalid' % mount_point,
+ value, state)
+ if mount_point in self.reserved_mount_points:
+ raise fe.Invalid('Mount point "%s" is reserved' % mount_point,
+ value, state)
+ if project.app_instance(mount_point) is not None:
+ raise fe.Invalid('Mount point "%s" is already in use' % mount_point,
+ value, state)
+ return mount_point
+
class TaskValidator(fev.FancyValidator):
def _to_python(self, value, state):
try:
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index ebccb3d..983ef66 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -26,6 +26,7 @@ from tg import config
from pylons import tmpl_context as c, app_globals as g
from pylons import request
from paste.deploy.converters import asbool
+import formencode as fe
from ming import schema as S
from ming.utils import LazyProperty
@@ -38,6 +39,7 @@ from allura.lib import helpers as h
from allura.lib import plugin
from allura.lib import exceptions
from allura.lib import security
+from allura.lib import validators as v
from allura.lib.security import has_access
from .session import main_orm_session
@@ -598,23 +600,10 @@ class Project(MappedClass, ActivityNode, ActivityObject):
def install_app(self, ep_name, mount_point=None, mount_label=None, ordinal=None, **override_options):
App = g.entry_points['tool'][ep_name]
- if not mount_point:
- base_mount_point = mount_point = App.default_mount_point
- for x in range(10):
- if self.app_instance(mount_point) is None: break
- mount_point = base_mount_point + '-%d' % x
- if not App.relaxed_mount_points:
- mount_point = mount_point.lower()
- if not App.validate_mount_point(mount_point):
- raise exceptions.ToolError, 'Mount point "%s" is invalid' % mount_point
- # HACK: reserved url components
- if mount_point in ('feed', 'index', 'icon', '_nav.json'):
- raise exceptions.ToolError, (
- 'Mount point "%s" is reserved' % mount_point)
- if self.app_instance(mount_point) is not None:
- raise exceptions.ToolError, (
- 'Mount point "%s" is already in use' % mount_point)
- assert self.app_instance(mount_point) is None
+ try:
+ mount_point = v.MountPointValidator(App).to_python(mount_point)
+ except fe.Invalid as e:
+ raise exceptions.ToolError(str(e))
if ordinal is None:
ordinal = int(self.ordered_mounts(include_hidden=True)[-1]['ordinal']) + 1
options = App.default_options()
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/ForgeImporters/forgeimporters/base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index e80235e..bdff43b 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -35,6 +35,7 @@ from allura.lib.security import require_access
from allura.lib.plugin import ProjectRegistrationProvider, AdminExtension
from allura.lib import helpers as h
from allura.lib import exceptions
+from allura.lib import validators as v
from allura.app import SitemapEntry
from paste.deploy.converters import aslist
@@ -58,6 +59,13 @@ class ProjectImportForm(schema.Schema):
project_name = fev.UnicodeString(not_empty=True, max=40)
+class ToolImportForm(schema.Schema):
+ def __init__(self, tool_class):
+ super(ToolImportForm, self).__init__()
+ self.add_field('mount_point', v.MountPointValidator(tool_class))
+ mount_label = fev.UnicodeString()
+
+
@task(notifications_disabled=True)
def import_tool(importer_name, project_name=None, mount_point=None, mount_label=None, **kw):
importer = ToolImporter.by_name(importer_name)
@@ -388,4 +396,4 @@ class ImportAdminExtension(AdminExtension):
def update_project_sidebar_menu(self, sidebar_links):
base_url = c.project.url() + 'admin/ext/'
link = SitemapEntry('Import', base_url+'import/')
- sidebar_links.append(link)
\ No newline at end of file
+ sidebar_links.append(link)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 849f924..ad5f8e5 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -1,4 +1,4 @@
-# Licensed to the Apache Software Foundation (ASF) under one
+# LNicensed 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
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/ForgeImporters/forgeimporters/google/code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/code.py b/ForgeImporters/forgeimporters/google/code.py
index 11dd7a6..60a0158 100644
--- a/ForgeImporters/forgeimporters/google/code.py
+++ b/ForgeImporters/forgeimporters/google/code.py
@@ -15,6 +15,8 @@
# specific language governing permissions and limitations
# under the License.
+import urllib2
+
import formencode as fe
from formencode import validators as fev
@@ -22,6 +24,7 @@ from pylons import tmpl_context as c
from pylons import app_globals as g
from tg import (
expose,
+ flash,
redirect,
validate,
)
@@ -31,25 +34,30 @@ from tg.decorators import (
)
from allura.controllers import BaseController
-from allura.lib.decorators import require_post
+from allura.lib import validators as v
+from allura.lib.decorators import require_post, task
from forgeimporters.base import ToolImporter
from forgeimporters.google import GoogleCodeProjectExtractor
+REPO_APPS = {}
TARGET_APPS = []
try:
from forgehg.hg_main import ForgeHgApp
TARGET_APPS.append(ForgeHgApp)
+ REPO_APPS['hg'] = ForgeHgApp
except ImportError:
pass
try:
from forgegit.git_main import ForgeGitApp
TARGET_APPS.append(ForgeGitApp)
+ REPO_APPS['git'] = ForgeGitApp
except ImportError:
pass
try:
from forgesvn.svn_main import ForgeSVNApp
TARGET_APPS.append(ForgeSVNApp)
+ REPO_APPS['svn'] = ForgeSVNApp
except ImportError:
pass
@@ -69,28 +77,70 @@ def get_repo_url(project_name, type_):
return REPO_URLS[type_].format(project_name)
-class GoogleRepoImportSchema(fe.Schema):
+def get_repo_class(type_):
+ return REPO_APPS[type_]
+
+
+@task(notifications_disabled=True)
+def import_tool(**kw):
+ GoogleRepoImporter().import_tool(c.project, c.user, **kw)
+
+
+class GoogleRepoImportForm(fe.schema.Schema):
gc_project_name = fev.UnicodeString(not_empty=True)
mount_point = fev.UnicodeString()
mount_label = fev.UnicodeString()
+ def _to_python(self, value, state):
+ value = super(self.__class__, self)._to_python(value, state)
+
+ gc_project_name = value['gc_project_name']
+ mount_point = value['mount_point']
+ try:
+ repo_type = GoogleCodeProjectExtractor(gc_project_name).get_repo_type()
+ except urllib2.HTTPError as e:
+ if e.code == 404:
+ msg = 'No such project'
+ else:
+ msg = str(e)
+ msg = 'gc_project_name:' + msg
+ raise fe.Invalid(msg, value, state)
+ except Exception:
+ raise
+ tool_class = REPO_APPS[repo_type]
+ try:
+ v.MountPointValidator(tool_class).to_python(mount_point)
+ except fe.Invalid as e:
+ raise fe.Invalid('mount_point:' + str(e), value, state)
+ return value
+
class GoogleRepoImportController(BaseController):
+ def __init__(self):
+ self.importer = GoogleRepoImporter()
+
+ @property
+ def target_app(self):
+ return self.importer.target_app[0]
+
@with_trailing_slash
@expose('jinja:forgeimporters.google:templates/code/index.html')
def index(self, **kw):
- return {}
+ return dict(importer=self.importer,
+ target_app=self.target_app)
@without_trailing_slash
@expose()
@require_post()
- @validate(GoogleRepoImportSchema(), error_handler=index)
+ @validate(GoogleRepoImportForm(), error_handler=index)
def create(self, gc_project_name, mount_point, mount_label, **kw):
- app = GoogleRepoImporter().import_tool(c.project, c.user,
+ import_tool.post(
project_name=gc_project_name,
mount_point=mount_point,
mount_label=mount_label)
- redirect(app.url())
+ flash('Repo import has begun. Your new repo will be available '
+ 'when the import is complete.')
+ redirect(c.project.url() + 'admin/')
class GoogleRepoImporter(ToolImporter):
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/ForgeImporters/forgeimporters/google/templates/code/index.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/templates/code/index.html b/ForgeImporters/forgeimporters/google/templates/code/index.html
index 7bace3a..e2cef0c 100644
--- a/ForgeImporters/forgeimporters/google/templates/code/index.html
+++ b/ForgeImporters/forgeimporters/google/templates/code/index.html
@@ -16,27 +16,16 @@
specific language governing permissions and limitations
under the License.
-#}
-{% extends g.theme.master %}
+{% extends 'forgeimporters:templates/importer_base.html' %}
{% block title %}
-{{c.project.name}} / Import Google Code Repo
+{{c.project.name}} / Import your repo from Google Code
{% endblock %}
-{% block header %}
-Import a Repo from a Google Code project
-{% endblock %}
-
-{% block content %}
-<form action="create" method="post" class="pad">
+{% block importer_fields %}
+<div>
<label for="gc_project_name">Google Code project name (as it appears in a URL)</label>
- <input name="gc_project_name" />
-
- <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>
+ <input name="gc_project_name" value="{{ c.form_values['gc_project_name'] }}" />
+ {{ error('gc_project_name') }}
+</div>
{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/ForgeImporters/forgeimporters/google/templates/tracker/index.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/templates/tracker/index.html b/ForgeImporters/forgeimporters/google/templates/tracker/index.html
new file mode 100644
index 0000000..08d0084
--- /dev/null
+++ b/ForgeImporters/forgeimporters/google/templates/tracker/index.html
@@ -0,0 +1,27 @@
+{#-
+ 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/importer_base.html' %}
+
+{% block importer_fields %}
+<div>
+ <label for="gc_project_name">Google Code project name (as it appears in a URL)</label>
+ <input name="gc_project_name" value="{{ c.form_values['gc_project_name'] }}" />
+ {{ error('gc_project_name') }}
+</div>
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/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 950246a..c6874ad 100644
--- a/ForgeImporters/forgeimporters/google/tests/test_code.py
+++ b/ForgeImporters/forgeimporters/google/tests/test_code.py
@@ -89,12 +89,8 @@ class TestGoogleRepoImportController(TestController, TestCase):
self.assertIsNotNone(r.html.find(attrs=dict(name="mount_point")))
@with_svn
- @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)
+ @patch('forgeimporters.google.code.import_tool')
+ def test_create(self, import_tool):
params = dict(gc_project_name='poop',
mount_label='mylabel',
mount_point='mymount',
@@ -102,8 +98,7 @@ class TestGoogleRepoImportController(TestController, TestCase):
r = self.app.post('/p/{}/admin/src/_importer/create'.format(test_project_with_repo),
params,
status=302)
- project = M.Project.query.get(shortname=test_project_with_repo)
- self.assertEqual(r.location, 'http://localhost/p/{}/mymount'.format(test_project_with_repo))
- self.assertEqual(project._id, gri.import_tool.call_args[0][0]._id)
- self.assertEqual(u'mymount', gri.import_tool.call_args[1]['mount_point'])
- self.assertEqual(u'mylabel', gri.import_tool.call_args[1]['mount_label'])
+ self.assertEqual(r.location, 'http://localhost/p/{}/admin/'.format(test_project_with_repo))
+ self.assertEqual(u'mymount', import_tool.post.call_args[1]['mount_point'])
+ self.assertEqual(u'mylabel', import_tool.post.call_args[1]['mount_label'])
+ self.assertEqual(u'poop', import_tool.post.call_args[1]['project_name'])
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index 8b1747f..b7fccbe 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -18,23 +18,81 @@
from collections import defaultdict
from datetime import datetime
+from formencode import validators as fev
+
from pylons import tmpl_context as c
from pylons import app_globals as g
from ming.orm import session, ThreadLocalORMSession
from allura import model as M
+#import gdata
+gdata = None
+from tg import (
+ expose,
+ flash,
+ redirect,
+ validate,
+ )
+from tg.decorators import (
+ with_trailing_slash,
+ without_trailing_slash,
+ )
+
+from allura.controllers import BaseController
from allura.lib import helpers as h
+from allura.lib.decorators import require_post, task
from forgetracker.tracker_main import ForgeTrackerApp
from forgetracker import model as TM
-from ..base import ToolImporter
from . import GoogleCodeProjectExtractor
+from forgeimporters.base import (
+ ToolImporter,
+ ToolImportForm,
+ )
+
+
+@task(notifications_disabled=True)
+def import_tool(**kw):
+ GoogleCodeTrackerImporter().import_tool(c.project, c.user, **kw)
+
+
+class GoogleCodeTrackerImportForm(ToolImportForm):
+ gc_project_name = fev.UnicodeString(not_empty=True)
+
+
+class GoogleCodeTrackerImportController(BaseController):
+ def __init__(self):
+ self.importer = GoogleCodeTrackerImporter()
+
+ @property
+ def target_app(self):
+ return self.importer.target_app
+
+ @with_trailing_slash
+ @expose('jinja:forgeimporters.google:templates/tracker/index.html')
+ def index(self, **kw):
+ return dict(importer=self.importer,
+ target_app=self.target_app)
+
+ @without_trailing_slash
+ @expose()
+ @require_post()
+ @validate(GoogleCodeTrackerImportForm(ForgeTrackerApp), error_handler=index)
+ def create(self, gc_project_name, mount_point, mount_label, **kw):
+ import_tool.post(
+ project_name=gc_project_name,
+ mount_point=mount_point,
+ mount_label=mount_label,
+ )
+ flash('Ticket import has begun. Your new tracker will be available '
+ 'when the import is complete.')
+ redirect(c.project.url() + 'admin/')
class GoogleCodeTrackerImporter(ToolImporter):
source = 'Google Code'
target_app = ForgeTrackerApp
- controller = None
+ controller = GoogleCodeTrackerImportController
tool_label = 'Issues'
field_types = defaultdict(lambda: 'string',
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/ForgeImporters/forgeimporters/templates/importer_base.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/templates/importer_base.html b/ForgeImporters/forgeimporters/templates/importer_base.html
new file mode 100644
index 0000000..4bdfe0f
--- /dev/null
+++ b/ForgeImporters/forgeimporters/templates/importer_base.html
@@ -0,0 +1,80 @@
+{#-
+ 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}} / {{ importer.tool_description }}
+{% endblock %}
+
+{% block header %}
+{{ importer.tool_description }}
+{% endblock %}
+
+{% block extra_css %}
+ <style type="text/css">
+ form {
+ padding: 0 20px 20px 20px;
+ }
+ form label {
+ display: inline-block;
+ width: 30%;
+ vertical-align: top;
+ }
+ form > div {
+ margin-bottom: 10px;
+ }
+ form > div input {
+ width: 30%;
+ vertical-align: top;
+ }
+ form .error {
+ display: inline-block;
+ color: #f00;
+ background: none;
+ border: none;
+ margin: 0;
+ width: 30%;
+ }
+ </style>
+{% endblock %}
+
+{%- macro error(field_name) %}
+ {% if c.form_errors[field_name] %}
+ <div class="error">{{c.form_errors[field_name]}}</div>
+ {% endif %}
+{%- endmacro %}
+
+{% block content %}
+<form action="create" method="post" enctype="multipart/form-data">
+ {% block importer_fields %}
+ {% endblock %}
+ <div>
+ <label for="mount_label">Label</label>
+ <input name="mount_label" value="{{ c.form_values['mount_label'] or target_app.default_mount_label }}" />
+ {{ error('mount_label') }}
+ </div>
+ <div>
+ <label for="mount_point">Mount Point</label>
+ <input name="mount_point" value="{{ c.form_values['mount_point'] or target_app.default_mount_point }}" />
+ {{ error('mount_point') }}
+ </div>
+
+ <input type="submit" />
+</form>
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/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 4a7c28f..3c7f0aa 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -16,9 +16,12 @@
# under the License.
from datetime import datetime
-from operator import itemgetter
from unittest import TestCase
import mock
+from mock import patch
+
+from allura.tests import TestController
+from allura.tests.decorators import with_tracker
from ...google import tracker
@@ -239,3 +242,32 @@ class TestTrackerImporter(TestCase):
'options': 'foo bar',
},
])
+
+
+class TestGoogleCodeTrackerImportController(TestController, TestCase):
+ def setUp(self):
+ """Mount Google Code importer on the Tracker admin controller"""
+ super(TestGoogleCodeTrackerImportController, self).setUp()
+ from forgetracker.tracker_main import TrackerAdminController
+ TrackerAdminController._importer = tracker.GoogleCodeTrackerImportController()
+
+ @with_tracker
+ def test_index(self):
+ r = self.app.get('/p/test/admin/bugs/_importer/')
+ self.assertIsNotNone(r.html.find(attrs=dict(name="gc_project_name")))
+ self.assertIsNotNone(r.html.find(attrs=dict(name="mount_label")))
+ self.assertIsNotNone(r.html.find(attrs=dict(name="mount_point")))
+
+ @with_tracker
+ @patch('forgeimporters.google.tracker.import_tool')
+ def test_create(self, import_tool):
+ params = dict(gc_project_name='test',
+ mount_label='mylabel',
+ mount_point='mymount',
+ )
+ r = self.app.post('/p/test/admin/bugs/_importer/create', params,
+ status=302)
+ self.assertEqual(r.location, 'http://localhost/p/test/admin/')
+ self.assertEqual(u'mymount', import_tool.post.call_args[1]['mount_point'])
+ self.assertEqual(u'mylabel', import_tool.post.call_args[1]['mount_label'])
+ self.assertEqual(u'test', import_tool.post.call_args[1]['project_name'])
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/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
index 7c278b0..42b8a9f 100644
--- a/ForgeImporters/forgeimporters/trac/templates/tickets/index.html
+++ b/ForgeImporters/forgeimporters/trac/templates/tickets/index.html
@@ -16,30 +16,19 @@
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" enctype="multipart/form-data" class="pad">
- <label for="trac_url">URL of the Trac instance</label>
- <input name="trac_url" />
-
- <label for="user_map">JSON file mapping Trac usernames to Allura usernames (optional)</label>
- <input name="user_map" type="file" />
-
- <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>
+{% extends 'forgeimporters:templates/importer_base.html' %}
+
+{% block importer_fields %}
+ <div>
+ <label for="trac_url">URL of the Trac instance</label>
+ <input name="trac_url" value="{{ c.form_values['trac_url'] }}" />
+ {{ error('trac_url') }}
+ </div>
+ <div>
+ <label for="user_map">User Map (optional)
+ <br><small>JSON file mapping Trac usernames to Allura usernames</small>
+ </label>
+ <input name="user_map" type="file" />
+ {{ error('user_map') }}
+ </div>
{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/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 2a539df..953c6d7 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -84,12 +84,8 @@ class TestTracTicketImportController(TestController, TestCase):
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 = importer.return_value
- importer.import_tool.return_value = Mock()
- importer.import_tool.return_value.url.return_value = '/p/test/mymount'
+ @patch('forgeimporters.trac.tickets.import_tool')
+ def test_create(self, import_tool):
params = dict(trac_url='http://example.com/trac/url',
mount_label='mylabel',
mount_point='mymount',
@@ -97,10 +93,8 @@ class TestTracTicketImportController(TestController, TestCase):
r = self.app.post('/p/test/admin/bugs/_importer/create', params,
upload_files=[('user_map', 'myfile', '{"orig_user": "new_user"}')],
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('{"orig_user": "new_user"}', importer.import_tool.call_args[1]['user_map'])
- self.assertEqual(u'http://example.com/trac/url', importer.import_tool.call_args[1]['trac_url'])
+ self.assertEqual(r.location, 'http://localhost/p/test/admin/')
+ self.assertEqual(u'mymount', import_tool.post.call_args[1]['mount_point'])
+ self.assertEqual(u'mylabel', import_tool.post.call_args[1]['mount_label'])
+ self.assertEqual('{"orig_user": "new_user"}', import_tool.post.call_args[1]['user_map'])
+ self.assertEqual(u'http://example.com/trac/url', import_tool.post.call_args[1]['trac_url'])
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8378ac03/ForgeImporters/forgeimporters/trac/tickets.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index dfbb2a2..3e0e486 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -21,7 +21,6 @@ from datetime import (
)
import json
-import formencode as fe
from formencode import validators as fev
from ming.orm import session
@@ -30,6 +29,7 @@ from pylons import app_globals as g
from tg import (
config,
expose,
+ flash,
redirect,
validate,
)
@@ -39,44 +39,60 @@ from tg.decorators import (
)
from allura.controllers import BaseController
-from allura.lib.decorators import require_post
+from allura.lib.decorators import require_post, task
from allura.lib.import_api import AlluraImportApiClient
-from allura.lib.validators import UserMapJsonFile
+from allura.lib import validators as v
from allura.model import ApiTicket
from allura.scripts.trac_export import (
TracExport,
DateJSONEncoder,
)
-from forgeimporters.base import ToolImporter
+from forgeimporters.base import (
+ ToolImporter,
+ ToolImportForm,
+ )
from forgetracker.tracker_main import ForgeTrackerApp
from forgetracker.scripts.import_tracker import import_tracker
-class TracTicketImportSchema(fe.Schema):
+@task(notifications_disabled=True)
+def import_tool(**kw):
+ TracTicketImporter().import_tool(c.project, c.user, **kw)
+
+
+class TracTicketImportForm(ToolImportForm):
trac_url = fev.URL(not_empty=True)
- user_map = UserMapJsonFile(as_string=True)
- mount_point = fev.UnicodeString()
- mount_label = fev.UnicodeString()
+ user_map = v.UserMapJsonFile(as_string=True)
class TracTicketImportController(BaseController):
+ def __init__(self):
+ self.importer = TracTicketImporter()
+
+ @property
+ def target_app(self):
+ return self.importer.target_app
+
@with_trailing_slash
@expose('jinja:forgeimporters.trac:templates/tickets/index.html')
def index(self, **kw):
- return {}
+ return dict(importer=self.importer,
+ target_app=self.target_app)
@without_trailing_slash
@expose()
@require_post()
- @validate(TracTicketImportSchema(), error_handler=index)
+ @validate(TracTicketImportForm(ForgeTrackerApp), error_handler=index)
def create(self, trac_url, mount_point, mount_label, user_map=None, **kw):
- app = TracTicketImporter().import_tool(c.project, c.user,
+ import_tool.post(
mount_point=mount_point,
mount_label=mount_label,
trac_url=trac_url,
user_map=user_map)
- redirect(app.url())
+ flash('Ticket import has begun. Your new tracker will be available '
+ 'when the import is complete.')
+ redirect(c.project.url() + 'admin/')
class TracTicketImporter(ToolImporter):