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

Refs #18597 -- Refactor new hooks for BaseModelFormSet and BaseInlineFormSet #17818

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

shjohnson-pi
Copy link

Added two new hooks:

  • BaseModelFormSet.get_instance_by_queryset_index
  • BaseInlineFormSet.prepare_queryset

Added documentation showing how new hooks can be used to allow an inline formset to use prefetch_related. Added a test for this scenario.


Context:

Because it's been ~2 years since there's been progress on these tickets, I'm sure extra time will be needed to determine if this is still the best direction to fix the inline/cache problem.

Also, this is my first major pull request for Django, so all feedback is welcome. In particular my documentation and tests may need changes.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Hello! Thank you for your contribution 💪

As it's your first contribution be sure to check out the patch review checklist.

If you're fixing a ticket from Trac make sure to set the "Has patch" flag and include a link to this PR in the ticket!

If you have any design or process questions then you can ask in the Django forum.

Welcome aboard ⛵️!

Copy link
Member

@felixxm felixxm left a comment

Choose a reason for hiding this comment

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

@shjohnson-pi Thanks 👍 I left initial comments.

@@ -1311,6 +1311,51 @@ Then when you create your inline formset, pass in the optional argument
>>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)

.. _inline-formset-interaction-with-prefetching:
Copy link
Member

Choose a reason for hiding this comment

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

get_instance_by_queryset_index() should also be documented under models.BaseModelFormSet docs. Is it useful in itself?

Copy link
Author

Choose a reason for hiding this comment

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

I'm not sure how to mention get_instance_by_queryset_index() in the 'Model formsets' section.
I can't think of a reason to override that method on it's own.

Comment on lines 1321 to 1322
The ``get_instance_by_queryset_index`` and ``prepare_queryset`` hooks were added.

Copy link
Member

Choose a reason for hiding this comment

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

This entire section is new so this comment is unnecessary

Suggested change
The ``get_instance_by_queryset_index`` and ``prepare_queryset`` hooks were added.


The ``get_instance_by_queryset_index`` and ``prepare_queryset`` hooks were added.

You'll need to create a subclass to take advantage of
Copy link
Member

Choose a reason for hiding this comment

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

Reading this section, it's not clear for me what we want to achieve. We should start from describing what we're trying to do here.

return queryset
return super().prepare_queryset(queryset)

Note that you'll also need to ensure that the related object has a default sort::
Copy link
Member

Choose a reason for hiding this comment

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

My immediate question is "why?".

Comment on lines 1148 to 1151
"""
Override BaseInlineFormSet.get_instance_by_queryset_index and
BaseInlineFormSet.prepare_queryset (#18597).
"""
Copy link
Member

Choose a reason for hiding this comment

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

This docstring doesn't describe anything, I'd remove it.

Comment on lines 1160 to 1163
with CaptureQueriesContext(connection) as ctx:
formset = BookFormSet(instance=author)
self.assertEqual(len(formset.forms), 6)
self.assertEqual(len(ctx), 1) # Only 1 query
Copy link
Member

Choose a reason for hiding this comment

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

You can use self.assertNumQueries():

Suggested change
with CaptureQueriesContext(connection) as ctx:
formset = BookFormSet(instance=author)
self.assertEqual(len(formset.forms), 6)
self.assertEqual(len(ctx), 1) # Only 1 query
with self.assertNumQueries(1):
formset = BookFormSet(instance=author)
self.assertEqual(len(formset.forms), 6

Book.objects.create(pk=2, author=author, title="Les Fleurs du Mal")
Book.objects.create(pk=3, author=author, title="Flowers of Evil")

# Check the default issues one query
Copy link
Member

Choose a reason for hiding this comment

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

To add more explanation, comments should state the expected behavior and omit prefixes like "Check" since all tests check things. This guideline is from Python coding style.

Comment on lines 1185 to 1188
with CaptureQueriesContext(connection) as ctx:
formset = BookFormSet(instance=author_with_books)
self.assertEqual(len(formset.forms), 6)
self.assertFalse(ctx.captured_queries) # No queries, prefetch_related is used
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
with CaptureQueriesContext(connection) as ctx:
formset = BookFormSet(instance=author_with_books)
self.assertEqual(len(formset.forms), 6)
self.assertFalse(ctx.captured_queries) # No queries, prefetch_related is used
with assertNumQueries(0):
formset = BookFormSet(instance=author_with_books)
self.assertEqual(len(formset.forms), 6)

Comment on lines 1191 to 1194
with CaptureQueriesContext(connection) as ctx:
formset = BookFormSet(instance=author)
self.assertEqual(len(formset.forms), 6)
self.assertEqual(len(ctx), 1) # Only 1 query
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
with CaptureQueriesContext(connection) as ctx:
formset = BookFormSet(instance=author)
self.assertEqual(len(formset.forms), 6)
self.assertEqual(len(ctx), 1) # Only 1 query
with assertNumQueries(1):
formset = BookFormSet(instance=author)
self.assertEqual(len(formset.forms), 6)

* New hooks were added to :class:`~django.forms.models.BaseModelFormSet` and
:class:`~django.forms.models.BaseInlineFormSet` to allow custom subclasses to
utilize :meth:`~django.db.models.query.QuerySet.prefetch_related`
(:ticket:`18597`). See :ref:`inline-formset-interaction-with-prefetching` for
Copy link
Member

Choose a reason for hiding this comment

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

Ticket number is unnecessary here

Suggested change
(:ticket:`18597`). See :ref:`inline-formset-interaction-with-prefetching` for
See :ref:`inline-formset-interaction-with-prefetching` for

…FormSet.

Added two new hooks:

- BaseModelFormSet.get_instance_by_queryset_index
- BaseInlineFormSet.prepare_queryset

Added documentation showing how new hooks can be used to allow an inline
formset to use prefetch_related. Added a test for this scenario.
@shjohnson-pi
Copy link
Author

Thank you for the detailed feedback felixxm.

I've made the following changes and rebased.

  • Removed redundant sentence.
  • Explained goal of docs section.
  • Explained why a sort is required.
  • Removed redundant test docstring.
  • Removed hardcoded pks in test.
  • Used self.assertNumQueries(). (Looks much better!)
  • Tweaked test comments.
  • Removed link to ticket in release notes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants