Skip to content

Commit 065f5c8

Browse files
committed
Allow bulk-delete via checkboxes on content & table query views.
Fixes #203
1 parent ed6c69f commit 065f5c8

5 files changed

Lines changed: 107 additions & 25 deletions

File tree

‎sqlite_web/sqlite_web.py‎

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,31 @@ def _query_view(template, table=None):
307307
if table:
308308
default_sql = 'SELECT * FROM "%s"' % table
309309
model_class = dataset[table].model_class
310+
pk = model_class._meta.primary_key
311+
is_composite_pk = isinstance(pk, CompositeKey)
312+
allow_edit = not dataset.is_readonly and pk is not False
313+
allow_bulk = allow_edit and not is_composite_pk
310314
else:
311315
default_sql = ''
312316
model_class = dataset._base_model
317+
pk = None
318+
is_composite_pk = False
319+
allow_edit = False
320+
allow_bulk = False
321+
322+
if request.method == 'POST':
323+
action = request.form.get('action')
324+
pks = request.form.getlist('pk')
325+
if action == 'bulk-delete':
326+
if not allow_bulk:
327+
flash('Cannot perform bulk operation on this table.', 'warning')
328+
elif not pks:
329+
flash('No rows were selected.', 'warning')
330+
else:
331+
n = (model_class.delete()
332+
.where(model_class._meta.primary_key.in_(pks))
333+
.execute())
334+
flash('Successfully deleted %s row(s)' % n, 'success')
313335

314336
page = page_next = page_prev = page_start = page_end = total_pages = 1
315337
total = -1 # Number of rows in query result.
@@ -358,19 +380,17 @@ def _query_view(template, table=None):
358380

359381
if data_description is not None and table:
360382
col_names = [r[0] for r in data_description]
361-
pk = model_class._meta.primary_key
362-
if not isinstance(pk, CompositeKey) and pk is not False:
363-
pk = pk.column_name
364-
if pk in col_names:
365-
pk_index = col_names.index(pk)
383+
if pk and not is_composite_pk and pk.column_name in col_names:
384+
pk_index = col_names.index(pk.column_name)
366385

367386
return render_template(
368387
template,
388+
allow_bulk=allow_bulk,
389+
allow_edit=allow_edit,
369390
data=data,
370391
data_description=data_description,
371392
default_sql=default_sql,
372393
error=error,
373-
is_view=any(v.name == table for v in dataset.get_all_views()),
374394
ordering=ordering,
375395
page=page,
376396
page_end=page_end,
@@ -641,16 +661,37 @@ def drop_trigger(table):
641661
table=table)
642662

643663

644-
@app.route('/<table>/content/')
664+
@app.route('/<table>/content/', methods=['GET', 'POST'])
645665
@require_table
646666
def table_content(table):
647-
page_number = request.args.get('page') or ''
648-
if page_number == 'last': page_number = '1000000'
649-
page_number = int(page_number) if page_number.isdigit() else 1
650-
651667
dataset.update_cache(table)
652668
ds_table = dataset[table]
653669
model = ds_table.model_class
670+
is_composite_pk = isinstance(model._meta.primary_key, CompositeKey)
671+
672+
allow_edit = not dataset.is_readonly and model._meta.primary_key is not False
673+
allow_bulk = allow_edit and not is_composite_pk
674+
675+
if request.method == 'POST':
676+
if not allow_bulk:
677+
flash('Cannot perform bulk operation on this table.', 'warning')
678+
else:
679+
action = request.form['action']
680+
pks = request.form.getlist('pk')
681+
if not pks:
682+
flash('No rows were selected.', 'warning')
683+
elif action == 'bulk-delete':
684+
n = (model.delete()
685+
.where(model._meta.primary_key.in_(pks))
686+
.execute())
687+
flash('Successfully deleted %s row(s)' % n, 'success')
688+
else:
689+
flash('Unrecognized action', 'warning')
690+
return redirect(request.full_path)
691+
692+
page_number = request.args.get('page') or ''
693+
if page_number == 'last': page_number = '1000000'
694+
page_number = int(page_number) if page_number.isdigit() else 1
654695

