You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by je...@apache.org on 2015/02/24 12:48:15 UTC

[21/50] [abbrv] allura git commit: [#4542] ticket:726 Move 'Webhooks' from sidebar to app admin

[#4542] ticket:726 Move 'Webhooks' from sidebar to app admin


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

Branch: refs/heads/ib/7827
Commit: 8cbd33080b76954ea7a0cc55898189d79f3f1f99
Parents: 13a8a60
Author: Igor Bondarenko <je...@gmail.com>
Authored: Wed Feb 11 14:46:49 2015 +0000
Committer: Igor Bondarenko <je...@gmail.com>
Committed: Mon Feb 16 10:17:40 2015 +0000

----------------------------------------------------------------------
 Allura/allura/app.py                            |  56 +++++++++-
 Allura/allura/ext/admin/admin_main.py           |  28 -----
 .../ext/admin/templates/webhooks_list.html      |  74 -------------
 Allura/allura/lib/app_globals.py                |   1 +
 Allura/allura/lib/repository.py                 |   3 -
 Allura/allura/model/webhook.py                  |  15 +--
 .../templates/app_admin_webhooks_list.html      |  71 ++++++++++++
 .../allura/templates/webhooks/create_form.html  |  11 --
 Allura/allura/webhooks.py                       | 108 +++++++------------
 ForgeBlog/forgeblog/main.py                     |   3 -
 ForgeShortUrl/forgeshorturl/main.py             |   3 -
 ForgeTracker/forgetracker/tracker_main.py       |   4 +-
 ForgeWiki/forgewiki/wiki_main.py                |   3 -
 13 files changed, 169 insertions(+), 211 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/Allura/allura/app.py
----------------------------------------------------------------------
diff --git a/Allura/allura/app.py b/Allura/allura/app.py
index fff483d..388b8a2 100644
--- a/Allura/allura/app.py
+++ b/Allura/allura/app.py
@@ -30,6 +30,7 @@ from paste.deploy.converters import asbool, asint
 from bson import ObjectId
 from bson.errors import InvalidId
 from formencode import validators as V
+from webob import exc
 
 from ming.orm import session
 from ming.utils import LazyProperty
@@ -40,6 +41,7 @@ from allura import model
 from allura.controllers import BaseController
 from allura.lib.decorators import require_post, memoize
 from allura.lib.utils import permanent_redirect, ConfigProxy
+from allura import model as M
 
 log = logging.getLogger(__name__)
 
@@ -282,6 +284,12 @@ class Application(object):
         """
         return self.config.url(project=self.project)
 
+    @LazyProperty
+    def admin_url(self):
+        return '{}{}/{}/'.format(
+            c.project.url(), 'admin',
+            self.config.options.mount_point)
+
     @property
     def email_address(self):
         """Return email address for this Application.
@@ -524,16 +532,28 @@ class Application(object):
         """
         return ""
 
+    @LazyProperty
+    def _webhooks(self):
+        """A list of webhooks that can be triggered by this app.
+
+        :return: a list of :class:`WebhookSender <allura.webhooks.WebhookSender>`
+        """
+        tool_name = self.config.tool_name.lower()
+        webhooks = [w for w in g.entry_points['webhooks'].itervalues()
+                    if tool_name in w.triggered_by]
+        return webhooks
+
     def admin_menu(self, force_options=False):
         """Return the admin menu for this Application.
 
-        Default implementation will return a menu with up to 3 links:
+        Default implementation will return a menu with up to 4 links:
 
             - 'Permissions', if the current user has admin access to the
                 project in which this Application is installed
             - 'Options', if this Application has custom options, or
                 ``force_options`` is True
             - 'Label', for editing this Application's label
+            - 'Webhooks', if this Application can trigger any webhooks
 
         Subclasses should override this method to provide additional admin
         menu items.
@@ -554,6 +574,8 @@ class Application(object):
                 SitemapEntry('Options', admin_url + 'options', className='admin_modal'))
         links.append(
             SitemapEntry('Label', admin_url + 'edit_label', className='admin_modal'))
+        if len(self._webhooks) > 0:
+            links.append(SitemapEntry('Webhooks', admin_url + 'webhooks'))
         return links
 
     def handle_message(self, topic, message):
@@ -672,7 +694,9 @@ class DefaultAdminController(BaseController):
         """Instantiate this controller for an :class:`app <Application>`.
 
         """
+        super(DefaultAdminController, self).__init__()
         self.app = app
+        self.webhooks = WebhooksLookup(app)
 
     @expose()
     def index(self, **kw):
@@ -858,3 +882,33 @@ class DefaultAdminController(BaseController):
                 if (ace.permission == perm) and (ace.access == model.ACE.DENY):
                     self.app.config.acl.append(ace)
         redirect(request.referer)
+
+
+class WebhooksLookup(BaseController):
+
+    def __init__(self, app):
+        super(WebhooksLookup, self).__init__()
+        self.app = app
+
+    @without_trailing_slash
+    @expose('jinja:allura:templates/app_admin_webhooks_list.html')
+    def index(self):
+        webhooks = self.app._webhooks
+        if len(webhooks) == 0:
+            raise exc.HTTPNotFound()
+        configured_hooks = {}
+        for hook in webhooks:
+            configured_hooks[hook.type] = M.Webhook.query.find({
+                'type': hook.type,
+                'app_config_id': self.app.config._id}
+            ).all()
+        return {'webhooks': webhooks,
+                'configured_hooks': configured_hooks,
+                'admin_url': self.app.admin_url + 'webhooks'}
+
+    @expose()
+    def _lookup(self, name, *remainder):
+        for hook in self.app._webhooks:
+            if hook.type == name and hook.controller:
+                return hook.controller(hook, self.app), remainder
+        raise exc.HTTPNotFound, name

http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/Allura/allura/ext/admin/admin_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index 787bb8c..b812b0d 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.py
@@ -148,7 +148,6 @@ class AdminApp(Application):
                     SitemapEntry('Categorization', admin_url + 'trove')
                 ]
         links.append(SitemapEntry('Tools', admin_url + 'tools'))
