Skip to content

Commit

Permalink
Use correct language in panels (#1717)
Browse files Browse the repository at this point in the history
* Use correct language in panels

PR #1703 introduced a bug where SQLPanel (and probably others) is rendered in `LANGUAGE_CODE` language, while it should use `TOOLBAR_LANGUAGE` if provided.
This PR fixes panel's content language.

* Add missing changelog entries

* Use code formatting and include more details on the recent change.

* Create decorator to use TOOLBAR_LANGUAGE in toolbar specific views.

This reviews the third-party panel documentation to reference
these two decorators and explain them. They aren't necessary
but should be used.

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Tim Schilling <schillingt@better-simple.com>
Co-authored-by: Matthias Kestenholz <mk@feinheit.ch>
  • Loading branch information
4 people committed Jan 16, 2023
1 parent 145b112 commit da88665
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 10 deletions.
16 changes: 16 additions & 0 deletions debug_toolbar/decorators.py
@@ -1,6 +1,9 @@
import functools

from django.http import Http404
from django.utils.translation import get_language, override as language_override

from debug_toolbar import settings as dt_settings


def require_show_toolbar(view):
Expand All @@ -15,3 +18,16 @@ def inner(request, *args, **kwargs):
return view(request, *args, **kwargs)

return inner


def render_with_toolbar_language(view):
"""Force any rendering within the view to use the toolbar's language."""

@functools.wraps(view)
def inner(request, *args, **kwargs):

lang = dt_settings.get_config()["TOOLBAR_LANGUAGE"] or get_language()
with language_override(lang):
return view(request, *args, **kwargs)

return inner
4 changes: 3 additions & 1 deletion debug_toolbar/panels/history/views.py
@@ -1,12 +1,13 @@
from django.http import HttpResponseBadRequest, JsonResponse
from django.template.loader import render_to_string

from debug_toolbar.decorators import require_show_toolbar
from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar
from debug_toolbar.panels.history.forms import HistoryStoreForm
from debug_toolbar.toolbar import DebugToolbar


@require_show_toolbar
@render_with_toolbar_language
def history_sidebar(request):
"""Returns the selected debug toolbar history snapshot."""
form = HistoryStoreForm(request.GET)
Expand Down Expand Up @@ -37,6 +38,7 @@ def history_sidebar(request):


@require_show_toolbar
@render_with_toolbar_language
def history_refresh(request):
"""Returns the refreshed list of table rows for the History Panel."""
form = HistoryStoreForm(request.GET)
Expand Down
5 changes: 4 additions & 1 deletion debug_toolbar/panels/sql/views.py
Expand Up @@ -2,7 +2,7 @@
from django.template.loader import render_to_string
from django.views.decorators.csrf import csrf_exempt

from debug_toolbar.decorators import require_show_toolbar
from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar
from debug_toolbar.forms import SignedDataForm
from debug_toolbar.panels.sql.forms import SQLSelectForm

Expand All @@ -18,6 +18,7 @@ def get_signed_data(request):

@csrf_exempt
@require_show_toolbar
@render_with_toolbar_language
def sql_select(request):
"""Returns the output of the SQL SELECT statement"""
verified_data = get_signed_data(request)
Expand Down Expand Up @@ -47,6 +48,7 @@ def sql_select(request):

@csrf_exempt
@require_show_toolbar
@render_with_toolbar_language
def sql_explain(request):
"""Returns the output of the SQL EXPLAIN on the given query"""
verified_data = get_signed_data(request)
Expand Down Expand Up @@ -85,6 +87,7 @@ def sql_explain(request):

@csrf_exempt
@require_show_toolbar
@render_with_toolbar_language
def sql_profile(request):
"""Returns the output of running the SQL and getting the profiling statistics"""
verified_data = get_signed_data(request)
Expand Down
3 changes: 2 additions & 1 deletion debug_toolbar/panels/templates/views.py
Expand Up @@ -5,10 +5,11 @@
from django.template.loader import render_to_string
from django.utils.html import format_html, mark_safe

from debug_toolbar.decorators import require_show_toolbar
from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar


@require_show_toolbar
@render_with_toolbar_language
def template_source(request):
"""
Return the source of a template, syntax-highlighted by Pygments if
Expand Down
3 changes: 2 additions & 1 deletion debug_toolbar/views.py
Expand Up @@ -2,11 +2,12 @@
from django.utils.html import escape
from django.utils.translation import gettext as _

from debug_toolbar.decorators import require_show_toolbar
from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar
from debug_toolbar.toolbar import DebugToolbar


@require_show_toolbar
@render_with_toolbar_language
def render_panel(request):
"""Render the contents of a panel"""
toolbar = DebugToolbar.fetch(request.GET["store_id"])
Expand Down
4 changes: 4 additions & 0 deletions docs/changes.rst
Expand Up @@ -5,6 +5,9 @@ Pending
-------

* Fixed PostgreSQL raw query with a tuple parameter during on explain.
* Use ``TOOLBAR_LANGUAGE`` setting when rendering individual panels
that are loaded via AJAX.
* Add decorator for rendering toolbar views with ``TOOLBAR_LANGUAGE``.

3.8.1 (2022-12-03)
------------------
Expand All @@ -27,6 +30,7 @@ Pending
* Fix highlighting on history panel so odd rows are highlighted when
selected.
* Formalize support for Python 3.11.
* Added ``TOOLBAR_LANGUAGE`` setting.

3.7.0 (2022-09-25)
------------------
Expand Down
6 changes: 3 additions & 3 deletions docs/configuration.rst
Expand Up @@ -151,6 +151,8 @@ Toolbar options
the request doesn't originate from the toolbar itself, EG that
``is_toolbar_request`` is false for a given request.

.. _TOOLBAR_LANGUAGE:

* ``TOOLBAR_LANGUAGE``

Default: ``None``
Expand All @@ -160,9 +162,7 @@ Toolbar options
render the toolbar in a different language than what the application is
rendered in. For example, if you wish to use English for development,
but want to render your application in French, you would set this to
``"en-us"`` and `settings.LANGUAGE_CODE`_ to ``"fr"``.

.. _settings.LANGUAGE_CODE: https://docs.djangoproject.com/en/stable/ref/settings/#std-setting-LANGUAGE_CODE
``"en-us"`` and :setting:`LANGUAGE_CODE` to ``"fr"``.

Panel options
~~~~~~~~~~~~~
Expand Down
15 changes: 12 additions & 3 deletions docs/panels.rst
Expand Up @@ -350,9 +350,18 @@ Third-party panels must subclass :class:`~debug_toolbar.panels.Panel`,
according to the public API described below. Unless noted otherwise, all
methods are optional.

Panels can ship their own templates, static files and views. All views should
be decorated with ``debug_toolbar.decorators.require_show_toolbar`` to prevent
unauthorized access. There is no public CSS API at this time.
Panels can ship their own templates, static files and views.

Any views defined for the third-party panel use the following decorators:

- ``debug_toolbar.decorators.require_show_toolbar`` - Prevents unauthorized
access to the view.
- ``debug_toolbar.decorators.render_with_toolbar_language`` - Supports
internationalization for any content rendered by the view. This will render
the response with the :ref:`TOOLBAR_LANGUAGE <TOOLBAR_LANGUAGE>` rather than
:setting:`LANGUAGE_CODE`.

There is no public CSS API at this time.

.. autoclass:: debug_toolbar.panels.Panel

Expand Down
26 changes: 26 additions & 0 deletions tests/test_decorators.py
@@ -0,0 +1,26 @@
from unittest.mock import patch

from django.http import HttpResponse
from django.test import RequestFactory, TestCase
from django.test.utils import override_settings

from debug_toolbar.decorators import render_with_toolbar_language


@render_with_toolbar_language
def stub_view(request):
return HttpResponse(200)


@override_settings(DEBUG=True, LANGUAGE_CODE="fr")
class RenderWithToolbarLanguageTestCase(TestCase):
@override_settings(DEBUG_TOOLBAR_CONFIG={"TOOLBAR_LANGUAGE": "de"})
@patch("debug_toolbar.decorators.language_override")
def test_uses_toolbar_language(self, mock_language_override):
stub_view(RequestFactory().get("/"))
mock_language_override.assert_called_once_with("de")

@patch("debug_toolbar.decorators.language_override")
def test_defaults_to_django_language_code(self, mock_language_override):
stub_view(RequestFactory().get("/"))
mock_language_override.assert_called_once_with("fr")
43 changes: 43 additions & 0 deletions tests/test_integration.py
Expand Up @@ -671,8 +671,51 @@ def test_toolbar_language_will_render_to_default_language_when_not_set(self):
hide_button = self.selenium.find_element(By.ID, "djHideToolBarButton")
assert hide_button.text == "Hide »"

self.get("/execute_sql/")
sql_panel = self.selenium.find_element(By.ID, "SQLPanel")

# Click to show the SQL panel
self.selenium.find_element(By.CLASS_NAME, "SQLPanel").click()

table = self.wait.until(
lambda selenium: sql_panel.find_element(By.TAG_NAME, "table")
)
self.assertIn("Query", table.text)
self.assertIn("Action", table.text)

@override_settings(DEBUG_TOOLBAR_CONFIG={"TOOLBAR_LANGUAGE": "pt-br"})
def test_toolbar_language_will_render_to_locale_when_set(self):
self.get("/regular/basic/")
hide_button = self.selenium.find_element(By.ID, "djHideToolBarButton")
assert hide_button.text == "Esconder »"

self.get("/execute_sql/")
sql_panel = self.selenium.find_element(By.ID, "SQLPanel")

# Click to show the SQL panel
self.selenium.find_element(By.CLASS_NAME, "SQLPanel").click()

table = self.wait.until(
lambda selenium: sql_panel.find_element(By.TAG_NAME, "table")
)
self.assertIn("Query", table.text)
self.assertIn("Linha", table.text)

@override_settings(DEBUG_TOOLBAR_CONFIG={"TOOLBAR_LANGUAGE": "en-us"})
@override_settings(LANGUAGE_CODE="de")
def test_toolbar_language_will_render_to_locale_when_set_both(self):
self.get("/regular/basic/")
hide_button = self.selenium.find_element(By.ID, "djHideToolBarButton")
assert hide_button.text == "Hide »"

self.get("/execute_sql/")
sql_panel = self.selenium.find_element(By.ID, "SQLPanel")

# Click to show the SQL panel
self.selenium.find_element(By.CLASS_NAME, "SQLPanel").click()

table = self.wait.until(
lambda selenium: sql_panel.find_element(By.TAG_NAME, "table")
)
self.assertIn("Query", table.text)
self.assertIn("Action", table.text)

0 comments on commit da88665

Please sign in to comment.