-
-
Notifications
You must be signed in to change notification settings - Fork 7k
Validation on many to many field when default=None #9790
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
base: main
Are you sure you want to change the base?
Conversation
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.
Pull Request Overview
This PR adds validation to prevent runtime errors when using PrimaryKeyRelatedField
with many=True
and default=None
on ManyToMany fields, which causes crashes when trying to save the serializer.
- Adds validation in
get_fields()
method to detect and prevent the problematic configuration - Includes comprehensive tests to verify the validation works correctly
- Updates contributing documentation with helpful testing tips
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
File | Description |
---|---|
rest_framework/serializers.py | Adds validation logic to detect ManyToMany fields with invalid default=None configuration |
tests/test_serializer.py | Adds test class to verify the new validation raises appropriate errors |
docs/community/contributing.md | Adds documentation for database testing and pytest output options |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
if field_name in info.relations and info.relations[field_name].to_many and declared_fields[field_name].default is None: | ||
raise ValueError( | ||
f"The field '{field_name}' on serializer '{self.__class__.__name__}' is a ManyToMany field and cannot have a default value of None. " | ||
"Please set an appropriate default value, such as an empty list, or remove the default." |
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.
Lists are mutable; so when using an empty list, it could be that it gets written to in one instance, and then the list is reused in another instance of the field.
Is there any logic that prevents this? If not, we should not suggest doing this.
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.
Hi! Sorry, but I don't understand your point. No list is being used in the block you mentioned. Only the info
dictionary is used here, which is computed only once. Regardless of whether it is later mutated by another function, the validation remains valid since the important thing is that the user does not set default=None
when the target is a ManyToMany field. Furthermore, no mutation operation is performed here; only read-only checks on the developer's configuration in the serializer
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.
I agree with you about what the code does.
My comment was about the help string (that's why it's attached to that line) which suggests setting an empty list as a default value. Suggesting that may not be a good idea.
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.
Oh! I understand now. How about:
The field ‘{field_name}’ in the serializer ‘{self.class.name}’ is a ManyToMany field and cannot have a default value of None. Set an appropriate default value, or remove it.
If you confirm, I'll commit to improve the message. If you have a better alternative in mind, feel free to let me know
fields = '__all__' | ||
|
||
# Instantiates with invalid data (not value for a ManyToMany field to force using the default) | ||
# Instantiates with invalid data (no value for a ManyToMany field to force using the default) |
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.
I don't understand what's written here in parentheses.
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.
Hi! It's explaining why the data is invalid. Maybe this comment could be replaced by Instantiates serializer without 'value' field to force using the default=None for the ManyToMany relation
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.
I understand why the data is invalid, but I do not understand the way the comment is worded ("a ManyToMany field to force using the default"). But maybe it's just me :)
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.
Sorry about that! English is not my native language. If you have any alternatives, I can make a commit to correct the message 😄
Note: Before submitting a code change, please review our contributing guidelines.
Description
Hi!
First of all, thank you for the library, it's very useful for my work!
While working on a project, I got a 500 error in production due to a configuration error on my part (copy-paste 🤦♂️): when using a
PrimaryKeySerializer
on a ManyToMany field and settingdefault=None
like this:It crashes at runtime with the following exception when no value is provided for
targets
field:Now it seems simple and intuitive, but in production, with thousands of lines, dozens of serializers, and hundreds of fields, it took me a long time to figure out what the problem was.
To prevent errors in production, I added a new validation in the
get_fields()
method with a super descriptive message indicating a solution (I also added some tests to check this new behavior).I couldn't find an earlier place to perform the validation, but this change allows the validation to run for other methods such as
create()
orupdate()
. If you have a better place where the code block could be moved, please let me know, and I'll make the necessary adjustments.Finally, since this is my first time collaborating on the project, I added some help to the contributing.md file to explain a few things, such as the
-s
parameter in pytests, which allows you to obtain the output during testing, ordjango.test.TestCase
inheritance so you can use the Django DB. If you think it shouldn't be there, or you don't like the format, let me know and I can make the changes.Thanks and best regards!