You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2012/09/25 04:13:10 UTC
[12/14] git commit: [#4637] ticket:146 Create URL shortener Allura App
[#4637] ticket:146 Create URL shortener Allura App
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/31f5f3ff
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/31f5f3ff
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/31f5f3ff
Branch: refs/heads/master
Commit: 31f5f3ff06c922c57a98b5b8f9a82437c25ece82
Parents: 593dfcc
Author: Yuriy Arhipov <yu...@yandex.ru>
Authored: Mon Sep 3 18:15:02 2012 +0400
Committer: Cory Johns <jo...@geek.net>
Committed: Fri Sep 21 20:06:16 2012 +0000
----------------------------------------------------------------------
Allura/allura/nf/allura/css/allura.css | 6 +
Allura/allura/nf/allura/css/site_style.css | 4 +
Allura/allura/tests/decorators.py | 4 +
ForgeShortUrl/forgeshorturl/main.py | 203 +++++++++++++++
ForgeShortUrl/forgeshorturl/model/shorturl.py | 30 +++
ForgeShortUrl/forgeshorturl/templates/add.html | 19 ++
ForgeShortUrl/forgeshorturl/templates/index.html | 69 +++++
.../forgeshorturl/tests/functional/test.py | 57 ++++
ForgeShortUrl/forgeshorturl/widgets/short_url.py | 8 +
ForgeShortUrl/setup.py | 20 ++
ForgeShortUrl/test.ini | 54 ++++
run_tests | 1 +
12 files changed, 475 insertions(+), 0 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/Allura/allura/nf/allura/css/allura.css
----------------------------------------------------------------------
diff --git a/Allura/allura/nf/allura/css/allura.css b/Allura/allura/nf/allura/css/allura.css
index 2896dea..5888fed 100644
--- a/Allura/allura/nf/allura/css/allura.css
+++ b/Allura/allura/nf/allura/css/allura.css
@@ -139,6 +139,9 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
#top_nav .ui-icon-tool-link {
background-image: url("../images/ext_32.png");
}
+#top_nav .ui-icon-tool-shorturl {
+ background-image: url("../images/ext_32.png");
+}
#top_nav .ui-icon-tool-blog {
background-image: url("../images/blog_32.png");
}
@@ -173,6 +176,9 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
.big_icon.ui-icon-tool-link {
background-image: url("../images/ext_48.png");
}
+.big_icon.ui-icon-tool-shorturl {
+ background-image: url("../images/ext_48.png");
+}
.big_icon.ui-icon-tool-blog {
background-image: url("../images/blog_48.png");
}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/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 ccfaccc..1d2ac7d 100644
--- a/Allura/allura/nf/allura/css/site_style.css
+++ b/Allura/allura/nf/allura/css/site_style.css
@@ -2222,6 +2222,10 @@ div.attachment_thumb .file_type span {
.ui-icon-tool-link {
background-repeat: no-repeat;
}
+.ui-icon-tool-shorturl {
+ background-repeat: no-repeat;
+}
+
.ui-icon-tool-blog {
background-repeat: no-repeat;
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/Allura/allura/tests/decorators.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/decorators.py b/Allura/allura/tests/decorators.py
index 4777f07..6595979 100644
--- a/Allura/allura/tests/decorators.py
+++ b/Allura/allura/tests/decorators.py
@@ -53,6 +53,10 @@ with_wiki = with_tool('test', 'Wiki', 'wiki')
with_git = with_tool('test', 'Git', 'src-git', 'Git', type='git')
with_hg = with_tool('test', 'Hg', 'src-hg', 'Mercurial', type='hg')
with_svn = with_tool('test', 'SVN', 'src', 'SVN')
+with_url = with_tool('test', 'ShortUrl', 'url')
+
+
+
def with_repos(func):
@wraps(func)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/forgeshorturl/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/__init__.py b/ForgeShortUrl/forgeshorturl/__init__.py
new file mode 100644
index 0000000..e69de29
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/forgeshorturl/main.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/main.py b/ForgeShortUrl/forgeshorturl/main.py
new file mode 100644
index 0000000..982d418
--- /dev/null
+++ b/ForgeShortUrl/forgeshorturl/main.py
@@ -0,0 +1,203 @@
+from tg import expose, validate, redirect, flash, request
+from allura.app import Application, SitemapEntry, DefaultAdminController
+from allura import model as M
+from allura.lib.security import require_access, has_access
+from allura.lib import helpers as h
+from allura.controllers import BaseController
+from allura.lib.widgets import form_fields as ffw
+from webob import exc
+from pylons import c, g
+from formencode import validators
+from formencode.compound import All
+import logging
+from forgeshorturl.model.shorturl import ShortUrl
+from datetime import datetime
+from forgeshorturl.widgets.short_url import CreateShortUrlWidget
+import pylons
+
+
+log = logging.getLogger(__name__)
+
+
+class W:
+ page_list = ffw.PageList()
+ page_size = ffw.PageSize()
+ create_short_url_lightbox = \
+ CreateShortUrlWidget(name='create_short_url',
+ trigger='#sidebar a.create_short_url')
+
+
+class ForgeShortUrlApp(Application):
+ permissions = ['create ', 'update', 'view_private']
+ searchable = True
+ tool_label = 'URL shortener'
+ default_mount_label = 'URL shortener'
+ default_mount_point = 'url'
+ sitemap = []
+ ordinal = 14
+ installable = False
+ hidden = True
+ icons = {
+ 24: 'images/ext_24.png',
+ 32: 'images/ext_32.png',
+ 48: 'images/ext_48.png'
+ }
+
+ def __init__(self, project, config):
+ Application.__init__(self, project, config)
+ self.root = RootController()
+ self.admin = ShortURLAdminController(self)
+
+ @property
+ @h.exceptionless([], log)
+ def sitemap(self):
+ menu_id = self.config.options.mount_label.title()
+ return [SitemapEntry(menu_id, '.')[self.sidebar_menu()]]
+
+ def sidebar_menu(self):
+ links = []
+ if has_access(c.app, "create"):
+ links += [SitemapEntry('Add Short URL',
+ c.project.url() +
+ 'admin/' +
+ self.config.options.mount_point +
+ '/add/',
+ ui_icon=g.icons['plus'],
+ className="create_short_url")]
+ return links
+
+ def admin_menu(self):
+ links = []
+ if has_access(c.app, "create"):
+ links = [SitemapEntry('Add Short URL',
+ c.project.url() +
+ 'admin/' +
+ self.config.options.mount_point +
+ '/add/',
+ className='admin_modal'), ]
+ links += [SitemapEntry('Browse',
+ c.project.url() +
+ self.config.options.mount_point), ]
+
+ links += super(ForgeShortUrlApp, self).admin_menu()
+ return links
+
+ def install(self, project):
+ 'Set up any default permissions and roles here'
+ self.config.options['project_name'] = project.name
+ super(ForgeShortUrlApp, self).install(project)
+ # Setup permissions
+ role_admin = M.ProjectRole.by_name('Admin')._id
+ role_anon = M.ProjectRole.anonymous()._id
+ self.config.acl = [
+ M.ACE.allow(role_admin, 'create'),
+ M.ACE.allow(role_admin, 'update'),
+ M.ACE.allow(role_admin, 'view_private'),
+ M.ACE.allow(role_anon, 'read'),
+ M.ACE.allow(role_admin, 'configure'), ]
+
+ def uninstall(self, project):
+ "Remove all the tool's artifacts from the database"
+ super(ForgeShortUrlApp, self).uninstall(project)
+
+
+class RootController(BaseController):
+ def __init__(self):
+ c.create_short_url_lightbox = W.create_short_url_lightbox
+
+ def _check_security(self):
+ require_access(c.app, 'read')
+
+ @expose('jinja:forgeshorturl:templates/index.html')
+ @validate(dict(page=validators.Int(if_empty=0),
+ limit=validators.Int(if_empty=100)))
+ def index(self, page=0, limit=100, **kw):
+ c.page_list = W.page_list
+ c.page_size = W.page_size
+ limit, pagenum, start = g.handle_paging(limit, page, default=100)
+ p = {'project_id': c.project._id}
+ if not has_access(c.app, 'view_private'):
+ p['private'] = False
+ short_urls = (ShortUrl.query.find(p))
+ count = short_urls.count()
+
+ short_urls = short_urls.skip(start).limit(limit)
+
+ return {
+ 'short_urls': short_urls,
+ 'limit': limit,
+ 'pagenum': pagenum,
+ 'count': count
+ }
+
+ @expose()
+ def _lookup(self, pname, *remainder):
+ if request.method == 'GET':
+ short_url = ShortUrl.query.find({'project_id': c.project._id,
+ 'short_name': pname}).first()
+ if short_url:
+ u = validators.URL(add_http=True)
+ redirect(u.to_python(short_url.url))
+
+ flash("We're sorry but we weren't able "
+ "to process this request.", "error")
+ raise exc.HTTPNotFound()
+
+
+class ShortURLAdminController(DefaultAdminController):
+ def __init__(self, app):
+ self.app = app
+
+ @expose()
+ def index(self, **kw):
+ redirect(c.project.url() + 'admin/tools')
+
+ @expose('jinja:forgeshorturl:templates/add.html')
+ @validate(dict(full_url=All(validators.URL(),
+ validators.NotEmpty()),
+ short_url=validators.NotEmpty()))
+ def add(self, short_url="",
+ full_url="",
+ description="",
+ private="off", **kw):
+ if (request.method == 'POST'):
+ if pylons.c.form_errors:
+ error_msg = "Error creating Short URL: "
+ for msg in list(pylons.c.form_errors):
+ names = {"short_url": "Short name", "full_url": "Full URL"}
+ error_msg += "%s - %s " % (names[msg], c.form_errors[msg])
+ flash(error_msg, "error")
+ redirect(request.referer)
+
+ if (short_url != full_url):
+ shorturl = ShortUrl.query.find({
+ 'short_name': short_url,
+ 'project_id': c.project._id}).first()
+ if shorturl is None:
+ shorturl = ShortUrl()
+ shorturl.created = datetime.utcnow()
+ log_msg = 'create short url %s for %s' %\
+ (short_url,
+ full_url)
+ else:
+ log_msg = 'update short url %s from %s to %s' %\
+ (short_url,
+ shorturl.url,
+ full_url)
+ shorturl.url = full_url
+ shorturl.short_name = short_url
+ shorturl.description = description
+ shorturl.create_user = c.user._id
+ shorturl.project_id = c.project._id
+ if private == "on":
+ shorturl.private = True
+ else:
+ shorturl.private = False
+ shorturl.last_updated = datetime.utcnow()
+ M.AuditLog.log(log_msg)
+ flash("Short url created")
+ else:
+ flash("Error creating Short URL: "
+ "Short Name and Full URL must be different", "error")
+ redirect(request.referer)
+ return dict(app=self.app)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/forgeshorturl/model/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/model/__init__.py b/ForgeShortUrl/forgeshorturl/model/__init__.py
new file mode 100644
index 0000000..e69de29
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/forgeshorturl/model/shorturl.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/model/shorturl.py b/ForgeShortUrl/forgeshorturl/model/shorturl.py
new file mode 100644
index 0000000..3fefe3d
--- /dev/null
+++ b/ForgeShortUrl/forgeshorturl/model/shorturl.py
@@ -0,0 +1,30 @@
+from ming.orm.declarative import MappedClass
+from allura.model import project_orm_session
+from ming.orm import FieldProperty, ForeignIdProperty
+from datetime import datetime
+from ming import schema
+from allura.model.auth import User
+from allura.model import Project
+from allura.model.timeline import ActivityNode, ActivityObject
+
+
+class ShortUrl(MappedClass, ActivityNode, ActivityObject):
+
+ class __mongometa__:
+ name = 'short_urs'
+ session = project_orm_session
+ indexes = ['short_name']
+
+ _id = FieldProperty(schema.ObjectId)
+ url = FieldProperty(str)
+ short_name = FieldProperty(str)
+ description = FieldProperty(str)
+ private = FieldProperty(bool)
+ create_user = ForeignIdProperty(User)
+ created = FieldProperty(datetime, if_missing=datetime.utcnow)
+ last_updated = FieldProperty(datetime, if_missing=datetime.utcnow)
+ project_id = ForeignIdProperty(Project)
+
+ @property
+ def user(self):
+ return User.query.get(_id=self.create_user)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/forgeshorturl/templates/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/templates/__init__.py b/ForgeShortUrl/forgeshorturl/templates/__init__.py
new file mode 100644
index 0000000..e69de29
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/forgeshorturl/templates/add.html
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/templates/add.html b/ForgeShortUrl/forgeshorturl/templates/add.html
new file mode 100644
index 0000000..63f5678
--- /dev/null
+++ b/ForgeShortUrl/forgeshorturl/templates/add.html
@@ -0,0 +1,19 @@
+{% block content %}
+<div>
+ <form method="post" action="{{c.project.url()}}admin/{{app.config.options.mount_point}}/add">
+ <label class="grid-13">Short name</label>
+ <div class="grid-13"><input type = "text" name = "short_url" style="width: 250px"></div>
+ <label class="grid-13">Full URL</label>
+ <div class="grid-13"><input type = "text" name = "full_url" style="width: 250px"></div>
+ <label class="grid-13">Description</label>
+ <div class="grid-13"><textarea name = "description" style="width: 250px; height: 100px"></textarea></div>
+ <div class="grid-1"><input type = "checkbox" name="private" id="private"></div>
+ <label for="private" class="grid-12">Private</label>
+ <div class="grid-13"> </div>
+ <hr>
+ <div class="grid-13"><div class="grid-13"> </div>
+ <input type="submit" value="Save">
+ <a href="#" class="close">Cancel</a></div>
+ </form>
+</div>
+{% endblock %}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/forgeshorturl/templates/index.html
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/templates/index.html b/ForgeShortUrl/forgeshorturl/templates/index.html
new file mode 100644
index 0000000..5bf36fb
--- /dev/null
+++ b/ForgeShortUrl/forgeshorturl/templates/index.html
@@ -0,0 +1,69 @@
+{% extends g.theme.master %}
+
+{% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / Link is not configured{% endblock %}
+
+{% block header %}{{c.app.config.options.mount_label}}{% endblock %}
+{% block short_url_content %}{% endblock %}
+{% block content %}
+
+
+<table>
+ <thead>
+ <tr>
+ <th>Private</th>
+ <th>Create user</th>
+ <th>Short URL</th>
+ <th>Full URL</th>
+ <th>Description</th>
+ <th>Created</th>
+ <th>Last updated</th>
+ </tr>
+ </thead>
+ {% for su in short_urls %}
+ <tr>
+ {%if su.private %}
+ <td><small>yes</small></td>
+ {% else %}
+ <td><small>no</small></td>
+ {% endif %}
+
+ <td><small>{{ su.user.username }}</small></td>
+ <td><small><a href="{{ c.app.url+su.short_name }}">{{ request.scheme+'://'+request.host+c.app.url+su.short_name}}</a></small></td>
+ <td><small>{{ su.url|urlize(20) }}</small></td>
+ <td><small>{{ su.description }}</small></td>
+ <td><small>{{ su.created }}</small></td>
+ <td><small>{{ su.last_updated }}</small></td>
+
+ </tr>
+ {% endfor %}
+
+</table>
+{{ c.page_list.display(limit=limit, count=count, page=pagenum) }}
+{% endblock %}
+{% block extra_js %}
+{{c.create_short_url_lightbox.display(content='''
+<div>
+ <h1>Add Short URL</h1>
+ <form method="post" action="{{c.project.url()}}admin/{{app.config.options.mount_point}}/add">
+ <label class="grid-13">Short name</label>
+ <div class="grid-13"><input type = "text" name = "short_url" style="width: 250px"></div>
+ <label class="grid-13">Full URL</label>
+ <div class="grid-13"><input type = "text" name = "full_url" style="width: 250px"></div>
+ <label class="grid-13">Description</label>
+ <div class="grid-13"><textarea name = "description" style="width: 250px; height: 100px"></textarea></div>
+ <div class="grid-1"><input type = "checkbox" name="private" id="private"></div>
+ <label for="private" class="grid-12">Private</label>
+ <div class="grid-13"> </div>
+ <hr>
+ <div class="grid-13"><div class="grid-13"> </div>
+ <input type="submit" value="Save">
+ <a href="#" class="close">Cancel</a></div>
+ </form>
+</div>
+''')}}
+<script type="text/javascript">
+ /*<![CDATA[*/
+ $('form').attr("action",$('#sidebar a.create_short_url').attr('href'));
+ /*]]>*/
+</script>
+{% endblock %}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/forgeshorturl/tests/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/tests/__init__.py b/ForgeShortUrl/forgeshorturl/tests/__init__.py
new file mode 100644
index 0000000..e69de29
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/forgeshorturl/tests/functional/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/tests/functional/__init__.py b/ForgeShortUrl/forgeshorturl/tests/functional/__init__.py
new file mode 100644
index 0000000..e69de29
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/forgeshorturl/tests/functional/test.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/tests/functional/test.py b/ForgeShortUrl/forgeshorturl/tests/functional/test.py
new file mode 100644
index 0000000..b143ed6
--- /dev/null
+++ b/ForgeShortUrl/forgeshorturl/tests/functional/test.py
@@ -0,0 +1,57 @@
+from allura.tests import decorators as td
+from alluratest.controller import TestController
+
+
+class TestRootController(TestController):
+ def setUp(self):
+ super(TestRootController, self).setUp()
+ self.setup_with_tools()
+
+ @td.with_url
+ def setup_with_tools(self):
+ pass
+
+ def test_shorturl_add(self):
+ response = self.app.get('/admin/url/add')
+ response.form['short_url'] = 'test'
+ response.form['full_url'] = 'http://www.google.com/'
+ response.form.submit()
+ redirected = self.app.get('/url/test').follow()
+ assert redirected.request.url == 'http://www.google.com/'
+
+ def test_shorturl_not_found(self):
+ self.app.post('/admin/url/add',
+ dict(short_url='test',
+ full_url='http://www.google.com/',
+ description="description2"))
+ r = self.app.get('/url/test2', status=404)
+ r = self.app.get('/url/')
+ assert 'http://www.google.com/' in r
+
+ def test_shorturl_private(self):
+ d = dict(short_url='test_private',
+ full_url='http://www.amazone.com/',
+ private='on',
+ description="description1")
+ self.app.post('/admin/url/add',
+ dict(short_url='test_private',
+ full_url='http://www.amazone.com/',
+ private='on',
+ description="description1"))
+
+ self.app.post('/admin/url/add',
+ dict(short_url='test',
+ full_url='http://www.google.com/',
+ description="description2"))
+ r = self.app.get('/url/', extra_environ=dict(username='*anonymous'))
+ assert 'http://www.google.com/' in r
+ assert 'http://www.amazone.com/' not in r
+
+ def test_shorturl_errors(self):
+ d = dict(short_url='http://www.amazone.com/',
+ full_url='http://www.amazone.com/')
+ r = self.app.post('/admin/url/add', params=d)
+ assert 'error' in self.webflash(r)
+ d = dict(short_url='http://www.amazone.com/', full_url='amazone')
+ r = self.app.post('/admin/url/add', params=d)
+ assert 'error' in self.webflash(r)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/forgeshorturl/widgets/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/widgets/__init__.py b/ForgeShortUrl/forgeshorturl/widgets/__init__.py
new file mode 100644
index 0000000..e69de29
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/forgeshorturl/widgets/short_url.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/widgets/short_url.py b/ForgeShortUrl/forgeshorturl/widgets/short_url.py
new file mode 100644
index 0000000..cc040e8
--- /dev/null
+++ b/ForgeShortUrl/forgeshorturl/widgets/short_url.py
@@ -0,0 +1,8 @@
+from allura.lib.widgets import form_fields as ffw
+
+
+class CreateShortUrlWidget(ffw.Lightbox):
+
+ def resources(self):
+ for r in super(CreateShortUrlWidget, self).resources():
+ yield r
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/setup.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/setup.py b/ForgeShortUrl/setup.py
new file mode 100644
index 0000000..641ae2f
--- /dev/null
+++ b/ForgeShortUrl/setup.py
@@ -0,0 +1,20 @@
+from setuptools import setup, find_packages
+
+
+setup(name='ForgeShortUrl',
+ description="",
+ long_description="",
+ classifiers=[],
+ keywords='',
+ author='',
+ author_email='',
+ url='',
+ license='',
+ packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=['Allura', ],
+ entry_points="""
+ # -*- Entry points: -*-
+ [allura]
+ ShortURL=forgeshorturl.main:ForgeShortUrlApp""",)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/ForgeShortUrl/test.ini
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/test.ini b/ForgeShortUrl/test.ini
new file mode 100644
index 0000000..8110f04
--- /dev/null
+++ b/ForgeShortUrl/test.ini
@@ -0,0 +1,54 @@
+#
+# allura - TurboGears 2 testing environment configuration
+#
+# The %(here)s variable will be replaced with the parent directory of this file
+#
+[DEFAULT]
+debug = true
+
+[server:main]
+use = egg:Paste#http
+host = 0.0.0.0
+port = 5000
+
+[app:main]
+use = config:../Allura/test.ini
+
+[app:main_without_authn]
+use = config:../Allura/test.ini#main_without_authn
+
+[app:main_with_amqp]
+use = config:../Allura/test.ini#main_with_amqp
+
+[loggers]
+keys = root, allura, tool
+
+[handlers]
+keys = test
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = test
+
+[logger_allura]
+level = DEBUG
+handlers =
+qualname = allura
+
+[logger_tool]
+level = DEBUG
+handlers =
+qualname = forgeshorturl
+
+[handler_test]
+class = FileHandler
+args = ('test.log',)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/31f5f3ff/run_tests
----------------------------------------------------------------------
diff --git a/run_tests b/run_tests
index b361ee3..decc004 100755
--- a/run_tests
+++ b/run_tests
@@ -23,6 +23,7 @@ if [ "$TEST_MODULES" == "" ]; then
ForgeWiki \
ForgeDownloads \
ForgeActivity \
+ ForgeShortUrl \
"
fi