PageRenderTime 75ms CodeModel.GetById 33ms app.highlight 38ms RepoModel.GetById 1ms app.codeStats 0ms

/src/sentry/admin.py

https://github.com/pombredanne/django-sentry
Python | 344 lines | 287 code | 53 blank | 4 comment | 5 complexity | 504d753ba6e9c2f38dec6fde59f4c5c2 MD5 | raw file
  1from __future__ import absolute_import
  2
  3from django import forms
  4from django.conf import settings
  5from django.conf.urls import url
  6from django.contrib import admin, messages
  7from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AdminPasswordChangeForm
  8from django.core.exceptions import PermissionDenied
  9from django.db import transaction
 10from django.http import Http404, HttpResponseRedirect
 11from django.utils.decorators import method_decorator
 12from django.views.decorators.csrf import csrf_protect
 13from django.views.decorators.debug import sensitive_post_parameters
 14from django.shortcuts import get_object_or_404
 15from django.template.response import TemplateResponse
 16from django.utils.translation import ugettext, ugettext_lazy as _
 17from pprint import saferepr
 18from sentry.models import (
 19    ApiKey,
 20    AuthIdentity,
 21    AuthProvider,
 22    AuditLogEntry,
 23    Option,
 24    Organization,
 25    OrganizationMember,
 26    Project,
 27    Team,
 28    User,
 29)
 30from sentry.utils.html import escape
 31
 32csrf_protect_m = method_decorator(csrf_protect)
 33sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())
 34
 35
 36class OptionAdmin(admin.ModelAdmin):
 37    list_display = ("key", "last_updated")
 38    fields = ("key", "value_repr", "last_updated")
 39    readonly_fields = ("key", "value_repr", "last_updated")
 40    search_fields = ("key",)
 41
 42    def value_repr(self, instance):
 43        return u'<pre style="display:inline-block;white-space:pre-wrap;">{}</pre>'.format(
 44            escape(saferepr(instance.value))
 45        )
 46
 47    value_repr.short_description = "Value"
 48    value_repr.allow_tags = True
 49
 50
 51admin.site.register(Option, OptionAdmin)
 52
 53
 54class ProjectAdmin(admin.ModelAdmin):
 55    list_display = ("name", "slug", "organization", "status", "date_added")
 56    list_filter = ("status", "public")
 57    search_fields = ("name", "organization__slug", "organization__name", "slug")
 58    raw_id_fields = ("organization",)
 59    readonly_fields = ("first_event", "date_added")
 60
 61
 62admin.site.register(Project, ProjectAdmin)
 63
 64
 65class OrganizationApiKeyInline(admin.TabularInline):
 66    model = ApiKey
 67    extra = 1
 68    fields = ("label", "key", "status", "allowed_origins", "date_added")
 69    raw_id_fields = ("organization",)
 70
 71
 72class OrganizationProjectInline(admin.TabularInline):
 73    model = Project
 74    extra = 1
 75    fields = ("name", "slug", "status", "date_added")
 76    raw_id_fields = ("organization",)
 77
 78
 79class OrganizationTeamInline(admin.TabularInline):
 80    model = Team
 81    extra = 1
 82    fields = ("name", "slug", "status", "date_added")
 83    raw_id_fields = ("organization",)
 84
 85
 86class OrganizationMemberInline(admin.TabularInline):
 87    model = OrganizationMember
 88    extra = 1
 89    fields = ("user", "organization", "role")
 90    raw_id_fields = ("user", "organization")
 91
 92
 93class OrganizationUserInline(OrganizationMemberInline):
 94    fk_name = "user"
 95
 96
 97class AuthIdentityInline(admin.TabularInline):
 98    model = AuthIdentity
 99    extra = 1
