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