Skip to content

Commit

Permalink
show links in table text (#204)
Browse files Browse the repository at this point in the history
Co-authored-by: Grey Li <[email protected]>
  • Loading branch information
PanderMusubi and greyli authored May 22, 2022
1 parent d2c3752 commit 525eb77
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 18 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Changelog

Release date: --

- Add ``safe_columns`` and ``urlize_columns`` parameters to ``render_table`` macro
to support rendering table column as HTML/URL (`#204 <https://github.com/helloflask/bootstrap-flask/pull/204>`__).


2.0.2
-----
Expand Down
19 changes: 13 additions & 6 deletions docs/macros.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ API
default to ``md``. This will overwrite config ``BOOTSTRAP_BTN_SIZE``.
:param form_group_classes: Bootstrap 5 only (``bootstrap5/form.html``). You can use this parameter to change the
form group classes, it will read the config ``BOOTSTRAP_FORM_GROUP_CLASSES`` first
(the default value is ``mb-3``)
(the default value is ``mb-3``).

.. tip:: See :ref:`button_customization` and :ref:`checkbox_customization` to learn more on customizations.

Expand Down Expand Up @@ -193,7 +193,7 @@ API
:param render_kw: A dictionary, specifying custom attributes for the
``<form>`` tag.
:param form_group_classes: Bootstrap 5 only (``bootstrap5/form.html``). You can use this parameter to change the form group classes, it will
read the config ``BOOTSTRAP_FORM_GROUP_CLASSES`` first (the default value is ``mb-3``)
read the config ``BOOTSTRAP_FORM_GROUP_CLASSES`` first (the default value is ``mb-3``).
:param form_inline_classes: Bootstrap 5 only (``bootstrap5/form.html``). You can use this parameter to change the form inline classes,
it will read the config ``BOOTSTRAP_FORM_INLINE_CLASSES`` first (the default value is
``row row-cols-lg-auto g-3 align-items-center``).
Expand Down Expand Up @@ -273,7 +273,7 @@ API
:param col_class_default: The default class to apply to the div that represents a column
if nothing more specific is said for the div column of the rendered field.
:param col_map: A dictionary, mapping field.name to a class definition that should be applied to
the div column that contains the field. For example: ``col_map={'username': 'col-md-2'})``
the div column that contains the field. For example: ``col_map={'username': 'col-md-2'})``.
:param button_style: Set button style for ``SubmitField``. Accept Bootstrap button style name (i.e. primary,
secondary, outline-success, etc.), default to ``primary`` (e.g. ``btn-primary``). This will
overwrite config ``BOOTSTRAP_BTN_STYLE``.
Expand All @@ -283,7 +283,7 @@ API
``{'submit': 'success'}``. This will overwrite ``button_style`` and ``BOOTSTRAP_BTN_STYLE``.
:param form_group_classes: Bootstrap 5 only (``bootstrap5/form.html``). You can use this parameter to change the
form group classes, it will read the config ``BOOTSTRAP_FORM_GROUP_CLASSES`` first
(the default value is ``mb-3``)
(the default value is ``mb-3``).
:param form_type: One of ``basic``, ``inline`` or ``horizontal``. See the Bootstrap docs for details on different
form layouts.
:param horizontal_columns: When using the horizontal layout, layout forms like this. Must be a 3-tuple of
Expand Down Expand Up @@ -366,7 +366,7 @@ API
:param size: Can be 'sm' or 'lg' for smaller/larger pagination.
:param args: Additional arguments passed to :func:`~flask.url_for`. If
``endpoint`` is ``None``, uses :attr:`~flask.Request.args` and
:attr:`~flask.Request.view_args`
:attr:`~flask.Request.view_args`.
:param fragment: Add URL fragment into link, such as ``#comment``.
:param align: The align of the pagination. Can be 'left', 'center' or 'right', default to 'left'.
:param kwargs: Extra attributes for the ``<ul>``-element.
Expand Down Expand Up @@ -443,7 +443,7 @@ API
:param dismissible: If true, will output a button to close an alert. For fully functioning dismissible alerts, you must use the alerts JavaScript plugin.
:param dismiss_animate: If true, will enable dismiss animate when click the dismiss button.

When you call ``flash('message', 'category')``, there are 8 category options available, mapping to Bootstrap 4's alerts type:
When you call ``flash('message', 'category')``, there are 8 category options available, mapping to Bootstrap's alerts type:

primary, secondary, success, danger, warning, info, light, dark.

Expand Down Expand Up @@ -492,6 +492,8 @@ API
header_classes=None,\
responsive=False,\
responsive_class='table-responsive',\
safe_columns=None,\
urlize_columns=None,\
show_actions=False,\
actions_title='Actions',\
model=None,\
Expand All @@ -511,6 +513,11 @@ API
:param header_classes: A string of classes to apply to the table header (e.g ``'thead-dark'``).
:param responsive: Whether to enable/disable table responsiveness.
:param responsive_class: The responsive class to apply to the table. Default is ``'table-responsive'``.
:param safe_columns: Tuple with columns names to render HTML safe using ``|safe``.
Has priority over ``urlize_columns`` parameter. Default is ``None``.
:param urlize_columns: Tuple with column names to render with HTML link on each URL
using ``|urlize``. Is overruled by ``safe_columns`` parameter. Default is ``None``.
WARNING: Only use this for sanitized user data to prevent XSS attacks.
:param show_actions: Whether to display the actions column. Default is ``False``.
:param model: The model used to build custom_action, view, edit, delete URLs.
:param actions_title: Title for the actions column header. Default is ``'Actions'``.
Expand Down
15 changes: 14 additions & 1 deletion examples/bootstrap4/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,21 @@ def before_first_request_func():
db.drop_all()
db.create_all()
for i in range(20):
url = 'mailto:[email protected]'
if i % 7 == 0:
url = 'www.t.me'
elif i % 7 == 1:
url = 'https://t.me'
elif i % 7 == 2:
url = 'http://t.me'
elif i % 7 == 3:
url = 'http://t'
elif i % 7 == 4:
url = 'http://'
elif i % 7 == 5:
url = '[email protected]'
m = Message(
text=f'Test message {i+1}',
text=f'Message {i+1} {url}',
author=f'Author {i+1}',
category=f'Category {i+1}',
create_time=4321*(i+1)
Expand Down
4 changes: 2 additions & 2 deletions examples/bootstrap4/templates/table.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ <h2>Responsive Table</h2>
{{ render_table(messages, responsive=True, responsive_class='table-responsive-sm') }}

<h2>Table with actions</h2>
<pre>{% raw %}{{ render_table(messages, show_actions=True, model=Message,
<pre>{% raw %}{{ render_table(messages, urlize_columns=('text'), show_actions=True, model=Message,
view_url=('view_message', [('message_id', ':id')]),
edit_url=('edit_message', [('message_id', ':id')]),
delete_url=('delete_message', [('message_id', ':id')]),
new_url=url_for('new_message')) }}{% endraw %}</pre>
{{ render_table(messages, show_actions=True, model=Message,
{{ render_table(messages, urlize_columns=('text'), show_actions=True, model=Message,
view_url=('view_message', [('message_id', ':id')]),
edit_url=('edit_message', [('message_id', ':id')]),
delete_url=('delete_message', [('message_id', ':id')]),
Expand Down
15 changes: 14 additions & 1 deletion examples/bootstrap5/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,21 @@ def before_first_request_func():
db.drop_all()
db.create_all()
for i in range(20):
url = 'mailto:[email protected]'
if i % 7 == 0:
url = 'www.t.me'
elif i % 7 == 1:
url = 'https://t.me'
elif i % 7 == 2:
url = 'http://t.me'
elif i % 7 == 3:
url = 'http://t'
elif i % 7 == 4:
url = 'http://'
elif i % 7 == 5:
url = '[email protected]'
m = Message(
text=f'Test message {i+1}',
text=f'Message {i+1} {url}',
author=f'Author {i+1}',
category=f'Category {i+1}',
create_time=4321*(i+1)
Expand Down
4 changes: 2 additions & 2 deletions examples/bootstrap5/templates/table.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ <h2>Responsive Table</h2>
{{ render_table(messages, responsive=True, responsive_class='table-responsive-sm') }}

<h2>Table with actions</h2>
<pre>{% raw %}{{ render_table(messages, show_actions=True, model=Message,
<pre>{% raw %}{{ render_table(messages, urlize_columns=('text'), show_actions=True, model=Message,
view_url=('view_message', [('message_id', ':id')]),
edit_url=('edit_message', [('message_id', ':id')]),
delete_url=('delete_message', [('message_id', ':id')]),
new_url=url_for('new_message')) }}{% endraw %}</pre>
{{ render_table(messages, show_actions=True, model=Message,
{{ render_table(messages, urlize_columns=('text'), show_actions=True, model=Message,
view_url=('view_message', [('message_id', ':id')]),
edit_url=('edit_message', [('message_id', ':id')]),
delete_url=('delete_message', [('message_id', ':id')]),
Expand Down
32 changes: 27 additions & 5 deletions flask_bootstrap/templates/base/table.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
header_classes=None,
responsive=False,
responsive_class='table-responsive',
safe_columns=None,
urlize_columns=None,
model=None,
show_actions=False,
actions_title='Actions',
Expand Down Expand Up @@ -79,11 +81,31 @@
{% for row in data %}
<tr>
{% for title in titles %}
{% if title[0] == primary_key %}
<th scope="row">{{ row[title[0]] }}</th>
{% else %}
<td>{{ row[title[0]] }}</td>
{% endif %}
{% set key = title[0] %}
{% set value = row[key] %}
{%- if key == primary_key -%}
<th scope="row">
{%- else -%}
<td>
{%- endif -%}
{%- if value is string -%}
{%- if safe_columns and key in safe_columns -%}
{{ value|safe }}
{%- else -%}
{%- if urlize_columns and key in urlize_columns -%}
{{ value|urlize }}
{%- else -%}
{{ value }}
{%- endif -%}
{%- endif -%}
{%- else -%}
{{ value }}
{%- endif -%}
{%- if key == primary_key -%}
</th>
{%- else -%}
</td>
{%- endif -%}
{% endfor %}
{% if show_actions %}
<td>
Expand Down
68 changes: 67 additions & 1 deletion tests/test_bootstrap4/test_render_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,77 @@ def test():
assert '<table class="table">' in data
assert '<th scope="col">#</th>' in data
assert '<th scope="col">Message</th>' in data
assert '<th scope="col">Message</th>' in data
assert '<th scope="row">1</th>' in data
assert '<td>Test message 1</td>' in data


def test_render_safe_table(app, client):
db = SQLAlchemy(app)

class Message(db.Model):
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.Text)

@app.route('/table')
def test():
db.drop_all()
db.create_all()
for i in range(10):
msg = Message(text=f'Test <em>message</em> {i+1}')
db.session.add(msg)
db.session.commit()
page = request.args.get('page', 1, type=int)
pagination = Message.query.paginate(page, per_page=10)
messages = pagination.items
titles = [('id', '#'), ('text', 'Message')]
return render_template_string('''
{% from 'bootstrap4/table.html' import render_table %}
{{ render_table(messages, titles, safe_columns=('text'), urlize_columns=('text')) }}
''', titles=titles, messages=messages)

response = client.get('/table')
data = response.get_data(as_text=True)
assert '<table class="table">' in data
print(data)
assert '<th scope="col">#</th>' in data
assert '<th scope="col">Message</th>' in data
assert '<th scope="row">1</th>' in data
assert '<td>Test <em>message</em> 1</td>' in data


def test_render_urlize_table(app, client):
db = SQLAlchemy(app)

class Message(db.Model):
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.Text)

@app.route('/table')
def test():
db.drop_all()
db.create_all()
for i in range(10):
msg = Message(text=f'Test https://t.me {i+1}')
db.session.add(msg)
db.session.commit()
page = request.args.get('page', 1, type=int)
pagination = Message.query.paginate(page, per_page=10)
messages = pagination.items
titles = [('id', '#'), ('text', 'Message')]
return render_template_string('''
{% from 'bootstrap4/table.html' import render_table %}
{{ render_table(messages, titles, urlize_columns=('text')) }}
''', titles=titles, messages=messages)

response = client.get('/table')
data = response.get_data(as_text=True)
assert '<table class="table">' in data
assert '<th scope="col">#</th>' in data
assert '<th scope="col">Message</th>' in data
assert '<th scope="row">1</th>' in data
assert '<td>Test <a href="https://t.me" rel="noopener">https://t.me</a> 1</td>' in data


def test_render_customized_table(app, client):
db = SQLAlchemy(app)

Expand Down

0 comments on commit 525eb77

Please sign in to comment.