100    fields = ("user", "auth_provider", "ident", "data", "last_verified")
101    raw_id_fields = ("user", "auth_provider")
102
103
104class OrganizationAdmin(admin.ModelAdmin):
105    list_display = ("name", "slug", "status")
106    list_filter = ("status",)
107    search_fields = ("name", "slug")
108    fields = ("name", "slug", "status")
109    inlines = (
110        OrganizationMemberInline,
111        OrganizationTeamInline,
112        OrganizationProjectInline,
113        OrganizationApiKeyInline,
114    )
115
116
117admin.site.register(Organization, OrganizationAdmin)
118
119
120class AuthProviderAdmin(admin.ModelAdmin):
121    list_display = ("organization", "provider", "date_added")
122    search_fields = ("organization__name",)
123    raw_id_fields = ("organization", "default_teams")
124    list_filter = ("provider",)
125
126
127admin.site.register(AuthProvider, AuthProviderAdmin)
128
129
130class AuthIdentityAdmin(admin.ModelAdmin):
131    list_display = ("user", "auth_provider", "ident", "date_added", "last_verified")
132    list_filter = ("auth_provider__provider",)
133    search_fields = ("user__email", "user__username", "auth_provider__organization__name")
134    raw_id_fields = ("user", "auth_provider")
135
136
137admin.site.register(AuthIdentity, AuthIdentityAdmin)
138
139
140class TeamAdmin(admin.ModelAdmin):
141    list_display = ("name", "slug", "organization", "status", "date_added")
142    list_filter = ("status",)
143    search_fields = ("name", "organization__name", "slug")
144    raw_id_fields = ("organization",)
145
146    def save_model(self, request, obj, form, change):
147        prev_org = obj.organization_id
148        super(TeamAdmin, self).save_model(request, obj, form, change)
149        if not change:
150            return
151        new_org = obj.organization_id
152        if new_org != prev_org:
153            return
154
155        obj.transfer_to(obj.organization)
156
157
158admin.site.register(Team, TeamAdmin)
159
160
161class UserChangeForm(UserChangeForm):
162    username = forms.RegexField(
163        label=_("Username"),
164        max_length=128,
165        regex=r"^[\w.@+-]+$",
166        help_text=_("Required. 128 characters or fewer. Letters, digits and " "@/./+/-/_ only."),
167        error_messages={
168            "invalid": _(
169                "This value may contain only letters, numbers and " "@/./+/-/_ characters."
170            )
171        },
172    )
173
174
175class UserCreationForm(UserCreationForm):
176    username = forms.RegexField(
177        label=_("Username"),
178        max_length=128,
179        regex=r"^[\w.@+-]+$",
180        help_text=_("Required. 128 characters or fewer. Letters, digits and " "@/./+/-/_ only."),
181        error_messages={
182            "invalid": _(
183                "This value may contain only letters, numbers and " "@/./+/-/_ characters."
184            )
185        },
186    )
187
188
189class UserAdmin(admin.ModelAdmin):
190    add_form_template = "admin/auth/user/add_form.html"
191    change_user_password_template = None
192    fieldsets = (
193        (None, {"fields": ("username", "password")}),
194        (_("Personal info"), {"fields": ("name", "email")}),
195        (_("Permissions"), {"fields": ("is_active", "is_staff", "is_superuser")}),
196        (_("Important dates"), {"fields": ("last_login", "date_joined")}),
197    )
198    add_fieldsets = (
199        (None, {"classes": ("wide",), "fields": ("username", "password1", "password2")}),
200    )
201    form = UserChangeForm
202    add_form = UserCreationForm
203    change_password_form = AdminPasswordChangeForm
204    list_display = ("username", "email", "name", "is_staff", "date_joined")
205    list_filter = ("is_staff", "is_superuser", "is_active", "is_managed")
206    search_fields = ("username", "name", "email")
207    ordering = ("username",)
208    inlines = (OrganizationUserInline, AuthIdentityInline)
209
210    def get_fieldsets(self, request, obj=None):
211        if not obj:
212            return self.add_fieldsets
213        return super(UserAdmin, self).get_fieldsets(request, obj)
214
215    def get_form(self, request, obj=None, **kwargs):
216        """
217        Use special form during user creation
218        """
219        defaults = {}
220        if obj is None:
221            defaults.update(
222                {"form": self.add_form, "fields": admin.util.flatten_fieldsets(self.add_fieldsets)}
223            )
224        defaults.update(kwargs)
225        return super(UserAdmin, self).get_form(request, obj, **defaults)
226
227    def get_urls(self):
228        return [
229            url(r"^(\d+)/password/$", self.admin_site.admin_view(self.user_change_password))
230        ] + super(UserAdmin, self).get_urls()
231
232    def lookup_allowed(self, lookup, value):
233        # See #20078: we don't want to allow any lookups involving passwords.
234        if lookup.startswith("password"):
235            return False
236        return super(UserAdmin, self).lookup_allowed(lookup, value)
237
238    @sensitive_post_parameters_m
239    @csrf_protect_m
240    @transaction.atomic
241    def add_view(self, request, form_url="", extra_context=None):
242        # It's an error for a user to have add permission but NOT change
243        # permission for users. If we allowed such users to add users, they
244        # could create superusers, which would mean they would essentially have
245        # the permission to change users. To avoid the problem entirely, we
246        # disallow users from adding users if they don't have change
247        # permission.
248        if not self.has_change_permission(request):
249            if self.has_add_permission(request) and settings.DEBUG:
250                # Raise Http404 in debug mode so that the user gets a helpful
251                # error message.
252                raise Http404(
253                    'Your user does not have the "Change user" permission. In '
254                    "order to add users, Django requires that your user "
255                    'account have both the "Add user" and "Change user" '
256                    "permissions set."
257                )
258            raise PermissionDenied
259        if extra_context is None:
260            extra_context = {}
261        username_field = self.model._meta.get_field(self.model.USERNAME_FIELD)
262        defaults = {"auto_populated_fields": (), "username_help_text": username_field.help_text}
263        extra_context.update(defaults)
264        return super(UserAdmin, self).add_view(request, form_url, extra_context)
265
266    @sensitive_post_parameters_m
267    def user_change_password(self, request, id, form_url=""):
268        if not self.has_change_permission(request):
269            raise PermissionDenied
270        user = get_object_or_404(self.queryset(request), pk=id)
271        if request.method == "POST":
272            form = self.change_password_form(user, request.POST)
273            if form.is_valid():
274                form.save()
275                msg = ugettext("Password changed successfully.")
276                messages.success(request, msg)
277                return HttpResponseRedirect("..")
278        else:
279            form = self.change_password_form(user)
280
281        fieldsets = [(None, {"fields": list(form.base_fields)})]
282        adminForm = admin.helpers.AdminForm(form, fieldsets, {})
283
284        context = {
285            "title": _("Change password: %s") % escape(user.get_username()),
286            "adminForm": adminForm,
287            "form_url": form_url,
288            "form": form,
289            "is_popup": "_popup" in request.GET,
290            "add": True,
291            "change": False,
292            "has_delete_permission": False,
293            "has_change_permission": True,
294            "has_absolute_url": False,
295            "opts": self.model._meta,
296            "original": user,
297            "save_as": False,
298            "show_save": True,
299        }
300        return TemplateResponse(
301            request,
302            self.change_user_password_template or "admin/auth/user/change_password.html",
303            context,
304            current_app=self.admin_site.name,
305        )
306
307    def response_add(self, request, obj, post_url_continue=None):
308        """
309        Determines the HttpResponse for the add_view stage. It mostly defers to
310        its superclass implementation but is customized because the User model
311        has a slightly different workflow.
312        """
313        # We should allow further modification of the user just added i.e. the
314        # 'Save' button should behave like the 'Save and continue editing'
315        # button except in two scenarios:
316        # * The user has pressed the 'Save and add another' button
317        # * We are adding a user in a popup
318        if "_addanother" not in request.POST and "_popup" not in request.POST:
319            request.POST["_continue"] = 1
320        return super(UserAdmin, self).response_add(request, obj, post_url_continue)
321
322
323admin.site.register(User, UserAdmin)
324
325
326class AuditLogEntryAdmin(admin.ModelAdmin):
327    list_display = ("event", "organization", "actor", "datetime")
328    list_filter = ("event", "datetime")
329    search_fields = ("actor__email", "organization__name", "organization__slug")
330    raw_id_fields = ("organization", "actor", "target_user")
331    readonly_fields = (
332        "organization",
333        "actor",
334        "actor_key",
335        "target_object",
336        "target_user",
337        "event",
338        "ip_address",
339        "data",
340        "datetime",
341    )
342
343
344admin.site.register(AuditLogEntry, AuditLogEntryAdmin)