-        links.append(SitemapEntry('Webhooks', admin_url + 'webhooks'))
         if asbool(config.get('bulk_export_enabled', True)):
             links.append(SitemapEntry('Export', admin_url + 'export'))
         if c.project.is_root and has_access(c.project, 'admin')():
@@ -193,32 +192,6 @@ class AdminExtensionLookup(object):
         raise exc.HTTPNotFound, name
 
 
-class WebhooksLookup(BaseController):
-
-    @LazyProperty
-    def _webhooks(self):
-        webhooks = h.iter_entry_points('allura.webhooks')
-        webhooks = [ep.load() for ep in webhooks]
-        return webhooks
-
-    @without_trailing_slash
-    @expose('jinja:allura.ext.admin:templates/webhooks_list.html')
-    def index(self):
-        webhooks = self._webhooks
-        configured_hooks = {}
-        for hook in webhooks:
-            configured_hooks[hook.type] = M.Webhook.find(hook.type, c.project)
-        return {'webhooks': webhooks,
-                'configured_hooks': configured_hooks}
-
-    @expose()
-    def _lookup(self, name, *remainder):
-        for hook in self._webhooks:
-            if hook.type == name and hook.controller:
-                return hook.controller(hook), remainder
-        raise exc.HTTPNotFound, name
-
-
 class ProjectAdminController(BaseController):
     def _check_security(self):
         require_access(c.project, 'admin')
@@ -228,7 +201,6 @@ class ProjectAdminController(BaseController):
         self.groups = GroupsController()
         self.audit = AuditController()
         self.ext = AdminExtensionLookup()
-        self.webhooks = WebhooksLookup()
 
     @with_trailing_slash
     @expose('jinja:allura.ext.admin:templates/project_admin.html')

