Skip to content

Commit 22374ff

Browse files
authored
Merge pull request #284 from nickovs/fix-268-table-actions-with-dicts
2 parents 2414daa + 1b85064 commit 22374ff

File tree

4 files changed

+58
-24
lines changed

4 files changed

+58
-24
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Changelog
77

88
Release date: -
99

10+
- Support creating action URLs for dict data (`#268 <https://github.com/helloflask/bootstrap-flask/issues/268>`__).
11+
1012

1113
2.2.0
1214
-----

docs/macros.rst

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,9 @@ API
519519
using ``|urlize``. Is overruled by ``safe_columns`` parameter. Default is ``None``.
520520
WARNING: Only use this for sanitized user data to prevent XSS attacks.
521521
:param show_actions: Whether to display the actions column. Default is ``False``.
522-
:param model: The model used to build custom_action, view, edit, delete URLs.
522+
:param model: An optional model used to build custom_action, view, edit,
523+
delete URLs. Set this if you need to pull the URL arguments from
524+
a different SQLAlchemy class indexed with the same primary key.
523525
:param actions_title: Title for the actions column header. Default is ``'Actions'``.
524526
:param custom_actions: A list of tuples for creating custom action buttons, where each tuple contains
525527
('Title Text displayed on hover', 'bootstrap icon name', 'URL tuple or fixed URL string')
@@ -541,8 +543,10 @@ an URL tuple in the form of ``('endpoint', [('url_parameter_name', ':db_model_fi
541543
it's a variable, otherwise it will becomes a fixed value). ``db_model_fieldname`` may also contain dots to access
542544
relationships and their fields (e.g. ``user.name``).
543545

544-
Remember to set the ``model`` when setting this URLs, so that Bootstrap-Flask will know where to get the actual value
545-
when building the URL.
546+
By default, Bootstrap-Flask will take the fields from the row data provided.
547+
Alternatively, you may set the ``model``, in which case a record from that
548+
model, indexed with the same primary key, will be used to get the actual
549+
value when building the URL.
546550

547551
For example, for the view below:
548552

@@ -563,13 +567,13 @@ Here is the full example:
563567
@app.route('/test')
564568
def test():
565569
data = Message.query.all()
566-
return render_template('test.html', data=data, Message=Message)
570+
return render_template('test.html', data=data)
567571
568572
.. code-block:: jinja
569573
570574
{% from 'bootstrap4/table.html' import render_table %}
571575
572-
{{ render_table(data, model=Message, view_url=('view_message', [('message_id', ':id')])) }}
576+
{{ render_table(data, view_url=('view_message', [('message_id', ':id')])) }}
573577
574578
The following arguments are expect to accpet an URL tuple:
575579

flask_bootstrap/templates/base/table.html

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
{% from 'base/utils.html' import render_icon, arg_url_for %}
22

33

4-
{% macro build_url(endpoint, model, pk, url_tuples) %}
5-
{% if model == None %}
6-
{{ raise("The model argument can't be None when setting action URLs.") }}
4+
{% macro build_url(record, endpoint, url_tuples, model, pk_field) %}
5+
{% if model != None %}
6+
{% set record = model.query.get(record[pk_field]) %}
77
{% endif %}
88
{% with url_params = {} -%}
99
{%- do url_params.update(request.view_args if not endpoint else {}),
1010
url_params.update(request.args if not endpoint else {}) -%}
11-
{% with record = model.query.get(pk) %}
1211
{% for url_parameter, db_field in url_tuples %}
1312
{% if db_field.startswith(':') and '.' in db_field %}
1413
{%- set db_field = db_field[1:].split('.') -%}
@@ -20,7 +19,6 @@
2019
{%- do url_params.update({url_parameter: db_field}) -%}
2120
{% endif %}
2221
{% endfor %}
23-
{% endwith -%}
2422
{{ arg_url_for(endpoint, url_params) }}
2523
{%- endwith %}
2624
{%- endmacro %}
@@ -115,7 +113,7 @@
115113
{% if action_url is string %}
116114
href="{{ action_url }}"
117115
{% else %}
118-
href="{{ build_url(action_url[0], model, row[primary_key], action_url[1]) | trim }}"
116+
href="{{ build_url(row, action_url[0], action_url[1], model, primary_key) | trim }}"
119117
{% endif %}
120118
title="{{ action_name }}">{{ render_icon(action_icon) }}</a>
121119
{% endfor %}
@@ -125,7 +123,7 @@
125123
{% if view_url is string %}
126124
href="{{ view_url }}"
127125
{% else %}
128-
href="{{ build_url(view_url[0], model, row[primary_key], view_url[1]) | trim }}"
126+
href="{{ build_url(row, view_url[0], view_url[1], model, primary_key) | trim }}"
129127
{% endif %}
130128
title="{{ config['BOOTSTRAP_TABLE_VIEW_TITLE'] }}">
131129
{{ render_icon('eye-fill') }}
@@ -136,7 +134,7 @@
136134
{% if edit_url is string %}
137135
href="{{ edit_url }}"
138136
{% else %}
139-
href="{{ build_url(edit_url[0], model, row[primary_key], edit_url[1]) | trim }}"
137+
href="{{ build_url(row, edit_url[0], edit_url[1], model, primary_key) | trim }}"
140138
{% endif %}
141139
title="{{ config['BOOTSTRAP_TABLE_EDIT_TITLE'] }}">
142140
{{ render_icon('pencil-fill') }}
@@ -147,7 +145,7 @@
147145
{% if delete_url is string %}
148146
action="{{ delete_url }}"
149147
{% else %}
150-
action="{{ build_url(delete_url[0], model, row[primary_key], delete_url[1]) | trim }}"
148+
action="{{ build_url(row, delete_url[0], delete_url[1], model, primary_key) | trim }}"
151149
{% endif %}
152150
method="post">
153151
{% if csrf_token is undefined %}

tests/test_bootstrap4/test_render_table.py

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def test_create_message():
243243
return 'New message'
244244

245245
@app.route('/table')
246-
def test():
246+
def table():
247247
db.drop_all()
248248
db.create_all()
249249
for i in range(10):
@@ -276,15 +276,45 @@ def test():
276276
) }}
277277
''', titles=titles, model=Message, messages=messages)
278278

279-
response = client.get('/table')
280-
data = response.get_data(as_text=True)
281-
assert 'icons/bootstrap-icons.svg#bootstrap-reboot' in data
282-
assert 'href="/table/john_doe/1/resend"' in data
283-
assert 'title="Resend">' in data
284-
assert 'href="/table/me/1/view"' in data
285-
assert 'action="/table/me/1/delete"' in data
286-
assert 'href="/table/me/1/edit"' in data
287-
assert 'href="/table/new-message"' in data
279+
@app.route('/table-with-dict-data')
280+
def dict_data_table():
281+
row_dicts = [{
282+
"id": i+1,
283+
"text": f'Test message {i + 1}',
284+
"sender": 'me',
285+
"recipient": 'john_doe'
286+
} for i in range(10)]
287+
288+
messages = row_dicts
289+
titles = [('id', '#'), ('text', 'Message')]
290+
return render_template_string('''
291+
{% from 'bootstrap4/table.html' import render_table %}
292+
# URL arguments with URL tuple
293+
{{ render_table(messages, titles, show_actions=True,
294+
custom_actions=[
295+
(
296+
'Resend',
297+
'bootstrap-reboot',
298+
('test_resend_message', [('recipient', ':recipient'), ('message_id', ':id')])
299+
)
300+
],
301+
view_url=('test_view_message', [('sender', ':sender'), ('message_id', ':id')]),
302+
edit_url=('test_edit_message', [('sender', ':sender'), ('message_id', ':id')]),
303+
delete_url=('test_delete_message', [('sender', ':sender'), ('message_id', ':id')]),
304+
new_url=('test_create_message')
305+
) }}
306+
''', titles=titles, messages=messages)
307+
308+
for url in ['/table', '/table-with-dict-data']:
309+
response = client.get(url)
310+
data = response.get_data(as_text=True)
311+
assert 'icons/bootstrap-icons.svg#bootstrap-reboot' in data
312+
assert 'href="/table/john_doe/1/resend"' in data
313+
assert 'title="Resend">' in data
314+
assert 'href="/table/me/1/view"' in data
315+
assert 'action="/table/me/1/delete"' in data
316+
assert 'href="/table/me/1/edit"' in data
317+
assert 'href="/table/new-message"' in data
288318

289319

290320
def test_customize_icon_title_of_table_actions(app, client):

0 commit comments

Comments
 (0)