Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Handle Django's ValidationErrors in ListField#6423

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
auvipy merged 1 commit intoencode:masterfromsigvef:list-field-django-errors
Dec 4, 2022

Conversation

@sigvef
Copy link
Contributor

@sigvefsigvef commentedJan 28, 2019
edited
Loading

Without this, Django's ValidationErrors will bypass the error collection
from ListField's children.

Here is an example that illustrates this change.

Consider a Serializer that uses ListField like this:

classSomeSerializer(serializers.Serializer):uuids=serializers.ListField(child=serializers.PrimaryKeyRelatedField(queryset=Model.objects.something(),validators=[SomeCustomValidator()])    )

Validating data that looks like this works fine:

{uuids: ['some-valid-uuid','some-valid-uuid']}

Raising a DRF ValidationError for one of the children works fine, giving
an error object like:

{'uuids': {0:ErrorDetail(string='Some validation error')}}

Raising a Django ValidationError for one of the children works
differently (which serializers.PrimaryKeyRelatedField can do in some
cases, like when the uuid is malformed). It gives an error object like:

{'uuids': ["'X' is not a valid UUID."]}

Handling Django's ValidationErrors in ListField explicitly (like in this
pull request), will maintain a regular error interface in this case:

{'uuids': {0:ErrorDetail(string="'X' is not a valid UUID.")}}

Copy link
Collaborator

@carltongibsoncarltongibson left a comment

Choose a reason for hiding this comment

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

Right, hi,@sigvef.

Can you add test cases for each of the new behaviours you're looking to add here.

Really I'm thinking there's two things going on, so two PRs.

Just catchingDjangoValidationErrors here isn't I think something we'd go for.
(This has a long history.)

@sigvef
Copy link
ContributorAuthor

sigvef commentedJan 28, 2019
edited
Loading

Hi! Thanks for taking a look so quickly. I split out the first commit into a separate pull request:#6424

As the the DjangoValidationError change, I'd be happy to read up on some previous discussions. If you have any links laying about I'd be grateful :)

@carltongibson
Copy link
Collaborator

Hi@sigvef. Still haven't sat down to think this through yet, but can you remove thestr() calls as per#6424. Thanks.

@sigvefsigvefforce-pushed thelist-field-django-errors branch from4c68042 to04883beCompareFebruary 14, 2019 15:19
@sigvefsigvef changed the titleImprove ListField error collection in run_child_validationHandle Django's ValidationErrors in ListFieldFeb 14, 2019
@sigvef
Copy link
ContributorAuthor

I've updated this pull request accordingly. I'll add some tests if this change seems reasonable.

@sigvef
Copy link
ContributorAuthor

Some additional context on what this does:

We have a Serializer that uses ListField like this:

classSomeSerializer(serializers.Serializer):uuids=serializers.ListField(child=serializers.PrimaryKeyRelatedField(queryset=Model.objects.something(),validators=[SomeCustomValidator()]))

Validating data that looks like this works fine:

{uuids: ['some-valid-uuid','some-valid-uuid']}

Raising a DRF ValidationError for one of the children works fine, giving an error object like:

{'uuids': {0:ErrorDetail(string='Some validation error')}}

Raising a Django ValidationError for one of the children works differently (which serializers.PrimaryKeyRelatedField can do in some cases, like when the uuid is malformed). It gives an error object like:

["'X' is not a valid UUID."]

Handling Django's ValidationErrors in ListField explicitly (like in this pull request), will maintain a regular error interface in this case:

{'uuids': {0:ErrorDetail(string="'X' is not a valid UUID.")}}

@rpkilby
Copy link
Contributor

Sounds reasonable enough to me? Would need to verify the results with test cases.@carltongibson?

@carltongibson
Copy link
Collaborator

Hey@rpkilby. Just haven't had a chance to think this through yet. Very happy for you to advise. 😀

@rpkilby
Copy link
Contributor

Yeah - I would go ahead and write tests for this. You may want to parameterize the tests against both Django and DRF validation errors to ensure both are handled the same.

Copy link
Contributor

@kevin-brownkevin-brown left a comment

Choose a reason for hiding this comment

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