http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/Allura/allura/ext/admin/templates/webhooks_list.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/templates/webhooks_list.html b/Allura/allura/ext/admin/templates/webhooks_list.html
deleted file mode 100644
index f54b7fb..0000000
--- a/Allura/allura/ext/admin/templates/webhooks_list.html
+++ /dev/null
@@ -1,74 +0,0 @@
-{#-
-       Licensed to the Apache Software Foundation (ASF) under one
-       or more contributor license agreements.  See the NOTICE file
-       distributed with this work for additional information
-       regarding copyright ownership.  The ASF licenses this file
-       to you under the Apache License, Version 2.0 (the
-       "License"); you may not use this file except in compliance
-       with the License.  You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-       Unless required by applicable law or agreed to in writing,
-       software distributed under the License is distributed on an
-       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-       KIND, either express or implied.  See the License for the
-       specific language governing permissions and limitations
-       under the License.
--#}
-{% extends g.theme.master %}
-
-{% block title %}{{c.project.name}} / Webhooks{% endblock %}
-{% block header %}Webhooks{% endblock %}
-
-{% block content %}
-  {% for hook in webhooks %}
-    <h1>{{ hook.type }}</h1>
-    <p><a href="{{c.app.url}}webhooks/{{ hook.type }}">Create</a></p>
-    {% if configured_hooks[hook.type] %}
-      <table>
-        {% for wh in configured_hooks[hook.type] %}
-        <tr>
-          <td>
-            <a href="{{ wh.url() }}">{{ wh.hook_url }}</a>
-          </td>
-          <td>
-            <a href="{{ wh.app_config.url() }}">{{ wh.app_config.options.mount_label }}</a>
-          </td>
-          <td>{{ wh.secret or '' }}</td>
-          <td>
-            <a href="{{c.app.url}}webhooks/{{hook.type}}/delete"
-               class="delete-link"
-               data-id="{{h.really_unicode(wh._id)}}"
-               title="Delete">
-              <b data-icon="{{g.icons['delete'].char}}" class="ico {{g.icons['delete'].css}}" title="Delete"></b>
-            </a>
-          </td>
-        </tr>
-        {% endfor %}
-      </table>
-    {% endif %}
-  {% endfor %}
-{% endblock %}
-
-{% block extra_js %}
-<script type="text/javascript">
-$(function() {
-  $('.delete-link').click(function(e) {
-    e.preventDefault();
-    var id = $(this).attr('data-id');
-    var csrf = $.cookie('_session_id');
-    var data = {'webhook': id, '_session_id': csrf};
-    var url = $(this).attr('href');
-    var $tr = $(this).parents('tr')
-    $.post(url, data, function(data) {
-      if (data['status'] == 'ok') {
-        $tr.remove();
-      } else {
-        console.log(data);
-      }
-    });
-  });
-});
-</script>
-{% endblock %}

http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/Allura/allura/lib/app_globals.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index f3cb551..fc49749 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -284,6 +284,7 @@ class Globals(object):
             # macro eps are used solely for ensuring that external macros are
             # imported (after load, the ep itself is not used)
             macros=_cache_eps('allura.macros'),
+            webhooks=_cache_eps('allura.webhooks'),
         )
 
         # Neighborhood cache

http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/Allura/allura/lib/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/repository.py b/Allura/allura/lib/repository.py
index e0b139e..9a4966f 100644
--- a/Allura/allura/lib/repository.py
+++ b/Allura/allura/lib/repository.py
@@ -220,9 +220,6 @@ class RepositoryApp(Application):
 
 class RepoAdminController(DefaultAdminController):
 
-    def __init__(self, app):
-        self.app = app
-
     @LazyProperty
     def repo(self):
         return self.app.repo

http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/Allura/allura/model/webhook.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/webhook.py b/Allura/allura/model/webhook.py
index 09cc7fa..4c39309 100644
--- a/Allura/allura/model/webhook.py
+++ b/Allura/allura/model/webhook.py
@@ -36,17 +36,10 @@ class Webhook(Artifact):
     last_sent = FieldProperty(dt.datetime, if_missing=None)
 
     def url(self):
-        return '{}{}/{}/{}'.format(
-            self.app_config.project.url(),
-            'admin/webhooks',
-            self.type,
-            self._id)
-
-    @classmethod
-    def find(cls, type, project):
-        ac_ids = [ac._id for ac in project.app_configs]
-        hooks = cls.query.find(dict(type=type, app_config_id={'$in': ac_ids}))
-        return hooks.all()
+        return '{}{}/{}/webhooks/{}/{}'.format(
+            self.app_config.project.url(), 'admin',
+            self.app_config.options.mount_point,
+            self.type, self._id)
 
     def enforce_limit(self):
         '''Returns False if limit is reached, otherwise True'''

http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/Allura/allura/templates/app_admin_webhooks_list.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/app_admin_webhooks_list.html b/Allura/allura/templates/app_admin_webhooks_list.html
new file mode 100644
index 0000000..0fe02e9
--- /dev/null
+++ b/Allura/allura/templates/app_admin_webhooks_list.html
@@ -0,0 +1,71 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+-#}
+{% extends g.theme.master %}
+
+{% block title %}{{c.project.name}} / Webhooks{% endblock %}
+{% block header %}Webhooks{% endblock %}
+
+{% block content %}
+  {% for hook in webhooks %}
+    <h1>{{ hook.type }}</h1>
+    <p><a href="{{admin_url}}/{{hook.type}}">Create</a></p>
+    {% if configured_hooks[hook.type] %}
+      <table>
+        {% for wh in configured_hooks[hook.type] %}
+        <tr>
+          <td>
+            <a href="{{ wh.url() }}">{{ wh.hook_url }}</a>
+          </td>
+          <td>{{ wh.secret or '' }}</td>
+          <td>
+            <a href="{{admin_url}}/{{hook.type}}/delete"
+               class="delete-link"
+               data-id="{{h.really_unicode(wh._id)}}"
+               title="Delete">
+              <b data-icon="{{g.icons['delete'].char}}" class="ico {{g.icons['delete'].css}}" title="Delete"></b>
+            </a>
+          </td>
+        </tr>
+        {% endfor %}
+      </table>
+    {% endif %}
+  {% endfor %}
+{% endblock %}
+
+{% block extra_js %}
+<script type="text/javascript">
+$(function() {
+  $('.delete-link').click(function(e) {
+    e.preventDefault();
+    var id = $(this).attr('data-id');
+    var csrf = $.cookie('_session_id');
+    var data = {'webhook': id, '_session_id': csrf};
+    var url = $(this).attr('href');
+    var $tr = $(this).parents('tr')
+    $.post(url, data, function(data) {
+      if (data['status'] == 'ok') {
+        $tr.remove();
+      } else {
+        console.log(data);
+      }
+    });
+  });
+});
+</script>
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/Allura/allura/templates/webhooks/create_form.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/webhooks/create_form.html b/Allura/allura/templates/webhooks/create_form.html
index 6653985..95bfdf6 100644
--- a/Allura/allura/templates/webhooks/create_form.html
+++ b/Allura/allura/templates/webhooks/create_form.html
@@ -72,17 +72,6 @@
     {{ error('url') }}
   </div>
   <div>
-    <label for="app">app</label>
-    <select name="app">
-      {% for ac in form.triggered_by %}
-        <option value="{{ac._id}}"{% if h.really_unicode(ac._id) == c.form_values['app'] %} selected{% endif %}>
-          {{ ac.options.mount_label }}
-        </option>
-      {% endfor %}
-    </select>
-    {{ error('app') }}
-  </div>
-  <div>
     <label for="secret">secret (leave empty to autogenerate)</label>
     <input name="secret" value="{{ c.form_values['secret'] }}">
     {{ error('secret') }}

http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/Allura/allura/webhooks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/webhooks.py b/Allura/allura/webhooks.py
index 17d7ebf..233c3d5 100644
--- a/Allura/allura/webhooks.py
+++ b/Allura/allura/webhooks.py
@@ -45,23 +45,9 @@ from allura import model as M
 log = logging.getLogger(__name__)
 
 
-class MingOneOf(av.Ming):
-    def __init__(self, ids, **kw):
-        self.ids = ids
-        super(MingOneOf, self).__init__(**kw)
-
-    def _to_python(self, value, state):
-        result = super(MingOneOf, self)._to_python(value, state)
-        if result and result._id in self.ids:
-            return result
-        raise Invalid(
-            u'Object must be one of: {}, not {}'.format(self.ids, value),
-            value, state)
-
-
 class WebhookValidator(fev.FancyValidator):
-    def __init__(self, sender, ac_ids, **kw):
-        self.ac_ids = ac_ids
+    def __init__(self, sender, app, **kw):
+        self.app = app
         self.sender = sender
         super(WebhookValidator, self).__init__(**kw)
 
@@ -76,50 +62,39 @@ class WebhookValidator(fev.FancyValidator):
                 wh = M.Webhook.query.get(_id=ObjectId(value))
             except:
                 pass
-        if wh and wh.type == self.sender.type and wh.app_config_id in self.ac_ids:
+        if wh and wh.type == self.sender.type and wh.app_config_id == self.app.config._id:
             return wh
         raise Invalid(u'Invalid webhook', value, state)
 
 
 class WebhookCreateForm(schema.Schema):
-    def __init__(self, sender):
-        super(WebhookCreateForm, self).__init__()
-        self.triggered_by = [ac for ac in c.project.app_configs
-                             if ac.tool_name.lower() in sender.triggered_by]
-        self.add_field('app', MingOneOf(
-            cls=M.AppConfig,
-            ids=[ac._id for ac in self.triggered_by],
-            not_empty=True))
-
     url = fev.URL(not_empty=True)
     secret = fev.UnicodeString()
 
 
 class WebhookEditForm(WebhookCreateForm):
