beda-software / drf-writable-nested

Writable nested model serializer for Django REST Framework
Other
1.07k stars 116 forks source link

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

Open VasiliyRusin opened 5 years ago

VasiliyRusin commented 5 years ago

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?

ruscoder commented 5 years ago

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.

VasiliyRusin commented 5 years ago

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

VasiliyRusin commented 5 years ago

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"
        }
    ]
}
ruscoder commented 5 years ago

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"
        }
    ],
}
VasiliyRusin commented 5 years ago

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"
        }
    ]
}
ruscoder commented 5 years ago

@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 commented 5 years ago

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

VasiliyRusin commented 5 years ago

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

ruscoder commented 5 years ago

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

VasiliyRusin commented 5 years ago

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)
samomar commented 4 years ago

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)