drf-writable-nested icon indicating copy to clipboard operation
drf-writable-nested copied to clipboard

Why are you creating a new nested element every "update"?

Open VasiliyRusin opened this issue 5 years ago • 11 comments

First of all, it's not an issue. It just questions about the realization of this package. This package creates newly nested every "update". Is any reason why you don't update nested fields by "pk" or "id" if it provided?

VasiliyRusin avatar Jul 18 '19 08:07 VasiliyRusin

Hello @VasiliyRusin. We update nested instances if pk or id are provided. Please double-check that you are passing pk or id field into the nested serializers.

ruscoder avatar Jul 30 '19 17:07 ruscoder

I have id field but after save I have a new id. Also, PATCH needs the whole object to update it.

VasiliyRusin avatar Jul 31 '19 09:07 VasiliyRusin

More information about this problem

models.py

class Name(models.Model):
    string = models.TextField()
    item = models.ForeignKey('Genre', on_delete=models.CASCADE, related_name='names', blank=True, null=True)

class Genre(models.Model):
    pass

serializers.py

class GenreNameSerializer(serializers.ModelSerializer):
    class Meta:
        model = Name
        fields = ('id', 'string')

class GenreSerializer(WritableNestedModelSerializer):
    names = GenreNameSerializer(many=True)

    class Meta:
        model = Genre

My current Genre

{
    "id": 3,
    "names": [
        {
            "id": 29,
            "string": "Genre3221221"
        }
    ],
}

And after PATCH:

{
    "id": 3,
    "names": [
        {
            "id": 30,
            "string": "Genre"
        }
    ]
}

VasiliyRusin avatar Jul 31 '19 09:07 VasiliyRusin

You have a new ID after you make patch query with nested id?

GET

{
    "id": 3,
    "names": [
        {
            "id": 29,
            "string": "Genre3221221"
        }
    ],
}

PATCH (with the following body)

{
    "id": 3,
    "names": [
        {
            "id": 29,
            "string": "Genre updated"
        }
    ],
}

GET

{
    "id": 3,
    "names": [
        {
            "id": 29,
            "string": "Genre updated"
        }
    ],
}

ruscoder avatar Jul 31 '19 09:07 ruscoder

You have a new ID after you make patch query with nested id?

Yes. Then I send JSON with PATCH

{
    "id": 3,
    "names": [
        {
            "id": 29,
            "string": "Genre updated"
        }
    ],
}

GET is

{
    "id": 3,
    "names": [
        {
            "id": 30,
            "string": "Genre updated"
        }
    ]
}

VasiliyRusin avatar Jul 31 '19 14:07 VasiliyRusin

@VasiliyRusin Please, share all your code and how you use it. It seems you use in an incorrect way.

I created a test with:

class Name(models.Model):
    string = models.TextField()
    item = models.ForeignKey('Genre', on_delete=models.CASCADE, related_name='names', blank=True, null=True)


class Genre(models.Model):
    pass


class GenreNameSerializer(serializers.ModelSerializer):
    class Meta:
        model = Name
        fields = ('id', 'string')


class GenreSerializer(WritableNestedModelSerializer):
    names = GenreNameSerializer(many=True)

    class Meta:
        model = Genre
        fields = ('id', 'names')
...
    def test_issue_86(self):
        serializer = serializers.GenreSerializer(data={
            'names': [
                {
                    'string': 'Genre'
                }
            ]
        })
        self.assertTrue(serializer.is_valid())
        instance = serializer.save()

        print(serializer.data)

        update_serailizer = serializers.GenreSerializer(instance=instance, data={'id': instance.pk, 'names': [
            {
                'id': instance.names.first().pk,
                'string': 'Genre changed'
            }
        ]})
        self.assertTrue(update_serializer.is_valid())
        update_serializer.save()
        print(update_serializer.data)

Output:

----------------------------------------------------------------------------- Captured stdout call ------------------------------------------------------------------------------
{'names': [OrderedDict([('id', 1), ('string', u'Genre')])], 'id': 1}
{'names': [OrderedDict([('id', 1), ('string', u'Genre changed')])], 'id': 1}

