Skip to content

Commit bacf5de

Browse files
committed
Fix for columns named after reserved words, closes #134
1 parent dd1bb18 commit bacf5de

File tree

6 files changed

+67
-8
lines changed

6 files changed

+67
-8
lines changed

django_sql_dashboard/templates/django_sql_dashboard/dashboard.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ <h2>Available tables</h2>
101101
<ul class="dashboard-columns">
102102
{% for table in available_tables %}
103103
<li>
104-
<a href="?sql={% filter sign_sql|urlencode %}select count(*) from {{ table.name }}{% endfilter %}&sql={% filter sign_sql|urlencode %}select {{ table.columns }} from {{ table.name }}{% endfilter %}">{{ table.name }}</a>
104+
<a href="?sql={% filter sign_sql|urlencode %}select count(*) from {{ table.name }}{% endfilter %}&sql={% autoescape off %}{% filter sign_sql|urlencode %}select {{ table.sql_columns }} from {{ table.name }}{% endfilter %}{% endautoescape %}">{{ table.name }}</a>
105105
<p>{{ table.columns }}</p>
106106
</li>
107107
{% endfor %}

django_sql_dashboard/utils.py

+12
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,15 @@ def is_valid_base64_json(s):
9393
return True
9494
except (json.JSONDecodeError, binascii.Error, UnicodeDecodeError):
9595
return False
96+
97+
98+
_reserved_words = None
99+
100+
101+
def postgresql_reserved_words(connection):
102+
global _reserved_words
103+
if _reserved_words is None:
104+
with connection.cursor() as cursor:
105+
cursor.execute("select word from pg_get_keywords() where catcode = 'R'")
106+
_reserved_words = [row[0] for row in cursor.fetchall()]
107+
return _reserved_words

django_sql_dashboard/views.py

+26-5
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616
StreamingHttpResponse,
1717
)
1818
from django.shortcuts import get_object_or_404, render
19+
from django.utils.safestring import mark_safe
20+
21+
from psycopg2.extensions import quote_ident
1922

2023
from .models import Dashboard
2124
from .utils import (
2225
check_for_base64_upgrade,
2326
displayable_rows,
2427
extract_named_parameters,
28+
postgresql_reserved_words,
2529
sign_sql,
2630
unsign_sql,
2731
)
@@ -137,18 +141,24 @@ def _dashboard_index(
137141
alias = getattr(settings, "DASHBOARD_DB_ALIAS", "dashboard")
138142
row_limit = getattr(settings, "DASHBOARD_ROW_LIMIT", None) or 100
139143
connection = connections[alias]
144+
reserved_words = postgresql_reserved_words(connection)
140145
with connection.cursor() as tables_cursor:
141146
tables_cursor.execute(
142147
"""
143148
with visible_tables as (
144149
select table_name
145-
from information_schema.tables
146-
where table_schema = 'public'
147-
order by table_name
150+
from information_schema.tables
151+
where table_schema = 'public'
152+
order by table_name
153+
),
154+
reserved_keywords as (
155+
select word
156+
from pg_get_keywords()
157+
where catcode = 'R'
148158
)
149159
select
150160
information_schema.columns.table_name,
151-
string_agg(column_name, ', ' order by ordinal_position) as columns
161+
array_to_json(array_agg(column_name order by ordinal_position)) as columns
152162
from
153163
information_schema.columns
154164
join
@@ -162,8 +172,19 @@ def _dashboard_index(
162172
information_schema.columns.table_name
163173
"""
164174
)
175+
fetched = tables_cursor.fetchall()
165176
available_tables = [
166-
{"name": t[0], "columns": t[1]} for t in tables_cursor.fetchall()
177+
{
178+
"name": row[0],
179+
"columns": ", ".join(row[1]),
180+
"sql_columns": ", ".join(
181+
[
182+
'"{}"'.format(column) if column in reserved_words else column
183+
for column in row[1]
184+
]
185+
),
186+
}
187+
for row in fetched
167188
]
168189

169190
parameters = []

test_project/config/settings.py

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"django.contrib.messages",
2323
"django.contrib.staticfiles",
2424
"django_sql_dashboard",
25+
"extra_models",
2526
]
2627

2728
MIDDLEWARE = [

test_project/extra_models/models.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.db import models
2+
3+
4+
class Switch(models.Model):
5+
name = models.SlugField()
6+
on = models.BooleanField(default=False)
7+
8+
class Meta:
9+
db_table = "switches"

test_project/test_dashboard.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -199,22 +199,38 @@ def test_saved_dashboard_errors_sql_not_in_textarea(admin_client, saved_dashboar
199199
assert '<pre class="sql">this is bad</pre>' in html
200200

201201

202-
def test_dashboard_show_available_tables(admin_client, dashboard_db):
202+
def test_dashboard_show_available_tables(admin_client):
203203
response = admin_client.get("/dashboard/")
204204
soup = BeautifulSoup(response.content, "html5lib")
205205
lis = soup.find("ul").findAll("li")
206206
details = [
207-
{"table": li.find("a").text, "columns": li.find("p").text}
207+
{
208+
"table": li.find("a").text,
209+
"columns": li.find("p").text,
210+
"href": li.find("a")["href"],
211+
}
208212
for li in lis
209213
if li.find("a").text.startswith("django_sql_dashboard")
214+
or li.find("a").text == "switches"
210215
]
216+
# Decode the href in each one into a SQL query
217+
for detail in details:
218+
href = detail.pop("href")
219+
detail["href_sql"] = urllib.parse.parse_qs(href)["sql"][0].rsplit(":", 1)[0]
211220
assert details == [
212221
{
213222
"table": "django_sql_dashboard_dashboard",
214223
"columns": "id, slug, title, description, created_at, edit_group_id, edit_policy, owned_by_id, view_group_id, view_policy",
224+
"href_sql": "select id, slug, title, description, created_at, edit_group_id, edit_policy, owned_by_id, view_group_id, view_policy from django_sql_dashboard_dashboard",
215225
},
216226
{
217227
"table": "django_sql_dashboard_dashboardquery",
218228
"columns": "id, sql, dashboard_id, _order",
229+
"href_sql": "select id, sql, dashboard_id, _order from django_sql_dashboard_dashboardquery",
230+
},
231+
{
232+
"table": "switches",
233+
"columns": "id, name, on",
234+
"href_sql": 'select id, name, "on" from switches',
219235
},
220236
]

0 commit comments

Comments
 (0)