Skip to content

Commit

Permalink
Merge pull request #313 from yezyilomo/fix-error-raised-when-create_o…
Browse files Browse the repository at this point in the history
…ps-or-update_ops_is_empty

Fix error raised `when create_ops=[]` or `update_ops=[]`
  • Loading branch information
yezyilomo authored Jan 25, 2024
2 parents a232d51 + faf3f05 commit 1044bbf
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12]

steps:
- uses: actions/checkout@v1
Expand Down
27 changes: 19 additions & 8 deletions django_restql/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ def BaseNestedFieldSerializerFactory(

def join_words(words, many='are', single='is'):
word_list = ["`" + word + "`" for word in words]
sentence = " & ".join([", ".join(word_list[:-1]), word_list[-1]])

if len(words) > 1:
sentence = " & ".join([", ".join(word_list[:-1]), word_list[-1]])
return "%s %s" % (many, sentence)
elif len(words) == 1:
return "%s %s" % (single, word_list[0])
Expand Down Expand Up @@ -130,7 +131,7 @@ def run_pk_list_validation(self, pks):
many=True
).run_validation(pks)

def run_data_list_validation(self, data, partial=None):
def run_data_list_validation(self, data, partial=None, operation=None):
ListField().run_validation(data)
model = self.parent.Meta.model
rel = getattr(model, self.source).rel
Expand All @@ -142,7 +143,7 @@ def run_data_list_validation(self, data, partial=None):
data=data,
many=True,
partial=partial,
context=self.context
context={**self.context, "parent_operation": operation}
)

# Remove parent field(field_name) for validation purpose
Expand All @@ -157,7 +158,7 @@ def run_data_list_validation(self, data, partial=None):
data=data,
many=True,
partial=partial,
context=self.context
context={**self.context, "parent_operation": operation}
)

# Check if a serializer is valid
Expand All @@ -169,7 +170,8 @@ def run_add_list_validation(self, data):
def run_create_list_validation(self, data):
self.run_data_list_validation(
data,
partial=self.is_partial(False)
partial=self.is_partial(False),
operation=CREATE
)

def run_remove_list_validation(self, data):
Expand All @@ -190,7 +192,8 @@ def run_update_list_validation(self, data):
values = list(data.values())
self.run_data_list_validation(
values,
partial=self.is_partial(True)
partial=self.is_partial(True),
operation=UPDATE
)

def run_data_validation(self, data, allowed_ops):
Expand Down Expand Up @@ -226,8 +229,14 @@ def run_data_validation(self, data, allowed_ops):

def to_internal_value(self, data):
if self.child.root.instance is None:
self.run_data_validation(data, create_ops)
parent_operation = self.context.get("parent_operation")
if parent_operation == "update":
# Definitely an update
self.run_data_validation(data, update_ops)
else:
self.run_data_validation(data, create_ops)
else:
# Definitely an update
self.run_data_validation(data, update_ops)
return data

Expand Down Expand Up @@ -265,13 +274,15 @@ def run_pk_validation(self, pk):
return validator.run_validation(pk)

def run_data_validation(self, data):
parent_operation = self.context.get("parent_operation")

