diff --git a/ietf/static/ietf/css/ietf.css b/ietf/static/ietf/css/ietf.css index 6a2911985..0cda09bad 100644 --- a/ietf/static/ietf/css/ietf.css +++ b/ietf/static/ietf/css/ietf.css @@ -829,17 +829,28 @@ blockquote { width: 90%; } -#debug-query-table .code { - background-color: #eee; +#debug-query-table .origin { font-family: 'PT Mono', monospace; font-size: 0.8em; word-break: break-all; } -#debug-query-table table { +#debug-query-table .code { + background-color: #eee; +} + +#debug-query-table .code td { + white-space: pre; +} + +#debug-query-table .table { max-width: 100%; } #debug-query-table .sql { border-top: 1px solid #555; } + +#debug-query-table .code .current { + background-color: #ddd; +} diff --git a/ietf/templates/debug.html b/ietf/templates/debug.html index 2d8ab7e39..2277a0892 100644 --- a/ietf/templates/debug.html +++ b/ietf/templates/debug.html @@ -57,10 +57,16 @@ {% if query.origin %} {% for origin in query.origin %} - + {{ origin.1 }}({{ origin.2 }}) {{ origin.6 }}() - {{ origin.4.0 }} + + {% for l in origin.4 %} + + +
 {{l}}
