25

django-debug-toolbar needs its output to be html, but django-tastypie's default output format is json.

I tried sending http://localhost/api/v1/resource/?format=html but it says Sorry, not implemented yet. Please append "?format=json" to your URL

Even though this doc lists html as one of valid option, it says its on the TODO list.
http://django-tastypie.readthedocs.org/en/latest/serialization.html#to-html

How do I use debug toolbar to debug tastypie api calls?
(eg, I'd like to see how many sql queries are being run for api calls.. and so on)

Maybe I can call the api from django views but how?

eugene
  • 33,301
  • 47
  • 188
  • 382

8 Answers8

39

Here is a middleware I wrote for similar purposes that wraps json in HTML for enabling the debug toolbar and also pretty prints it. Furthermore, it supports binary data. I'm not using tastypie, but I think it should work with that too.

# settings-dev.py
from django.http import HttpResponse
import json   

MIDDLEWARE_CLASSES += (
    'debug_toolbar.middleware.DebugToolbarMiddleware',
    'NonHtmlDebugToolbarMiddleware',
)

class NonHtmlDebugToolbarMiddleware(object):
    """
    The Django Debug Toolbar usually only works for views that return HTML.
    This middleware wraps any non-HTML response in HTML if the request
    has a 'debug' query parameter (e.g. http://localhost/foo?debug)
    Special handling for json (pretty printing) and
    binary data (only show data length)
    """

    @staticmethod
    def process_response(request, response):
        if request.GET.get('debug') == '':
            if response['Content-Type'] == 'application/octet-stream':
                new_content = '<html><body>Binary Data, ' \
                    'Length: {}</body></html>'.format(len(response.content))
                response = HttpResponse(new_content)
            elif response['Content-Type'] != 'text/html':
                content = response.content
                try:
                    json_ = json.loads(content)
                    content = json.dumps(json_, sort_keys=True, indent=2)
                except ValueError:
                    pass
                response = HttpResponse('<html><body><pre>{}'
                                        '</pre></body></html>'.format(content))

        return response
Benjamin Wasty
  • 506
  • 4
  • 5
  • 4
    Minor fix: this did not work for me as answered for Django 1.7. I had to make a change to the MIDDLEWARE_CLASSES include: instead of 'NonHtmlDebugToolbarMiddleware' I had to specify 'myapp.settings.NonHtmlDebugToolbarMiddleware'. Other than that - awesome! – autonomy Dec 05 '14 at 16:00
  • @karan it works perfectly fine with django rest API, but when I used it with swagger it shows 0 number of queries in sql panel, even though I ran some queries. Can you please suggest some thing ? – Tasawer Nawaz Feb 13 '15 at 12:03
  • This will break the toolbar panels on the current version of django-debug-toolbar because it tries to parse the response from its own endpoint as JSON. I modified this to allow `'application/json'` responses to pass through without modification in order for it to work on the current package version. – JHS Dec 14 '20 at 21:10
8

The Django Debug Toolbar's middleware actually has code in it to prevent it being activated for non-html type responses like those returned by TastyPie. What I have done in the past is create a bit of middleware that converts json responses into HTML so the toolbar will be activated and I can count queries etc... It is a bit of a hack but it gets to job done and is easy to turn on/off.

from django.conf import settings


class JsonAsHTML(object):
    '''
    View a JSON response in your browser as HTML
    Useful for viewing stats using Django Debug Toolbar 

    This middleware should be place AFTER Django Debug Toolbar middleware   
    '''

    def process_response(self, request, response):

        #not for production or production like environment 
        if not settings.DEBUG:
            return response

        #do nothing for actual ajax requests
        if request.is_ajax():
            return response

        #only do something if this is a json response
        if "application/json" in response['Content-Type'].lower():
            title = "JSON as HTML Middleware for: %s" % request.get_full_path()
            response.content = "<html><head><title>%s</title></head><body>%s</body></html>" % (title, response.content)
            response['Content-Type'] = 'text/html'
        return response
Mike Harris
  • 81
  • 1
  • 1
2

I'm afraid that's not possible. See the accepted answer for a workable solution.

Here's why your approach did not work:

The toolbar does not kick-in because the answer is not in HTML. All other formats can't be "parsed" by the toolbar's middleware to include the Toolbar.

You can add your own tools to show SQL queries though. Take a look at this simple snippet: http://djangosnippets.org/snippets/161/ Or you can use a third party app for this, like django-snippetscream.

For example, you could check if DEBUG is True and add this info to the "meta" object returned by Tastypie.

Also, take a look at the SQL logging in your console (runserver). Some useful resource for this: http://dabapps.com/blog/logging-sql-queries-django-13/

Cedric Reichenbach
  • 8,157
  • 6
  • 50
  • 82
santiagobasulto
  • 10,542
  • 9
  • 61
  • 85
2

Django 1.10 introduced 'new style middleware': https://docs.djangoproject.com/en/2.0/releases/1.10/#new-style-middleware

This is a new style middleware version:

import json

from django.http import HttpResponse


class NonHtmlDebugToolbarMiddleware:
    """
    The Django Debug Toolbar usually only works for views that return HTML.
    This middleware wraps any non-HTML response in HTML if the request
    has a 'debug' query parameter (e.g. http://localhost/foo?debug)
    Special handling for json (pretty printing) and
    binary data (only show data length)
    """
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)

        if response['Content-Type'] == 'application/json':
            content = response.content
            try:
                json_ = json.loads(content)
                content = json.dumps(json_, sort_keys=True, indent=2)
            except ValueError:
                pass

            response = HttpResponse('<html><body><pre>{}'
                                    '</pre></body></html>'.format(content),
                                    content_type='text/html')

        return response
