Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions django_restql/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def BaseNestedFieldSerializerFactory(
*args,
accept_pk=False,
accept_pk_only=False,
delete_on_null=False,
allow_remove_all=False,
create_ops=CREATE_OPERATIONS,
update_ops=UPDATE_OPERATIONS,
Expand Down Expand Up @@ -80,6 +81,14 @@ def BaseNestedFieldSerializerFactory(
"nested fields, ensure the kwarg `many=True` is set."
)

assert not (
delete_on_null and accept_pk
), "`delete_on_null=True` can not be used if `accept_pk=True`."

assert not (
delete_on_null and accept_pk_only
), "`delete_on_null=True` can not be used if `accept_pk_only=True`."

def join_words(words, many='are', single='is'):
word_list = ["`" + word + "`" for word in words]

Expand Down Expand Up @@ -248,10 +257,11 @@ def __repr__(self):

class BaseNestedFieldSerializer(serializer_class, BaseNestedField):

# might be used before `to_internal_value` method is called
# so we're creating this property to make sure it's available
# These variables might be used before `to_internal_value` method is called
# so we're creating them to make sure they're available
# as long as the class is created
is_replaceable = accept_pk_only or accept_pk
should_delete_on_null = delete_on_null

class Meta(serializer_class.Meta):
list_serializer_class = BaseNestedFieldListSerializer
Expand Down
10 changes: 10 additions & 0 deletions django_restql/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,9 +806,13 @@ def update_writable_foreignkey_related(self, instance, data):
context={**self.context, "parent_operation": UPDATE},
)
serializer.is_valid(raise_exception=True)

delete_previous_nested_obj = False
if values is None:
setattr(instance, field, None)
needs_save = True
if nested_field_serializer.should_delete_on_null and nested_obj is not None:
delete_previous_nested_obj = True
else:
obj = serializer.save()
if nested_obj is None:
Expand All @@ -818,6 +822,12 @@ def update_writable_foreignkey_related(self, instance, data):
if needs_save:
instance.save()

# We delete the previous nested obj AFTER we are done saving
# the instance to avoid accidental deletion of the instance
# itself if on_delete=models.CASCADE is used
if delete_previous_nested_obj:
nested_obj.delete()

def bulk_create_many_to_many_related(self, field, nested_obj, data):
# Get nested field serializer
nested_field_serializer = self.restql_writable_nested_fields[field].child
Expand Down
23 changes: 23 additions & 0 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2123,6 +2123,29 @@ def test_put_on_writable_nested_nullable_foreignkey_related_field_with_empty_str
},
)

def test_put_with_delete_on_null_kwarg(
self,
):
course = Course.objects.create(name="Test case", code="TS101")
student = Student.objects.create(name="Yezy", age=24, course=course)

url = reverse_lazy("wstudent-detail", args=[student.id])
data = {"name": "yezy", "age": 33, "course": None}
response = self.client.put(url, data, format="json")

self.assertEqual(
response.data,
{
"name": "yezy",
"age": 33,
"course": None,
"phone_numbers": [],
},
)

# Check if course has been deleted
self.assertEqual(Course.objects.filter(id=course.id).count(), 0)

def test_put_with_add_operation(self):
url = reverse_lazy("wcourse-detail", args=[self.course2.id])
data = {"name": "Data Structures", "code": "CS410", "books": {"add": [2]}}
Expand Down
5 changes: 4 additions & 1 deletion tests/testapp/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ class Meta:


class WritableStudentSerializer(DynamicFieldsMixin, NestedModelSerializer):
course = NestedField(WritableCourseSerializer, allow_null=True, required=False)
course = NestedField(
WritableCourseSerializer,
allow_null=True, required=False, delete_on_null=True
)
phone_numbers = NestedField(
PhoneSerializer, many=True, required=False, allow_remove_all=True
)
Expand Down