-
-
Notifications
You must be signed in to change notification settings - Fork 7.1k
Fix #9607: Resolve N+1 queries with PrimaryKeyRelatedField many=True #9947
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -262,6 +262,28 @@ def to_internal_value(self, data): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except (TypeError, ValueError): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.fail('incorrect_type', data_type=type(data).__name__) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def many_to_internal_value(self, data): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pks = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for item in data: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if self.pk_field is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| item = self.pk_field.to_internal_value(item) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(item, bool): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.fail('incorrect_type', data_type=type(item).__name__) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pks.append(item) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryset = self.get_queryset() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| objs = {str(obj.pk): obj for obj in queryset.filter(pk__in=pks)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except (ValueError, TypeError): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Fall back to per-item validation to surface correct error messages | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [self.to_internal_value(pk) for pk in pks] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+273
to
+278
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for pk in pks: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| obj = objs.get(str(pk)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+266
to
+281
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pks = [] | |
| for item in data: | |
| if self.pk_field is not None: | |
| item = self.pk_field.to_internal_value(item) | |
| if isinstance(item, bool): | |
| self.fail('incorrect_type', data_type=type(item).__name__) | |
| pks.append(item) | |
| queryset = self.get_queryset() | |
| try: | |
| objs = {str(obj.pk): obj for obj in queryset.filter(pk__in=pks)} | |
| except (ValueError, TypeError): | |
| # Fall back to per-item validation to surface correct error messages | |
| return [self.to_internal_value(pk) for pk in pks] | |
| result = [] | |
| for pk in pks: | |
| obj = objs.get(str(pk)) | |
| queryset = self.get_queryset() | |
| pks = [] | |
| try: | |
| for item in data: | |
| if self.pk_field is not None: | |
| item = self.pk_field.to_internal_value(item) | |
| if isinstance(item, bool): | |
| self.fail('incorrect_type', data_type=type(item).__name__) | |
| if self.pk_field is None: | |
| item = queryset.model._meta.pk.to_python(item) | |
| pks.append(item) | |
| objs = queryset.in_bulk(pks) | |
| except (ValueError, TypeError): | |
| # Fall back to per-item validation to surface correct error messages | |
| return [self.to_internal_value(pk) for pk in data] | |
| result = [] | |
| for pk in pks: | |
| obj = objs.get(pk) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This introduces a new optimized code path for
many=Truedeserialization. Please add tests that (1) assert it does a single batched DB query (no N+1) and (2) covers pk normalization cases (e.g. UUID string casing / integer strings with leading zeros) to prevent regressions in the new mapping logic.