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/05/22 15:36:27 UTC

[01/14] git commit: [#6232] update ForgeHg to use zip binary

Updated Branches:
  refs/heads/cj/5913 105071e79 -> 983b827de (forced update)


[#6232] update ForgeHg to use zip binary


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/05d660db
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/05d660db
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/05d660db

Branch: refs/heads/cj/5913
Commit: 05d660db9299bf5fa58a3479feeb1e27dd31cbc2
Parents: fbf88bb
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Mon May 20 14:36:56 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon May 20 14:36:56 2013 +0000

----------------------------------------------------------------------
 requirements-sf.txt |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/05d660db/requirements-sf.txt
----------------------------------------------------------------------
diff --git a/requirements-sf.txt b/requirements-sf.txt
index 939dca3..2714786 100644
--- a/requirements-sf.txt
+++ b/requirements-sf.txt
@@ -4,7 +4,7 @@ akismet==0.2.0
 amqplib==0.6.1
 kombu==1.0.4
 coverage==3.5a1-20110413
-ForgeHg==0.1.9
+ForgeHg==0.1.10
 ForgePastebin==0.2.6
 mechanize==0.2.4
 MySQL-python==1.2.3c1


[07/14] git commit: [#6219] Add public-only options for tracker email monitoring

Posted by jo...@apache.org.
[#6219] Add public-only options for tracker email monitoring

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/d786c637
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/d786c637
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/d786c637

Branch: refs/heads/cj/5913
Commit: d786c63781f86b2c02e4d9baaeab448061a33771
Parents: e34ca49
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue May 21 19:43:40 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Tue May 21 19:43:40 2013 +0000

----------------------------------------------------------------------
 ForgeTracker/forgetracker/model/ticket.py          |    9 +-
 .../forgetracker/tests/functional/test_root.py     |  123 +++++++++++++--
 ForgeTracker/forgetracker/tracker_main.py          |   29 ++--
 ForgeTracker/forgetracker/widgets/admin.py         |    5 +-
 4 files changed, 141 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/d786c637/ForgeTracker/forgetracker/model/ticket.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index 7bd2386..fa470be 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -461,7 +461,10 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
 
     @property
     def notify_post(self):
-        return c.app.config.options.get('TicketMonitoringType') == 'AllTicketChanges'
+        monitoring_type = c.app.config.options.get('TicketMonitoringType')
+        return monitoring_type == 'AllTicketChanges' or (
+                monitoring_type == 'AllPublicTicketChanges' and
+                not self.private)
 
     def get_custom_user(self, custom_user_field_name):
         fld = None
@@ -549,7 +552,9 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
             Thread.new(discussion_id=self.app_config.discussion_id,
                    ref_id=self.index_id())
             n = Notification.post(artifact=self, topic='metadata', text=description, subject=subject)
-            if monitoring_email and n:
+            if monitoring_email and n and (not self.private or
+                    self.app.config.options.get('TicketMonitoringType') in (
+                        'NewTicketsOnly', 'AllTicketChanges')):
                 n.send_simple(monitoring_email)
         Feed.post(
             self,

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/d786c637/ForgeTracker/forgetracker/tests/functional/test_root.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 2bd1747..8d2deb0 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -26,7 +26,7 @@ import allura
 
 from mock import patch
 from nose.tools import assert_true, assert_false, assert_equal, assert_in
-from nose.tools import assert_raises
+from nose.tools import assert_raises, assert_not_in
 from formencode.variabledecode import variable_encode
 
 from alluratest.controller import TestController
@@ -1227,7 +1227,7 @@ class TestFunctionalController(TrackerTestController):
             'TicketMonitoringEmail': 'monitoring@email.com',
             'TicketMonitoringType': 'AllTicketChanges',
         })
-        self.new_ticket(summary='test first ticket', status='open', _milestone='2.0')
+        self.new_ticket(summary='test first ticket', status='open', _milestone='2.0', private=True)
         ThreadLocalORMSession.flush_all()
         M.MonQTask.run_ready()
         ThreadLocalORMSession.flush_all()
@@ -1257,6 +1257,82 @@ class TestFunctionalController(TrackerTestController):
         monitoring_email_text = monitoring_email[0].kwargs.text
         assert_equal(admin_email_text, monitoring_email_text)
 
+    def test_bulk_edit_notifications_monitoring_email_public_only(self):
+        """Test that private tickets are not included in bulk edit
+        notifications if the "public only" option is selected.
+        """
+        self.app.post('/admin/bugs/set_options', params={
+            'TicketMonitoringEmail': 'monitoring@email.com',
+            'TicketMonitoringType': 'AllPublicTicketChanges',
+        })
+        self.new_ticket(summary='test first ticket', status='open', _milestone='2.0')
+        self.new_ticket(summary='test second ticket', status='open', private=True)
+        ThreadLocalORMSession.flush_all()
+        M.MonQTask.run_ready()
+        ThreadLocalORMSession.flush_all()
+        tickets = tm.Ticket.query.find(dict(status='open')).all()
+        M.MonQTask.query.remove()
+        self.app.post('/p/test/bugs/update_tickets', {
+                      '__search': '',
+                      '__ticket_ids': [t._id for t in tickets],
+                      'status': 'accepted'})
+        M.MonQTask.run_ready()
+        emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
+        assert_equal(len(emails), 2)  # one for admin and one for monitoring email
+        for email in emails:
+            assert_equal(email.kwargs.subject, '[test:bugs] Mass edit changes by Test Admin')
+        admin = M.User.by_username('test-admin')
+        admin_email = M.MonQTask.query.find({
+            'task_name': 'allura.tasks.mail_tasks.sendmail',
+            'kwargs.destinations': str(admin._id)
+        }).all()
+        monitoring_email = M.MonQTask.query.find({
+            'task_name': 'allura.tasks.mail_tasks.sendmail',
+            'kwargs.destinations': 'monitoring@email.com'
+        }).all()
+        assert_equal(len(admin_email), 1)
+        assert_equal(len(monitoring_email), 1)
+        admin_email_text = admin_email[0].kwargs.text
+        monitoring_email_text = monitoring_email[0].kwargs.text
+        assert_in('second ticket', admin_email_text)
+        assert_not_in('second ticket', monitoring_email_text)
+
+    def test_bulk_edit_monitoring_email_all_private_edits(self):
+        """Test that no monitoring email is sent if the "public only"
+        option is selected, and only private tickets were updated.
+        """
+        self.app.post('/admin/bugs/set_options', params={
+            'TicketMonitoringEmail': 'monitoring@email.com',
+            'TicketMonitoringType': 'AllPublicTicketChanges',
+        })
+        self.new_ticket(summary='test first ticket', status='open', private=True)
+        self.new_ticket(summary='test second ticket', status='open', private=True)
+        ThreadLocalORMSession.flush_all()
+        M.MonQTask.run_ready()
+        ThreadLocalORMSession.flush_all()
+        tickets = tm.Ticket.query.find(dict(status='open')).all()
+        M.MonQTask.query.remove()
+        self.app.post('/p/test/bugs/update_tickets', {
+                      '__search': '',
+                      '__ticket_ids': [t._id for t in tickets],
+                      'status': 'accepted'})
+        M.MonQTask.run_ready()
+        emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
+        assert_equal(len(emails), 1)  # only admin email sent
+        for email in emails:
+            assert_equal(email.kwargs.subject, '[test:bugs] Mass edit changes by Test Admin')
+        admin = M.User.by_username('test-admin')
+        admin_email = M.MonQTask.query.find({
+            'task_name': 'allura.tasks.mail_tasks.sendmail',
+            'kwargs.destinations': str(admin._id)
+        }).all()
+        monitoring_email = M.MonQTask.query.find({
+            'task_name': 'allura.tasks.mail_tasks.sendmail',
+            'kwargs.destinations': 'monitoring@email.com'
+        }).all()
+        assert_equal(len(admin_email), 1)
+        assert_equal(len(monitoring_email), 0)
+
     def test_filtered_by_subscription(self):
         self.new_ticket(summary='test first ticket', status='open')
         self.new_ticket(summary='test second ticket', status='open')
@@ -1873,23 +1949,28 @@ class TestEmailMonitoring(TrackerTestController):
     @patch('forgetracker.model.ticket.Notification.send_simple')
     def test_notifications_new(self, send_simple):
         self._set_options('NewTicketsOnly')
-        self.new_ticket(summary='test')
-        self.app.post('/bugs/1/update_ticket',{
-            'summary':'test',
-            'description':'update',
-        })
+        self.new_ticket(summary='test', private=True)
         send_simple.assert_called_once_with(self.test_email)
 
+    @patch('forgetracker.model.ticket.Notification.send_simple')
+    def test_notifications_new_public_only(self, send_simple):
+        """Test that notification not sent for new private ticket
+        if "public only" option selected.
+        """
+        self._set_options('NewPublicTicketsOnly')
+        self.new_ticket(summary='test', private=True)
+        assert not send_simple.called
+
     @patch('forgetracker.tracker_main.M.Notification.send_simple')
     def test_notifications_all(self, send_simple):
         self._set_options()
         self.new_ticket(summary='test')
         send_simple.assert_called_once_with(self.test_email)
         send_simple.reset_mock()
-        response = self.app.post(
-            '/bugs/1/update_ticket',
-            {'summary': 'test',
-            'description': 'update'})
+        response = self.app.post('/bugs/1/update_ticket', {
+            'summary': 'test',
+            'description': 'update',
+            'private': '1'})
         assert send_simple.call_count == 1, send_simple.call_count
         send_simple.assert_called_with(self.test_email)
         send_simple.reset_mock()
@@ -1906,6 +1987,25 @@ class TestEmailMonitoring(TrackerTestController):
         assert send_simple.call_count == 1, send_simple.call_count
         send_simple.assert_called_with(self.test_email)
 
+    @patch('forgetracker.tracker_main.M.Notification.send_simple')
+    def test_notifications_all_public_only(self, send_simple):
+        """Test that notifications are not sent for private tickets
+        if "public only" option selected.
+        """
+        self._set_options('AllPublicTicketChanges')
+        self.new_ticket(summary='test')
+        send_simple.assert_called_once_with(self.test_email)
+        send_simple.reset_mock()
+        self.app.post('/bugs/1/update_ticket', {
+                'summary': 'test',
+                'description': 'update 1'})
+        send_simple.assert_called_once_with(self.test_email)
+        send_simple.reset_mock()
+        self.app.post('/bugs/1/update_ticket', {
+                'summary': 'test',
+                'description': 'update 2',
+                'private': '1'})
+        assert not send_simple.called
 
     @patch('forgetracker.tracker_main.M.Notification.send_simple')
     def test_notifications_off(self, send_simple):
@@ -1920,6 +2020,7 @@ class TestEmailMonitoring(TrackerTestController):
             self.new_ticket(summary='test')
         assert send_simple.call_count == 0, send_simple.call_count
 
+
 class TestCustomUserField(TrackerTestController):
     def setUp(self):
         super(TestCustomUserField, self).setUp()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/d786c637/ForgeTracker/forgetracker/tracker_main.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index f76cf74..17f3b6b 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -172,7 +172,8 @@ class ForgeTrackerApp(Application):
         ConfigOption('EnableVoting', bool, False),
         ConfigOption('TicketMonitoringEmail', str, ''),
         ConfigOption('TicketMonitoringType',
-            schema.OneOf('NewTicketsOnly', 'AllTicketChanges'), None)
+            schema.OneOf('NewTicketsOnly', 'AllTicketChanges',
+                'NewPublicTicketsOnly', 'AllPublicTicketChanges'), None)
         ]
     searchable=True
     tool_label='Tickets'
@@ -862,17 +863,23 @@ class RootController(BaseController, FeedController):
                 destinations = [str(user._id)]))
             mail_tasks.sendmail.post(**mail)
 