Mikle
  • 1,777
  • 3
  • 19
  • 32
1

Try https://github.com/django-debug-toolbar/django-debug-toolbar/pull/253

pip install git+https://github.com/caktus/django-debug-toolbar@ajax-panel#egg=django-debug-toolbar

This will allow debug toolbar to display information about the request on the calling page.

Alternatively if you want an HTML renderer and your not too far into your project, I highly recommend django-rest-framework

Thomas
  • 11,032
  • 4
  • 37
  • 53
1
@html_decorator
def test(request):

    view = resolve("/api/v1/albumimage/like/user/%d/" % 2 )

    accept =  request.META.get("HTTP_ACCEPT")
    accept += ",application/json"
    request.META["HTTP_ACCEPT"] = accept   
    res = view.func(request, **view.kwargs)

    return HttpResponse(res._container)



def html_decorator(func):
    """                                                                                                                                                                                                                                                                       
    wrap it inside html                                                                                                                                                                                                                                                       
    """

    def _decorated(*args, ** kwargs):
        response = func(*args, **kwargs)

        wrapped = ("<html><body>",
                   response.content,
                   "</body></html>")

        return HttpResponse(wrapped)

    return _decorated

This is how I solved it.
Ok it's not automatic, but will do for now.

eugene
  • 33,301
  • 47
  • 188
  • 382
1

I fixed that by rebuilding django snippest from http://djangosnippets.org/snippets/344/

"""
Database and request debug info for Tastypie.

Based of idea from http://djangosnippets.org/snippets/344/

# settings.py:
DEBUG=True
DEBUG_SQL=True

MIDDLEWARE_CLASSES = (
    'YOURPATH.SQLLogMiddleware.SQLLogMiddleware',
    'django.middleware.transaction.TransactionMiddleware',
    ...)

"""

# Python
import time
import logging
import json

# Django
from django.conf import settings
from django.db import connection


class SQLLogMiddleware:
    """\
    Attach debug information to result json.
    """
    def process_request(self, request):
        request.sqllog_start = time.time()

    def process_response (self, request, response):
        # request.sqllog_start is empty if an append slash redirect happened.
        debug_sql = getattr(settings, "DEBUG_SQL", False)
        if not getattr(request, 'sqllog_start', False):
            return response
        if (not request.sqllog_start) or not (settings.DEBUG and debug_sql):
            return response

        try:
            content = json.loads(response.content)
        except ValueError:
            return response
        timesql = 0.0
        for query in connection.queries:
            timesql += float(query['time'])
        seen = {}
        duplicate = 0
        for query in connection.queries:
            sql = query["sql"]
            c = seen.get(sql, 0)
            if c:
                duplicate += 1
            if c:
                query["seen"] = c + 1
            seen[sql] = c + 1

        timerequest = round(time.time() - request.sqllog_start, 3)
        queries = connection.queries

        debug = {'request_path': request.path,
                 'query_count': len(queries),
                 'duplicate_query_count': duplicate,
                 'sql_execute_time': timesql,
                 'request_execution_time': timerequest,
                 'queries': []}

        for query in queries:
            debug['queries'].append({'time': query['time'],
                                     'sql': query['sql']})

        content['debug'] = debug
        response.content = json.dumps(content)
        logging.info(debug)
        return response
Bartosz Dabrowski
  • 1,822
  • 2
  • 14
  • 21
  • 1
    django.middleware.transaction.TransactionMiddleware is deprecated in Django 1.6. without it no debug info is added to the response. – mab Nov 20 '13 at 15:05
0

With version 3.1 of the Django Debug Toolbar, there is a History Panel which removes the need to wrap the JSON responses with html.

schillingt
  • 11,930
  • 1
  • 25
  • 31