Merge branch 'db/6640' of https://git-wip-us.apache.org/repos/asf/incubator-allura into db/6640
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index f600363..8e24cb2 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -947,3 +947,35 @@
:rtype: ``None``
"""
pass
+
+class ImportIdConverter(object):
+ '''
+ An interface to convert to and from import_id values for indexing,
+ searching, or displaying.
+
+ To provide a new converter, expose an entry point in setup.py:
+
+ [allura.import_id_converter]
+ mysource = foo.bar:SourceIdConverter
+
+ Then in your .ini file, set import_id_converter=mysource
+ '''
+
+ @classmethod
+ def get(cls):
+ converter = config.get('import_id_converter')
+ if converter:
+ return g.entry_points['allura.import_id_converter'][converter]()
+ return cls()
+
+ def simplify(self, import_id):
+ if hasattr(import_id, 'get'):
+ return import_id.get('source_id')
+ return None
+
+ def expand(self, source_id, app_instance):
+ import_id = {
+ 'source_id': source_id,
+ }
+ import_id.update(app_instance.config.options.get('import_id', {}))
+ return import_id
diff --git a/Allura/allura/model/artifact.py b/Allura/allura/model/artifact.py
index 82baad6..3910cad 100644
--- a/Allura/allura/model/artifact.py
+++ b/Allura/allura/model/artifact.py
@@ -83,8 +83,11 @@
references = FieldProperty(S.Deprecated)
backreferences = FieldProperty(S.Deprecated)
app_config = RelationProperty('AppConfig')
- # Not null if artifact originated from external import, then API ticket id
- import_id = FieldProperty(str, if_missing=None)
+ # Not null if artifact originated from external import. The import ID is
+ # implementation specific, but should probably be an object indicating
+ # the source, original ID, and any other info needed to identify where
+ # the artifact came from. But if you only have one source, a str might do.
+ import_id = FieldProperty(None, if_missing=None)
deleted=FieldProperty(bool, if_missing=False)
def __json__(self):
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index e0e9d6f..ffb1650 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -37,6 +37,7 @@
from allura.lib import exceptions
from allura.lib import validators as v
from allura.app import SitemapEntry
+from allura import model as M
from paste.deploy.converters import aslist
@@ -245,6 +246,7 @@
self.after_project_create(c.project, **kw)
for importer_name in kw['tools']:
import_tool.post(importer_name, **kw)
+ M.AuditLog.log('import project from %s' % self.source)
flash('Welcome to the %s Project System! '
'Your project data will be imported and should show up here shortly.' % config['site_name'])
diff --git a/ForgeImporters/forgeimporters/google/code.py b/ForgeImporters/forgeimporters/google/code.py
index 4cf752d..134ecb6 100644
--- a/ForgeImporters/forgeimporters/google/code.py
+++ b/ForgeImporters/forgeimporters/google/code.py
@@ -36,6 +36,7 @@
from allura.controllers import BaseController
from allura.lib import validators as v
from allura.lib.decorators import require_post, task
+from allura import model as M
from forgeimporters.base import (
ToolImporter,
@@ -168,6 +169,15 @@
mount_point=mount_point or 'code',
mount_label=mount_label or 'Code',
init_from_url=repo_url,
- )
+ import_id={
+ 'source': self.source,
+ 'project_name': project_name,
+ }
+ )
+ M.AuditLog.log(
+ 'import tool %s from %s on %s' % (
+ app.config.options.mount_point,
+ project_name, self.source,
+ ), project=project, user=user, url=app.url)
g.post_event('project_updated')
return app
diff --git a/ForgeImporters/forgeimporters/google/tests/test_code.py b/ForgeImporters/forgeimporters/google/tests/test_code.py
index c6874ad..806a004 100644
--- a/ForgeImporters/forgeimporters/google/tests/test_code.py
+++ b/ForgeImporters/forgeimporters/google/tests/test_code.py
@@ -57,20 +57,31 @@
return project
@patch('forgeimporters.google.code.g')
+ @patch('forgeimporters.google.code.M')
@patch('forgeimporters.google.code.GoogleCodeProjectExtractor')
@patch('forgeimporters.google.code.get_repo_url')
- def test_import_tool_happy_path(self, get_repo_url, gcpe, g):
+ def test_import_tool_happy_path(self, get_repo_url, gcpe, M, g):
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, Mock(name='c.user'),
- project_name='project_name')
+ u = Mock(name='c.user')
+ app = p.install_app.return_value
+ app.config.options.mount_point = 'code'
+ app.url = 'foo'
+ GoogleRepoImporter().import_tool(p, u, 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',
mount_label='Code',
init_from_url='http://remote/clone/url/',
- )
+ import_id={
+ 'source': 'Google Code',
+ 'project_name': 'project_name',
+ },
+ )
+ M.AuditLog.log.assert_called_once_with(
+ 'import tool code from project_name on Google Code',
+ project=p, user=u, url='foo')
g.post_event.assert_called_once_with('project_updated')
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index 40dd939..9c259dd 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -24,9 +24,6 @@
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,
@@ -40,11 +37,13 @@
from allura.controllers import BaseController
from allura.lib import helpers as h
+from allura.lib.plugin import ImportIdConverter
from allura.lib.decorators import require_post, task
+from allura import model as M
from forgetracker.tracker_main import ForgeTrackerApp
from forgetracker import model as TM
-from . import GoogleCodeProjectExtractor
+from forgeimporters.google import GoogleCodeProjectExtractor
from forgeimporters.base import (
ToolImporter,
ToolImportForm,
@@ -112,10 +111,15 @@
def import_tool(self, project, user, project_name, mount_point=None,
mount_label=None, **kw):
+ import_id_converter = ImportIdConverter.get()
app = project.install_app('tickets', mount_point, mount_label,
EnableVoting=True,
open_status_names='New Accepted Started',
closed_status_names='Fixed Verified Invalid Duplicate WontFix Done',
+ import_id={
+ 'source': self.source,
+ 'project_name': project_name,
+ },
)
ThreadLocalORMSession.flush_all()
try:
@@ -126,7 +130,8 @@
ticket = TM.Ticket(
app_config_id=app.config._id,
custom_fields=dict(),
- ticket_num=ticket_num)
+ ticket_num=ticket_num,
+ import_id=import_id_converter.expand(ticket_num, app))
self.process_fields(ticket, issue)
self.process_labels(ticket, issue)
self.process_comments(ticket, issue)
@@ -135,6 +140,15 @@
app.globals.custom_fields = self.postprocess_custom_fields()
app.globals.last_ticket_num = self.max_ticket_num
ThreadLocalORMSession.flush_all()
+ M.AuditLog.log(
+ 'import tool %s from %s on %s' % (
+ app.config.options.mount_point,
+ project_name, self.source,
+ ),
+ project=project,
+ user=user,
+ url=app.url,
+ )
g.post_event('project_updated')
app.globals.invalidate_bin_counts()
return app
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
index 24081d9..9f48257 100644
--- a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
@@ -50,6 +50,7 @@
self.assertIsNone(self.project.app_instance('test-issue'))
with mock.patch.object(base.h, 'urlopen') as urlopen,\
mock.patch.object(google.tracker, 'GoogleCodeProjectExtractor') as GPE,\
+ mock.patch.object(google.tracker.M, 'AuditLog') as AL,\
mock.patch('forgetracker.tasks.update_bin_counts') as ubc:
urlopen.side_effect = lambda req, **kw: mock.Mock(read=req.get_full_url, info=lambda:{'content-type': 'text/plain'})
GPE.iter_issues.return_value = [(issue_id, issue)]
@@ -140,6 +141,19 @@
self.assertEqual(ticket.votes_up, 1)
self.assertEqual(ticket.votes, 1)
+ def test_import_id(self):
+ ticket = self._make_ticket(self.test_issue, issue_id=6)
+ self.assertEqual(ticket.app.config.options.import_id, {
+ 'source': 'Google Code',
+ 'project_name': 'test-issue-project',
+ })
+ self.assertEqual(ticket.ticket_num, 6)
+ self.assertEqual(ticket.import_id, {
+ 'source': 'Google Code',
+ 'project_name': 'test-issue-project',
+ 'source_id': 6,
+ })
+
@skipif(module_not_available('html2text'))
def test_html2text_escaping(self):
ticket = self._make_ticket(self.test_issue)
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index 5bbf836..d795bab 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -42,6 +42,13 @@
importer.postprocess_custom_fields = mock.Mock()
project, user = mock.Mock(), mock.Mock()
app = project.install_app.return_value
+ app.config.options.mount_point = 'mount_point'
+ app.config.options.import_id = {
+ 'source': 'Google Code',
+ 'project_name': 'project_name',
+ }
+ app.config.options.get = lambda *a: getattr(app.config.options, *a)
+ app.url = 'foo'
issues = gpe.iter_issues.return_value = [(50, mock.Mock()), (100, mock.Mock())]
tickets = TM.Ticket.side_effect = [mock.Mock(), mock.Mock()]
@@ -52,6 +59,10 @@
EnableVoting=True,
open_status_names='New Accepted Started',
closed_status_names='Fixed Verified Invalid Duplicate WontFix Done',
+ import_id={
+ 'source': 'Google Code',
+ 'project_name': 'project_name',
+ }
)
gpe.iter_issues.assert_called_once_with('project_name')
self.assertEqual(importer.process_fields.call_args_list, [
@@ -79,6 +90,9 @@
mock.call(tickets[1]),
])
self.assertEqual(app.globals.last_ticket_num, 100)
+ M.AuditLog.log.assert_called_once_with(
+ 'import tool mount_point from project_name on Google Code',
+ project=project, user=user, url='foo')
g.post_event.assert_called_once_with('project_updated')
app.globals.invalidate_bin_counts.assert_called_once_with()
diff --git a/ForgeImporters/forgeimporters/tests/test_base.py b/ForgeImporters/forgeimporters/tests/test_base.py
index 7de0625..4aefc35 100644
--- a/ForgeImporters/forgeimporters/tests/test_base.py
+++ b/ForgeImporters/forgeimporters/tests/test_base.py
@@ -99,6 +99,30 @@
self.assertEqual(pi.tool_importers, {'ep1': eps[0].lv, 'ep3': eps[2].lv})
iep.assert_called_once_with('allura.importers')
+ @mock.patch.object(base, 'redirect')
+ @mock.patch.object(base, 'flash')
+ @mock.patch.object(base, 'import_tool')
+ @mock.patch.object(base, 'M')
+ @mock.patch.object(base, 'c')
+ def test_process(self, c, M, import_tool, flash, redirect):
+ pi = base.ProjectImporter(mock.Mock())
+ pi.source = 'Source'
+ pi.after_project_create = mock.Mock()
+ pi.neighborhood.register_project.return_value.script_name = 'script_name/'
+ kw = {
+ 'project_name': 'project_name',
+ 'project_shortname': 'shortname',
+ 'tools': ['tool'],
+ }
+ with mock.patch.dict(base.config, {'site_name': 'foo'}):
+ pi.process(**kw)
+ pi.neighborhood.register_project.assert_called_once_with('shortname', project_name='project_name')
+ pi.after_project_create.assert_called_once_with(c.project, **kw)
+ import_tool.post.assert_called_once_with('tool', **kw)
+ M.AuditLog.log.assert_called_once_with('import project from Source')
+ self.assertEqual(flash.call_count, 1)
+ redirect.assert_called_once_with('script_name/admin/overview')
+
TA1 = mock.Mock(tool_label='foo', tool_description='foo_desc')
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
index 74e1049..92e7854 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -19,6 +19,8 @@
from unittest import TestCase
from mock import Mock, patch
+from tg import config
+
from allura.tests import TestController
from allura.tests.decorators import with_tracker
@@ -31,18 +33,22 @@
class TestTracTicketImporter(TestCase):
@patch('forgeimporters.trac.tickets.session')
@patch('forgeimporters.trac.tickets.g')
+ @patch('forgeimporters.trac.tickets.AuditLog')
@patch('forgeimporters.trac.tickets.import_tracker')
@patch('forgeimporters.trac.tickets.AlluraImportApiClient')
@patch('forgeimporters.trac.tickets.datetime')
@patch('forgeimporters.trac.tickets.ApiTicket')
@patch('forgeimporters.trac.tickets.TracExport')
- def test_import_tool(self, TracExport, ApiTicket, dt, ApiClient, import_tracker, g, session):
+ def test_import_tool(self, TracExport, ApiTicket, dt, ApiClient, import_tracker, AuditLog, g, session):
from datetime import datetime, timedelta
now = datetime.utcnow()
dt.utcnow.return_value = now
user_map = {"orig_user":"new_user"}
importer = TracTicketImporter()
app = Mock(name='ForgeTrackerApp')
+ app.config.options.mount_point = 'bugs'
+ app.config.options.get = lambda *a: getattr(app.config.options, *a)
+ app.url = 'foo'
project = Mock(name='Project', shortname='myproject')
project.install_app.return_value = app
user = Mock(name='User', _id='id')
@@ -55,7 +61,11 @@
)
self.assertEqual(res, app)
project.install_app.assert_called_once_with(
- 'Tickets', mount_point='bugs', mount_label='Bugs')
+ 'Tickets', mount_point='bugs', mount_label='Bugs',
+ import_id={
+ 'source': 'Trac',
+ 'trac_url': 'http://example.com/trac/url/',
+ })
TracExport.return_value = []
TracExport.assert_called_once_with('http://example.com/trac/url/')
ApiTicket.assert_called_once_with(
@@ -67,6 +77,9 @@
api_client, 'myproject', 'bugs',
{"user_map": user_map}, '[]',
validate=False)
+ AuditLog.log.assert_called_once_with(
+ 'import tool bugs from http://example.com/trac/url/',
+ project=project, user=user, url='foo')
g.post_event.assert_called_once_with('project_updated')
@patch('forgeimporters.trac.tickets.session')
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index 3063d71..9e4f493 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -43,7 +43,7 @@
from allura.lib.import_api import AlluraImportApiClient
from allura.lib import validators as v
from allura.lib import helpers as h
-from allura.model import ApiTicket
+from allura.model import ApiTicket, AuditLog
from allura.scripts.trac_export import (
TracExport,
DateJSONEncoder,
@@ -117,7 +117,11 @@
'Tickets',
mount_point=mount_point,
mount_label=mount_label or 'Tickets',
- )
+ import_id={
+ 'source': self.source,
+ 'trac_url': trac_url,
+ },
+ )
session(app.config).flush(app.config)
session(app.globals).flush(app.globals)
try:
@@ -132,6 +136,13 @@
import_tracker(cli, project.shortname, mount_point,
{'user_map': json.loads(user_map) if user_map else {}},
export_string, validate=False)
+ AuditLog.log(
+ 'import tool %s from %s' % (
+ app.config.options.mount_point,
+ trac_url,
+ ),
+ project=project, user=user, url=app.url,
+ )
g.post_event('project_updated')
return app
except Exception as e:
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index 9995257..505a991 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -47,9 +47,9 @@
from allura.lib.search import search_artifact, SearchError
from allura.lib import utils
from allura.lib import helpers as h
+from allura.lib.plugin import ImportIdConverter
from allura.tasks import mail_tasks
-from forgetracker.plugins import ImportIdConverter
log = logging.getLogger(__name__)
diff --git a/ForgeTracker/forgetracker/plugins.py b/ForgeTracker/forgetracker/plugins.py
index a32dcfc..3afbca2 100644
--- a/ForgeTracker/forgetracker/plugins.py
+++ b/ForgeTracker/forgetracker/plugins.py
@@ -22,28 +22,3 @@
log = logging.getLogger(__name__)
-
-class ImportIdConverter(object):
- '''
- An interface to provide authentication services for Allura.
-
- To provide a new converter, expose an entry point in setup.py:
-
- [allura.tickets.import_id_converter]
- mylegacy = foo.bar:LegacyConverter
-
- Then in your .ini file, set tickets.import_id_converter=mylegacy
- '''
-
- @classmethod
- def get(cls):
- converter = config.get('tickets.import_id_converter')
- if converter:
- return g.entry_points['allura.tickets.import_id_converter'][converter]()
- return cls()
-
- def simplify(self, import_id):
- return import_id
-
- def expand(self, url_part, app_instance):
- return url_part
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 0ba4f0b..86ef522 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -1573,7 +1573,7 @@
def test_imported_tickets_redirect(self):
self.new_ticket(summary='Imported ticket')
ticket = tm.Ticket.query.get(ticket_num=1)
- ticket.import_id = '42000'
+ ticket.import_id = {'source_id': '42000'}
ThreadLocalORMSession.flush_all()
# expect permanent redirect to /p/test/bugs/1/
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index 8aa29f6..61f7a99 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -58,6 +58,7 @@
from allura.lib.widgets import form_fields as ffw
from allura.lib.widgets.subscriptions import SubscribeForm
from allura.lib.zarkov_helpers import zero_fill_zarkov_result
+from allura.lib.plugin import ImportIdConverter
from allura.controllers import AppDiscussionController, AppDiscussionRestController
from allura.controllers import attachments as ac
from allura.controllers import BaseController
@@ -75,7 +76,6 @@
from forgetracker.widgets.ticket_search import TicketSearchResults, MassEdit, MassEditForm, MassMoveForm, SearchHelp
from forgetracker.widgets.admin_custom_fields import TrackerFieldAdmin, TrackerFieldDisplay
from forgetracker.import_support import ImportSupport
-from forgetracker.plugins import ImportIdConverter
log = logging.getLogger(__name__)
@@ -1197,10 +1197,13 @@
ticket_num=self.ticket_num)
if self.ticket is None:
self.ticket = TM.Ticket.query.get(
- app_config_id=c.app.config._id,
- import_id=str(ImportIdConverter.get().expand(ticket_num, c.app)))
+ app_config_id = c.app.config._id,
+ import_id = ImportIdConverter.get().expand(ticket_num, c.app),
+ )
if self.ticket is not None:
utils.permanent_redirect(self.ticket.url())
+ else:
+ raise exc.HTTPNotFound('Ticket #%s does not exist.' % ticket_num)
self.attachment = AttachmentsController(self.ticket)
# self.comments = CommentController(self.ticket)
diff --git a/requirements-common.txt b/requirements-common.txt
index 5e261a0..5e4d6cc 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -21,7 +21,7 @@
iso8601==0.1.4
Jinja2==2.6
Markdown==2.2.0
-Ming==0.3.7
+Ming==0.3.9
oauth2==1.5.170
# tg2 dep PasteDeploy must specified before TurboGears2, to avoid a version/allow-hosts problem
Paste==1.7.5.1
diff --git a/requirements-sf.txt b/requirements-sf.txt
index 286c95a..ecad4a2 100644
--- a/requirements-sf.txt
+++ b/requirements-sf.txt
@@ -6,7 +6,7 @@
coverage==3.5a1-20110413
ForgeHg==0.1.16
ForgePastebin==0.2.7
-GoogleCodeWikiImporter==0.3.1
+GoogleCodeWikiImporter==0.3.3
mechanize==0.2.4
mercurial==1.4.3
MySQL-python==1.2.3c1
@@ -20,7 +20,7 @@
pyzmq==2.1.7
html2text==3.200.3dev-20121112
PyMollom==0.1
-TracWikiImporter==0.2.1
+TracWikiImporter==0.2.2
# use version built from https://github.com/johnsca/GitPython/commits/tv/6000
# for unmerged fixes for [#5411], [#6000], and [#6078]