655696
total_rows = ds_table.all().count()
656697
rows_per_page = app.config['ROWS_PER_PAGE']
@@ -677,9 +718,12 @@ def table_content(table):
677718

678719
return render_template(
679720
'table_content.html',
721+
allow_bulk=allow_bulk,
722+
allow_edit=allow_edit,
680723
columns=columns,
681724
ds_table=ds_table,
682725
field_names=field_names,
726+
is_composite_pk=isinstance(model._meta.primary_key, CompositeKey),
683727
next_page=next_page,
684728
ordering=ordering,
685729
page=page_number,

‎sqlite_web/static/css/sqlbrowse.css‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,8 @@ ul.pagination .page-item.disabled .page-link {
1818
border-color: #dee2e6 !important;
1919
color: #aaa;
2020
}
21+
.btn-sm {
22+
line-height: 1;
23+
padding: 0.125rem 0.25rem;
24+
font-size: 0.75rem;
25+
}

‎sqlite_web/static/js/app.js‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ App = window.App || {};
8080
});
8181
});
8282

83+
$('input#toggle-pk-all').on('click', function (evt) {
84+
$('input.toggle-pk').prop('checked', $(this).is(':checked'));
85+
$('button.bulk-action').prop('disabled', ($('input.toggle-pk:checked').length == 0));
86+
});
87+
$('input.toggle-pk').on('click', function (evt) {
88+
$('button.bulk-action').prop('disabled', ($('input.toggle-pk:checked').length == 0));
89+
});
90+
8391
/* Initialize focus on SQL textarea. */
8492
var sqlTextarea = $('textarea#sql');
8593
if (sqlTextarea.length > 0) {

‎sqlite_web/templates/table_content.html‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,35 @@
99

1010
{% block inner_content %}
1111
{# add filters #}
12+
{% if allow_bulk %}
13+
<form action="." autocomplete="off" method="post">
14+
<div class="float-right">
15+
<button class="btn btn-sm btn-danger bulk-action" disabled name="action" type="submit" value="bulk-delete">Bulk Delete</button>
16+
</div>
17+
{% endif %}
1218
<table class="table table-striped small cell-content">
1319
<thead>
1420
<tr>
21+
{% if allow_bulk %}
22+
<th style="width:24px;"><input id="toggle-pk-all" type="checkbox" /></th>
23+
{% endif %}
1524
{% for column in columns %}
1625
<th>
1726
<a href="./?ordering={% if ordering == column %}-{% endif %}{{ column }}">{{ column }}</a>
1827
</th>
1928
{% endfor %}
20-
{% if not dataset.is_readonly and table_pk %}<th></th>{% endif %}
29+
{% if allow_edit %}<th></th>{% endif %}
2130
</tr>
2231
</thead>
2332
<tbody>
2433
{% for row in query %}
34+
{% if allow_edit %}
35+
{% set pk = row|encode_pk(table_pk) %}
36+
{% endif %}
2537
<tr>
38+
{% if allow_bulk %}
39+
<td><input class="toggle-pk" name="pk" type="checkbox" value="{{ pk }}" /></td>
40+
{% endif %}
2641
{% for field in field_names %}
2742
{% set value = row[field] %}
2843
<td>
@@ -33,21 +48,17 @@
3348
{% endif %}
3449
</td>
3550
{% endfor %}
36-
{% if not dataset.is_readonly and table_pk %}
51+
{% if allow_edit %}
3752
<td>
38-
{% set pk = row|encode_pk(table_pk) %}
39-
{% if not is_view or 'update' in view_ops %}
4053
<a class="small" href="{{ url_for('table_update', table=table, pk=pk) }}">Edit</a>
41-
{% endif %}
42-
{% if not is_view or 'delete' in view_ops %}
4354
<a class="small" href="{{ url_for('table_delete', table=table, pk=pk) }}">Delete</a>
44-
{% endif %}
4555
</td>
4656
{% endif %}
4757
</tr>
4858
{% endfor %}
4959
</tbody>
5060
</table>
61+
{% if allow_bulk %}</form>{% endif %}
5162
<nav>
5263
<ul class="pagination">
5364
<li class="{% if not previous_page %}disabled {% endif %}page-item">

‎sqlite_web/templates/table_query.html‎

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,43 +75,57 @@ <h3>
7575
{% if not data %}
7676
<p>Empty result set.</p>
7777
{% else %}
78-
<a class="float-right" href="{{ url_for('table_query', table=table, sql=sql) }}{% if ordering %}&ordering={{ ordering }}{% endif %}{% if page > 1 %}&page={{ page }}{% endif %}">Permalink</a>
78+
{% if allow_bulk %}
79+
<form action="." autocomplete="off" method="post">
80+
<textarea name="sql" style="display: none;">{% if sql %}{{ sql }}{% else %}{{ default_sql }}{% endif %}</textarea>
81+
<div class="float-right">
82+
<a class="btn btn-default" href="{{ url_for('table_query', table=table, sql=sql) }}{% if ordering %}&ordering={{ ordering }}{% endif %}{% if page > 1 %}&page={{ page }}{% endif %}">Permalink</a>
83+
<button class="btn btn-danger bulk-action" disabled name="action" type="submit" value="bulk-delete">Bulk Delete</button>
84+
</div>
85+
{% else %}
86+
<div class="float-right">
87+
<a class="btn btn-default" href="{{ url_for('table_query', table=table, sql=sql) }}{% if ordering %}&ordering={{ ordering }}{% endif %}{% if page > 1 %}&page={{ page }}{% endif %}">Permalink</a>
88+
</div>
89+
{% endif %}
7990
<h3>
8091
Results ({% if total >= 0 %}{% if total_pages > 1 %}{{ page_start }}&ndash;{{ page_end }} of {% endif %}{{ total }}{% else %}unable to determine{% endif %})
8192
</h3>
8293
<table class="table table-striped small cell-content">
8394
<thead>
8495
<tr>
96+
{% if allow_bulk %}
97+
<th style="width:24px;"><input id="toggle-pk-all" type="checkbox" /></th>
98+
{% endif %}
8599
{% for col_desc in data_description %}
86100
<th>
87101
<a class="sort-header" href="{{ url_for('table_query', table=table, sql=sql) }}&ordering={% if loop.index == ordering %}-{% endif %}{{ loop.index }}">{{ col_desc[0] }}{% if ordering is not none %}{% if loop.index == ordering %}&#9660;{% elif loop.index == -ordering %}&#9650;{% endif %}{% endif %}</a>
88102
</th>
89103
{% endfor %}
90-
{% if not dataset.is_readonly and pk_index is not none %}<th></th>{% endif %}
104+
{% if allow_edit %}<th></th>{% endif %}
91105
</tr>
92106
</thead>
93107
<tbody>
94108
{% for row in data %}
95109
<tr>
110+
{% if allow_bulk %}
111+
<td><input class="toggle-pk" name="pk" type="checkbox" value="{{ row[pk_index] }}" /></td>
112+
{% endif %}
96113
{% for value in row %}
97114
<td>
98115
{% if value is none %}<code>NULL</code>{% else %}{{ value|value_filter|safe }}{% endif %}
99116
</td>
100117
{% endfor %}
101-
{% if not dataset.is_readonly and pk_index is not none %}
118+
{% if allow_edit %}
102119
<td>
103-
{% if not is_view or 'update' in view_ops %}
104120
<a class="small" href="{{ url_for('table_update', table=table, pk=row[pk_index]) }}">Edit</a>
105-
{% endif %}
106-
{% if not is_view or 'delete' in view_ops %}
107121
<a class="small" href="{{ url_for('table_delete', table=table, pk=row[pk_index]) }}">Delete</a>
108-
{% endif %}
109122
</td>
110123
{% endif %}
111124
</tr>
112125
{% endfor %}
113126
</tbody>
114127
</table>
128+
{% if pk_index is not none and not dataset.is_readonly %}</form>{% endif %}
115129
{% include "pagination.html" %}
116130
{% endif %}
117131
{% endif %}

0 commit comments

Comments
 (0)