-        if c.app.config.options.get('TicketMonitoringType') == 'AllTicketChanges':
+        if c.app.config.options.get('TicketMonitoringType') in (
+                'AllTicketChanges', 'AllPublicTicketChanges'):
             monitoring_email = c.app.config.options.get('TicketMonitoringEmail')
-            def all_changes():
-                for t_id in changed_tickets.keys():
-                    yield (changed_tickets[t_id], changes[t_id])
-            tmpl_context['data'].update({'changes': all_changes()})
-            mail.update(dict(
-                message_id = h.gen_message_id(),
-                text = tmpl.render(tmpl_context),
-                destinations = [monitoring_email]))
-            mail_tasks.sendmail.post(**mail)
+            visible_changes = []
+            for t_id, t in changed_tickets.items():
+                if (not t.private or
+                        c.app.config.options.get('TicketMonitoringType') ==
+                        'AllTicketChanges'):
+                    visible_changes.append(
+                            (changed_tickets[t_id], changes[t_id]))
+            if visible_changes:
+                tmpl_context['data'].update({'changes': visible_changes})
+                mail.update(dict(
+                    message_id = h.gen_message_id(),
+                    text = tmpl.render(tmpl_context),
+                    destinations = [monitoring_email]))
+                mail_tasks.sendmail.post(**mail)
 
         c.app.globals.invalidate_bin_counts()
         ThreadLocalORMSession.flush_all()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/d786c637/ForgeTracker/forgetracker/widgets/admin.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/widgets/admin.py b/ForgeTracker/forgetracker/widgets/admin.py