Still seems reasonable, but I agree with having tests before accepting this.

@smithdc1
Copy link
Contributor

Hi All

Appreciate it's been a while and therefore don't expect anyone to recall this. I tried to write tests for this patch, but I must be miss-understanding this change.

Thetry block where this PR proposes a change always seems to return aValidationError as this linehere convertsDjangoValidationErrors to the required format before re-raising the captured errors as aValidationError.

As an example, the tests below raise aDjangoValidationError on the child, as suggested above. These tests currently pass on the main branch. The tests are alsohere.

+classTestListDjangoValidation(FieldValues):+valid_inputs= [+    ]+invalid_inputs= [+        ([1,2], {0: [ErrorDetail(string='A Django Validation Error',code='invalid')],+1: [ErrorDetail(string='A Django Validation Error',code='invalid')]})]++outputs= [+    ]++defvalidate(value):+raiseDjangoValidationError('A Django Validation Error')++field=serializers.ListField(child=serializers.IntegerField(validators=[validate]))+classTestNestedListFieldDjangoValidators(FieldValues):+valid_inputs= [+    ]+invalid_inputs= [+        (+            [[1], [2]],+            {+0: [ErrorDetail(string='A Django Validation Error',code='invalid')],+1: [ErrorDetail(string='A Django Validation Error',code='invalid')],+            },+        ),+    ]+outputs= [+    ]++defvalidate(value):+raiseDjangoValidationError(ADjangoValidationError')++field=serializers.ListField(child=serializers.ListField(child=serializers.IntegerField(),validators=[validate]))

@stale
Copy link

stalebot commentedMay 1, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stalestalebot added the stale labelMay 1, 2022
@sigvefsigvefforce-pushed thelist-field-django-errors branch from04883be tocea9527CompareSeptember 22, 2022 23:11
@stalestalebot removed the stale labelSep 22, 2022
@sigvef
Copy link
ContributorAuthor

sigvef commentedSep 22, 2022
edited
Loading

If anyone is still interested in this: turns out that the PrimaryKeyRelatedField + UUID pk combination was important to reproduce the issue. I've updated the commit with a test case that fails without the proposed change. An alternative solution to the same testcase could perhaps be to add a corresponding change inside PrimaryKeyRelatedField instead.

@stale
Copy link

stalebot commentedNov 23, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stalestalebot added the stale labelNov 23, 2022
Copy link
Collaborator

@auvipyauvipy left a comment

Choose a reason for hiding this comment

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

I will review myself and check if anything else is needed. in the mean time it would be great if you rebase this again on top of master branch

@auvipyauvipy added Bug and removed stale labelsNov 24, 2022
@sigvefsigvefforce-pushed thelist-field-django-errors branch 2 times, most recently froma5a3669 to6d6900dCompareDecember 3, 2022 14:01
@sigvef
Copy link
ContributorAuthor

Great! Rebased!

@auvipy
Copy link
Collaborator

Hi All

Appreciate it's been a while and therefore don't expect anyone to recall this. I tried to write tests for this patch, but I must be miss-understanding this change.

Thetry block where this PR proposes a change always seems to return aValidationError as this linehere convertsDjangoValidationErrors to the required format before re-raising the captured errors as aValidationError.

As an example, the tests below raise aDjangoValidationError on the child, as suggested above. These tests currently pass on the main branch. The tests are alsohere.

+classTestListDjangoValidation(FieldValues):+valid_inputs= [+    ]+invalid_inputs= [+        ([1,2], {0: [ErrorDetail(string='A Django Validation Error',code='invalid')],+1: [ErrorDetail(string='A Django Validation Error',code='invalid')]})]++outputs= [+    ]++defvalidate(value):+raiseDjangoValidationError('A Django Validation Error')++field=serializers.ListField(child=serializers.IntegerField(validators=[validate]))+classTestNestedListFieldDjangoValidators(FieldValues):+valid_inputs= [+    ]+invalid_inputs= [+        (+            [[1], [2]],+            {+0: [ErrorDetail(string='A Django Validation Error',code='invalid')],+1: [ErrorDetail(string='A Django Validation Error',code='invalid')],+            },+        ),+    ]+outputs= [+    ]++defvalidate(value):+raiseDjangoValidationError(ADjangoValidationError')++field=serializers.ListField(child=serializers.ListField(child=serializers.IntegerField(),validators=[validate]))

can you confirm you addressed this concern?

Without this, Django's ValidationErrors will bypass the error collectionfrom ListField's children.Here is an example that illustrates this change.Consider a Serializer that uses ListField like this:```pythonclass SomeSerializer(serializers.Serializer):    uuids = serializers.ListField(child=serializers.PrimaryKeyRelatedField(    queryset=Model.objects.something(),    validators=[SomeCustomValidator()])    )```Validating data that looks like this works fine:```python{uuids: ['some-valid-uuid', 'some-valid-uuid']}```Raising a DRF ValidationError for one of the children works fine, givingan error object like:```python{'uuids': {0: ErrorDetail(string='Some validation error')}}```Raising a Django ValidationError for one of the children worksdifferently (which serializers.PrimaryKeyRelatedField can do in somecases, like when the uuid is malformed). It gives an error object like:```python{'uuids': ["'X' is not a valid UUID."]}```Handling Django's ValidationErrors in ListField explicitly (like in thispull request), will maintain a regular error interface in this case:```python{'uuids': {0: ErrorDetail(string="'X' is not a valid UUID.")}}```
@sigvefsigvefforce-pushed thelist-field-django-errors branch from6d6900d to6b66005CompareDecember 3, 2022 23:30
@sigvef
Copy link
ContributorAuthor

sigvef commentedDec 3, 2022
edited
Loading

Yeah. I had to do a little bit of spelunking in the code base to refresh my memory 😅, but here is an improved explanation. (incoming wall-of-text, sorry!)

Most Django ValidationErrors are already properly handled by DRF. Django ValidationErrors raised during validation (i.e., inside a DRF Field'srun_validators(..)) are already properly handled by DRF without the change in this PR. That is why the tests posted by@smithdc1 pass already without this pull request -- they raise Django ValidationErrors from withinvalidate(..), which is called from withinrun_validators(..). DRF already knows how to properly handle that.

However, some Django ValidationErrors partially sneak past DRF's handling. DRF/Django can raise Django ValidationErrors from a DRF Field'sto_internal_value(..) as well. This is not currently covered by the same exception handling asrun_validators(..) is. One case where that can happen is if a serializer has a ListField, that ListField has PrimaryKeyRelatedField as its child, that PrimaryKeyRelatedField does not have a pk_field set explicitly, and the relation itself uses UUIDs as the key.

How does that happen, specifically? Where does the Django ValidationError come from in the aforementioned case? It comes from line 262 (thequeryset.get(pk=data)), part of PrimaryKeyRelatedField'sto_internal_value(..):

defto_internal_value(self,data):
ifself.pk_fieldisnotNone:
data=self.pk_field.to_internal_value(data)
queryset=self.get_queryset()
try:
ifisinstance(data,bool):
raiseTypeError
returnqueryset.get(pk=data)
exceptObjectDoesNotExist:
self.fail('does_not_exist',pk_value=data)
except (TypeError,ValueError):
self.fail('incorrect_type',data_type=type(data).__name__)

Within thequeryset.get(..) call, Django tries to convertdata to a UUID via Django'sUUIDField.to_python(..). If that doesn't work, say, if the data is not a valid UUID, Django raises a Django ValidationError. This Django Validation ends up bubbling out beyond thePrimaryKeyRelatedField. When tested in isolation, this exception is uncaught, and that is what fails the tests in this PR if they are run without the rest of the changes in this PR.

Okay, what about DRF Serializer's "outer" exception handling? And why hasn't anyone else complained about this? DRF Serializer's exception handling inSerializer.to_internal_value(..) does kick in, which is why in most cases where you try to validate an invalid UUID you wouldn't have any issues. In order to observe any difference, you need to have the specific aforementioned combo of a DRF Serializer with a DRF ListField using a DRF PrimaryKeyRelatedField child with no explicitly declared pk_field, and the primary key relation itself needs to use a UUID pk. (Maybe other combos have similar issues too -- this is the combo that I know of). And even then, the output itself can be considered correct, when viewed in isolation. The only issue is that it is structurally inconsistent with other error messages that could be returned from the same list of uuids being passed in. That makes this issue pretty niche, and probably explains why nobody else has complained about this.

Quick reminder, what are we actually trying to fix? The goal is to fix a slight inconsistency in how certain error messages are structured in the API. The commit message/PR text contains an example of the difference -- although take heed, I've fixed a typo in the output that I discovered while typing up this comment. Mentioning it in case anyone has been following this thread for a while.

How we fix this? The change in this PR is one way of solving this issue. It adds handling of Django ValidationErrors to DRF's ListField. Different options are also available:

  • Add handling of Django ValidationErrors to DRF's ListField (implemented is in this PR)
  • We could add an extra except clause to the try-except in DRF'sPrimaryKeyRelatedField.to_internal_value(..) (embedded as a code snippet above). After all, Django internally documents thatqueryset.get(..) (indirectly, via code comments of methods called from within.get(..)), that it can throw Django ValidationErrors if it is not happy with the data that is passed in, so it does not seem out of place to check for Django ValidationErrors here.
  • Explicitly pass DRF's UUIDField aspk_field on DRF's PrimaryKeyRelatedField when automatically constructing them via DRF's ModelSerializer, as DRF's UUIDField has similar error handling that would catch this case.
  • Ignore it and don't fix it at all, given how DRF has since become a mature project that generally does not accept new changes. This PR does introduce a breaking change that could break someone's project, after all.

I don't really have any particular preference for any which option. For what it's worth, at my day job we've been using a parallel reimplementation of DRF'sListField to fix this and some other small things for some time.

@auvipy
Copy link
Collaborator

auvipy commentedDec 4, 2022
edited
Loading

thanks for your detail break downs. I think while we can accept this PR as minimal effort to fix the issue, the other two suggestion of overhaul also seems valid to me. can you open related PR or some failing tests two other suggestion you have in this Thread?
the following two --
We could add an extra except clause to the try-except in DRF's PrimaryKeyRelatedField.to_internal_value(..) (embedded as a code snippet above). After all, Django internally documents that queryset.get(..) (indirectly, via code comments of methods called from within .get(..)), that it can throw Django ValidationErrors if it is not happy with the data that is passed in, so it does not seem out of place to check for Django ValidationErrors here.
Explicitly pass DRF's UUIDField as pk_field on DRF's PrimaryKeyRelatedField when automatically constructing them via DRF's ModelSerializer, as DRF's UUIDField has similar error handling that would catch this case.

@auvipy
Copy link
Collaborator

I don't really have any particular preference for any which option. For what it's worth, at my day job we've been using a parallel reimplementation of DRF'sListField to fix this and some other small things for some time.

Also if possible can you open source that implementation for the community?

@sigvef
Copy link
ContributorAuthor

I don't really have any particular preference for any which option. For what it's worth, at my day job we've been using a parallel reimplementation of DRF'sListField to fix this and some other small things for some time.

Also if possible can you open source that implementation for the community?

Taking a look at our parallel reimplementation, the parts that would maybe be interesting to the community would be covered by the changes in this PR and in#6424.

@sigvef
Copy link
ContributorAuthor

can you open related PR or some failing tests two other suggestion you have in this Thread?

As far as I can tell, the test added in this PR uncovers behaviour that each of the suggested fixes would sufficiently fix by itself.

auvipy reacted with thumbs up emoji

@auvipyauvipy merged commitee15731 intoencode:masterDec 4, 2022
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@carltongibsoncarltongibsoncarltongibson left review comments

@auvipyauvipyauvipy approved these changes

@lovelydinosaurlovelydinosaurAwaiting requested review from lovelydinosaur

+1 more reviewer

@kevin-brownkevin-brownkevin-brown requested changes

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Labels

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

6 participants

@sigvef@carltongibson@rpkilby@smithdc1@auvipy@kevin-brown

[8]ページ先頭

©2009-2025 Movatter.jp