Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix errors caused in jinja StrictUndefined mode #2571

Merged
merged 7 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

2.0.0a3
-------

Fixes:

* Jinja templates can now be loaded in StrictUndefined mode.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure if 1) this change warranted an entry,
2) this was the correct version header

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great - that'll be the next version, so perfect :)


2.0.0a2
-------

Expand Down
2 changes: 2 additions & 0 deletions examples/sqla/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from admin import app
from admin.data import build_sample_db
from jinja2 import StrictUndefined
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the optimal import sorting, given that "admin" is a local folder, but ruff does not know that. I could use "." to make it clearer, or we could just be okay with it.
This seems to be the only example complex enough to have both an app.py and a folder of other modules.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am personally fine with it. If someone was so inclined, we could probably add some ruff config that would help it sort "correctly" - but I consider it broadly unimportant given the small scope, as you note.


# Build a sample db on the fly, if one does not exist yet.
app_dir = op.join(op.realpath(os.path.dirname(__file__)), "admin")
Expand All @@ -13,4 +14,5 @@

if __name__ == "__main__":
# Start app
app.jinja_env.undefined = StrictUndefined
app.run(debug=True)
6 changes: 6 additions & 0 deletions flask_admin/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ def index(self):
)
"""

extra_css: list[str] = []
"""Extra CSS files to include in the page"""

extra_js: list[str] = []
"""Extra JavaScript files to include in the page"""

@property
def _template_args(self):
"""
Expand Down
10 changes: 7 additions & 3 deletions flask_admin/templates/bootstrap4/admin/actions.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@
{% macro form(actions, url) %}
{% if actions %}
<form id="action_form" action="{{ url }}" method="POST" class="d-none">
{% if action_form.csrf_token %}
{% if action_form.csrf_token is defined and action_form.csrf_token %}
{{ action_form.csrf_token }}
{% elif csrf_token %}
{% elif csrf_token is defined and csrf_token %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
{% endif %}
{{ action_form.url(value=return_url) }}
{% if return_url is defined and return_url %}
{{ action_form.url(value=return_url) }}
{% else %}
{{ action_form.url() }}
{% endif %}
{{ action_form.action() }}
</form>
{% endif %}
Expand Down
4 changes: 4 additions & 0 deletions flask_admin/templates/bootstrap4/admin/file/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@
{% if name != '..' and admin_view.can_delete_dirs %}
<form class="icon" method="POST" action="{{ get_url('.delete') }}">
{{ delete_form.path(value=path) }}
{% if delete_form.csrf_token is defined and delete_form.csrf_token %}
{{ delete_form.csrf_token }}
{% endif %}
<button onclick="return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\' recursively?', name=name) }}')">
<i class="fa fa-times glyphicon glyphicon-remove"></i>
</button>
Expand All @@ -95,7 +97,9 @@
{% else %}
<form class="icon" method="POST" action="{{ get_url('.delete') }}">
{{ delete_form.path(value=path) }}
{% if delete_form.csrf_token is defined and delete_form.csrf_token %}
{{ delete_form.csrf_token }}
{% endif %}
<button onclick="return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\'?', name=name) }}')">
<i class="fa fa-trash glyphicon glyphicon-trash"></i>
</button>
Expand Down
28 changes: 14 additions & 14 deletions flask_admin/templates/bootstrap4/admin/lib.html
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
{% set prepend = kwargs.pop('prepend', None) %}
{% set append = kwargs.pop('append', None) %}
<div class="form-group {{ kwargs.get('column_class', '') }}">
<label for="{{ field.id }}" class="col-form-label {% if field.widget.input_type == 'checkbox' %}d-block mb-0{% endif %}">{{ field.label.text }}
<label for="{{ field.id }}" class="col-form-label {% if field.widget.input_type is defined and field.widget.input_type == 'checkbox' %}d-block mb-0{% endif %}">{{ field.label.text }}
{% if h.is_required_form_field(field) %}
<strong class="text-danger">&#42;</strong>
{%- else -%}
Expand All @@ -135,9 +135,9 @@
</div>
{%- endif -%}
{% endif %}
{% if field.widget.input_type == 'checkbox' %}
{% if field.widget.input_type is defined and field.widget.input_type == 'checkbox' %}
{% set _class = kwargs.setdefault('class', '') %}
{% elif field.widget.input_type == 'file' %}
{% elif field.widget.input_type is defined and field.widget.input_type == 'file' %}
{% set _class = kwargs.setdefault('class', 'form-control-file') %}
{% else %}
{% set _class = kwargs.setdefault('class', 'form-control') %}
Expand Down Expand Up @@ -179,10 +179,10 @@ <h3>{{ text }}</h3>
{% if form.hidden_tag is defined %}
{{ form.hidden_tag() }}
{% else %}
{% if csrf_token %}
{% if csrf_token is defined and csrf_token %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
{% endif %}
{% for f in form if f.widget.input_type == 'hidden' %}
{% for f in form if f.widget.input_type is defined and f.widget.input_type == 'hidden' %}
{{ f }}
{% endfor %}
{% endif %}
Expand All @@ -192,7 +192,7 @@ <h3>{{ text }}</h3>
{{ r(form, form_opts=form_opts) }}
{% endfor %}
{% else %}
{% for f in form if f.widget.input_type != 'hidden' %}
{% for f in form if f.widget.input_type is undefined or f.widget.input_type != 'hidden' %}
{% if form_opts %}
{% set kwargs = form_opts.widget_args.get(f.short_name, {}) %}
{% else %}
Expand Down Expand Up @@ -228,7 +228,7 @@ <h3>{{ text }}</h3>
{% if extra %}
{{ extra }}
{% endif %}
{% if cancel_url %}
{% if cancel_url is defined and cancel_url %}
<a href="{{ cancel_url }}" class="btn btn-danger" role="button" {% if is_modal %}data-dismiss="modal"{% endif %}>{{ _gettext('Cancel') }}</a>
{% endif %}
</div>
Expand All @@ -247,39 +247,39 @@ <h3>{{ text }}</h3>
<link href="{{ admin_static.url(filename='vendor/select2/select2.css', v='4.2.1') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='vendor/select2/select2-bootstrap4.css', v='1.4.6') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='vendor/bootstrap-daterangepicker/daterangepicker-bs4.css', v='1.3.22') }}" rel="stylesheet">
{% if config.FLASK_ADMIN_MAPS %}
{% if config.FLASK_ADMIN_MAPS is defined and config.FLASK_ADMIN_MAPS %}
<link href="{{ admin_static.url(filename='vendor/leaflet/leaflet.css', v='1.0.2') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='vendor/leaflet/leaflet.draw.css', v='0.4.6') }}" rel="stylesheet">
{% endif %}
{% if editable_columns %}
{% if editable_columns is defined and editable_columns %}
<link href="{{ admin_static.url(filename='vendor/x-editable/css/bootstrap4-editable.css', v='1.5.1.1') }}" rel="stylesheet">
{% endif %}
{% endmacro %}

{% macro form_js() %}
{% if config.FLASK_ADMIN_MAPS %}
{% if config.FLASK_ADMIN_MAPS is defined and config.FLASK_ADMIN_MAPS %}
<script {{ admin_csp_nonce_attribute }}>
window.FLASK_ADMIN_MAPS = true;
window.FLASK_ADMIN_MAPBOX_MAP_ID = "{{ config.FLASK_ADMIN_MAPBOX_MAP_ID }}";
{% if config.FLASK_ADMIN_MAPBOX_ACCESS_TOKEN %}
{% if config.FLASK_ADMIN_MAPBOX_ACCESS_TOKEN is defined and config.FLASK_ADMIN_MAPBOX_ACCESS_TOKEN %}
window.FLASK_ADMIN_MAPBOX_ACCESS_TOKEN = "{{ config.FLASK_ADMIN_MAPBOX_ACCESS_TOKEN }}";
{% endif %}
{% if config.FLASK_ADMIN_DEFAULT_CENTER_LAT and config.FLASK_ADMIN_DEFAULT_CENTER_LONG %}
{% if config.FLASK_ADMIN_DEFAULT_CENTER_LAT is defined and config.FLASK_ADMIN_DEFAULT_CENTER_LONG %}
window.FLASK_ADMIN_DEFAULT_CENTER_LAT = "{{ config.FLASK_ADMIN_DEFAULT_CENTER_LAT }}";
window.FLASK_ADMIN_DEFAULT_CENTER_LONG = "{{ config.FLASK_ADMIN_DEFAULT_CENTER_LONG }}";
{% endif %}
</script>
<script {{ admin_csp_nonce_attribute }} src="{{ admin_static.url(filename='vendor/leaflet/leaflet.js', v='1.0.2') }}"></script>
<script {{ admin_csp_nonce_attribute }} src="{{ admin_static.url(filename='vendor/leaflet/leaflet.draw.js', v='0.4.6') }}"></script>
{% if config.FLASK_ADMIN_MAPS_SEARCH %}
{% if config.FLASK_ADMIN_MAPS_SEARCH is defined and config.FLASK_ADMIN_MAPS_SEARCH %}
<script {{ admin_csp_nonce_attribute }}>
window.FLASK_ADMIN_MAPS_SEARCH = "{{ config.FLASK_ADMIN_MAPS_SEARCH }}";
</script>
<script {{ admin_csp_nonce_attribute }} src="https://maps.googleapis.com/maps/api/js?v=3&libraries=places&key={{ config.get('FLASK_ADMIN_GOOGLE_MAPS_API_KEY') }}"></script>
{% endif %}
{% endif %}
<script {{ admin_csp_nonce_attribute }} src="{{ admin_static.url(filename='vendor/bootstrap-daterangepicker/daterangepicker.js', v='1.3.22') }}"></script>
{% if editable_columns %}
{% if editable_columns is defined and editable_columns %}
<script {{ admin_csp_nonce_attribute }} src="{{ admin_static.url(filename='vendor/x-editable/js/bootstrap4-editable.min.js', v='1.5.1.1') }}"></script>
{% endif %}
<script {{ admin_csp_nonce_attribute }} src="{{ admin_static.url(filename='admin/js/form.js', v='1.0.1') }}"></script>
Expand Down
4 changes: 2 additions & 2 deletions flask_admin/templates/bootstrap4/admin/model/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@
<td class="col-{{c}}">
{% if admin_view.is_editable(c) %}
{% set form = list_forms[get_pk_value(row)] %}
{% if form.csrf_token %}
{% if form.csrf_token is defined and form.csrf_token %}
{{ form[c](pk=get_pk_value(row), display_value=get_value(row, c), csrf=form.csrf_token._value()) }}
{% elif csrf_token %}
{% elif csrf_token is defined and csrf_token %}
{{ form[c](pk=get_pk_value(row), display_value=get_value(row, c), csrf=csrf_token()) }}
{% else %}
{{ form[c](pk=get_pk_value(row), display_value=get_value(row, c)) }}
Expand Down
4 changes: 2 additions & 2 deletions flask_admin/templates/bootstrap4/admin/model/row_actions.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
<form class="icon" method="POST" action="{{ get_url('.delete_view') }}">
{{ delete_form.id(value=get_pk_value(row)) }}
{{ delete_form.url(value=return_url) }}
{% if delete_form.csrf_token %}
{% if delete_form.csrf_token is defined and delete_form.csrf_token %}
{{ delete_form.csrf_token }}
{% elif csrf_token %}
{% elif csrf_token is defined and csrf_token %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
{% endif %}
<button onclick="return faHelpers.safeConfirm('{{ _gettext('Are you sure you want to delete this record?') }}');" title="{{ _gettext('Delete Record') }}">
Expand Down
3 changes: 2 additions & 1 deletion flask_admin/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest
from flask import Flask
from jinja2 import StrictUndefined

from flask_admin import Admin

Expand All @@ -9,7 +10,7 @@ def app():
app = Flask(__name__)
app.config["SECRET_KEY"] = "1"
app.config["WTF_CSRF_ENABLED"] = False

app.jinja_env.undefined = StrictUndefined
yield app


Expand Down
3 changes: 2 additions & 1 deletion flask_admin/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from flask import request
from flask import url_for
from flask.views import MethodView
from jinja2 import StrictUndefined

from flask_admin import base

Expand All @@ -18,7 +19,7 @@ def app():
app = Flask(__name__)
app.config["SECRET_KEY"] = "1"
app.config["WTF_CSRF_ENABLED"] = False

app.jinja_env.undefined = StrictUndefined
yield app


Expand Down