child_serializer = serializer_class(
**self.validation_kwargs,
data=data,
partial=self.is_partial(
# Use the partial value passed, if it's not passed
# Use the one from the top level parent
self.root.partial
True if parent_operation == UPDATE else False
),
context=self.context
)
Expand Down
27 changes: 18 additions & 9 deletions django_restql/mixins.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.db.models import Prefetch
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.fields.related import ManyToManyRel, ManyToOneRel
from django.http import QueryDict
from django.utils.functional import cached_property
Expand Down Expand Up @@ -597,7 +598,7 @@ def create_writable_foreignkey_related(self, data):
# Reject partial update by default(if partial kwarg is not passed)
# since we need all required fields when creating object
partial=nested_field_serializer.is_partial(False),
context=self.context
context={**self.context, "parent_operation": CREATE}
)
serializer.is_valid(raise_exception=True)
if value is None:
Expand All @@ -622,7 +623,7 @@ def bulk_create_objs(self, field, data):
# Reject partial update by default(if partial kwarg is not passed)
# since we need all required fields when creating object
partial=nested_field_serializer.is_partial(False),
context=self.context,
context={**self.context, "parent_operation": CREATE},
)
serializer.is_valid(raise_exception=True)
obj = serializer.save()
Expand Down Expand Up @@ -770,7 +771,7 @@ def update_writable_foreignkey_related(self, instance, data):
# Allow partial update by default(if partial kwarg is not passed)
# since this is nested update
partial=nested_field_serializer.is_partial(True),
context=self.context
context={**self.context, "parent_operation": UPDATE}
)
serializer.is_valid(raise_exception=True)
if values is None:
Expand Down Expand Up @@ -798,7 +799,7 @@ def bulk_create_many_to_many_related(self, field, nested_obj, data):
# Reject partial update by default(if partial kwarg is not passed)
# since we need all required fields when creating object
partial=nested_field_serializer.is_partial(False),
context=self.context
context={**self.context, "parent_operation": CREATE}
)
serializer.is_valid(raise_exception=True)
obj = serializer.save()
Expand All @@ -819,7 +820,7 @@ def bulk_create_many_to_one_related(self, field, nested_obj, data):
# Reject partial update by default(if partial kwarg is not passed)
# since we need all required fields when creating object
partial=nested_field_serializer.is_partial(False),
context=self.context
context={**self.context, "parent_operation": CREATE}
)
serializer.is_valid(raise_exception=True)
obj = serializer.save()
Expand All @@ -834,15 +835,19 @@ def bulk_update_many_to_many_related(self, field, nested_obj, data):
serializer_class = nested_field_serializer.serializer_class
kwargs = nested_field_serializer.validation_kwargs
for pk, values in data.items():
obj = nested_obj.get(pk=pk)
try:
obj = nested_obj.get(pk=pk)
except ObjectDoesNotExist:
# This pk does't belong to nested field
continue
serializer = serializer_class(
obj,
**kwargs,
data=values,
# Allow partial update by default(if partial kwarg is not passed)
# since this is nested update
partial=nested_field_serializer.is_partial(True),
context=self.context
context={**self.context, "parent_operation": UPDATE}
)
serializer.is_valid(raise_exception=True)
obj = serializer.save()
Expand All @@ -858,7 +863,11 @@ def bulk_update_many_to_one_related(self, field, instance, data):
foreignkey = getattr(model, field).field.name
nested_obj = getattr(instance, field)
for pk, values in data.items():
obj = nested_obj.get(pk=pk)
try:
obj = nested_obj.get(pk=pk)
except ObjectDoesNotExist:
# This pk does't belong to nested field
continue
values.update({foreignkey: instance.pk})
serializer = serializer_class(
obj,
Expand All @@ -867,7 +876,7 @@ def bulk_update_many_to_one_related(self, field, instance, data):
# Allow partial update by default(if partial kwarg is not passed)
# since this is nested update
partial=nested_field_serializer.is_partial(True),
context=self.context
context={**self.context, "parent_operation": UPDATE}
)
serializer.is_valid(raise_exception=True)
obj = serializer.save()
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def get_info(info_name):
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
],
test_suite='runtests',
)
39 changes: 36 additions & 3 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2535,7 +2535,7 @@ def test_patch_with_update_operation_missing_one_required_nested_field(self):
}
)

def test_patch_on_deep_nested_fields(self):
def test_patch_with_mixed_operations(self):
url = reverse_lazy("wstudent-detail", args=[self.student.id])
data = {
"name": "yezy",
Expand All @@ -2544,7 +2544,34 @@ def test_patch_on_deep_nested_fields(self):
"name": "Programming",
"code": "CS50",
"books": {
"remove": [1]
"update": {
1: {
"title": "React Programming",
"author": "K.Kennedy",
"genres": {
# update_operations applies here since the parent is "update"
"remove": [],
"add": [],
"create": [
{"title": "Modern Programming", "description": "New tools"}
],
"update": {}
}
}
},
"create": [
{
"title": "CS Foundation",
"author": "T.Howard",
"genres": {
# create_operations applies here since the parent is "create"
"add": [],
"create": [
{"title": "Classical Programming", "description": "Classical tech tools"}
]
}
}
]
}
}
}
Expand All @@ -2557,7 +2584,13 @@ def test_patch_on_deep_nested_fields(self):
'course': {
'name': 'Programming', 'code': 'CS50',
'books': [
{'title': 'Basic Data Structures', 'author': 'S.Mobit', "genres": []}
{'title': 'React Programming', 'author': 'K.Kennedy', "genres": [
{"title": "Modern Programming", "description": "New tools"}
]},
{'title': 'Basic Data Structures', 'author': 'S.Mobit', 'genres': []},
{'title': 'CS Foundation', 'author': 'T.Howard', "genres": [
{"title": "Classical Programming", "description": "Classical tech tools"}
]}
],
'instructor': None
},
Expand Down
2 changes: 1 addition & 1 deletion tests/testapp/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class Meta:

############### Serializers for Nested Data Mutation Testing ##############
class WritableBookSerializer(DynamicFieldsMixin, NestedModelSerializer):
genres = NestedField(GenreSerializer, many=True, required=False)
genres = NestedField(GenreSerializer, many=True, required=False, partial=False)

class Meta:
model = Book
Expand Down
8 changes: 4 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@

[tox]
envlist =
py{36,37}-dj{111}-drf{35,36,37,38,39,310,311}
py{36,37}-dj{20,21,22}-drf{37,38,39,310,311}
py{37}-dj{111}-drf{35,36,37,38,39,310,311}
py{37}-dj{20,21,22}-drf{37,38,39,310,311}
py{38,39}-dj{22}-drf{37,38,39,310,311,312}
py{36,37,38,39}-dj{30}-drf{310,311,312}
py{36,37,38,39,310}-dj{31,32}-drf{311,312,313,314}
py{38,39,310,311}-dj{40,41}-drf{313,314}
py{38,39,310,311,312}-dj{40,41}-drf{313,314}

[gh-actions]
python =
3.6: py36
3.7: py37
3.8: py38
3.9: py39
3.10: py310
3.11: py311
3.12: py312

DJANGO =
1.11: dj111
Expand Down

0 comments on commit 1044bbf

Please sign in to comment.