+ {% endfor %} + {% endfor %} {% endif %} diff --git a/patch/trace-django-queryset-origin.patch b/patch/trace-django-queryset-origin.patch index d23eb8357..eb5586062 100644 --- a/patch/trace-django-queryset-origin.patch +++ b/patch/trace-django-queryset-origin.patch @@ -1,23 +1,22 @@ --- ../x/env/lib/python2.7/site-packages/django/db/models/query.py 2017-04-07 15:10:29.426831000 -0700 -+++ env/lib/python2.7/site-packages/django/db/models/query.py 2017-04-07 15:18:39.938521412 -0700 -@@ -5,6 +5,8 @@ ++++ env/lib/python2.7/site-packages/django/db/models/query.py 2017-04-12 08:23:20.061651277 -0700 +@@ -5,6 +5,7 @@ import copy import sys import warnings +import inspect -+import logging from collections import OrderedDict, deque from django.conf import settings -@@ -34,6 +36,7 @@ +@@ -34,7 +35,6 @@ # Pull into this namespace for backwards compatibility. EmptyResultSet = sql.EmptyResultSet -+logger = logging.getLogger('django.db.backends') - +- class BaseIterable(object): def __init__(self, queryset): -@@ -51,6 +54,7 @@ + self.queryset = queryset +@@ -51,6 +51,7 @@ compiler = queryset.query.get_compiler(using=db) # Execute the query. This will also fill compiler.select, klass_info, # and annotations. @@ -25,7 +24,7 @@ results = compiler.execute_sql() select, klass_info, annotation_col_map = (compiler.select, compiler.klass_info, compiler.annotation_col_map) -@@ -174,6 +178,8 @@ +@@ -174,6 +175,8 @@ self._known_related_objects = {} # {rel_field, {pk: rel_obj}} self._iterable_class = ModelIterable self._fields = None @@ -34,15 +33,16 @@ def as_manager(cls): # Address the circular dependency between `Queryset` and `Manager`. -@@ -316,6 +322,37 @@ +@@ -316,6 +319,31 @@ combined.query.combine(other.query, sql.OR) return combined + def _add_origin(self, depth=1): ++ import debug + if settings.DEBUG: + # get list of frame records. Each is: + # [ frame, filename, lineno, function, code_context, index ] -+ stack = inspect.stack() ++ stack = inspect.stack(5) + # caller stack record + method = stack[depth][3] + # look for the first stack entry which is not from django @@ -56,23 +56,16 @@ + if function == 'render' and 'context' in frame.f_locals: + that = frame.f_locals['self'] + if hasattr(that, 'filename'): -+ import debug + debug.show('that.filename') -+ self._origin += [stack[0]+(method,), ] ++ origin = stack[0]+(method,) + else: -+ self._origin += [stack[2]+(method,), ] -+ -+ def _log_origin(self): -+ if settings.DEBUG: -+ self._add_origin(depth=3) -+ for place in self._origin: -+ frame, filename, lineno, function, code_context, index, method = place[:7] -+ logger.debug("QuerySet: %s(%s) in %s: %s()\n %s", filename, lineno, function, method, code_context[index].rstrip()) ++ origin = stack[2]+(method,) ++ self._origin.append(origin) + #################################### # METHODS THAT DO DATABASE QUERIES # #################################### -@@ -793,6 +830,7 @@ +@@ -793,6 +821,7 @@ Returns a new QuerySet instance with the args ANDed to the existing set. """ @@ -80,7 +73,7 @@ return self._filter_or_exclude(False, *args, **kwargs) def exclude(self, *args, **kwargs): -@@ -800,6 +838,7 @@ +@@ -800,6 +829,7 @@ Returns a new QuerySet instance with NOT (args) ANDed to the existing set. """ @@ -88,7 +81,7 @@ return self._filter_or_exclude(True, *args, **kwargs) def _filter_or_exclude(self, negate, *args, **kwargs): -@@ -824,6 +863,7 @@ +@@ -824,6 +854,7 @@ This exists to support framework features such as 'limit_choices_to', and usually it will be more natural to use other methods. """ @@ -96,7 +89,7 @@ if isinstance(filter_obj, Q) or hasattr(filter_obj, 'add_to_query'): clone = self._clone() clone.query.add_q(filter_obj) -@@ -836,6 +876,7 @@ +@@ -836,6 +867,7 @@ Returns a new QuerySet instance that will select objects with a FOR UPDATE lock. """ @@ -104,7 +97,7 @@ obj = self._clone() obj._for_write = True obj.query.select_for_update = True -@@ -855,6 +896,7 @@ +@@ -855,6 +887,7 @@ if self._fields is not None: raise TypeError("Cannot call select_related() after .values() or .values_list()") @@ -112,7 +105,7 @@ obj = self._clone() if fields == (None,): obj.query.select_related = False -@@ -874,6 +916,7 @@ +@@ -874,6 +907,7 @@ prefetch is appended to. If prefetch_related(None) is called, the list is cleared. """ @@ -120,7 +113,7 @@ clone = self._clone() if lookups == (None,): clone._prefetch_related_lookups = [] -@@ -886,6 +929,7 @@ +@@ -886,6 +920,7 @@ Return a query set in which the returned objects have been annotated with extra data or aggregations. """ @@ -128,7 +121,7 @@ annotations = OrderedDict() # To preserve ordering of args for arg in args: # The default_alias property may raise a TypeError, so we use -@@ -929,6 +973,7 @@ +@@ -929,6 +964,7 @@ """ assert self.query.can_filter(), \ "Cannot reorder a query once a slice has been taken." @@ -136,7 +129,7 @@ obj = self._clone() obj.query.clear_ordering(force_empty=False) obj.query.add_ordering(*field_names) -@@ -940,6 +985,7 @@ +@@ -940,6 +976,7 @@ """ assert self.query.can_filter(), \ "Cannot create distinct fields once a slice has been taken." @@ -144,7 +137,7 @@ obj = self._clone() obj.query.add_distinct_fields(*field_names) return obj -@@ -951,6 +997,7 @@ +@@ -951,6 +988,7 @@ """ assert self.query.can_filter(), \ "Cannot change a query once a slice has been taken" @@ -152,7 +145,7 @@ clone = self._clone() clone.query.add_extra(select, select_params, where, params, tables, order_by) return clone -@@ -959,6 +1006,7 @@ +@@ -959,6 +997,7 @@ """ Reverses the ordering of the QuerySet. """ @@ -160,7 +153,7 @@ clone = self._clone() clone.query.standard_ordering = not clone.query.standard_ordering return clone -@@ -973,6 +1021,7 @@ +@@ -973,6 +1012,7 @@ """ if self._fields is not None: raise TypeError("Cannot call defer() after .values() or .values_list()") @@ -168,7 +161,7 @@ clone = self._clone() if fields == (None,): clone.query.clear_deferred_loading() -@@ -992,6 +1041,7 @@ +@@ -992,6 +1032,7 @@ # Can only pass None to defer(), not only(), as the rest option. # That won't stop people trying to do this, so let's be explicit. raise TypeError("Cannot pass None as an argument to only().") @@ -176,7 +169,7 @@ clone = self._clone() clone.query.add_immediate_loading(fields) return clone -@@ -1078,13 +1128,17 @@ +@@ -1078,6 +1119,7 @@ clone._known_related_objects = self._known_related_objects clone._iterable_class = self._iterable_class clone._fields = self._fields @@ -184,10 +177,9 @@ clone.__dict__.update(kwargs) return clone - +@@ -1085,6 +1127,8 @@ def _fetch_all(self): if self._result_cache is None: -+ self._log_origin() self._result_cache = list(self.iterator()) + if settings.DEBUG: + connections[self.db].queries_log[-1]['origin'] = self._origin