-    def __init__(self, sender):
-        super(WebhookEditForm, self).__init__(sender)
+    def __init__(self, sender, app):
+        super(WebhookEditForm, self).__init__()
         self.add_field('webhook', WebhookValidator(
-            sender=sender,
-            ac_ids=[ac._id for ac in self.triggered_by],
-            not_empty=True))
+            sender=sender, app=app, not_empty=True))
 
 
 class WebhookControllerMeta(type):
-    def __call__(cls, sender, *args, **kw):
+    def __call__(cls, sender, app, *args, **kw):
         """Decorate post handlers with a validator that references
         the appropriate webhook sender for this controller.
         """
         if hasattr(cls, 'create'):
             cls.create = validate(
-                cls.create_form(sender),
+                cls.create_form(),
                 error_handler=cls.index.__func__,
             )(cls.create)
         if hasattr(cls, 'edit'):
             cls.edit = validate(
-                cls.edit_form(sender),
+                cls.edit_form(sender, app),
                 error_handler=cls._default.__func__,
             )(cls.edit)
-        return type.__call__(cls, sender, *args, **kw)
+        return type.__call__(cls, sender, app, *args, **kw)
 
 
 class WebhookController(BaseController):
@@ -127,80 +102,71 @@ class WebhookController(BaseController):
     create_form = WebhookCreateForm
     edit_form = WebhookEditForm
 
-    def __init__(self, sender):
+    def __init__(self, sender, app):
         super(WebhookController, self).__init__()
         self.sender = sender()
+        self.app = app
 
     def gen_secret(self):
         return h.cryptographic_nonce(20)
 
-    def update_webhook(self, wh, url, ac, secret=None):
+    def update_webhook(self, wh, url, secret=None):
         if not secret:
             secret = self.gen_secret()
         wh.hook_url = url
-        wh.app_config_id = ac._id
         wh.secret = secret
         try:
             session(wh).flush(wh)
         except DuplicateKeyError:
             session(wh).expunge(wh)
             msg = u'_the_form: "{}" webhook already exists for {} {}'.format(
-                wh.type, ac.options.mount_label, url)
+                wh.type, self.app.config.options.mount_label, url)
             raise Invalid(msg, None, None)
 
-    def form_app_id(self, app):
-        if app and isinstance(app, M.AppConfig):
-            _app = unicode(app._id)
-        elif app:
-            _app = unicode(app)
-        else:
-            _app = None
-        return _app
-
     @with_trailing_slash
     @expose('jinja:allura:templates/webhooks/create_form.html')
     def index(self, **kw):
         if not c.form_values and kw:
             # Executes if update_webhook raises an error
-            _app = self.form_app_id(kw.get('app'))
             c.form_values = {'url': kw.get('url'),
-                             'app': _app,
                              'secret': kw.get('secret')}
         return {'sender': self.sender,
                 'action': 'create',
-                'form': self.create_form(self.sender)}
+                'form': self.create_form()}
 
     @expose()
     @require_post()
-    def create(self, url, app, secret):
-        if self.sender.enforce_limit(app):
-            wh = M.Webhook(type=self.sender.type)
-            self.update_webhook(wh, url, app, secret)
+    def create(self, url, secret):
+        if self.sender.enforce_limit(self.app):
+            webhook = M.Webhook(
+                type=self.sender.type,
+                app_config_id=self.app.config._id)
+            self.update_webhook(webhook, url, secret)
             M.AuditLog.log('add webhook %s %s %s',
-                           wh.type, wh.hook_url, wh.app_config.url())
+                           webhook.type, webhook.hook_url,
+                           webhook.app_config.url())
             flash('Created successfully', 'ok')
         else:
-            flash('You have exceeded the maximum number of projects '
+            flash('You have exceeded the maximum number of webhooks '
                   'you are allowed to create for this project/app', 'error')
-        redirect(c.project.url() + 'admin/webhooks/')
+        redirect(self.app.admin_url + 'webhooks')
 
     @expose()
     @require_post()
-    def edit(self, webhook, url, app, secret):
+    def edit(self, webhook, url, secret):
         old_url = webhook.hook_url
-        old_app = webhook.app_config.url()
         old_secret = webhook.secret
-        self.update_webhook(webhook, url, app, secret)
-        M.AuditLog.log('edit webhook %s\n%s => %s\n%s => %s\n%s',
-            webhook.type, old_url, url, old_app, app.url(),
-            'secret changed' if old_secret != secret else '')
+        self.update_webhook(webhook, url, secret)
+        M.AuditLog.log('edit webhook %s\n%s => %s\n%s',
+                       webhook.type, old_url, url,
+                       'secret changed' if old_secret != secret else '')
         flash('Edited successfully', 'ok')
-        redirect(c.project.url() + 'admin/webhooks/')
+        redirect(self.app.admin_url + 'webhooks')
 
     @expose('json:')
     @require_post()
     def delete(self, webhook):
-        form = self.edit_form(self.sender)
+        form = self.edit_form(self.sender, self.app)
         try:
             wh = form.fields['webhook'].to_python(webhook)
         except Invalid:
@@ -213,14 +179,12 @@ class WebhookController(BaseController):
     @without_trailing_slash
     @expose('jinja:allura:templates/webhooks/create_form.html')
     def _default(self, webhook, **kw):
-        form = self.edit_form(self.sender)
+        form = self.edit_form(self.sender, self.app)
         try:
             wh = form.fields['webhook'].to_python(webhook)
         except Invalid:
             raise exc.HTTPNotFound()
-        _app = self.form_app_id(kw.get('app')) or unicode(wh.app_config._id)
         c.form_values = {'url': kw.get('url') or wh.hook_url,
-                         'app': _app,
                          'secret': kw.get('secret') or wh.secret,
                          'webhook': unicode(wh._id)}
         return {'sender': self.sender,
@@ -336,7 +300,7 @@ class WebhookSender(object):
                 else:
                     log.warn('Webhook fires too often: %s. Skipping', webhook)
 
-    def enforce_limit(self, app_config):
+    def enforce_limit(self, app):
         '''
         Checks if limit of webhooks created for given project/app is reached.
         Returns False if limit is reached, True otherwise.
@@ -344,10 +308,10 @@ class WebhookSender(object):
         _type = self.type.replace('-', '_')
         limits = json.loads(config.get('webhook.%s.max_hooks' % _type, '{}'))
         count = M.Webhook.query.find(dict(
-            app_config_id=app_config._id,
+            app_config_id=app.config._id,
             type=self.type,
         )).count()
-        return count < limits.get(app_config.tool_name.lower(), 3)
+        return count < limits.get(app.config.tool_name.lower(), 3)
 
 
 class RepoPushWebhookSender(WebhookSender):

http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/ForgeBlog/forgeblog/main.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/main.py b/ForgeBlog/forgeblog/main.py
index 1e3f604..93cd01b 100644
--- a/ForgeBlog/forgeblog/main.py
+++ b/ForgeBlog/forgeblog/main.py
@@ -395,9 +395,6 @@ class PostController(BaseController, FeedController):
 
 class BlogAdminController(DefaultAdminController):
 
-    def __init__(self, app):
-        self.app = app
-
     @without_trailing_slash
     @expose('jinja:forgeblog:templates/blog/admin_options.html')
     def options(self):

http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/ForgeShortUrl/forgeshorturl/main.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/main.py b/ForgeShortUrl/forgeshorturl/main.py
index f4ab4ed..b259e92 100644
--- a/ForgeShortUrl/forgeshorturl/main.py
+++ b/ForgeShortUrl/forgeshorturl/main.py
@@ -198,9 +198,6 @@ class ShortURLAdminController(DefaultAdminController):
         )
     )
 
-    def __init__(self, app):
-        self.app = app
-
     @expose()
     def index(self, **kw):
         redirect(c.project.url() + 'admin/tools')

http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/ForgeTracker/forgetracker/tracker_main.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index b38bf1d..a7c4e61 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -1528,8 +1528,8 @@ NONALNUM_RE = re.compile(r'\W+')
 class TrackerAdminController(DefaultAdminController):
 
     def __init__(self, app):
-        self.app = app
-        self.bins = BinController(app=app)
+        super(TrackerAdminController, self).__init__(app)
+        self.bins = BinController(app=self.app)
         # if self.app.globals and self.app.globals.milestone_names is None:
         #     self.app.globals.milestone_names = ''
 

http://git-wip-us.apache.org/repos/asf/allura/blob/8cbd3308/ForgeWiki/forgewiki/wiki_main.py
----------------------------------------------------------------------
diff --git a/ForgeWiki/forgewiki/wiki_main.py b/ForgeWiki/forgewiki/wiki_main.py
index 3341377..ecbddef 100644
--- a/ForgeWiki/forgewiki/wiki_main.py
+++ b/ForgeWiki/forgewiki/wiki_main.py
@@ -801,9 +801,6 @@ class PageRestController(BaseController):
 
 class WikiAdminController(DefaultAdminController):
 
-    def __init__(self, app):
-        self.app = app
-
     def _check_security(self):
         require_access(self.app, 'configure')