ruscoder avatar Aug 01 '19 04:08 ruscoder

@VasiliyRusin See commit for details https://github.com/beda-software/drf-writable-nested/commit/af86b7514d66130bd6b1cebfceead49debb2d273

ruscoder avatar Aug 01 '19 04:08 ruscoder

Maybe a problem is that Name model use InheritanceManager from django-model-utils. Is drf-writable-nested compatible with django-model-utils?

VasiliyRusin avatar Aug 01 '19 06:08 VasiliyRusin

@VasiliyRusin I don't know about compatibility with 3rd party packages. Could you please check your code without InheritanceManager?

ruscoder avatar Aug 01 '19 06:08 ruscoder

It seems it crashes with inheritance tables. I'm not absolutely sure why this error was thrown the only now.

Log below if it can help

IntegrityError: UNIQUE constraint failed: genre_genrel10nstring.l10nstring_ptr_id

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/rest_framework/viewsets.py", line 114, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/rest_framework/views.py", line 497, in dispatch
    response = self.handle_exception(exc)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/rest_framework/views.py", line 457, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/rest_framework/views.py", line 468, in raise_uncaught_exception
    raise exc
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/rest_framework/views.py", line 494, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/rest_framework/mixins.py", line 82, in partial_update
    return self.update(request, *args, **kwargs)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/rest_framework/mixins.py", line 68, in update
    self.perform_update(serializer)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/rest_framework/mixins.py", line 78, in perform_update
    serializer.save()
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/drf_writable_nested/mixins.py", line 220, in save
    return super(BaseNestedModelSerializer, self).save(**kwargs)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/rest_framework/serializers.py", line 208, in save
    self.instance = self.update(self.instance, validated_data)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/drf_writable_nested/mixins.py", line 277, in update
    self.update_or_create_reverse_relations(instance, reverse_relations)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/drf_writable_nested/mixins.py", line 175, in update_or_create_reverse_relations
    related_instance = serializer.save(**save_kwargs)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/rest_framework/serializers.py", line 213, in save
    self.instance = self.create(validated_data)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/rest_framework/serializers.py", line 932, in create
    instance = ModelClass._default_manager.create(**validated_data)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/models/query.py", line 422, in create
    obj.save(force_insert=True, using=self.db)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/models/base.py", line 741, in save
    force_update=force_update, update_fields=update_fields)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/models/base.py", line 779, in save_base
    force_update, using, update_fields,
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/models/base.py", line 870, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/models/base.py", line 908, in _do_insert
    using=using, raw=raw)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/models/query.py", line 1186, in _insert
    return query.get_compiler(using=using).execute_sql(return_id)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 1335, in execute_sql
    cursor.execute(sql, params)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/backends/utils.py", line 99, in execute
    return super().execute(sql, params)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/vasiliyrusin/.local/share/virtualenvs/onyama_backend-L3ZWsIo3/lib/python3.6/site-packages/django/db/backends/sqlite3/base.py", line 383, in execute
    return Database.Cursor.execute(self, query, params)

VasiliyRusin avatar Aug 01 '19 06:08 VasiliyRusin

Just chiming in,

TLDR; Creating/deleting nested elements with put requests works as intended.

My structure

JSON

PUT

{
    "fbid": 1,
    "evaluation_spec": {
        "filters": [
            {
                "value": [
                    "1",
                    "2"
                ],
                "field": "mobile_app_purchase_roas",
                "operator": "IN_RANGE",
                "id": "136"
            }
        ],
        "evaluation_type": "SCHEDULE"
    },
    "status": "ENABLED",
    "name": "rule 1"
}

Result: AdRuleEvaluationSpecFilter object (136)

PUT

            {
                "value": [
                    "1",
                    "2"
                ],
                "field": "mobile_app_purchase_roas",
                "operator": "IN_RANGE",
                "id": "135"

Result: AdRuleEvaluationSpecFilter object (137)

PUT

            {
                "value": [
                    "1",
                    "2"
                ],
                "field": "mobile_app_purchase_roas",
                "operator": "IN_RANGE",

Result: AdRuleEvaluationSpecFilter object (138)

samomar avatar Dec 16 '19 21:12 samomar