index 1e13d2f..09bc443 100644
--- a/ForgeTracker/forgetracker/widgets/admin.py
+++ b/ForgeTracker/forgetracker/widgets/admin.py
@@ -44,7 +44,10 @@ class OptionsAdmin(ff.AdminForm):
                 grid_width='7',
                 options=[
                     ew.Option(py_value='NewTicketsOnly', label='New tickets only'),
-                    ew.Option(py_value='AllTicketChanges', label='All ticket changes')]),
+                    ew.Option(py_value='NewPublicTicketsOnly', label='New public tickets only'),
+                    ew.Option(py_value='AllTicketChanges', label='All ticket changes'),
+                    ew.Option(py_value='AllPublicTicketChanges', label='All public ticket changes'),
+                    ]),
             ffw.MarkdownEdit(
                 name='TicketHelpNew',
                 label='Help text to display on new ticket page',


[04/14] git commit: [#5652] ticket:325 added attachments info for Allura REST API

Posted by jo...@apache.org.
[#5652]  ticket:325 added attachments info for Allura REST API


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/79e39b3c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/79e39b3c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/79e39b3c

Branch: refs/heads/cj/5913
Commit: 79e39b3cac09fdeca9af2a456330f367ede7930a
Parents: a211a0d
Author: Yuriy Arhipov <yu...@yandex.ru>
Authored: Tue May 7 01:44:48 2013 +0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon May 20 17:22:09 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/artifact.py                    |    5 ++-
 Allura/allura/model/discuss.py                     |    8 +++-
 ForgeTracker/forgetracker/model/ticket.py          |    5 ++
 .../forgetracker/tests/functional/test_root.py     |   38 +++++++++++++++
 ForgeWiki/forgewiki/tests/functional/test_root.py  |   20 ++++++--
 ForgeWiki/forgewiki/wiki_main.py                   |   13 ++++-
 6 files changed, 81 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/79e39b3c/Allura/allura/model/artifact.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/artifact.py b/Allura/allura/model/artifact.py
index 4a7f2c1..a8f573c 100644
--- a/Allura/allura/model/artifact.py
+++ b/Allura/allura/model/artifact.py
@@ -19,6 +19,8 @@ import logging
 import cPickle as pickle
 from collections import defaultdict
 from datetime import datetime
+from urlparse import urljoin
+from tg import config
 
 import bson
 import pymongo
@@ -96,7 +98,8 @@ class Artifact(MappedClass):
             labels=self.labels,
             related_artifacts=[a.url() for a in self.related_artifacts()],
             discussion_thread=self.discussion_thread,
-            discussion_thread_url=self.discussion_thread.url(),
+            discussion_thread_url=urljoin(config.get('base_url', 'http://sourceforge.net/'),
+                                          '/rest%s' % self.discussion_thread.url()),
         )
 
     def parent_security_context(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/79e39b3c/Allura/allura/model/discuss.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/discuss.py b/Allura/allura/model/discuss.py
index 4375077..e1da83c 100644
--- a/Allura/allura/model/discuss.py
+++ b/Allura/allura/model/discuss.py
@@ -21,6 +21,8 @@ from datetime import datetime
 import pymongo
 from pymongo.errors import DuplicateKeyError
 from pylons import tmpl_context as c, app_globals as g
+from urlparse import urljoin
+from tg import config
 
 from ming import schema
 from ming.orm.base import session
@@ -163,7 +165,11 @@ class Thread(Artifact, ActivityObject):
             _id=self._id,
             discussion_id=str(self.discussion_id),
             subject=self.subject,
-            posts=[dict(slug=p.slug, subject=p.subject)
+            posts=[dict(slug=p.slug,
+                        subject=p.subject,
+                        attachments=[dict(bytes=attach.length,
+                                          url=urljoin(config.get('base_url', 'http://sourceforge.net/'),
+                                                      attach.url())) for attach in p.attachments])
                    for p in self.posts])
 
     @property

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/79e39b3c/ForgeTracker/forgetracker/model/ticket.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index 45bef8b..00851bc 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -25,6 +25,8 @@ import pymongo
 from pymongo.errors import OperationFailure
 from pylons import tmpl_context as c, app_globals as g
 from pprint import pformat
+from urlparse import urljoin
+from tg import config as tg_config
 
 from ming import schema
 from ming.utils import LazyProperty
@@ -734,6 +736,9 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
             assigned_to_id=self.assigned_to_id and str(self.assigned_to_id) or None,
             status=self.status,
             private=self.private,
+            attachments=[dict(bytes=attach.length,
+                              url=urljoin(tg_config.get('base_url', 'http://sourceforge.net/'),
+                                          attach.url())) for attach in self.attachments],
             custom_fields=self.custom_fields)
 
     @classmethod

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/79e39b3c/ForgeTracker/forgetracker/tests/functional/test_root.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 6723778..bf498be 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -1694,6 +1694,44 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.get('/p/test/bugs/tags?term=')
         assert_equal(json.loads(r.body), [])
 
+    def test_rest_tickets(self):
+        ticket_view = self.new_ticket(summary='test').follow()
+        for f in ticket_view.html.findAll('form'):
+            if f.get('action', '').endswith('/post'):
+                break
+        params = dict()
+        inputs = f.findAll('input')
+        for field in inputs:
+            if field.has_key('name'):
+                params[field['name']] = field.has_key('value') and field['value'] or ''
+        params[f.find('textarea')['name']] = 'test comment'
+        self.app.post(f['action'].encode('utf-8'), params=params,
+                          headers={'Referer': '/bugs/1/'.encode("utf-8")})
+        r = self.app.get('/bugs/1/', dict(page=1))
+        post_link = str(r.html.find('div', {'class':'edit_post_form reply'}).find('form')['action'])
+        self.app.post(post_link + 'attach',
+                          upload_files=[('file_info', 'test.txt', 'test attach')])
+        r = self.app.get('/p/test/bugs/1/')
+        discussion_url = r.html.findAll('form')[-1]['action'][:-4]
+        r = self.app.get('/rest/p/test/bugs/1/')
+        r = json.loads(r.body)
+        assert_equal(r['ticket']['discussion_thread_url'],'http://localhost/rest%s' % discussion_url)
+        slug = r['ticket']['discussion_thread']['posts'][0]['slug']
+        assert_equal(r['ticket']['discussion_thread']['posts'][0]['attachments'][0]['url'],
+                     'http://localhost%s%s/attachment/test.txt' % (discussion_url, slug))
+        assert_equal(r['ticket']['discussion_thread']['posts'][0]['attachments'][0]['bytes'], 11)
+
+        file_name = 'test_root.py'
+        file_data = file(__file__).read()
+        upload = ('attachment', file_name, file_data)
+        r = self.app.post('/bugs/1/update_ticket',{
+            'summary':'test rest attach'
+        }, upload_files=[upload]).follow()
+        r = self.app.get('/rest/p/test/bugs/1/')
+        r = json.loads(r.body)
+        assert_equal(r['ticket']['attachments'][0]['url'], 'http://localhost/p/test/bugs/1/attachment/test_root.py')
+
+
 
 class TestMilestoneAdmin(TrackerTestController):
     def _post(self, params, **kw):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/79e39b3c/ForgeWiki/forgewiki/tests/functional/test_root.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/tests/functional/test_root.py b/ForgeWiki/forgewiki/tests/functional/test_root.py
index 49a39e8..d943530 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_root.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_root.py
@@ -20,6 +20,7 @@
 import os
 import Image, StringIO
 import allura
+import json
 
 from nose.tools import assert_true, assert_equal, assert_in
 
@@ -216,15 +217,15 @@ class TestRootController(TestController):
         response = self.app.get('/wiki/tést/history')
         assert 'tést' in response
         # two revisions are shown
-        assert '2 by Test Admin (test-admin)' in response
-        assert '1 by Test Admin (test-admin)' in response
+        assert '2 by Test Admin' in response
+        assert '1 by Test Admin' in response
         # you can revert to an old revison, but not the current one
         assert response.html.find('a',{'href':'./revert?version=1'})
         assert not response.html.find('a',{'href':'./revert?version=2'})
         response = self.app.get('/wiki/tést/history', extra_environ=dict(username='*anonymous'))
         # two revisions are shown
-        assert '2 by Test Admin (test-admin)' in response
-        assert '1 by Test Admin (test-admin)' in response
+        assert '2 by Test Admin' in response
+        assert '1 by Test Admin' in response
         # you cannot revert to any revision
         assert not response.html.find('a',{'href':'./revert?version=1'})
         assert not response.html.find('a',{'href':'./revert?version=2'})
@@ -620,3 +621,14 @@ class TestRootController(TestController):
     def test_user_browse_page(self):
         r = self.app.get('/wiki/browse_pages/')
         assert '<td>Test Admin (test-admin)</td>' in r
+
+    def test_rest_wiki(self):
+        r = self.app.get('/p/test/wiki/Home/')
+        discussion_url = r.html.findAll('form')[2]['action'][:-4]
+        content = file(__file__).read()
+        self.app.post('/wiki/Home/attach', upload_files=[('file_info', 'test_root.py', content)])
+        r = self.app.get('/rest/p/test/wiki/Home/')
+        r = json.loads(r.body)
+        assert_equal(r['attachments'][0]['url'], 'http://localhost/p/test/wiki/Home/attachment/test_root.py')
+        assert_equal(r['discussion_thread_url'], 'http://localhost/rest%s' % discussion_url)
+        assert_equal(r['discussion_thread']['_id'], discussion_url.split('/')[-2])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/79e39b3c/ForgeWiki/forgewiki/wiki_main.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/wiki_main.py b/ForgeWiki/forgewiki/wiki_main.py
index 0f5151e..1dc9e06 100644
--- a/ForgeWiki/forgewiki/wiki_main.py
+++ b/ForgeWiki/forgewiki/wiki_main.py
@@ -22,7 +22,7 @@ from urllib import unquote
 from datetime import datetime
 
 # Non-stdlib imports
-from tg import expose, validate, redirect, response, flash
+from tg import expose, validate, redirect, response, flash, config
 from tg.decorators import with_trailing_slash, without_trailing_slash
 from tg.controllers import RestController
 from pylons import tmpl_context as c, app_globals as g
@@ -30,6 +30,7 @@ from pylons import request
 from formencode import validators
 from webob import exc
 from ming.orm import session
+from urlparse import urljoin
 
 # Pyforge-specific imports
 from allura import model as M
@@ -697,7 +698,15 @@ class RootRestController(RestController):
         if page is None:
             raise exc.HTTPNotFound, title
         require_access(page, 'read')
-        return dict(title=page.title, text=page.text, labels=page.labels)
+        return dict(title=page.title,
+                    text=page.text,
+                    labels=page.labels,
+                    discussion_thread=page.discussion_thread,
+                    discussion_thread_url=urljoin(config.get('base_url', 'http://sourceforge.net/'),
+                                                  '/rest%s' % page.discussion_thread.url()),
+                    attachments=[dict(bytes=attach.length,
+                                      url=urljoin(config.get('base_url', 'http://sourceforge.net/'),
+                                                  attach.url())) for attach in page.attachments])
 
     @h.vardec
     @expose()


[12/14] git commit: [#5913] Fixed styling of new project header

Posted by jo...@apache.org.
[#5913] Fixed styling of new project header

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/bcd3012b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/bcd3012b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/bcd3012b

Branch: refs/heads/cj/5913
Commit: bcd3012bc1597c8231ba7fd9fd74392719d20e78
Parents: 931003c
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue May 21 20:31:22 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed May 22 13:35:43 2013 +0000

----------------------------------------------------------------------
 Allura/allura/nf/allura/css/site_style.css         |   29 ++++++++++++++-
 Allura/allura/templates/jinja_master/nav_menu.html |    2 +-
 2 files changed, 28 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bcd3012b/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 06b93ca..6623b94 100644
--- a/Allura/allura/nf/allura/css/site_style.css
+++ b/Allura/allura/nf/allura/css/site_style.css
@@ -2091,8 +2091,33 @@ nav .ico {
   vertical-align: middle;
 }
 
-.project_title {
-  line-height: 48px;
+#nav_menu_holder {
+    margin: 0 0 15px;
+}
+
+#nav_menu_holder h1.project_title {
+  line-height: 1em;
+  font-size: 32px;
+  margin-bottom: 0;
+}
+
+#nav_menu_holder h1.project_title a,
+#nav_menu_holder h1.project_title a:hover,
+#nav_menu_holder h1.project_title a:visited,
+#nav_menu_holder h1.project_title a:focus {
+    color: #555;
+    text-decoration: none;
+}
+
+#nav_menu_holder h2.project_summary {
+  line-height: 1em;
+  font-size: 16px;
+  font-weight: normal;
+  margin-bottom: 0;
+}
+
+#nav_menu_holder .brought-by.with-icon {
+  margin-left: 55px;
 }
 
 .neighborhood_icon {

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bcd3012b/Allura/allura/templates/jinja_master/nav_menu.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/nav_menu.html b/Allura/allura/templates/jinja_master/nav_menu.html
index 15f7fac..27c7b56 100644
--- a/Allura/allura/templates/jinja_master/nav_menu.html
+++ b/Allura/allura/templates/jinja_master/nav_menu.html
@@ -43,7 +43,7 @@
     <h1 class="project_title">
       <a href="{{c.project.url()}}" class="project_link">{{ c.project.neighborhood.name if c.project.is_nbhd_project else c.project.name }}</a>
     </h1>
-    <h2>
+    <h2 class="project_summary">
         {{c.project.summary}}
     </h2>
     <div class="brought-by{% if c.project.icon %} with-icon{% endif %}">


[14/14] git commit: [#5913] Don't link entire project header

Posted by jo...@apache.org.
[#5913] Don't link entire project header

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/931003c5
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/931003c5
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/931003c5

Branch: refs/heads/cj/5913
Commit: 931003c5096dca842c2fa233fe3a726082d17996
Parents: 7eab5c3
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue May 21 19:53:17 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed May 22 13:35:43 2013 +0000

----------------------------------------------------------------------
 Allura/allura/templates/jinja_master/nav_menu.html |   14 +++++---------
 1 files changed, 5 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/931003c5/Allura/allura/templates/jinja_master/nav_menu.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/nav_menu.html b/Allura/allura/templates/jinja_master/nav_menu.html
index 67c44ab..15f7fac 100644
--- a/Allura/allura/templates/jinja_master/nav_menu.html
+++ b/Allura/allura/templates/jinja_master/nav_menu.html
@@ -35,21 +35,17 @@
          {% endif %}
       </div>
   {% endif %}
-  <a href="{{c.project.url()}}" class="project_link">
     {% if c.project.user_project_of %}
       {{lib.gravatar(c.project.user_project_of, size=48, className='project_icon')}}
     {% elif c.project.icon %}
       <img src="{{c.project.url()}}/icon?{{c.project.icon._id.generation_time}}" class="project_icon" alt="Project Logo">
     {% endif %}
-	<h1 class="project_title">
-      {% if c.project.is_nbhd_project %}
-        {{c.project.neighborhood.name}}
-      {% else %}
-        {{c.project.name}}
-      {% endif %}
-	</h1>
+    <h1 class="project_title">
+      <a href="{{c.project.url()}}" class="project_link">{{ c.project.neighborhood.name if c.project.is_nbhd_project else c.project.name }}</a>
+    </h1>
+    <h2>
         {{c.project.summary}}
-    </a>
+    </h2>
     <div class="brought-by{% if c.project.icon %} with-icon{% endif %}">
         Brought to you by:
         {% set admins = c.project.admins()|sort(attribute='username') %}


[08/14] git commit: [#5913] Add summary and members to project header

Posted by jo...@apache.org.
[#5913] Add summary and members to project header

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/11009bde
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/11009bde
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/11009bde

Branch: refs/heads/cj/5913
Commit: 11009bde89c27988bdceb75473768f43addbae16
Parents: d786c63
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Wed May 15 21:48:34 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed May 22 13:35:42 2013 +0000

----------------------------------------------------------------------
 Allura/allura/templates/jinja_master/nav_menu.html |   15 +++++++++++++--
 1 files changed, 13 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/11009bde/Allura/allura/templates/jinja_master/nav_menu.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/nav_menu.html b/Allura/allura/templates/jinja_master/nav_menu.html
index 76648a1..1b6a8f9 100644
--- a/Allura/allura/templates/jinja_master/nav_menu.html
+++ b/Allura/allura/templates/jinja_master/nav_menu.html
@@ -35,7 +35,7 @@
          {% endif %}
       </div>
   {% endif %}
-  <a href="{{c.project.url()}}">
+  <a href="{{c.project.url()}}" class="project_link">
     {% if c.project.user_project_of %}
       {{lib.gravatar(c.project.user_project_of, size=48, className='project_icon')}}
     {% elif c.project.icon %}
@@ -48,5 +48,16 @@
         {{c.project.name}}
       {% endif %}
 	</h1>
-	</a>
+        {{c.project.summary}}
+    </a>
+    <div class="brought-by{% if c.project.icon %} with-icon{% endif %}">
+        Brought to you by:
+        {% set users = c.project.users()|sort(attribute='display_name') %}
+        {% for user in users[:4] %}
+            <a href="{{ user.url() }}">{{ user.username }}</a>{{ ',' if not loop.last }}
+        {%- endfor -%}
+        {% if users|length > 4 -%}
+            , and <a href="{{ c.project.url() }}_members/">{{ users|length - 4 }} others</a>
+        {% endif %}
+    </div>
 {% endif %}


[09/14] git commit: [#5913] Add triangle to indicate nav menus with dropdowns

Posted by jo...@apache.org.
[#5913] Add triangle to indicate nav menus with dropdowns

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/32805238
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/32805238
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/32805238

Branch: refs/heads/cj/5913
Commit: 32805238a933c221a7c8d95beab741d46be50db6
Parents: 11009bd
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Thu May 16 13:43:09 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed May 22 13:35:42 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/project.py           |    2 +-
 Allura/allura/tests/unit/test_project.py |   18 +++++++++---------
 2 files changed, 10 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/32805238/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index da36ada..e98b44c 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -498,7 +498,7 @@ class Project(MappedClass, ActivityNode, ActivityObject):
                 if tool_name not in grouped_nav:
                     child = deepcopy(e)
                     # change label to be the tool name (type)
-                    e.label = g.entry_points['tool'][tool_name].tool_label
+                    e.label = g.entry_points['tool'][tool_name].tool_label + u' \u25be'
                     # add tool url to list of urls that will match this nav entry
                     # have to do this before changing the url to the list page
                     e.matching_urls.append(e.url)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/32805238/Allura/allura/tests/unit/test_project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/unit/test_project.py b/Allura/allura/tests/unit/test_project.py
index 7329dba..f83cfe9 100644
--- a/Allura/allura/tests/unit/test_project.py
+++ b/Allura/allura/tests/unit/test_project.py
@@ -38,10 +38,10 @@ class TestProject(unittest.TestCase):
         p.sitemap = Mock(return_value=sitemap_entries)
         entries = p.grouped_navbar_entries()
         expected = [
-            ('Tickets', 'proj_url/_list/tickets', 3),
-            ('wiki', 'wiki url', 0),
-            ('Discussion', 'proj_url/_list/discussion', 2),
-            ('subproject', 'subproject url', 0),
+            (u'Tickets \u25be', 'proj_url/_list/tickets', 3),
+            (u'wiki', 'wiki url', 0),
+            (u'Discussion \u25be', 'proj_url/_list/discussion', 2),
+            (u'subproject', 'subproject url', 0),
         ]
         expected_ticket_urls = ['bugs url', 'features url', 'support url']
         actual = [(e.label, e.url, len(e.matching_urls)) for e in entries]
@@ -64,11 +64,11 @@ class TestProject(unittest.TestCase):
         p.tool_data['allura'] = {'grouping_threshold': 2}
         entries = p.grouped_navbar_entries()
         expected = [
-            ('Tickets', 'proj_url/_list/tickets', 3),
-            ('wiki', 'wiki url', 0),
-            ('discuss', 'discuss url', 0),
-            ('subproject', 'subproject url', 0),
-            ('help', 'help url', 0),
+            (u'Tickets \u25be', 'proj_url/_list/tickets', 3),
+            (u'wiki', 'wiki url', 0),
+            (u'discuss', 'discuss url', 0),
+            (u'subproject', 'subproject url', 0),
+            (u'help', 'help url', 0),
         ]
         expected_ticket_urls = ['bugs url', 'features url', 'support url']
         actual = [(e.label, e.url, len(e.matching_urls)) for e in entries]


[02/14] git commit: [#6149] ticket:356 Fix select field value splitting

Posted by jo...@apache.org.
[#6149] ticket:356 Fix select field value splitting

* Fall back to regular splitting if shlex.split raises exception
* Always encode unicode-strings before passing it to shlex (it doesn't work well with unicode)


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/a211a0d7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/a211a0d7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/a211a0d7

Branch: refs/heads/cj/5913
Commit: a211a0d7ffa2e50e635f05188a11796c2e1da5be
Parents: 05d660d
Author: Igor Bondarenko <je...@gmail.com>
Authored: Mon May 20 08:14:39 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon May 20 17:20:30 2013 +0000

----------------------------------------------------------------------
 .../forgetracker/tests/functional/test_root.py     |   34 +++++++++++++++
 ForgeTracker/forgetracker/widgets/ticket_form.py   |   19 +++++++-
 2 files changed, 51 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a211a0d7/ForgeTracker/forgetracker/tests/functional/test_root.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index b80e28b..6723778 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -645,6 +645,40 @@ class TestFunctionalController(TrackerTestController):
         ticket_view = self.new_ticket(summary='test select custom fields', **kw).follow()
         assert '<option selected value="test select">test select</option>' in ticket_view
 
+    def test_select_custom_field_unicode(self):
+        params = dict(
+            custom_fields=[
+                dict(name='_testselect', label='Test', type='select',
+                     options='oné "one and á half" two'),
+               ],
+            open_status_names='aa bb',
+            closed_status_names='cc',
+            )
+        self.app.post(
+            '/admin/bugs/set_custom_fields',
+            params=variable_encode(params))
+        r = self.app.get('/bugs/new/')
+        assert u'<option value="oné">oné</option>'.encode('utf-8') in r
+        assert u'<option value="one and á half">one and á half</option>'.encode('utf-8') in r
+        assert u'<option value="two">two</option>' in r
+
+    def test_select_custom_field_invalid_quotes(self):
+        params = dict(
+            custom_fields=[
+                dict(name='_testselect', label='Test', type='select',
+                     options='closéd "quote missing'),
+               ],
+            open_status_names='aa bb',
+            closed_status_names='cc',
+            )
+        self.app.post(
+            '/admin/bugs/set_custom_fields',
+            params=variable_encode(params))
+        r = self.app.get('/bugs/new/')
+        assert u'<option value="closéd">closéd</option>'.encode('utf-8') in r
+        assert u'<option value="quote">quote</option>' in r
+        assert u'<option value="missing">missing</option>' in r
+
     def test_custom_field_update_comments(self):
         params = dict(
             custom_fields=[

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a211a0d7/ForgeTracker/forgetracker/widgets/ticket_form.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/widgets/ticket_form.py b/ForgeTracker/forgetracker/widgets/ticket_form.py
index f253484..5e3b61b 100644
--- a/ForgeTracker/forgetracker/widgets/ticket_form.py
+++ b/ForgeTracker/forgetracker/widgets/ticket_form.py
@@ -15,7 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-from allura.lib.widgets import form_fields as ffw
 import shlex
 
 from pylons import tmpl_context as c
@@ -25,6 +24,8 @@ import ew as ew_core
 import ew.jinja2_ew as ew
 
 from allura import model as M
+from allura.lib.widgets import form_fields as ffw
+from allura.lib import helpers as h
 
 class TicketCustomFields(ew.CompoundField):
     template='jinja:forgetracker:templates/tracker_widgets/ticket_custom_fields.html'
@@ -150,7 +151,21 @@ class TicketCustomField(object):
 
     def _select(field):
         options = []
-        for opt in shlex.split(field.options):
+        field_options = h.really_unicode(field.options)
+        try:
+            # shlex have problems with parsing unicode,
+            # it's better to pass properly encoded byte-string
+            field_options = shlex.split(field_options.encode('utf-8'))
+            # convert splitted string back to unicode
+            field_options = map(h.really_unicode, field_options)
+        except ValueError:
+            field_options = field_options.split()
+            # After regular split field_options might contain a " characters,
+            # which would break html when rendered inside tag's value attr.
+            # Escaping doesn't help here, 'cause it breaks EasyWidgets' validation,
+            # so we're getting rid of those.
+            field_options = [o.replace('"', '') for o in field_options]
+        for opt in field_options:
             selected = False
             if opt.startswith('*'):
                 opt = opt[1:]


[10/14] git commit: [#5913] Project header should show admins, not members

Posted by jo...@apache.org.
[#5913] Project header should show admins, not members

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/7eab5c3c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/7eab5c3c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/7eab5c3c

Branch: refs/heads/cj/5913
Commit: 7eab5c3c3043664b41693c95de84defe894a4447
Parents: 3280523
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue May 21 19:36:57 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed May 22 13:35:42 2013 +0000

----------------------------------------------------------------------
 Allura/allura/templates/jinja_master/nav_menu.html |   10 +++++-----
 1 files changed, 5 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/7eab5c3c/Allura/allura/templates/jinja_master/nav_menu.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/nav_menu.html b/Allura/allura/templates/jinja_master/nav_menu.html
index 1b6a8f9..67c44ab 100644
--- a/Allura/allura/templates/jinja_master/nav_menu.html
+++ b/Allura/allura/templates/jinja_master/nav_menu.html
@@ -52,12 +52,12 @@
     </a>
     <div class="brought-by{% if c.project.icon %} with-icon{% endif %}">
         Brought to you by:
-        {% set users = c.project.users()|sort(attribute='display_name') %}
-        {% for user in users[:4] %}
-            <a href="{{ user.url() }}">{{ user.username }}</a>{{ ',' if not loop.last }}
+        {% set admins = c.project.admins()|sort(attribute='username') %}
+        {% for admin in admins[:4] %}
+            <a href="{{ admin.url() }}">{{ admin.username }}</a>{{ ',' if not loop.last }}
         {%- endfor -%}
-        {% if users|length > 4 -%}
-            , and <a href="{{ c.project.url() }}_members/">{{ users|length - 4 }} others</a>
+        {% if admins|length > 4 -%}
+            , and <a href="{{ c.project.url() }}_members/">{{ admins|length - 4 }} others</a>
         {% endif %}
     </div>
 {% endif %}


[05/14] git commit: [#5652] ticket:357 restore output format for wiki page api

Posted by jo...@apache.org.
[#5652] ticket:357 restore output format for wiki page api


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/0d1c69e8
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/0d1c69e8
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/0d1c69e8

Branch: refs/heads/cj/5913
Commit: 0d1c69e81b2777911775c6e3d091bad630ec7f63
Parents: 703ccaa
Author: Yuriy Arhipov <yu...@yandex.ru>
Authored: Fri May 17 14:12:39 2013 +0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon May 20 17:22:10 2013 +0000

----------------------------------------------------------------------
 Allura/allura/tests/functional/test_rest.py        |    6 +++---
 .../tests/functional/test_rest_api_tickets.py      |    4 ++--
 ForgeWiki/forgewiki/tests/functional/test_root.py  |    8 ++++----
 ForgeWiki/forgewiki/wiki_main.py                   |    2 +-
 4 files changed, 10 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/0d1c69e8/Allura/allura/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index eb9dcd6..fcfb373 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -59,13 +59,13 @@ class TestRestHome(TestRestApiBase):
     def test_project_ping(self):
         r = self.api_get('/rest/p/test/wiki/Home/')
         assert r.status_int == 200
-        assert r.json['page']['title'] == 'Home', r.json
+        assert r.json['title'] == 'Home', r.json
 
     @td.with_tool('test/sub1', 'Wiki', 'wiki')
     def test_subproject_ping(self):
         r = self.api_get('/rest/p/test/sub1/wiki/Home/')
         assert r.status_int == 200
-        assert r.json['page']['title'] == 'Home', r.json
+        assert r.json['title'] == 'Home', r.json
 
     def test_project_code(self):
         r = self.api_get('/rest/p/test/')
@@ -105,7 +105,7 @@ class TestRestHome(TestRestApiBase):
                 'viewable_by-0.id':'all'})
         r = self.api_get('/rest/p/test/wiki/tést/')
         assert r.status_int == 200
-        assert r.json['page']['title'].encode('utf-8') == 'tést', r.json
+        assert r.json['title'].encode('utf-8') == 'tést', r.json
 
     @td.with_wiki
     def test_deny_access(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/0d1c69e8/Allura/allura/tests/functional/test_rest_api_tickets.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_rest_api_tickets.py b/Allura/allura/tests/functional/test_rest_api_tickets.py
index 219f235..11e149f 100644
--- a/Allura/allura/tests/functional/test_rest_api_tickets.py
+++ b/Allura/allura/tests/functional/test_rest_api_tickets.py
@@ -68,7 +68,7 @@ class TestApiTicket(TestRestApiBase):
         self.set_api_ticket()
         r = self.api_get('/rest/p/test/wiki/Home/')
         assert r.status_int == 200
-        assert r.json['page']['title'] == 'Home', r.json
+        assert r.json['title'] == 'Home', r.json
 
     def test_project_ping_expired_ticket(self):
         self.set_api_ticket(timedelta(seconds=-1))
@@ -80,4 +80,4 @@ class TestApiTicket(TestRestApiBase):
         self.set_api_ticket()
         r = self.api_get('/rest/p/test/sub1/wiki/Home/')
         assert r.status_int == 200
-        assert r.json['page']['title'] == 'Home', r.json
+        assert r.json['title'] == 'Home', r.json

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/0d1c69e8/ForgeWiki/forgewiki/tests/functional/test_root.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/tests/functional/test_root.py b/ForgeWiki/forgewiki/tests/functional/test_root.py
index 5682d2a..1baa28b 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_root.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_root.py
@@ -629,10 +629,10 @@ class TestRootController(TestController):
         self.app.post('/wiki/Home/attach', upload_files=[('file_info', 'test_root.py', content)])
         r = self.app.get('/rest/p/test/wiki/Home/')
         r = json.loads(r.body)
-        assert_equal(r['page']['attachments'][0]['url'], 'http://localhost:80/p/test/wiki/Home/attachment/test_root.py')
-        assert_equal(r['page']['discussion_thread_url'], 'http://localhost:80/rest%s' % discussion_url)
-        assert_equal(r['page']['discussion_thread']['_id'], discussion_url.split('/')[-2])
+        assert_equal(r['attachments'][0]['url'], 'http://localhost:80/p/test/wiki/Home/attachment/test_root.py')
+        assert_equal(r['discussion_thread_url'], 'http://localhost:80/rest%s' % discussion_url)
+        assert_equal(r['discussion_thread']['_id'], discussion_url.split('/')[-2])
         self.app.post('/wiki/Home/attach', upload_files=[('file_info', '__init__.py', content),])
         r = self.app.get('/rest/p/test/wiki/Home/')
         r = json.loads(r.body)
-        assert_equal(len(r['page']['attachments']), 2)
+        assert_equal(len(r['attachments']), 2)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/0d1c69e8/ForgeWiki/forgewiki/wiki_main.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/wiki_main.py b/ForgeWiki/forgewiki/wiki_main.py
index 66d01b7..94f98a5 100644
--- a/ForgeWiki/forgewiki/wiki_main.py
+++ b/ForgeWiki/forgewiki/wiki_main.py
@@ -736,7 +736,7 @@ class PageRestController(BaseController):
 
     @expose('json:')
     def index(self, **kw):
-        return dict(page=self.page)
+        return self.page.__json__()
 
 
 class WikiAdminController(DefaultAdminController):


[13/14] git commit: [#5913] Add development status badge to project header

Posted by jo...@apache.org.
[#5913] Add development status badge to project header

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/99563770
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/99563770
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/99563770

Branch: refs/heads/cj/5913
Commit: 99563770c7a8909185a467de873894fd038d9b51
Parents: bcd3012
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue May 21 21:23:06 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed May 22 13:35:43 2013 +0000

----------------------------------------------------------------------
 Allura/allura/nf/allura/css/site_style.css         |   14 ++++++++++++++
 Allura/allura/templates/jinja_master/nav_menu.html |    5 +++++
 2 files changed, 19 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/99563770/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 6623b94..b067f6d 100644
--- a/Allura/allura/nf/allura/css/site_style.css
+++ b/Allura/allura/nf/allura/css/site_style.css
@@ -2099,6 +2099,7 @@ nav .ico {
   line-height: 1em;
   font-size: 32px;
   margin-bottom: 0;
+  display: inline;
 }
 
 #nav_menu_holder h1.project_title a,
@@ -2109,6 +2110,19 @@ nav .ico {
     text-decoration: none;
 }
 
+#nav_menu_holder #dev-status {
+    vertical-align: top;
+    text-transform: capitalize;
+    font-size: 10px;
+    -webkit-border-radius: 2px;
+    -moz-border-radius: 2px;
+    -o-border-radius: 2px;
+    border-radius: 2px;
+    background-color: #09c;
+    color: white;
+    padding: 2px;
+}
+
 #nav_menu_holder h2.project_summary {
   line-height: 1em;
   font-size: 16px;

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/99563770/Allura/allura/templates/jinja_master/nav_menu.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/nav_menu.html b/Allura/allura/templates/jinja_master/nav_menu.html
index 27c7b56..74f7509 100644
--- a/Allura/allura/templates/jinja_master/nav_menu.html
+++ b/Allura/allura/templates/jinja_master/nav_menu.html
@@ -43,6 +43,11 @@
     <h1 class="project_title">
       <a href="{{c.project.url()}}" class="project_link">{{ c.project.neighborhood.name if c.project.is_nbhd_project else c.project.name }}</a>
     </h1>
+    {% set status = c.project.troves_by_type('developmentstatus')|sort(attribute='fullname') %}
+    {% set status = status[-1] %}
+    {% if status and status.shortname not in ['production', 'mature'] %}
+    <span id="dev-status" class="{{ status.shortname }}">{{ status.shortname }}</span>
+    {% endif %}
     <h2 class="project_summary">
         {{c.project.summary}}
     </h2>


[06/14] git commit: [#6226] Added missing indexes for project_id for purge script

Posted by jo...@apache.org.
[#6226] Added missing indexes for project_id for purge script

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/e34ca49a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/e34ca49a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/e34ca49a

Branch: refs/heads/cj/5913
Commit: e34ca49ab28ced893db93010d8560cd17e9879df
Parents: 0d1c69e
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Fri May 17 14:29:22 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon May 20 18:25:40 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/notification.py |    1 +
 ForgeChat/forgechat/model/chat.py   |    1 +
 2 files changed, 2 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e34ca49a/Allura/allura/model/notification.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/notification.py b/Allura/allura/model/notification.py
index 6326bcc..3c3517f 100644
--- a/Allura/allura/model/notification.py
+++ b/Allura/allura/model/notification.py
@@ -69,6 +69,7 @@ class Notification(MappedClass):
     class __mongometa__:
         session = main_orm_session
         name = 'notification'
+        indexes = ['project_id']
 
     _id = FieldProperty(str, if_missing=h.gen_message_id)
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e34ca49a/ForgeChat/forgechat/model/chat.py
----------------------------------------------------------------------
diff --git a/ForgeChat/forgechat/model/chat.py b/ForgeChat/forgechat/model/chat.py
index 0035946..981dd38 100644
--- a/ForgeChat/forgechat/model/chat.py
+++ b/ForgeChat/forgechat/model/chat.py
@@ -28,6 +28,7 @@ class ChatChannel(MappedClass):
     class __mongometa__:
         name = 'globals'
         session = M.main_orm_session
+        indexes = ['project_id']
         unique_indexes = [ 'channel' ]
 
     _id = FieldProperty(S.ObjectId)


[11/14] git commit: [#5913] Added breadcrumb navigation to project header

Posted by jo...@apache.org.
[#5913] Added breadcrumb navigation to project header

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/983b827d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/983b827d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/983b827d

Branch: refs/heads/cj/5913
Commit: 983b827de5eb38377bcb197951b190b18a1f3857
Parents: 9956377
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue May 21 23:18:06 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Wed May 22 13:35:43 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/project.py                     |   18 ++++++++++++
 Allura/allura/nf/allura/css/site_style.css         |   22 +++++++++++++++
 Allura/allura/templates/jinja_master/nav_menu.html |    2 +
 .../templates/jinja_master/theme_macros.html       |   18 ++++++++++++
 4 files changed, 60 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/983b827d/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index e98b44c..c11c148 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -117,6 +117,24 @@ class TroveCategory(MappedClass):
             trove = trove.parent_category
         return trove.shortname
 
+    @LazyProperty
+    def ancestors(self):
+        ancestors = []
+        trove = self
+        while trove:
+            ancestors.append(trove)
+            trove = trove.parent_category
+        return ancestors
+
+    @LazyProperty
+    def breadcrumbs(self):
+        url = '/directory/'
+        crumbs = []
+        for trove in reversed(self.ancestors[:-1]):
+            url += trove.shortname + '/'
+            crumbs.append((trove.fullname, url))
+        return crumbs
+
 class ProjectMapperExtension(MapperExtension):
     def after_insert(self, obj, st, sess):
         g.zarkov_event('project_create', project=obj)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/983b827d/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 b067f6d..cfbfc9e 100644
--- a/Allura/allura/nf/allura/css/site_style.css
+++ b/Allura/allura/nf/allura/css/site_style.css
@@ -2095,6 +2095,28 @@ nav .ico {
     margin: 0 0 15px;
 }
 
+#nav_menu_holder #breadcrumbs {
+    margin-bottom: 5px;
+}
+
+#nav_menu_holder #breadcrumbs ul {
+    list-style: none;
+    margin: 0;
+}
+
+#nav_menu_holder #breadcrumbs li {
+    display: inline;
+    font-size: 11px;
+}
+
+#nav_menu_holder #breadcrumbs li:after {
+    content: ' / ';
+}
+
+#nav_menu_holder #breadcrumbs li:last-child:after {
+    content: '';
+}
+
 #nav_menu_holder h1.project_title {
   line-height: 1em;
   font-size: 32px;

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/983b827d/Allura/allura/templates/jinja_master/nav_menu.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/nav_menu.html b/Allura/allura/templates/jinja_master/nav_menu.html
index 74f7509..724ca1c 100644
--- a/Allura/allura/templates/jinja_master/nav_menu.html
+++ b/Allura/allura/templates/jinja_master/nav_menu.html
@@ -17,10 +17,12 @@
        under the License.
 -#}
 {% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+{% import g.theme.jinja_macros as theme_macros with context %}
 {% set n = c.project.neighborhood %}
 {% if not c.project or (n.neighborhood_project == c.project and not n.show_title) %}
   <div id="nav_menu_missing"></div>
 {% else %}
+  {{ theme_macros.breadcrumbs(c.project, c.app) }}
   {% if c.project.neighborhood.icon %}
     <a href="{{c.project.neighborhood.url()}}"><img src="{{c.project.neighborhood.url()}}/icon" class="neighborhood_icon"
        alt="Return to {{c.project.neighborhood.name}}" title="Return to {{c.project.neighborhood.name}}"></a>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/983b827d/Allura/allura/templates/jinja_master/theme_macros.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/theme_macros.html b/Allura/allura/templates/jinja_master/theme_macros.html
index e1c9903..a493075 100644
--- a/Allura/allura/templates/jinja_master/theme_macros.html
+++ b/Allura/allura/templates/jinja_master/theme_macros.html
@@ -78,3 +78,21 @@
 {%- macro extra_header(path_to_static) %}
 
 {%- endmacro %}
+
+{%- macro breadcrumbs(project, app) %}
+    <nav id="breadcrumbs">
+        <ul>
+            <li><a href="/">Home</a></li>
+            {% for label,url in project.breadcrumbs() %}
+                {% if not loop.last or app %}
+                    <li><a href="{{ url }}">{{ label }}</a></li>
+                {% else %}
+                    <li>{{ label }}</li>
+                {% endif %}
+            {% endfor %}
+            {% if app %}
+                <li>{{ app.config.options.mount_label }}</li>
+            {% endif %}
+        </ul>
+    </nav>
+{%- endmacro %}


[03/14] git commit: [#5652] ticket:325 fixed discussion_thread_url and tests

Posted by jo...@apache.org.
[#5652]  ticket:325 fixed discussion_thread_url and tests


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/703ccaab
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/703ccaab
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/703ccaab

Branch: refs/heads/cj/5913
Commit: 703ccaab9bc4354f919129cfb85d611bad0eb056
Parents: 79e39b3
Author: Yuriy Arhipov <yu...@yandex.ru>
Authored: Thu May 9 18:50:45 2013 +0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Mon May 20 17:22:09 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/artifact.py                    |    5 +-
 Allura/allura/model/discuss.py                     |    5 +-
 Allura/allura/tests/functional/test_rest.py        |    6 +-
 .../tests/functional/test_rest_api_tickets.py      |    4 +-
 ForgeTracker/forgetracker/model/ticket.py          |    5 +-
 .../forgetracker/tests/functional/test_root.py     |    6 +-
 ForgeWiki/forgewiki/model/wiki.py                  |    8 ++
 ForgeWiki/forgewiki/tests/functional/test_root.py  |   10 ++-
 ForgeWiki/forgewiki/wiki_main.py                   |   51 +++++++++------
 9 files changed, 57 insertions(+), 43 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/703ccaab/Allura/allura/model/artifact.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/artifact.py b/Allura/allura/model/artifact.py
index a8f573c..5aea67f 100644
--- a/Allura/allura/model/artifact.py
+++ b/Allura/allura/model/artifact.py
@@ -19,8 +19,6 @@ import logging
 import cPickle as pickle
 from collections import defaultdict
 from datetime import datetime
-from urlparse import urljoin
-from tg import config
 
 import bson
 import pymongo
@@ -98,8 +96,7 @@ class Artifact(MappedClass):
             labels=self.labels,
             related_artifacts=[a.url() for a in self.related_artifacts()],
             discussion_thread=self.discussion_thread,
-            discussion_thread_url=urljoin(config.get('base_url', 'http://sourceforge.net/'),
-                                          '/rest%s' % self.discussion_thread.url()),
+            discussion_thread_url=h.absurl('/rest%s' % self.discussion_thread.url()),
         )
 
     def parent_security_context(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/703ccaab/Allura/allura/model/discuss.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/discuss.py b/Allura/allura/model/discuss.py
index e1da83c..c947c82 100644
--- a/Allura/allura/model/discuss.py
+++ b/Allura/allura/model/discuss.py
@@ -21,8 +21,6 @@ from datetime import datetime
 import pymongo
 from pymongo.errors import DuplicateKeyError
 from pylons import tmpl_context as c, app_globals as g
-from urlparse import urljoin
-from tg import config
 
 from ming import schema
 from ming.orm.base import session
@@ -168,8 +166,7 @@ class Thread(Artifact, ActivityObject):
             posts=[dict(slug=p.slug,
                         subject=p.subject,
                         attachments=[dict(bytes=attach.length,
-                                          url=urljoin(config.get('base_url', 'http://sourceforge.net/'),
-                                                      attach.url())) for attach in p.attachments])
+                                          url=h.absurl(attach.url())) for attach in p.attachments])
                    for p in self.posts])
 
     @property

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/703ccaab/Allura/allura/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index fcfb373..eb9dcd6 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -59,13 +59,13 @@ class TestRestHome(TestRestApiBase):
     def test_project_ping(self):
         r = self.api_get('/rest/p/test/wiki/Home/')
         assert r.status_int == 200
-        assert r.json['title'] == 'Home', r.json
+        assert r.json['page']['title'] == 'Home', r.json
 
     @td.with_tool('test/sub1', 'Wiki', 'wiki')
     def test_subproject_ping(self):
         r = self.api_get('/rest/p/test/sub1/wiki/Home/')
         assert r.status_int == 200
-        assert r.json['title'] == 'Home', r.json
+        assert r.json['page']['title'] == 'Home', r.json
 
     def test_project_code(self):
         r = self.api_get('/rest/p/test/')
@@ -105,7 +105,7 @@ class TestRestHome(TestRestApiBase):
                 'viewable_by-0.id':'all'})
         r = self.api_get('/rest/p/test/wiki/tést/')
         assert r.status_int == 200
-        assert r.json['title'].encode('utf-8') == 'tést', r.json
+        assert r.json['page']['title'].encode('utf-8') == 'tést', r.json
 
     @td.with_wiki
     def test_deny_access(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/703ccaab/Allura/allura/tests/functional/test_rest_api_tickets.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_rest_api_tickets.py b/Allura/allura/tests/functional/test_rest_api_tickets.py
index 11e149f..219f235 100644
--- a/Allura/allura/tests/functional/test_rest_api_tickets.py
+++ b/Allura/allura/tests/functional/test_rest_api_tickets.py
@@ -68,7 +68,7 @@ class TestApiTicket(TestRestApiBase):
         self.set_api_ticket()
         r = self.api_get('/rest/p/test/wiki/Home/')
         assert r.status_int == 200
-        assert r.json['title'] == 'Home', r.json
+        assert r.json['page']['title'] == 'Home', r.json
 
     def test_project_ping_expired_ticket(self):
         self.set_api_ticket(timedelta(seconds=-1))
@@ -80,4 +80,4 @@ class TestApiTicket(TestRestApiBase):
         self.set_api_ticket()
         r = self.api_get('/rest/p/test/sub1/wiki/Home/')
         assert r.status_int == 200
-        assert r.json['title'] == 'Home', r.json
+        assert r.json['page']['title'] == 'Home', r.json

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/703ccaab/ForgeTracker/forgetracker/model/ticket.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index 00851bc..7bd2386 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -25,8 +25,6 @@ import pymongo
 from pymongo.errors import OperationFailure
 from pylons import tmpl_context as c, app_globals as g
 from pprint import pformat
-from urlparse import urljoin
-from tg import config as tg_config
 
 from ming import schema
 from ming.utils import LazyProperty
@@ -737,8 +735,7 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
             status=self.status,
             private=self.private,
             attachments=[dict(bytes=attach.length,
-                              url=urljoin(tg_config.get('base_url', 'http://sourceforge.net/'),
-                                          attach.url())) for attach in self.attachments],
+                              url=h.absurl(attach.url())) for attach in self.attachments],
             custom_fields=self.custom_fields)
 
     @classmethod

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/703ccaab/ForgeTracker/forgetracker/tests/functional/test_root.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index bf498be..2bd1747 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -1715,10 +1715,10 @@ class TestFunctionalController(TrackerTestController):
         discussion_url = r.html.findAll('form')[-1]['action'][:-4]
         r = self.app.get('/rest/p/test/bugs/1/')
         r = json.loads(r.body)
-        assert_equal(r['ticket']['discussion_thread_url'],'http://localhost/rest%s' % discussion_url)
+        assert_equal(r['ticket']['discussion_thread_url'],'http://localhost:80/rest%s' % discussion_url)
         slug = r['ticket']['discussion_thread']['posts'][0]['slug']
         assert_equal(r['ticket']['discussion_thread']['posts'][0]['attachments'][0]['url'],
-                     'http://localhost%s%s/attachment/test.txt' % (discussion_url, slug))
+                     'http://localhost:80%s%s/attachment/test.txt' % (discussion_url, slug))
         assert_equal(r['ticket']['discussion_thread']['posts'][0]['attachments'][0]['bytes'], 11)
 
         file_name = 'test_root.py'
@@ -1729,7 +1729,7 @@ class TestFunctionalController(TrackerTestController):
         }, upload_files=[upload]).follow()
         r = self.app.get('/rest/p/test/bugs/1/')
         r = json.loads(r.body)
-        assert_equal(r['ticket']['attachments'][0]['url'], 'http://localhost/p/test/bugs/1/attachment/test_root.py')
+        assert_equal(r['ticket']['attachments'][0]['url'], 'http://localhost:80/p/test/bugs/1/attachment/test_root.py')
 
 
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/703ccaab/ForgeWiki/forgewiki/model/wiki.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/model/wiki.py b/ForgeWiki/forgewiki/model/wiki.py
index a7ac642..6f054a1 100644
--- a/ForgeWiki/forgewiki/model/wiki.py
+++ b/ForgeWiki/forgewiki/model/wiki.py
@@ -97,6 +97,14 @@ class Page(VersionedArtifact, ActivityObject):
     def activity_name(self):
         return 'wiki page %s' % self.title
 
+    def __json__(self):
+        return dict(super(Page, self).__json__(),
+                    title=self.title,
+                    text=self.text,
+                    labels=self.labels,
+                    attachments=[dict(bytes=attach.length,
+                                      url=h.absurl(attach.url())) for attach in self.attachments])
+
     def commit(self):
         ss = VersionedArtifact.commit(self)
         session(self).flush()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/703ccaab/ForgeWiki/forgewiki/tests/functional/test_root.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/tests/functional/test_root.py b/ForgeWiki/forgewiki/tests/functional/test_root.py
index d943530..5682d2a 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_root.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_root.py
@@ -629,6 +629,10 @@ class TestRootController(TestController):
         self.app.post('/wiki/Home/attach', upload_files=[('file_info', 'test_root.py', content)])
         r = self.app.get('/rest/p/test/wiki/Home/')
         r = json.loads(r.body)
-        assert_equal(r['attachments'][0]['url'], 'http://localhost/p/test/wiki/Home/attachment/test_root.py')
-        assert_equal(r['discussion_thread_url'], 'http://localhost/rest%s' % discussion_url)
-        assert_equal(r['discussion_thread']['_id'], discussion_url.split('/')[-2])
+        assert_equal(r['page']['attachments'][0]['url'], 'http://localhost:80/p/test/wiki/Home/attachment/test_root.py')
+        assert_equal(r['page']['discussion_thread_url'], 'http://localhost:80/rest%s' % discussion_url)
+        assert_equal(r['page']['discussion_thread']['_id'], discussion_url.split('/')[-2])
+        self.app.post('/wiki/Home/attach', upload_files=[('file_info', '__init__.py', content),])
+        r = self.app.get('/rest/p/test/wiki/Home/')
+        r = json.loads(r.body)
+        assert_equal(len(r['page']['attachments']), 2)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/703ccaab/ForgeWiki/forgewiki/wiki_main.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/wiki_main.py b/ForgeWiki/forgewiki/wiki_main.py
index 1dc9e06..66d01b7 100644
--- a/ForgeWiki/forgewiki/wiki_main.py
+++ b/ForgeWiki/forgewiki/wiki_main.py
@@ -22,7 +22,7 @@ from urllib import unquote
 from datetime import datetime
 
 # Non-stdlib imports
-from tg import expose, validate, redirect, response, flash, config
+from tg import expose, validate, redirect, response, flash
 from tg.decorators import with_trailing_slash, without_trailing_slash
 from tg.controllers import RestController
 from pylons import tmpl_context as c, app_globals as g
@@ -30,7 +30,6 @@ from pylons import request
 from formencode import validators
 from webob import exc
 from ming.orm import session
-from urlparse import urljoin
 
 # Pyforge-specific imports
 from allura import model as M
@@ -39,7 +38,7 @@ from allura.app import Application, SitemapEntry, DefaultAdminController
 from allura.lib.search import search_app
 from allura.lib.decorators import require_post, Property
 from allura.lib.security import require_access, has_access
-from allura.controllers import AppDiscussionController, BaseController
+from allura.controllers import AppDiscussionController, BaseController, AppDiscussionRestController
 from allura.controllers import DispatchIndex
 from allura.controllers import attachments as ac
 from allura.controllers.feed import FeedArgs, FeedController
@@ -681,10 +680,16 @@ Some *emphasized* and **strong** text
 
 '''
 
-class RootRestController(RestController):
+class RootRestController(BaseController):
+
+    def __init__(self):
+        self._discuss = AppDiscussionRestController()
+
+    def _check_security(self):
+        require_access(c.app, 'read')
 
     @expose('json:')
-    def get_all(self, **kw):
+    def index(self, **kw):
         page_titles = []
         pages = WM.Page.query.find(dict(app_config_id=c.app.config._id, deleted=False))
         for page in pages:
@@ -692,21 +697,10 @@ class RootRestController(RestController):
                 page_titles.append(page.title)
         return dict(pages=page_titles)
 
-    @expose('json:')
-    def get_one(self, title, **kw):
-        page = WM.Page.query.get(app_config_id=c.app.config._id, title=h.really_unicode(title), deleted=False)
-        if page is None:
-            raise exc.HTTPNotFound, title
-        require_access(page, 'read')
-        return dict(title=page.title,
-                    text=page.text,
-                    labels=page.labels,
-                    discussion_thread=page.discussion_thread,
-                    discussion_thread_url=urljoin(config.get('base_url', 'http://sourceforge.net/'),
-                                                  '/rest%s' % page.discussion_thread.url()),
-                    attachments=[dict(bytes=attach.length,
-                                      url=urljoin(config.get('base_url', 'http://sourceforge.net/'),
-                                                  attach.url())) for attach in page.attachments])
+    @expose()
+    def _lookup(self, title, *remainder):
+        return PageRestController(title), remainder
+
 
     @h.vardec
     @expose()
@@ -727,6 +721,23 @@ class RootRestController(RestController):
             page.labels = post_data['labels'].split(',')
         page.commit()
 
+class PageRestController(BaseController):
+
+    def __init__(self, title):
+        if title is not None:
+            self.page = WM.Page.query.get(app_config_id=c.app.config._id,
+                                          title=h.really_unicode(unquote(title)),
+                                          deleted=False)
+            if self.page is None:
+                raise exc.HTTPNotFound()
+
+    def _check_security(self):
+        require_access(self.page, 'read')
+
+    @expose('json:')
+    def index(self, **kw):
+        return dict(page=self.page)
+
 
 class WikiAdminController(DefaultAdminController):