From 652af4d525d81e7314d45295424045566ca35f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Hovm=C3=B6ller?= Date: Thu, 5 Dec 2024 07:25:34 +0100 Subject: [PATCH 1/2] Add `root` object into evaluate parameters to easier be able to access sibling components --- docs/requirements.txt | 1 + docs/templates/albums.html | 4 ++-- docs/test_doc_architecture.py | 1 + docs/test_doc_cookbook_general.py | 35 ++++++++++++++++++++++++++++++- iommi/traversable.py | 2 +- setup.cfg | 1 + tests/settings.py | 1 + 7 files changed, 41 insertions(+), 4 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index c75a4604..822396bb 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,5 @@ sphinx furo Django +django-fastdev -r../test_requirements.txt diff --git a/docs/templates/albums.html b/docs/templates/albums.html index b255bb94..999665b5 100644 --- a/docs/templates/albums.html +++ b/docs/templates/albums.html @@ -13,7 +13,7 @@ {{ query }} diff --git a/docs/test_doc_architecture.py b/docs/test_doc_architecture.py index dd12a0e7..f38a8ed8 100644 --- a/docs/test_doc_architecture.py +++ b/docs/test_doc_architecture.py @@ -207,6 +207,7 @@ class User: row Artist cells Cells bound_cell Cell + root Table """ # @test ) diff --git a/docs/test_doc_cookbook_general.py b/docs/test_doc_cookbook_general.py index e311849b..36436372 100644 --- a/docs/test_doc_cookbook_general.py +++ b/docs/test_doc_cookbook_general.py @@ -1,4 +1,15 @@ -from tests.helpers import req +from django.template import Template + +from docs.models import Album +from iommi import ( + Form, + html, + Page, +) +from tests.helpers import ( + req, + show_output, +) request = req('get') @@ -75,3 +86,25 @@ def test_how_do_i_find_the_path_to_a_parameter(): """ # @test assert True # Until I come up with a nice way to test this + # @end + + +def test_access_other_component(album): + # language=rst + """ + How do I access a sibling component? + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Let's say we have a `Page` with a form and a custom template, and we want + to access the form, we can do that via the `root` object: + """ + + class MyPage(Page): + edit_album = Form.edit(auto__model=Album, instance=album) + view_album = Template(''' + {{ root.parts.edit_album.fields.name.value }} + ''') + + # @test + show_output(MyPage()) + # @end diff --git a/iommi/traversable.py b/iommi/traversable.py index f4628ee0..a338a464 100644 --- a/iommi/traversable.py +++ b/iommi/traversable.py @@ -270,7 +270,7 @@ def on_bind(self) -> None: pass def own_evaluate_parameters(self): - return {} + return dict(root=self.iommi_root()) def iommi_evaluate_parameters(self): return self._evaluate_parameters diff --git a/setup.cfg b/setup.cfg index 6396d264..a85b7a48 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,6 +36,7 @@ filterwarnings = ignore:'cgi' is deprecated and slated for removal in Python 3.13.* ignore:Passing unrecognized arguments to super ignore:.*Reloading models is not advised as it can lead to inconsistencies, most notably with related models.* + ignore:.*FASTDEV_STRICT_IF.* # Silence warning about pytest 6.0 junit_family=xunit1 diff --git a/tests/settings.py b/tests/settings.py index fd44e3fe..52e17534 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -75,6 +75,7 @@ def __mod__(self, other): 'iommi', 'tests', 'docs', + 'django_fastdev', ] From 666010f5204bf85e96b6ba878b2814423b25cb08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Hovm=C3=B6ller?= Date: Thu, 5 Dec 2024 07:54:24 +0100 Subject: [PATCH 2/2] Use django-fastdev for tests instead of iommi test specific code, fix issues found from that, and update tests for `root` object --- docs/requirements.txt | 1 - iommi/base__tests.py | 14 -------------- iommi/form__tests.py | 9 +++++---- iommi/traversable.py | 2 +- iommi/traversable__tests.py | 17 ++++++++++++----- test_requirements.txt | 1 + tests/settings.py | 9 --------- 7 files changed, 19 insertions(+), 34 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 822396bb..c75a4604 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,4 @@ sphinx furo Django -django-fastdev -r../test_requirements.txt diff --git a/iommi/base__tests.py b/iommi/base__tests.py index a213e5b3..519af182 100644 --- a/iommi/base__tests.py +++ b/iommi/base__tests.py @@ -101,20 +101,6 @@ def test_get_display_name(): assert get_display_name(mock) == 'Some other THING' -def test_crash_in_templates(): - # We should crash in template rendering during tests if we try to render non-existent stuff - with pytest.raises(Exception) as e: - Template('{{ foo }}').render(context=RequestContext(req('get'))) - - assert ( - str(e.value) == 'Tried to render non-existent variable foo' - or str(e.value) == "Undefined template variable 'foo' in ''" - ) - - # ...but inside if it's fine - assert Template('{% if foo %}foo{% endif %}').render(context=RequestContext(req('get'))) == '' - - def function_based_view(request): return HttpResponse('hello!') # pragma: no cover diff --git a/iommi/form__tests.py b/iommi/form__tests.py index 2f14714c..92bef6ef 100644 --- a/iommi/form__tests.py +++ b/iommi/form__tests.py @@ -1120,10 +1120,11 @@ class MyForm(Form): assert [x.pk for x in MyForm().bind(request=req('get')).fields.foo.choices] == [user.pk] assert MyForm().bind(request=req('get', foo=smart_str(user2.pk))).fields.foo._errors == { - 'User matching query does not exist.' + f"User matching query does not exist.\n\nQuery kwargs:\n\n pk: '{user2.pk}'", } assert MyForm().bind(request=req('get', foo=[smart_str(user2.pk), smart_str(user3.pk)])).fields.foo._errors == { - 'User matching query does not exist.' + f"User matching query does not exist.\n\nQuery kwargs:\n\n pk: '{user2.pk}'", + f"User matching query does not exist.\n\nQuery kwargs:\n\n pk: '{user3.pk}'", } form = MyForm().bind(request=req('get', foo=[smart_str(user.pk)])) @@ -1167,7 +1168,7 @@ class MyForm(Form): assert [x.pk for x in MyForm().bind(request=req('get')).fields.foo.choices] == [user.pk] assert MyForm().bind(request=req('get', foo=smart_str(user2.pk))).fields.foo._errors == { - 'User matching query does not exist.' + f"User matching query does not exist.\n\nQuery kwargs:\n\n pk: '{user2.pk}'" } form = MyForm().bind(request=req('get', foo=[smart_str(user.pk)])) @@ -3638,7 +3639,7 @@ class MyForm(Form): assert [x.pk for x in MyForm().bind(request=req('get')).fields.foo.choices] == [user.pk] assert MyForm().bind(request=req('get', foo=smart_str(user2.pk))).fields.foo._errors == { - 'User matching query does not exist.' + f"User matching query does not exist.\n\nQuery kwargs:\n\n pk: '{user2.pk}'" } form = MyForm().bind(request=req('get', foo=[smart_str(user.pk)])) diff --git a/iommi/traversable.py b/iommi/traversable.py index a338a464..9a7054e1 100644 --- a/iommi/traversable.py +++ b/iommi/traversable.py @@ -287,7 +287,7 @@ def invoke_callback(self, callback, **kwargs): ): raise TypeError( f'TypeError when invoking callback {get_callable_description(callback)}.\n' - f'(Keyword arguments: {", ".join(sorted([*kwargs, *self.iommi_evaluate_parameters()]))})' + f'Keyword arguments:\n {"\n ".join(sorted([*kwargs, *self.iommi_evaluate_parameters()]))}' ) from e raise diff --git a/iommi/traversable__tests.py b/iommi/traversable__tests.py index 2f42aa34..7d1b4c03 100644 --- a/iommi/traversable__tests.py +++ b/iommi/traversable__tests.py @@ -1,4 +1,5 @@ import itertools +from textwrap import dedent from typing import Dict from unittest import mock @@ -471,10 +472,16 @@ def test_invoke_callback_error_message_lambda(): with pytest.raises(TypeError) as e: t.invoke_callback(lambda a: None, b=2) - assert str(e.value) == ( - 'TypeError when invoking callback lambda found at: `t.invoke_callback(lambda a: None, b=2)`.\n' - '(Keyword arguments: b, params, request, traversable, user)' - ) + assert str(e.value) == dedent(''' + TypeError when invoking callback lambda found at: `t.invoke_callback(lambda a: None, b=2)`. + Keyword arguments: + b + params + request + root + traversable + user + ''').strip() def test_invoke_callback_error_message_function(): @@ -489,7 +496,7 @@ def broken_callback(a): assert actual.startswith( 'TypeError when invoking callback `.broken_callback at 0x' ) - assert actual.endswith('`.\n(Keyword arguments: params, request, traversable, user)') + assert actual.endswith('`.\nKeyword arguments:\n params\n request\n root\n traversable\n user') def test_invoke_callback_transparent_type_error(): diff --git a/test_requirements.txt b/test_requirements.txt index 9c185baa..7d8b224b 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -7,4 +7,5 @@ pytest==8.1.1 pytest_snapshot==0.9.0 ruff==0.3.7 time-machine==2.14.1 +django-fastdev -rrequirements.txt diff --git a/tests/settings.py b/tests/settings.py index 52e17534..b0c51cb4 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -13,14 +13,6 @@ TEMPLATE_DEBUG = True -class HighlightBrokenVariable: - def __contains__(self, item): - return True - - def __mod__(self, other): - assert False, f'Tried to render non-existent variable {other}' - - TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', @@ -28,7 +20,6 @@ def __mod__(self, other): 'APP_DIRS': True, 'OPTIONS': { 'debug': TEMPLATE_DEBUG, - 'string_if_invalid': HighlightBrokenVariable(), 'context_processors': [ 'tests.context_processors.context_processor_is_called', ],