Codebase list python-django-debug-toolbar / ec92032
Update upstream source from tag 'upstream/3.6' Update to upstream version '3.6' with Debian dir 038f6170e38189e678732b6ccfc7c945ba6c956c Carsten Schoenert 1 year, 8 months ago
23 changed file(s) with 153 addition(s) and 116 deletion(s). Raw diff Collapse all Expand all
2929 - 3306:3306
3030
3131 steps:
32 - uses: actions/checkout@v2
33
34 - name: Set up Python ${{ matrix.python-version }}
35 uses: actions/setup-python@v2
32 - uses: actions/checkout@v3
33
34 - name: Set up Python ${{ matrix.python-version }}
35 uses: actions/setup-python@v4
3636 with:
3737 python-version: ${{ matrix.python-version }}
3838
4242 echo "::set-output name=dir::$(pip cache dir)"
4343
4444 - name: Cache
45 uses: actions/cache@v2
45 uses: actions/cache@v3
4646 with:
4747 path: ${{ steps.pip-cache.outputs.dir }}
4848 key:
7070 DB_PORT: 3306
7171
7272 - name: Upload coverage data
73 uses: actions/upload-artifact@v2
73 uses: actions/upload-artifact@v3
7474 with:
7575 name: coverage-data
7676 path: ".coverage.*"
100100 --health-retries 5
101101
102102 steps:
103 - uses: actions/checkout@v2
104
105 - name: Set up Python ${{ matrix.python-version }}
106 uses: actions/setup-python@v2
103 - uses: actions/checkout@v3
104
105 - name: Set up Python ${{ matrix.python-version }}
106 uses: actions/setup-python@v4
107107 with:
108108 python-version: ${{ matrix.python-version }}
109109
113113 echo "::set-output name=dir::$(pip cache dir)"
114114
115115 - name: Cache
116 uses: actions/cache@v2
116 uses: actions/cache@v3
117117 with:
118118 path: ${{ steps.pip-cache.outputs.dir }}
119119 key:
139139 DB_PORT: 5432
140140
141141 - name: Upload coverage data
142 uses: actions/upload-artifact@v2
142 uses: actions/upload-artifact@v3
143143 with:
144144 name: coverage-data
145145 path: ".coverage.*"
153153 python-version: ['3.7', '3.8', '3.9', '3.10']
154154
155155 steps:
156 - uses: actions/checkout@v2
157
158 - name: Set up Python ${{ matrix.python-version }}
159 uses: actions/setup-python@v2
156 - uses: actions/checkout@v3
157
158 - name: Set up Python ${{ matrix.python-version }}
159 uses: actions/setup-python@v4
160160 with:
161161 python-version: ${{ matrix.python-version }}
162162
166166 echo "::set-output name=dir::$(pip cache dir)"
167167
168168 - name: Cache
169 uses: actions/cache@v2
169 uses: actions/cache@v3
170170 with:
171171 path: ${{ steps.pip-cache.outputs.dir }}
172172 key:
186186 DB_NAME: ":memory:"
187187
188188 - name: Upload coverage data
189 uses: actions/upload-artifact@v2
189 uses: actions/upload-artifact@v3
190190 with:
191191 name: coverage-data
192192 path: ".coverage.*"
196196 runs-on: "ubuntu-latest"
197197 needs: [sqlite, mysql, postgres]
198198 steps:
199 - uses: actions/checkout@v2
200 - uses: actions/setup-python@v2
199 - uses: actions/checkout@v3
200 - uses: actions/setup-python@v4
201201 with:
202202 # Use latest, so it understands all syntax.
203203 python-version: "3.10"
205205 - run: python -m pip install --upgrade coverage
206206
207207 - name: Download coverage data.
208 uses: actions/download-artifact@v2
208 uses: actions/download-artifact@v3
209209 with:
210210 name: coverage-data
211211
216216 python -m coverage report
217217
218218 - name: Upload HTML report if check failed.
219 uses: actions/upload-artifact@v2
219 uses: actions/upload-artifact@v3
220220 with:
221221 name: html-report
222222 path: htmlcov
228228 fail-fast: false
229229
230230 steps:
231 - uses: actions/checkout@v2
232
233 - name: Set up Python ${{ matrix.python-version }}
234 uses: actions/setup-python@v2
231 - uses: actions/checkout@v3
232
233 - name: Set up Python ${{ matrix.python-version }}
234 uses: actions/setup-python@v4
235235 with:
236236 python-version: 3.8
237237
241241 echo "::set-output name=dir::$(pip cache dir)"
242242
243243 - name: Cache
244 uses: actions/cache@v2
244 uses: actions/cache@v3
245245 with:
246246 path: ${{ steps.pip-cache.outputs.dir }}
247247 key:
66 - id: trailing-whitespace
77 - id: mixed-line-ending
88 - repo: https://github.com/pycqa/flake8
9 rev: 4.0.1
9 rev: 5.0.4
1010 hooks:
1111 - id: flake8
1212 - repo: https://github.com/pycqa/doc8
13 rev: 0.11.2
13 rev: v1.0.0
1414 hooks:
1515 - id: doc8
1616 - repo: https://github.com/asottile/pyupgrade
17 rev: v2.34.0
17 rev: v2.37.3
1818 hooks:
1919 - id: pyupgrade
2020 args: [--py37-plus]
2121 - repo: https://github.com/adamchainz/django-upgrade
22 rev: 1.7.0
22 rev: 1.8.0
2323 hooks:
2424 - id: django-upgrade
2525 args: [--target-version, "3.2"]
4242 - id: prettier
4343 types_or: [javascript, css]
4444 - repo: https://github.com/pre-commit/mirrors-eslint
45 rev: v8.18.0
45 rev: v8.22.0
4646 hooks:
4747 - id: eslint
4848 files: \.js?$
5050 args:
5151 - --fix
5252 - repo: https://github.com/psf/black
53 rev: 22.3.0
53 rev: 22.6.0
5454 hooks:
5555 - id: black
5656 language_version: python3
4343 In addition to the built-in panels, a number of third-party panels are
4444 contributed by the community.
4545
46 The current stable version of the Debug Toolbar is 3.5.0. It works on
47 Django ≥ 3.2.
46 The current stable version of the Debug Toolbar is 3.6.0. It works on
47 Django ≥ 3.2.4.
4848
4949 Documentation, including installation and configuration instructions, is
5050 available at https://django-debug-toolbar.readthedocs.io/.
33
44 # Do not use pkg_resources to find the version but set it here directly!
55 # see issue #1446
6 VERSION = "3.5.0"
6 VERSION = "3.6.0"
77
88 # Code that discovers files or modules in INSTALLED_APPS imports this module.
99 urls = "debug_toolbar.urls", APP_NAME
00 import functools
11
2 from django.http import Http404, HttpResponseBadRequest
2 from django.http import Http404
33
44
55 def require_show_toolbar(view):
1414 return view(request, *args, **kwargs)
1515
1616 return inner
17
18
19 def signed_data_view(view):
20 """Decorator that handles unpacking a signed data form"""
21
22 @functools.wraps(view)
23 def inner(request, *args, **kwargs):
24 from debug_toolbar.forms import SignedDataForm
25
26 data = request.GET if request.method == "GET" else request.POST
27 signed_form = SignedDataForm(data)
28 if signed_form.is_valid():
29 return view(
30 request, *args, verified_data=signed_form.verified_data(), **kwargs
31 )
32 return HttpResponseBadRequest("Invalid signature")
33
34 return inner
2020 panel_form = PanelForm(signed_form.verified_data)
2121 if panel_form.is_valid():
2222 # Success
23 Or wrap the FBV with ``debug_toolbar.decorators.signed_data_view``
2423 """
2524
2625 salt = "django_debug_toolbar"
2323 observe_request = self.toolbar.get_observe_request()
2424 store_id = getattr(self.toolbar, "store_id")
2525 if store_id and observe_request(request):
26 headers["DJDT-STORE-ID"] = store_id
26 headers["djdt-store-id"] = store_id
2727 return headers
2828
2929 @property
6868
6969 def contrasting_color_generator():
7070 """
71 Generate constrasting colors by varying most significant bit of RGB first,
71 Generate contrasting colors by varying most significant bit of RGB first,
7272 and then vary subsequent bits systematically.
7373 """
7474
11 from django.template.loader import render_to_string
22 from django.views.decorators.csrf import csrf_exempt
33
4 from debug_toolbar.decorators import require_show_toolbar, signed_data_view
4 from debug_toolbar.decorators import require_show_toolbar
5 from debug_toolbar.forms import SignedDataForm
56 from debug_toolbar.panels.sql.forms import SQLSelectForm
7
8
9 def get_signed_data(request):
10 """Unpack a signed data form, if invalid returns None"""
11 data = request.GET if request.method == "GET" else request.POST
12 signed_form = SignedDataForm(data)
13 if signed_form.is_valid():
14 return signed_form.verified_data()
15 return None
616
717
818 @csrf_exempt
919 @require_show_toolbar
10 @signed_data_view
11 def sql_select(request, verified_data):
20 def sql_select(request):
1221 """Returns the output of the SQL SELECT statement"""
22 verified_data = get_signed_data(request)
23 if not verified_data:
24 return HttpResponseBadRequest("Invalid signature")
1325 form = SQLSelectForm(verified_data)
1426
1527 if form.is_valid():
3446
3547 @csrf_exempt
3648 @require_show_toolbar
37 @signed_data_view
38 def sql_explain(request, verified_data):
49 def sql_explain(request):
3950 """Returns the output of the SQL EXPLAIN on the given query"""
51 verified_data = get_signed_data(request)
52 if not verified_data:
53 return HttpResponseBadRequest("Invalid signature")
4054 form = SQLSelectForm(verified_data)
4155
4256 if form.is_valid():
7084
7185 @csrf_exempt
7286 @require_show_toolbar
73 @signed_data_view
74 def sql_profile(request, verified_data):
87 def sql_profile(request):
7588 """Returns the output of running the SQL and getting the profiling statistics"""
89 verified_data = get_signed_data(request)
90 if not verified_data:
91 return HttpResponseBadRequest("Invalid signature")
7692 form = SQLSelectForm(verified_data)
7793
7894 if form.is_valid():
22 from django.template import Origin, TemplateDoesNotExist
33 from django.template.engine import Engine
44 from django.template.loader import render_to_string
5 from django.utils.safestring import mark_safe
5 from django.utils.html import format_html, mark_safe
66
77 from debug_toolbar.decorators import require_show_toolbar
88
4949 from pygments import highlight
5050 from pygments.formatters import HtmlFormatter
5151 from pygments.lexers import HtmlDjangoLexer
52
52 except ModuleNotFoundError:
53 source = format_html("<code>{}</code>", source)
54 else:
5355 source = highlight(source, HtmlDjangoLexer(), HtmlFormatter())
5456 source = mark_safe(source)
55 source.pygmentized = True
56 except ImportError:
57 pass
5857
5958 content = render_to_string(
6059 "debug_toolbar/panels/template_source.html",
263263 const origOpen = XMLHttpRequest.prototype.open;
264264 XMLHttpRequest.prototype.open = function () {
265265 this.addEventListener("load", function () {
266 let store_id = this.getResponseHeader("djdt-store-id");
267 if (store_id !== null) {
266 // Chromium emits a "Refused to get unsafe header" uncatchable warning
267 // when the header can't be fetched. While it doesn't impede execution
268 // it's worrisome to developers.
269 if (
270 this.getAllResponseHeaders().indexOf("djdt-store-id") >= 0
271 ) {
272 let store_id = this.getResponseHeader("djdt-store-id");
268273 store_id = encodeURIComponent(store_id);
269274 const dest = `${sidebar_url}?store_id=${store_id}`;
270275 slowjax(dest).then(function (data) {
44 </div>
55 <div class="djDebugPanelContent">
66 <div class="djdt-scroll">
7 {% if not source.pygmentized %}
8 <code>{{ source }}</code>
9 {% else %}
10 {{ source }}
11 {% endif %}
7 {{ source }}
128 </div>
139 </div>
00 Change log
11 ==========
2
3 Pending
4 -------
5
6 3.6.0 (2022-08-17)
7 ------------------
8
9 * Remove decorator ``signed_data_view`` as it was causing issues with
10 `django-urlconfchecks <https://github.com/AliSayyah/django-urlconfchecks/>`__.
11 * Added pygments to the test environment and fixed a crash when using the
12 template panel with Django 4.1 and pygments installed.
13 * Stayed on top of pre-commit hook and GitHub actions updates.
14 * Added some workarounds to avoid a Chromium warning which was worrisome to
15 developers.
16 * Avoided using deprecated Selenium methods to find elements.
17 * Raised the minimum Django version from 3.2 to 3.2.4 so that we can take
18 advantage of backported improvements to the cache connection handler.
219
320 3.5.0 (2022-06-23)
421 ------------------
2424 copyright = copyright.format(datetime.date.today().year)
2525
2626 # The full version, including alpha/beta/rc tags
27 release = "3.5.0"
27 release = "3.6.0"
2828
2929
3030 # -- General configuration ---------------------------------------------------
141141
142142 * ``OBSERVE_REQUEST_CALLBACK``
143143
144 Default: ``'debug_toolbar.middleware.observe_request'``
144 Default: ``'debug_toolbar.toolbar.observe_request'``
145145
146146 This is the dotted path to a function used for determining whether the
147147 toolbar should update on AJAX requests or not. The default checks are that
137137 If using Docker the following will set your ``INTERNAL_IPS`` correctly in Debug mode::
138138
139139 if DEBUG:
140 import os # only if you haven't already imported this
141140 import socket # only if you haven't already imported this
142141 hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
143142 INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + ["127.0.0.1", "10.0.2.2"]
00 backend
11 backends
2 backported
23 checkbox
34 contrib
45 django
2930 Pympler
3031 querysets
3132 refactoring
33 resizing
34 spellchecking
3235 spooler
3336 stacktrace
3437 stacktraces
38 startup
3539 timeline
3640 tox
3741 Transifex
44 import subprocess
55 from time import sleep
66
7 from selenium.webdriver.common.by import By
78 from selenium.webdriver.common.keys import Keys
89 from selenium.webdriver.support import expected_conditions as EC
910 from selenium.webdriver.support.wait import WebDriverWait
5354 def submit_form(selenium, data):
5455 url = selenium.current_url
5556 for name, value in data.items():
56 el = selenium.find_element_by_name(name)
57 el = selenium.find_element(By.NAME, name)
5758 el.send_keys(value)
5859 el.send_keys(Keys.RETURN)
5960 WebDriverWait(selenium, timeout=5).until(EC.url_changes(url))
7172
7273 selenium.get("http://localhost:8000/admin/auth/user/")
7374 # Check if SQL Panel is already visible:
74 sql_panel = selenium.find_element_by_id("djdt-SQLPanel")
75 sql_panel = selenium.find_element(By.ID, "djdt-SQLPanel")
7576 if not sql_panel:
7677 # Open the admin sidebar.
77 el = selenium.find_element_by_id("djDebugToolbarHandle")
78 el = selenium.find_element(By.ID, "djDebugToolbarHandle")
7879 el.click()
79 sql_panel = selenium.find_element_by_id("djdt-SQLPanel")
80 sql_panel = selenium.find_element(By.ID, "djdt-SQLPanel")
8081 # Open the SQL panel.
8182 sql_panel.click()
8283
00 [metadata]
11 name = django-debug-toolbar
2 version = 3.5.0
2 version = 3.6.0
33 description = A configurable set of panels that display various debug information about the current request/response.
44 long_description = file: README.rst
55 long_description_content_type = text/x-rst
1515 Framework :: Django
1616 Framework :: Django :: 3.2
1717 Framework :: Django :: 4.0
18 Framework :: Django :: 4.1
1819 Intended Audience :: Developers
1920 License :: OSI Approved :: BSD License
2021 Operating System :: OS Independent
3031 [options]
3132 python_requires = >=3.7
3233 install_requires =
33 Django >= 3.2
34 Django >= 3.2.4
3435 sqlparse >= 0.2.0
3536 packages = find:
3637 include_package_data = true
9898 response = self.client.get(reverse("djdt:history_sidebar"))
9999 self.assertEqual(response.status_code, 400)
100100
101 def test_history_headers(self):
102 """Validate the headers injected from the history panel."""
103 response = self.client.get("/json_view/")
104 store_id = list(DebugToolbar._store)[0]
105 self.assertEqual(response.headers["djdt-store-id"], store_id)
106
107 @override_settings(
108 DEBUG_TOOLBAR_CONFIG={"OBSERVE_REQUEST_CALLBACK": lambda request: False}
109 )
110 def test_history_headers_unobserved(self):
111 """Validate the headers aren't injected from the history panel."""
112 response = self.client.get("/json_view/")
113 self.assertNotIn("djdt-store-id", response.headers)
114
101115 def test_history_sidebar(self):
102116 """Validate the history sidebar view."""
103117 self.client.get("/json_view/")
443443
444444 # Reset the queries
445445 self.panel._queries = []
446 # Run it again, but with prettyify off. Verify that it's different.
446 # Run it again, but with prettify off. Verify that it's different.
447447 dt_settings.get_config()["PRETTIFY_SQL"] = False
448448 list(User.objects.filter(username__istartswith="spam"))
449449 response = self.panel.process_request(self.request)
452452 self.assertNotEqual(pretty_sql, self.panel._queries[-1]["sql"])
453453
454454 self.panel._queries = []
455 # Run it again, but with prettyify back on.
455 # Run it again, but with prettify back on.
456456 # This is so we don't have to check what PRETTIFY_SQL does exactly,
457457 # but we know it's doing something.
458458 dt_settings.get_config()["PRETTIFY_SQL"] = True
481481
482482 def test_basic(self):
483483 self.get("/regular/basic/")
484 version_panel = self.selenium.find_element_by_id("VersionsPanel")
484 version_panel = self.selenium.find_element(By.ID, "VersionsPanel")
485485
486486 # Versions panel isn't loaded
487487 with self.assertRaises(NoSuchElementException):
488 version_panel.find_element_by_tag_name("table")
488 version_panel.find_element(By.TAG_NAME, "table")
489489
490490 # Click to show the versions panel
491 self.selenium.find_element_by_class_name("VersionsPanel").click()
491 self.selenium.find_element(By.CLASS_NAME, "VersionsPanel").click()
492492
493493 # Version panel loads
494494 table = self.wait.until(
495 lambda selenium: version_panel.find_element_by_tag_name("table")
495 lambda selenium: version_panel.find_element(By.TAG_NAME, "table")
496496 )
497497 self.assertIn("Name", table.text)
498498 self.assertIn("Version", table.text)
504504 )
505505 def test_basic_jinja(self):
506506 self.get("/regular_jinja/basic")
507 template_panel = self.selenium.find_element_by_id("TemplatesPanel")
507 template_panel = self.selenium.find_element(By.ID, "TemplatesPanel")
508508
509509 # Click to show the template panel
510 self.selenium.find_element_by_class_name("TemplatesPanel").click()
510 self.selenium.find_element(By.CLASS_NAME, "TemplatesPanel").click()
511511
512512 self.assertIn("Templates (2 rendered)", template_panel.text)
513513 self.assertIn("base.html", template_panel.text)
522522 self.get("/regular_jinja/basic")
523523 # Make a new request so the history panel has more than one option.
524524 self.get("/execute_sql/")
525 template_panel = self.selenium.find_element_by_id("HistoryPanel")
525 template_panel = self.selenium.find_element(By.ID, "HistoryPanel")
526526 # Record the current side panel of buttons for later comparison.
527 previous_button_panel = self.selenium.find_element_by_id(
528 "djDebugPanelList"
527 previous_button_panel = self.selenium.find_element(
528 By.ID, "djDebugPanelList"
529529 ).text
530530
531531 # Click to show the history panel
532 self.selenium.find_element_by_class_name("HistoryPanel").click()
532 self.selenium.find_element(By.CLASS_NAME, "HistoryPanel").click()
533533 # Click to switch back to the jinja page view snapshot
534 list(template_panel.find_elements_by_css_selector("button"))[-1].click()
535
536 current_button_panel = self.selenium.find_element_by_id("djDebugPanelList").text
534 list(template_panel.find_elements(By.CSS_SELECTOR, "button"))[-1].click()
535
536 current_button_panel = self.selenium.find_element(
537 By.ID, "djDebugPanelList"
538 ).text
537539 # Verify the button side panels have updated.
538540 self.assertNotEqual(previous_button_panel, current_button_panel)
539541 self.assertNotIn("1 query", current_button_panel)
542544 @override_settings(DEBUG_TOOLBAR_CONFIG={"RESULTS_CACHE_SIZE": 0})
543545 def test_expired_store(self):
544546 self.get("/regular/basic/")
545 version_panel = self.selenium.find_element_by_id("VersionsPanel")
547 version_panel = self.selenium.find_element(By.ID, "VersionsPanel")
546548
547549 # Click to show the version panel
548 self.selenium.find_element_by_class_name("VersionsPanel").click()
550 self.selenium.find_element(By.CLASS_NAME, "VersionsPanel").click()
549551
550552 # Version panel doesn't loads
551553 error = self.wait.until(
552 lambda selenium: version_panel.find_element_by_tag_name("p")
554 lambda selenium: version_panel.find_element(By.TAG_NAME, "p")
553555 )
554556 self.assertIn("Data for this panel isn't available anymore.", error.text)
555557
573575 )
574576 def test_django_cached_template_loader(self):
575577 self.get("/regular/basic/")
576 version_panel = self.selenium.find_element_by_id("TemplatesPanel")
578 version_panel = self.selenium.find_element(By.ID, "TemplatesPanel")
577579
578580 # Click to show the templates panel
579 self.selenium.find_element_by_class_name("TemplatesPanel").click()
581 self.selenium.find_element(By.CLASS_NAME, "TemplatesPanel").click()
580582
581583 # Templates panel loads
582584 trigger = self.wait.until(
583 lambda selenium: version_panel.find_element_by_css_selector(".remoteCall")
585 lambda selenium: version_panel.find_element(By.CSS_SELECTOR, ".remoteCall")
584586 )
585587 trigger.click()
586588
587589 # Verify the code is displayed
588590 self.wait.until(
589 lambda selenium: self.selenium.find_element_by_css_selector(
590 "#djDebugWindow code"
591 lambda selenium: self.selenium.find_element(
592 By.CSS_SELECTOR, "#djDebugWindow code"
591593 )
592594 )
593595
594596 def test_sql_action_and_go_back(self):
595597 self.get("/execute_sql/")
596 sql_panel = self.selenium.find_element_by_id("SQLPanel")
597 debug_window = self.selenium.find_element_by_id("djDebugWindow")
598 sql_panel = self.selenium.find_element(By.ID, "SQLPanel")
599 debug_window = self.selenium.find_element(By.ID, "djDebugWindow")
598600
599601 # Click to show the SQL panel
600 self.selenium.find_element_by_class_name("SQLPanel").click()
602 self.selenium.find_element(By.CLASS_NAME, "SQLPanel").click()
601603
602604 # SQL panel loads
603605 button = self.wait.until(
610612 self.assertIn("SQL selected", debug_window.text)
611613
612614 # Close the SQL selected window
613 debug_window.find_element_by_class_name("djDebugClose").click()
615 debug_window.find_element(By.CLASS_NAME, "djDebugClose").click()
614616 self.wait.until(EC.invisibility_of_element(debug_window))
615617
616618 # SQL panel is still visible
619621 @override_settings(DEBUG_TOOLBAR_PANELS=["tests.test_integration.BuggyPanel"])
620622 def test_displays_server_error(self):
621623 self.get("/regular/basic/")
622 debug_window = self.selenium.find_element_by_id("djDebugWindow")
623 self.selenium.find_element_by_class_name("BuggyPanel").click()
624 debug_window = self.selenium.find_element(By.ID, "djDebugWindow")
625 self.selenium.find_element(By.CLASS_NAME, "BuggyPanel").click()
624626 self.wait.until(EC.visibility_of(debug_window))
625627 self.assertEqual(debug_window.text, "»\n500: Internal Server Error")
88 deps =
99 dj32: django~=3.2.9
1010 dj40: django~=4.0.0
11 dj41: django>=4.1b1,<4.2
11 dj41: django~=4.1.0
1212 postgresql: psycopg2-binary
1313 postgis: psycopg2-binary
1414 mysql: mysqlclient
1616 coverage
1717 Jinja2
1818 html5lib
19 pygments
1920 selenium
2021 sqlparse
2122 passenv=
3233 PYTHONPATH = {toxinidir}
3334 PYTHONWARNINGS = d
3435 py39-dj32-postgresql: DJANGO_SELENIUM_TESTS = true
36 py310-dj40-postgresql: DJANGO_SELENIUM_TESTS = true
3537 DB_NAME = {env:DB_NAME:debug_toolbar}
3638 DB_USER = {env:DB_USER:debug_toolbar}
3739 DB_HOST = {env:DB_HOST:localhost}