Skip to content

Commit

Permalink
Merge pull request #266 from yezyilomo/fix_requestless_data_mutation
Browse files Browse the repository at this point in the history
Fix requestless data mutation
  • Loading branch information
yezyilomo authored Aug 12, 2021
2 parents 708d4eb + 552dde5 commit 9a9f95a
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 38 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ You can do a lot with **Django RESTQL** apart from querying data, like
- Data filtering and pagination by using query arguments
- Data mutation(Create and update nested data of any level in a single request)

Full documentation for this project is available at [https://yezyilomo.github.io/django-restql](https://yezyilomo.github.io/django-restql), you are encouraged to read it inorder to utilize this library to the fullest.
Full documentation for this project is available at [https://yezyilomo.github.io/django-restql](https://yezyilomo.github.io/django-restql), you are advised to read it inorder to utilize this library to the fullest.


## [Django RESTQL Play Ground](https://django-restql-playground.yezyilomo.me)
Expand Down
67 changes: 30 additions & 37 deletions django_restql/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,10 @@ def is_partial(self, default):
return default

def set_top_parent(self):
if self.parent.parent is None:
self._top_parent = self.parent
else:
self._top_parent = self.parent._top_parent
# Pass dow the parent's _top_parent value to a child serializer
self._top_parent = self.parent._top_parent

class BaseNestedFieldListSerializer(ListSerializer, BaseNestedField):

def validate_pk_list(self, pks):
ListField().run_validation(pks)
queryset = self.child.Meta.model.objects.all()
Expand All @@ -132,30 +129,38 @@ def validate_data_list(self, data, partial=None):
if isinstance(rel, ManyToOneRel):
# ManyToOne Relation
field_name = getattr(model, self.field_name).field.name
parent_serializer = serializer_class(
child_serializer = serializer_class(
**self.child.validation_kwargs,
data=data,
many=True,
partial=partial,
context=self.context
)

# Pass dow the parent's _top_parent value to a child serializer
child_serializer._top_parent = self.parent._top_parent

# Remove parent field(field_name) for validation purpose
parent_serializer.child.fields.pop(field_name, None)
child_serializer.child.fields.pop(field_name, None)

# Check if a serializer is valid
parent_serializer.is_valid(raise_exception=True)
child_serializer.is_valid(raise_exception=True)
else:
# ManyToMany Relation
parent_serializer = serializer_class(
child_serializer = serializer_class(
**self.child.validation_kwargs,
data=data,
many=True,
partial=partial,
context=self.context
)
parent_serializer.is_valid(raise_exception=True)
return parent_serializer.validated_data

# Pass dow the parent's _top_parent value to a child serializer
child_serializer._top_parent = self.parent._top_parent

# Check if a serializer is valid
child_serializer.is_valid(raise_exception=True)
return child_serializer.validated_data

def validate_add_list(self, data):
return self.validate_pk_list(data)
Expand Down Expand Up @@ -225,21 +230,10 @@ def validated_data(self, data, request_ops):
return data

def to_internal_value(self, data):
request = self.context.get('request')
if request is None:
self.set_top_parent()
if self._top_parent is None:
return self.validated_data(data, create_ops)
return self.validated_data(data, update_ops)

if request.method in ["PUT", "PATCH"]:
return self.validated_data(data, update_ops)

if request.method in ["POST"]:
self.set_top_parent()
if self._top_parent.instance is None:
return self.validated_data(data, create_ops)

# Unreachable return
return data
return self.validated_data(data, update_ops)

def __repr__(self):
return (
Expand Down Expand Up @@ -278,25 +272,27 @@ def validate_data_based_nested(self, data):
if data == empty:
# No value is provided pass an empty value as it is
return empty
parent_serializer = serializer_class(
child_serializer = serializer_class(
**self.validation_kwargs,
data=data,
partial=self.is_partial(self.get_partial_value()),
context=self.context
)
parent_serializer.is_valid(raise_exception=True)
return parent_serializer.validated_data

# Pass dow the parent's _top_parent value to a child serializer
child_serializer._top_parent = self.parent._top_parent

# Check if a serializer is valid
child_serializer.is_valid(raise_exception=True)
return child_serializer.validated_data

def get_partial_value(self):
request = self.context.get('request')
if request is not None and request.method in ["PATCH"]:
if self._top_parent.instance:
return True
return False

def to_internal_value(self, data):
request = self.context.get('request')
if request is None:
self.set_top_parent()
self.set_top_parent()

required = kwargs.get('required', True)
default = kwargs.get('default', empty)
Expand All @@ -320,15 +316,12 @@ def to_internal_value(self, data):

if accept_pk_only:
return self.validate_pk_based_nested(data)

if accept_pk:
elif accept_pk:
if isinstance(data, dict):
self.is_replaceable = False
return self.validate_data_based_nested(data)
else:
self.is_replaceable = True
return self.validate_pk_based_nested(data)

return self.validate_data_based_nested(data)

def __repr__(self):
Expand Down
4 changes: 4 additions & 0 deletions django_restql/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,10 @@ def __init__(self, *args, **kwargs):
self.build_restql_nested_fields()
self.build_restql_source_field_map()

# This will be used to check top parent's instance to determine
# whether it's a create or update request during data validation
self._top_parent = self

def build_restql_nested_fields(self):
# Make field_name -> field_value map for restql nested fields
self.restql_nested_fields = {}
Expand Down

0 comments on commit 9a9f95a

Please sign in to comment.