Просмотр исходного кода

Massive refactoring of plugins. New SentryMiddleware is now required as part of configuration.

David Cramer 13 лет назад
Родитель
Сommit
ae9e90041c

+ 11 - 0
docs/install/index.rst

@@ -48,6 +48,8 @@ upgrading the actual Sentry server.
 This includes several new tables (such as Project), and alters on almost all existing tables. It
 also means it needs to backfill the project_id column on all related tables.
 
+You should also read over the installation guide again, as some things have likely changed.
+
 Running a Sentry Server
 -----------------------
 
@@ -157,6 +159,15 @@ need to update your settings.py and add ``sentry`` and ``raven.contrib.django``
 
 .. note:: Raven is a seperate project, and the official Python client for Sentry.
 
+Next, add the required middleware to your settings::
+
+    MIDDLEWARE_CLASSES = (
+        ...
+        'sentry.middleware.SentryMiddleware',
+    )
+
+
+
 You will also need to add ``sentry.web.urls`` to your url patterns::
 
     urlpatterns = patterns('',

+ 6 - 38
runtests.py

@@ -13,46 +13,14 @@ logging.getLogger('sentry').addHandler(logging.StreamHandler())
 from django.conf import settings
 
 if not settings.configured:
-    settings.configure(
-        DATABASE_ENGINE='sqlite3',
-        DATABASES={
-            'default': {
-                'ENGINE': 'sqlite3',
-                'TEST_NAME': 'sentry_tests.db',
-            },
-        },
-        # HACK: this fixes our threaded runserver remote tests
-        # DATABASE_NAME='test_sentry',
-        TEST_DATABASE_NAME='sentry_tests.db',
-        INSTALLED_APPS=[
-            'django.contrib.auth',
-            'django.contrib.admin',
-            'django.contrib.sessions',
-            'django.contrib.sites',
+    os.environ['DJANGO_SETTINGS_MODULE'] = 'sentry.conf.server'
 
-            # Included to fix Disqus' test Django which solves IntegrityMessage case
-            'django.contrib.contenttypes',
+# override a few things with our test specifics
+settings.INSTALLED_APPS = tuple(settings.INSTALLED_APPS) + (
+    'tests',
+)
+settings.SENTRY_KEY = base64.b64encode(os.urandom(40))
 
-            'djkombu',
-            'south',
-
-            'sentry',
-
-            # included plugin tests
-            'sentry.plugins.sentry_servers',
-            'sentry.plugins.sentry_sites',
-            'sentry.plugins.sentry_urls',
-            'sentry.plugins.sentry_redmine',
-
-            'tests',
-        ],
-        ROOT_URLCONF='',
-        DEBUG=False,
-        SITE_ID=1,
-        SENTRY_THRASHING_LIMIT=0,
-        TEMPLATE_DEBUG=True,
-        SENTRY_KEY=base64.b64encode(os.urandom(40)),
-    )
 
 from django_nose import NoseTestSuiteRunner
 

+ 1 - 0
sentry/conf/server.py

@@ -78,6 +78,7 @@ MIDDLEWARE_CLASSES = (
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.csrf.CsrfViewMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'sentry.middleware.SentryMiddleware',
     # 'django.contrib.messages.middleware.MessageMiddleware',
 )
 

+ 25 - 0
sentry/middleware.py

@@ -0,0 +1,25 @@
+from sentry.models import Project
+from sentry.plugins import Plugin
+
+
+def get_plugins(request, project):
+    plugins = []
+    for cls in Plugin.plugins.itervalues():
+        plugin = cls(request)
+        plugin.configure(project)
+        plugins.append(plugin)
+    return plugins
+
+
+class SentryMiddleware(object):
+    def process_view(self, request, view_func, view_args, view_kwargs):
+        if 'project_id' not in view_kwargs:
+            return
+        project = Project.objects.get(pk=view_kwargs['project_id'])
+
+        request.plugins = get_plugins(request, project)
+
+    def process_response(self, request, response):
+        request.plugins = []
+
+        return response

+ 6 - 1
sentry/models.py

@@ -210,7 +210,12 @@ class Group(MessageBase):
         return int(math.log(self.times_seen) * 600 + float(time.mktime(self.last_seen.timetuple())))
 
     def get_latest_event(self):
-        return self.event_set.order_by('-id')[0]
+        if not hasattr(self, '_latest_event'):
+            try:
+                self._latest_event = self.event_set.order_by('-id')[0]
+            except IndexError:
+                self._latest_event = None
+        return self._latest_event
 
     def mail_admins(self, request=None, fail_silently=True):
         from django.core.mail import send_mail

+ 46 - 38
sentry/plugins/__init__.py

@@ -6,8 +6,19 @@ sentry.plugins
 :license: BSD, see LICENSE for more details.
 """
 
-# Based on http://martyalchin.com/2008/jan/10/simple-plugin-framework/
 from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+from sentry.web.helpers import render_to_response
+
+
+class Response(object):
+    def __init__(self, template, context=None):
+        self.template = template
+        self.context = context
+
+    def respond(self, request, context):
+        context.update(self.context)
+        return render_to_response(self.template, context, request)
 
 
 class PluginMount(type):
@@ -26,73 +37,70 @@ class PluginMount(type):
             cls.plugins[cls.slug] = cls
 
 
-class ActionProvider(object):
+class Plugin(object):
+    """
+    All children should allow **kwargs on all inherited methods.
     """
-    Base interface for adding action providers.
-
-    Plugins implementing this reference should provide the following attributes:
-
-    ========  ========================================================
-    title     The text to be displayed, describing the action
 
-    view      The view which will perform this action
+    __metaclass__ = PluginMount
 
-    selected  Boolean indicating whether the action is the one
-              currently being performed
+    enabled = True
 
-    ========  ========================================================
-    """
-    __metaclass__ = PluginMount
+    def __init__(self, request):
+        self.request = request
 
-    def __init__(self):
-        self.url = reverse('sentry-plugin-action', args=(self.slug,))
+    def __call__(self, group):
+        self.selected = self.request.path == self.get_url(group)
 
-    def __call__(self, request):
-        self.selected = request.path == self.url
         if not self.selected:
             return
 
-        return self.perform(request)
+        response = self.view(group)
 
+        if not response:
+            return
 
-class GroupActionProvider(object):
-    # TODO: should be able to specify modal support
+        if isinstance(response, HttpResponseRedirect):
+            return response
 
-    __metaclass__ = PluginMount
+        if not isinstance(response, Response):
+            raise NotImplementedError('Please use self.render() when returning responses.')
 
-    new_window = False
+        return response.respond(self.request, {
+            'project': group.project,
+            'group': group,
+        })
 
-    @classmethod
-    def get_url(cls, project_id, group_id):
-        return reverse('sentry-group-plugin-action', args=(project_id, group_id, cls.slug))
+    def redirect(self, url):
+        return HttpResponseRedirect(url)
 
-    def __init__(self, project_id, group_id):
-        self.url = self.__class__.get_url(project_id, group_id)
+    def render(self, template, context=None):
+        return Response(template, context)
 
-    def __call__(self, request, project, group):
-        self.selected = request.path == self.url
-        if not self.selected:
-            return
-        return self.view(request, project, group)
+    def configure(self, project):
+        pass
+
+    def get_url(self, group):
+        return reverse('sentry-group-plugin-action', args=(group.project_id, group.pk, self.slug))
 
-    def view(self, request, project, group):
+    def view(self, group, **kwargs):
         """
         Handles the view logic. If no response is given, we continue to the next action provider.
         """
 
-    def tags(self, request, tag_list, project, group):
+    def tags(self, group, tag_list, **kwargs):
         """Modifies the tag list for a grouped message."""
         return tag_list
 
-    def actions(self, request, action_list, project, group):
+    def actions(self, group, action_list, **kwargs):
         """Modifies the action list for a grouped message."""
         return action_list
 
-    def panels(self, request, panel_list, project, group):
+    def panels(self, group, panel_list, **kwargs):
         """Modifies the panel list for a grouped message."""
         return panel_list
 
-    def widget(self, request, project, group):
+    def widget(self, group, **kwargs):
         """
         Renders as a widget in the group details sidebar.
         """

+ 14 - 17
sentry/plugins/sentry_redmine/models.py

@@ -11,11 +11,10 @@ from django.core.context_processors import csrf
 from django.core.urlresolvers import reverse
 from django.db import models
 from django.http import HttpResponseRedirect
-from django.shortcuts import render_to_response
 from django.utils.safestring import mark_safe
 
 from sentry.models import Group
-from sentry.plugins import GroupActionProvider
+from sentry.plugins import Plugin
 from sentry.plugins.sentry_redmine import conf
 from sentry.utils import json
 
@@ -34,17 +33,17 @@ class RedmineIssueForm(forms.Form):
     description = forms.CharField(widget=forms.Textarea())
 
 
-class CreateRedmineIssue(GroupActionProvider):
+class CreateRedmineIssue(Plugin):
     title = 'Create Redmine Issue'
 
-    def actions(self, request, action_list, project, group):
+    def actions(self, group, action_list, **kwargs):
         if 'redmine' not in group.data:
-            action_list.append((self.title, self.__class__.get_url(project.pk, group.pk)))
+            action_list.append((self.title, self.get_url(group)))
         return action_list
 
-    def view(self, request, group):
-        if request.POST:
-            form = RedmineIssueForm(request.POST)
+    def view(self, group, **kwargs):
+        if self.request.POST:
+            form = RedmineIssueForm(self.request.POST)
             if form.is_valid():
                 data = json.dumps({
                     'key': conf.REDMINE_API_KEY,
@@ -86,9 +85,9 @@ class CreateRedmineIssue(GroupActionProvider):
                     RedmineIssue.objects.create(group=group, issue_id=data['issue']['id'])
                     group.data['redmine'] = {'issue_id': data['issue']['id']}
                     group.save()
-                    return HttpResponseRedirect(reverse('sentry-group', args=[group.project_id, group.pk]))
+                    return HttpResponseRedirect(reverse('sentry-group', args=[group.project.pk, group.pk]))
         else:
-            description = 'Sentry Message: %s' % request.build_absolute_uri(group.get_absolute_url())
+            description = 'Sentry Message: %s' % self.request.build_absolute_uri(group.get_absolute_url())
             description += '\n\n<pre>' + (group.traceback or group.message) + '</pre>'
 
             form = RedmineIssueForm(initial={
@@ -97,21 +96,19 @@ class CreateRedmineIssue(GroupActionProvider):
             })
 
         context = {
-            'request': request,
-            'group': group,
             'form': form,
             'global_errors': form.errors.get('__all__'),
             'BASE_TEMPLATE': 'sentry/groups/details.html',
         }
-        context.update(csrf(request))
+        context.update(csrf(self.request))
 
-        return render_to_response('sentry/plugins/redmine/create_issue.html', context)
+        return self.render('sentry/plugins/redmine/create_issue.html', context)
 
-    def tags(self, request, tags, project, group):
+    def tags(self, group, tag_list):
         if 'redmine' in group.data:
             issue_id = group.data['redmine']['issue_id']
-            tags.append(mark_safe('<a href="%s">#%s</a>' % (
+            tag_list.append(mark_safe('<a href="%s">#%s</a>' % (
                 '%s/issues/%s' % (conf.REDMINE_URL, issue_id),
                 issue_id,
             )))
-        return tags
+        return tag_list

+ 9 - 20
sentry/plugins/sentry_servers/models.py

@@ -6,13 +6,10 @@ sentry.plugins.sentry_servers.models
 :license: BSD, see LICENSE for more details.
 """
 
-from django.shortcuts import render_to_response
-from django.template.loader import render_to_string
+from sentry.plugins import Plugin
 
-from sentry.plugins import GroupActionProvider
 
-
-class ServerGroupPanel(GroupActionProvider):
+class ServerGroupPanel(Plugin):
     """Adds additional support for showing information about servers including:
 
     * A panel which shows all servers a message was seen on.
@@ -21,20 +18,12 @@ class ServerGroupPanel(GroupActionProvider):
 
     title = 'Servers'
 
-    def panels(self, request, panel_list, project, group):
-        panel_list.append((self.title, self.__class__.get_url(project.pk, group.pk)))
+    def panels(self, group, panel_list, **kwargs):
+        panel_list.append((self.title, self.get_url(group)))
         return panel_list
 
-    def view(self, request, project, group):
-        return render_to_response('sentry/plugins/sentry_servers/index.html', {
-            'request': request,
-            'project': project,
-            'group': group,
-        })
-
-    def widget(self, request, project, group):
-        return render_to_string('sentry/plugins/sentry_servers/widget.html', {
-            'request': request,
-            'project': project,
-            'group': group,
-        })
+    def view(self, group, **kwargs):
+        return self.render('sentry/plugins/sentry_servers/index.html')
+
+    def widget(self, group, **kwargs):
+        return self.render('sentry/plugins/sentry_servers/widget.html')

+ 9 - 20
sentry/plugins/sentry_sites/models.py

@@ -6,13 +6,10 @@ sentry.plugins.sentry_sites.models
 :license: BSD, see LICENSE for more details.
 """
 
-from django.shortcuts import render_to_response
-from django.template.loader import render_to_string
+from sentry.plugins import Plugin
 
-from sentry.plugins import GroupActionProvider
 
-
-class SiteGroupPanel(GroupActionProvider):
+class SiteGroupPanel(Plugin):
     """Adds additional support for showing information about sites including:
 
     * A panel which shows all sites a message was seen on.
@@ -21,20 +18,12 @@ class SiteGroupPanel(GroupActionProvider):
 
     title = 'Sites'
 
-    def panels(self, request, panel_list, project, group):
-        panel_list.append((self.title, self.__class__.get_url(project.pk, group.pk)))
+    def panels(self, group, panel_list, **kwargs):
+        panel_list.append((self.title, self.get_url(group)))
         return panel_list
 
-    def view(self, request, project, group):
-        return render_to_response('sentry/plugins/sentry_sites/index.html', {
-            'request': request,
-            'project': project,
-            'group': group,
-        })
-
-    def widget(self, request, project, group):
-        return render_to_string('sentry/plugins/sentry_sites/widget.html', {
-            'request': request,
-            'project': project,
-            'group': group,
-        })
+    def view(self, group, **kwargs):
+        return self.render('sentry/plugins/sentry_sites/index.html')
+
+    def widget(self, group, **kwargs):
+        return self.render('sentry/plugins/sentry_sites/widget.html')

+ 9 - 20
sentry/plugins/sentry_urls/models.py

@@ -6,13 +6,10 @@ sentry.plugins.sentry_urls.models
 :license: BSD, see LICENSE for more details.
 """
 
-from django.shortcuts import render_to_response
-from django.template.loader import render_to_string
+from sentry.plugins import Plugin
 
-from sentry.plugins import GroupActionProvider
 
-
-class ServerUrlsPanel(GroupActionProvider):
+class ServerUrlsPanel(Plugin):
     """Adds additional support for showing information about urls including:
 
     * A panel which shows all urls a message was seen on.
@@ -21,20 +18,12 @@ class ServerUrlsPanel(GroupActionProvider):
 
     title = 'URLs'
 
-    def panels(self, request, panel_list, project, group):
-        panel_list.append((self.title, self.__class__.get_url(project.pk, group.pk)))
+    def panels(self, group, panel_list, **kwargs):
+        panel_list.append((self.title, self.get_url(group)))
         return panel_list
 
-    def view(self, request, project, group):
-        return render_to_response('sentry/plugins/sentry_urls/index.html', {
-            'request': request,
-            'project': project,
-            'group': group,
-        })
-
-    def widget(self, request, project, group):
-        return render_to_string('sentry/plugins/sentry_urls/widget.html', {
-            'request': request,
-            'project': project,
-            'group': group,
-        })
+    def view(self, group, **kwargs):
+        return self.render('sentry/plugins/sentry_urls/index.html')
+
+    def widget(self, group, **kwargs):
+        return self.render('sentry/plugins/sentry_urls/widget.html')

Некоторые файлы не были показаны из-за большого количества измененных файлов