Skip to content

Commit 1746d14

Browse files
Make copying context easier (for pasting into an editor or shell). django-commons#1840
1 parent 2fc00c6 commit 1746d14

File tree

3 files changed

+37
-14
lines changed

3 files changed

+37
-14
lines changed

debug_toolbar/panels/settings.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,28 @@
44

55
from debug_toolbar.panels import Panel
66

7+
# Use the safe settings filter to exclude sensitive settings
78
get_safe_settings = get_default_exception_reporter_filter().get_safe_settings
89

9-
1010
class SettingsPanel(Panel):
1111
"""
1212
A panel to display all variables in django.conf.settings
1313
"""
14-
14+
# Path to the template used to render the panel content
1515
template = "debug_toolbar/panels/settings.html"
1616

17+
# Mark this panel as asynchronous (optional, based on usage)
1718
is_async = True
1819

20+
# The title of the panel in the Debug Toolbar
1921
nav_title = _("Settings")
2022

2123
def title(self):
24+
# The title of the panel, which will appear in the toolbar
2225
return _("Settings from %s") % settings.SETTINGS_MODULE
2326

2427
def generate_stats(self, request, response):
28+
# This method collects the settings data, filters out sensitive ones,
29+
# and passes it to the template for rendering
2530
self.record_stats({"settings": dict(sorted(get_safe_settings().items()))})
31+
s

debug_toolbar/panels/sql/views.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
from django.http import HttpResponseBadRequest, JsonResponse
22
from django.template.loader import render_to_string
33
from django.views.decorators.csrf import csrf_exempt
4-
54
from debug_toolbar._compat import login_not_required
65
from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar
76
from debug_toolbar.forms import SignedDataForm
87
from debug_toolbar.panels.sql.forms import SQLSelectForm
8+
from django.conf import settings
9+
from django.utils.translation import gettext_lazy as _
10+
from django.views.debug import get_default_exception_reporter_filter
911

10-
12+
# Ensure we handle session value for the copy button
1113
def get_signed_data(request):
1214
"""Unpack a signed data form, if invalid returns None"""
1315
data = request.GET if request.method == "GET" else request.POST
@@ -36,12 +38,19 @@ def sql_select(request):
3638
headers = [d[0] for d in cursor.description]
3739
result = cursor.fetchall()
3840

41+
# Handle session data for copying (increment session value)
42+
if 'value' not in request.session:
43+
request.session['value'] = 0 # Initialize if not set
44+
request.session['value'] += 1 # Increment the value to show for copying
45+
46+
# Prepare context for rendering
3947
context = {
4048
"result": result,
4149
"sql": form.reformat_sql(),
4250
"duration": form.cleaned_data["duration"],
4351
"headers": headers,
4452
"alias": form.cleaned_data["alias"],
53+
"session_value": request.session['value'] # Pass session value to template
4554
}
4655
content = render_to_string("debug_toolbar/panels/sql_select.html", context)
4756
return JsonResponse({"content": content})
@@ -65,9 +74,6 @@ def sql_explain(request):
6574
vendor = form.connection.vendor
6675
with form.cursor as cursor:
6776
if vendor == "sqlite":
68-
# SQLite's EXPLAIN dumps the low-level opcodes generated for a query;
69-
# EXPLAIN QUERY PLAN dumps a more human-readable summary
70-
# See https://www.sqlite.org/lang_explain.html for details
7177
cursor.execute(f"EXPLAIN QUERY PLAN {sql}", params)
7278
elif vendor == "postgresql":
7379
cursor.execute(f"EXPLAIN ANALYZE {sql}", params)
@@ -110,8 +116,6 @@ def sql_profile(request):
110116
cursor.execute("SET PROFILING=1") # Enable profiling
111117
cursor.execute(sql, params) # Execute SELECT
112118
cursor.execute("SET PROFILING=0") # Disable profiling
113-
# The Query ID should always be 1 here but I'll subselect to get
114-
# the last one just in case...
115119
cursor.execute(
116120
"""
117121
SELECT *

example/templates/index.html

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,40 @@ <h1>Index of Tests</h1>
2424
<span id="session-value">{{ request.session.value|default:0 }}</span>
2525
<button id="incrementFetch" data-url="{% url 'ajax_increment' %}" type="button">Increment via fetch</button>
2626
<button id="incrementXHR" data-url="{% url 'ajax_increment' %}" type="button">Increment via XHR</button>
27+
<!-- New Copy Button -->
28+
<button id="copyValue" type="button">Copy</button>
2729
</p>
2830
<script>
2931
const incrementFetch = document.querySelector("#incrementFetch");
3032
const incrementXHR = document.querySelector("#incrementXHR");
3133
const value = document.querySelector("#session-value");
34+
const copyButton = document.querySelector("#copyValue");
35+
3236
incrementFetch.addEventListener("click", function () {
33-
fetch(incrementFetch.dataset.url).then( function (response) {
34-
response.json().then(function(data) {
37+
fetch(incrementFetch.dataset.url).then(function (response) {
38+
response.json().then(function (data) {
3539
value.innerHTML = data.value;
3640
});
3741
});
3842
});
43+
3944
incrementXHR.addEventListener("click", function () {
4045
const xhr = new XMLHttpRequest();
4146
xhr.onreadystatechange = () => {
4247
if (xhr.readyState === 4) {
4348
value.innerHTML = JSON.parse(xhr.response).value;
4449
}
45-
}
46-
xhr.open('GET', incrementXHR.dataset.url, true);
47-
xhr.send('');
50+
};
51+
xhr.open("GET", incrementXHR.dataset.url, true);
52+
xhr.send("");
53+
});
54+
55+
// Copy Button Functionality
56+
copyButton.addEventListener("click", function () {
57+
const valueToCopy = value.innerText;
58+
navigator.clipboard.writeText(valueToCopy)
59+
.then(() => alert("Value copied to clipboard: " + valueToCopy))
60+
.catch(err => console.error("Failed to copy:", err));
4861
});
4962
</script>
5063
</body>

0 commit comments

Comments
 (0)