You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by tv...@apache.org on 2013/07/19 16:55:20 UTC

[3/3] git commit: [#6456] Added importer framework base

[#6456] Added importer framework base

Signed-off-by: Cory Johns <cj...@slashdotmedia.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/da05d219
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/da05d219
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/da05d219

Branch: refs/heads/master
Commit: da05d219e6723cccb9360010424d838f8b0ef1d7
Parents: 5167a9d
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue Jul 16 19:23:08 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Fri Jul 19 14:54:50 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/project.py            |   4 +-
 Allura/allura/nf/allura/css/site_style.css      |  66 +++++++++++
 ForgeImporters/forgeimporters/__init__.py       |   0
 ForgeImporters/forgeimporters/base.py           | 111 ++++++++++++++++++
 .../forgeimporters/templates/project_base.html  |  54 +++++++++
 ForgeImporters/forgeimporters/tests/__init__.py |   0
 .../forgeimporters/tests/test_base.py           | 115 +++++++++++++++++++
 ForgeImporters/setup.py                         |  39 +++++++
 ForgeImporters/test.ini                         |  70 +++++++++++
 9 files changed, 458 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/da05d219/Allura/allura/controllers/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index 3cf4112..24bba79 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.py
@@ -23,7 +23,7 @@ from itertools import chain, islice
 
 from bson import ObjectId
 from tg import expose, flash, redirect, validate, request, response, config
-from tg.decorators import with_trailing_slash, without_trailing_slash
+from tg.decorators import with_trailing_slash, without_trailing_slash, override_template
 from pylons import tmpl_context as c, app_globals as g
 from paste.deploy.converters import asbool
 from webob import exc
@@ -47,6 +47,7 @@ from allura.lib.widgets import forms as ff
 from allura.lib.widgets import form_fields as ffw
 from allura.lib.widgets import project_list as plw
 from allura.lib import plugin, exceptions
+from forgeimporters.base import ProjectImporterDispatcher
 from .auth import AuthController
 from .search import SearchController, ProjectBrowseController
 from .static import NewForgeController
@@ -73,6 +74,7 @@ class NeighborhoodController(object):
         self.browse = NeighborhoodProjectBrowseController(neighborhood=self.neighborhood)
         self._admin = NeighborhoodAdminController(self.neighborhood)
         self._moderate = NeighborhoodModerateController(self.neighborhood)
+        self.import_project = ProjectImporterDispatcher()
 
     def _check_security(self):
         require_access(self.neighborhood, 'read')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/da05d219/Allura/allura/nf/allura/css/site_style.css
----------------------------------------------------------------------
diff --git a/Allura/allura/nf/allura/css/site_style.css b/Allura/allura/nf/allura/css/site_style.css
index 1322fee..0b4eb92 100644
--- a/Allura/allura/nf/allura/css/site_style.css
+++ b/Allura/allura/nf/allura/css/site_style.css
@@ -3041,3 +3041,69 @@ ul.dropdown ul li a:hover {
     text-align: left;
     margin-right: 0;
 }
+#project-import-form {
+    margin-left: 5px;
+}
+#project-import-form fieldset {
+    margin-top: 10px;
+}
+#project-import-form label {
+    display: block;
+}
+#project-import-form #project-fields label {
+    text-align: right;
+    font-size: 18px;
+    font-weight: 300;
+    line-height: 28px;
+}
+#project-import-form .error {
+    background: transparent;
+    color: red;
+    border: 0;
+}
+#project-import-form .tool {
+    float: left;
+    position: relative;
+    padding: 10px 20px 10px 70px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+    -o-border-radius: 4px;
+    -ms-border-radius: 4px;
+    -khtml-border-radius: 4px;
+    border-radius: 4px;
+    border: 1px solid #aaa;
+    width: 360px;
+    margin: 5px;
+    background-color: whiteSmoke;
+    height: 74px;
+}
+#project-import-form .tool img {
+    left: 10px;
+    position: absolute;
+    top: 26px;
+}
+#project-import-form .tool label {
+    line-height: 24px;
+    font-size: 18px;
+    font-weight: 600;
+}
+#project-import-form input[type="submit"] {
+    margin-top: 30px;
+    margin-left: 5px;
+    float: none;
+    display: inline-block;
+    position: relative;
+    top: -1em;
+    font-size: 18px;
+    font-weight: 600;
+    text-shadow: none;
+    color: white !important;
+    padding: 1em 2em 1em 2em;
+    text-decoration: none;
+    -webkit-border-radius: 5px;
+    -moz-border-radius: 5px;
+    background: rgb(0,0,0);
+    background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, to(rgb(0,0,0)), from(rgb(90,90,90)));
+    background-image: -moz-linear-gradient(100% 100% 90deg, rgb(0,0,0), rgb(90,90,90) 100%);
+    border: 1px solid black;
+}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/da05d219/ForgeImporters/forgeimporters/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/__init__.py b/ForgeImporters/forgeimporters/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/da05d219/ForgeImporters/forgeimporters/base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
new file mode 100644
index 0000000..39a350c
--- /dev/null
+++ b/ForgeImporters/forgeimporters/base.py
@@ -0,0 +1,111 @@
+#       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 pkg_resources import iter_entry_points
+
+from tg import expose
+from formencode import validators as fev
+
+from ming.utils import LazyProperty
+from allura.controllers import BaseController
+
+
+class ProjectImporterDispatcher(BaseController):
+    @expose()
+    def _lookup(self, source, *rest):
+        for ep in iter_entry_points('allura.project_importers', source):
+            return ep.load()(), rest
+
+
+class ProjectImporter(BaseController):
+    source = None
+
+    @LazyProperty
+    def tool_importers(self):
+        tools = {}
+        for ep in iter_entry_points('allura.importers'):
+            epv = ep.load()
+            if epv.source == self.source:
+                tools[ep.name] = epv()
+        return tools
+
+    def index(self, **kw):
+        """
+        Override and expose this view to present the project import form.
+
+        The template used by this view should extend the base template in:
+
+            jinja:forgeimporters:templates/project_base.html
+
+        This will list the available tool importers.  Other project fields
+        (e.g., project_name) should go in the project_fields block.
+        """
+        raise NotImplemented
+
+    def process(self, tools=None, **kw):
+        """
+        Override and expose this to handle a project import.
+
+        This should at a minimum create the stub project with the appropriate
+        tools installed and redirect to the new project, presumably with a
+        message indicating that some data will not be available immediately.
+        """
+        raise NotImplemented
+
+
+class ToolImporter(object):
+    target_app = None
+    source = None
+    controller = None
+
+    def import_tool(self, project, mount_point):
+        """
+        Override this method to perform the tool import.
+        """
+        raise NotImplementedError
+
+    @property
+    def tool_label(self):
+        return getattr(self.target_app, 'tool_label', None)
+
+    @property
+    def tool_description(self):
+        return getattr(self.target_app, 'tool_description', None)
+
+
+class ToolsValidator(fev.Set):
+    def __init__(self, source, *a, **kw):
+        super(ToolsValidator, self).__init__(*a, **kw)
+        self.source = source
+
+    def to_python(self, value, state=None):
+        value = super(ToolsValidator, self).to_python(value, state)
+        valid = []
+        invalid = []
+        for i, v in enumerate(value):
+            try:
+                ep = iter_entry_points('allura.importers', v).next().load()
+            except StopIteration:
+                ep = None
+            if getattr(ep, 'source', None) != self.source:
+                invalid.append(v)
+            else:
+                valid.append(ep())
+        if invalid:
+            pl = 's' if len(invalid) > 1 else ''
+            raise fev.Invalid('Invalid tool%s selected: %s' % (pl, ', '.join(invalid)), value, state)
+        return valid

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/da05d219/ForgeImporters/forgeimporters/templates/project_base.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/templates/project_base.html b/ForgeImporters/forgeimporters/templates/project_base.html
new file mode 100644
index 0000000..02405f4
--- /dev/null
+++ b/ForgeImporters/forgeimporters/templates/project_base.html
@@ -0,0 +1,54 @@
+{#-
+       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.
+-#}
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+
+{% block title %}{{importer.source}} Project Importer{% endblock %}
+{% block header %}{{importer.source}} Project Importer{% endblock %}
+
+{% block content %}
+
+<form id="project-import-form" method="POST" action="process">
+    <input type="hidden" name="source" value="{{request.params.source}}" />
+
+    <fieldset id="project-fields">
+        {% block project_fields %}{% endblock %}
+    </fieldset>
+
+    <fieldset id="tool-fields">
+        {% if c.form_errors['tools'] %}
+        <div class="error">{{c.form_errors['tools']}}</div>
+        {% endif %}
+        {% for name, tool_importer in importer.tool_importers.iteritems() %}
+        <div class="tool">
+            <img src="{{ g.theme.app_icon_url(tool_importer.target_app, 48) }}" alt="{{ tool_importer.tool_label }} icon">
+            <label>
+                <input name="tools" value="{{name}}" type="checkbox"{% if not c.form_errors or name in c.form_values['tools'] %} checked="checked"{% endif %}/>
+                {{tool_importer.tool_label}}
+            </label>
+            {{tool_importer.tool_description}}
+        </div>
+        {% endfor %}
+    </fieldset>
+
+    <input type="submit" value="Import"/>
+</form>
+
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/da05d219/ForgeImporters/forgeimporters/tests/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/__init__.py b/ForgeImporters/forgeimporters/tests/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/da05d219/ForgeImporters/forgeimporters/tests/test_base.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/test_base.py b/ForgeImporters/forgeimporters/tests/test_base.py
new file mode 100644
index 0000000..394723d
--- /dev/null
+++ b/ForgeImporters/forgeimporters/tests/test_base.py
@@ -0,0 +1,115 @@
+#       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 formencode import Invalid
+import mock
+
+from .. import base
+
+
+def ep(name, source=None):
+    mep = mock.Mock(name='mock_ep')
+    mep.name = name
+    mep.load.return_value.source = source
+    mep.lv = mep.load.return_value.return_value
+    return mep
+
+
+class TestProjectImporterDispatcher(TestCase):
+    @mock.patch('forgeimporters.base.iter_entry_points')
+    def test_lookup(self, iep):
+        eps = iep.return_value = [ep('ep1', 'first'), ep('ep2', 'second')]
+        result = base.ProjectImporterDispatcher()._lookup('source', 'rest1', 'rest2')
+        self.assertEqual(result, (eps[0].lv, ('rest1', 'rest2')))
+        iep.assert_called_once_with('allura.project_importers', 'source')
+
+
+class TestProjectImporter(TestCase):
+    @mock.patch('forgeimporters.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.source = 'foo'
+        self.assertEqual(pi.tool_importers, {'ep1': eps[0].lv, 'ep3': eps[2].lv})
+        iep.assert_called_once_with('allura.importers')
+
+
+class TestImporter(TestCase):
+    class TI1(base.ToolImporter):
+        target_app = mock.Mock(tool_label='foo', tool_description='foo_desc')
+
+    class TI2(base.ToolImporter):
+        target_app = mock.Mock(tool_label='foo', tool_description='foo_desc')
+        tool_label = 'bar'
+        tool_description = 'bar_desc'
+
+    def test_tool_label(self):
+        self.assertEqual(self.TI1().tool_label, 'foo')
+        self.assertEqual(self.TI2().tool_label, 'bar')
+
+    def test_tool_description(self):
+        self.assertEqual(self.TI1().tool_description, 'foo_desc')
+        self.assertEqual(self.TI2().tool_description, 'bar_desc')
+
+
+class TestToolsValidator(TestCase):
+    def setUp(self):
+        self.tv = base.ToolsValidator('good-source')
+
+    @mock.patch('forgeimporters.base.iter_entry_points')
+    def test_empty(self, iep):
+        self.assertEqual(self.tv.to_python(''), [])
+        self.assertEqual(iep.call_count, 0)
+
+    @mock.patch('forgeimporters.base.iter_entry_points')
+    def test_no_ep(self, iep):
+        eps = iep.return_value.next.side_effect = StopIteration
+        with self.assertRaises(Invalid) as cm:
+            self.tv.to_python('my-value')
+        self.assertEqual(cm.exception.msg, 'Invalid tool selected: my-value')
+        iep.assert_called_once_with('allura.importers', 'my-value')
+
+    @mock.patch('forgeimporters.base.iter_entry_points')
+    def test_bad_source(self, iep):
+        eps = iep.return_value.next.side_effect = [ep('ep1', 'bad-source'), ep('ep2', 'good-source')]
+        with self.assertRaises(Invalid) as cm:
+            self.tv.to_python('my-value')
+        self.assertEqual(cm.exception.msg, 'Invalid tool selected: my-value')
+        iep.assert_called_once_with('allura.importers', 'my-value')
+
+    @mock.patch('forgeimporters.base.iter_entry_points')
+    def test_multiple(self, iep):
+        eps = iep.return_value.next.side_effect = [ep('ep1', 'bad-source'), ep('ep2', 'good-source'), ep('ep3', 'bad-source')]
+        with self.assertRaises(Invalid) as cm:
+            self.tv.to_python(['value1', 'value2', 'value3'])
+        self.assertEqual(cm.exception.msg, 'Invalid tools selected: value1, value3')
+        self.assertEqual(iep.call_args_list, [
+                mock.call('allura.importers', 'value1'),
+                mock.call('allura.importers', 'value2'),
+                mock.call('allura.importers', 'value3'),
+            ])
+
+    @mock.patch('forgeimporters.base.iter_entry_points')
+    def test_valid(self, iep):
+        eps = iep.return_value.next.side_effect = [ep('ep1', 'good-source'), ep('ep2', 'good-source'), ep('ep3', 'bad-source')]
+        self.assertEqual(self.tv.to_python(['value1', 'value2']), [eps[0].lv, eps[1].lv])
+        self.assertEqual(iep.call_args_list, [
+                mock.call('allura.importers', 'value1'),
+                mock.call('allura.importers', 'value2'),
+            ])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/da05d219/ForgeImporters/setup.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/setup.py b/ForgeImporters/setup.py
new file mode 100644
index 0000000..5e45638
--- /dev/null
+++ b/ForgeImporters/setup.py
@@ -0,0 +1,39 @@
+#       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 setuptools import setup, find_packages
+
+
+setup(name='ForgeImporters',
+      description="",
+      long_description="",
+      classifiers=[],
+      keywords='',
+      author='',
+      author_email='',
+      url='',
+      license='',
+      packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=['Allura', ],
+      entry_points="""
+      # -*- Entry points: -*-
+      [allura.project_importers]
+
+      [allura.importers]
+      """,)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/da05d219/ForgeImporters/test.ini
----------------------------------------------------------------------
diff --git a/ForgeImporters/test.ini b/ForgeImporters/test.ini
new file mode 100644
index 0000000..019c262
--- /dev/null
+++ b/ForgeImporters/test.ini
@@ -0,0 +1,70 @@
+#       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.
+#
+# allura - TurboGears 2 testing environment configuration
+#
+# The %(here)s variable will be replaced with the parent directory of this file
+#
+[DEFAULT]
+debug = true
+
+[server:main]
+use = egg:Paste#http
+host = 0.0.0.0
+port = 5000
+
+[app:main]
+use = config:../Allura/test.ini
+
+[app:main_without_authn]
+use = config:../Allura/test.ini#main_without_authn
+
+[app:main_with_amqp]
+use = config:../Allura/test.ini#main_with_amqp
+
+[loggers]
+keys = root, allura, tool
+
+[handlers]
+keys = test
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = test
+
+[logger_allura]
+level = DEBUG
+handlers =
+qualname = allura
+
+[logger_tool]
+level = DEBUG
+handlers =
+qualname = forgeshorturl
+
+[handler_test]
+class = FileHandler
+args = ('test.log',)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S