import json from django.utils.html import escape, mark_safe from django.contrib import admin from django.views.decorators.csrf import csrf_protect from django.utils.decorators import method_decorator from bugsink.transaction import immediate_atomic from projects.admin import ProjectFilter from .models import Event csrf_protect_m = method_decorator(csrf_protect) @admin.register(Event) class EventAdmin(admin.ModelAdmin): # A note on performance: when using this particular (list) admin on the playground (~150K events), I ran into the # fact that it was unusably slow (at least more slow than 30s to render). I examined this for a while, but in the # end the conclusion was: "this will simply never work". There's simply too much brokenness here. The admin works # fine for the "scaffolding" case, and perhaps for things like users, projects etc, but for the "main event" # (events) we'll have to build it ourselves. Regarding the brokenness, some thoughts/links: # # * arbitrary sorting is (multiple columns) is possible, which means you'll need arbitrary indexes # * https://code.djangoproject.com/ticket/8408 # * https://github.com/django/django/blob/9a3454f6046b/django/contrib/admin/options.py#L1816 # no point of configuration for this one, I simply clobbered Django's code directly to turn it off # * `actions_selection_counter = False` (possible to set as an admin option) # * then I ran into the query itself just being super-slow (presumably caused by sorting) # open question: when we'll "build this ourselves", is not some of the sqlite(?) slowness surfacing in other ways? ordering = ['-timestamp'] search_fields = ['event_id', 'debug_info'] list_display = [ 'timestamp', # 'project', 'platform', 'level', 'sdk_name', 'sdk_version', 'debug_info', 'on_site', ] list_filter = [ ProjectFilter, 'platform', 'level', 'sdk_name', 'sdk_version', ] fields = [ 'id', 'event_id', 'ingested_at', 'digested_at', 'calculated_type', 'calculated_value', 'issue', 'project', 'timestamp', 'platform', 'level', 'logger', 'transaction', 'server_name', 'release', 'dist', 'environment', 'sdk_name', 'sdk_version', 'debug_info', 'pretty_data', ] readonly_fields = [ 'id', 'event_id', 'ingested_at', 'digested_at', 'calculated_type', 'calculated_value', 'issue', 'timestamp', 'project', 'pretty_data', ] def pretty_data(self, obj): return mark_safe("
" + escape(json.dumps(json.loads(obj.data), indent=2)) + "") pretty_data.short_description = "Data" def on_site(self, obj): return mark_safe('View') def get_deleted_objects(self, objs, request): to_delete = list(objs) + ["...all its related objects... (delayed)"] model_count = { Event: len(objs), } perms_needed = set() protected = [] return to_delete, model_count, perms_needed, protected def delete_queryset(self, request, queryset): # NOTE: not the most efficient; it will do for a first version. with immediate_atomic(): for obj in queryset: obj.delete_deferred() def delete_model(self, request, obj): with immediate_atomic(): obj.delete_deferred() @csrf_protect_m def delete_view(self, request, object_id, extra_context=None): # the superclass version, but with the transaction.atomic context manager commented out (we do this ourselves) # with transaction.atomic(using=router.db_for_write(self.model)): return self._delete_view(request, object_id, extra_context)