From d7da5ac3288fe4ca9d6b7ed99270adcde318ace6 Mon Sep 17 00:00:00 2001 From: Xavier Fernandez Date: Wed, 10 Jul 2024 09:44:37 +0200 Subject: [PATCH] fail-on-template-vars: improve compatibility with Django behavior With `OneToOneField`, Django raises `Model.DoesNotExist` which is converted by its template engine to `string_if_invalid`: https://github.com/django/django/blob/5.0.7/django/template/base.py#L932-L933 It is usually falsy, hence the need for `InvalidVarException.__bool__` to return `bool(self.origin_value)` to be consistent with Django's default behavior. However to trigger `InvalidVarException` behavior and its dreaded `InvalidVarException.__mod__`, it needs to go through this check: https://github.com/django/django/blob/5.0.7/django/template/base.py#L716 and thus also needs to be truthy hence the stack inspecting `__bool__` method to know what to return. --- pytest_django/plugin.py | 8 ++++++++ tests/test_environment.py | 42 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 62fa1791..07bd5035 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -693,6 +693,14 @@ def _get_origin() -> str | None: return name return None + def __bool__(self) -> bool: + for frame_info in inspect.stack(): + if frame_info.function == "resolve" and frame_info.filename.endswith("base.py"): + # To go through this guard: + # https://github.com/django/django/blob/5.0.7/django/template/base.py#L716 + return True + return bool(self.origin_value) + def __mod__(self, var: str) -> str: origin = self._get_origin() if origin: diff --git a/tests/test_environment.py b/tests/test_environment.py index a3549732..0e4dd403 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -188,6 +188,48 @@ def test_for_invalid_template(client): ) +@pytest.mark.django_project( + extra_settings=""" + TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + ) + """ +) +def test_invalid_template_variable_object_does_not_exists_behaves_like_an_empty_string( + django_pytester: DjangoPytester, +) -> None: + django_pytester.create_app_file( + "
{% if object_exists %}This should not appear{% endif %}
", + "templates/invalid_template_base.html", + ) + django_pytester.create_app_file( + "{% include 'invalid_template_base.html' %}", + "templates/invalid_template.html", + ) + django_pytester.create_test_module( + """ + from django.core.exceptions import ObjectDoesNotExist + from django.template.loader import render_to_string + + import pytest + + def fake_one_to_one_relation_missing(): + raise ObjectDoesNotExist() + + def test_ignore(): + assert render_to_string( + 'invalid_template.html', + {"object_exists": fake_one_to_one_relation_missing}, + ) == "
" + """ + ) + + result = django_pytester.runpytest_subprocess("-s", "--fail-on-template-vars") + + result.assert_outcomes(passed=1) + + @pytest.mark.django_project( extra_settings=""" TEMPLATE_LOADERS = (