Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ValueError: Cannot add "<Object:value>": instance is on database "default", value is on database "None" #1513

Closed
mark0978 opened this issue Apr 8, 2014 · 5 comments

Comments

@mark0978
Copy link

mark0978 commented Apr 8, 2014

I only have one database, so something is failing to set the database on the ManyToManyField models. Django==1.6.2, djangorestframework==2.3.13

class EmployeeSkillType(models.Model):
    customer = models.ForeignKey(Customer)
    display = models.CharField(max_length=32)

    class Meta:
        unique_together = ('customer', 'display')
        ordering = ('display', )

class Employee(models.Model):
    skills = models.ManyToManyField(EmployeeSkillType)

class EmployeeSkillTypeSerializer(serializers.ModelSerializer):
    class Meta:
        model = EmployeeSkillType
        exclude = ('customer', )

class EmployeeFullSerializer(serializers.ModelSerializer):
    skills = EmployeeSkillTypeSerializer(source="skills", many=True, allow_add_remove=True, required=False)

File "---/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 114, in get_response
response = wrapped_callback(request, _callback_args, *_callback_kwargs)
File "---/local/lib/python2.7/site-packages/rest_framework/viewsets.py", line 78, in view
return self.dispatch(request, _args, *_kwargs)
File "---/local/lib/python2.7/site-packages/django/views/decorators/csrf.py", line 57, in wrapped_view
return view_func(_args, *_kwargs)
File "---/local/lib/python2.7/site-packages/rest_framework/views.py", line 400, in dispatch
response = self.handle_exception(exc)
File "---/local/lib/python2.7/site-packages/rest_framework/views.py", line 397, in dispatch
response = handler(request, _args, *_kwargs)
File "--/apps/employees/views.py", line 79, in update
self.object = serializer.save(**save_kwargs)
File "--/apps/employees/serializers.py", line 176, in save
super(EmployeeFullSerializer, self).save(**kwargs)
File "---/local/lib/python2.7/site-packages/rest_framework/serializers.py", line 596, in save
self.save_object(self.object, *_kwargs)
File "---/local/lib/python2.7/site-packages/rest_framework/serializers.py", line 995, in save_object
setattr(obj, accessor_name, object_list)
File "---/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 840, in set
manager.add(_value)
File "---/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 581, in add
self._add_items(self.source_field_name, self.target_field_name, *objs)
File "---/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 640, in _add_items
(obj, self.instance._state.db, obj._state.db))
ValueError: Cannot add "<EmployeeSkillType: Animal Training>": instance is on database "default", value is on database "None"

@mark0978
Copy link
Author

mark0978 commented Apr 8, 2014

Turns out I can fix this error by bringing in my SmartModelSerializer and changing the base class for the EmployeeSkillTypeSerializer. I've had to do this countless other times (though not for this error) and it seems like something DRF should be doing for me.

class EmployeeSkillTypeSerializer(SmartModelSerializer):
    class Meta:
        model = EmployeeSkillType
        exclude = ('customer', )

class SmartModelSerializer(serializers.ModelSerializer):
    def restore_object(self, attrs, instance=None):
        """
        Deserialize a dictionary of attributes into an object instance.
        You should override this method to control how deserialized objects
        are instantiated.
        """
        if isinstance(instance, dict):
            instance.update(attrs)
            return instance
        return instance

    def from_native(self, data, files):
        """
        Deserialize primitives -> objects.
        """
        if isinstance(data, dict) and "id" in data:
            data = int(data["id"])
        if isinstance(data, int):
            try:
                return self.get_instance(data)
            except self.Meta.model.DoesNotExist as xcpt:
                self._errors['non_field_errors'] = [ str(xcpt) ]
        else:
            self._errors = {}
            if data is not None or files is not None:
                attrs = self.restore_fields(data, files)
                if attrs is not None:
                    attrs = self.perform_validation(attrs)
            else:
                self._errors['non_field_errors'] = ['No input provided']

        if not self._errors:
            return self.restore_object(attrs, instance=getattr(self, 'object', None))

    def get_instance(self, pk):
        return self.Meta.model.objects.get(
            pk=pk, customer_id=self.context["request"].session["customer_id"])

@joeribekker
Copy link

Hi,

I came across this exception as well. However, I found that, in my case, the m2m related objects were simply not created in the database. Hence, the m2m related objects had their db set to None while the parent object had db set to 'default' causing the exception to occur.

I first figured they should be part of ModelSerializer.restore_object: _nested_forward_relations so they could be marked for saving but that would take some more refactoring (and I'm not sure if thats the correct place). So I patched ModelSerializer.save_object to just save the m2m related objects.

See: https://github.com/maykinmedia/django-rest-framework/tree/fix_1513

[django-rest-framework==2.3.13, django==1.6.2]

Index: rest_framework/serializers.py
===================================================================
--- rest_framework/serializers.py   (revision )
+++ rest_framework/serializers.py   (revision )
@@ -988,15 +988,11 @@
                     self.save_object(sub_object)
                 setattr(obj, field_name, sub_object)

         obj.save(**kwargs)

         if getattr(obj, '_m2m_data', None):
             for accessor_name, object_list in obj._m2m_data.items():
+                [self.save_object(o) for o in object_list]
                 setattr(obj, accessor_name, object_list)
             del(obj._m2m_data)

joeribekker added a commit to maykinmedia/django-rest-framework that referenced this issue May 1, 2014
@joeribekker
Copy link

Looks like #1457 fixes this issue but topic starter should probably confirm.

@DenJohX
Copy link

DenJohX commented May 29, 2014

@joeribekker 's patch works here, thanks!

Django==1.6.5
djangorestframework==2.3.13

I was having the same problem when saving a m2m relation.

This is closed for me.

@tomchristie
Copy link
Member

Given that this is a serializer bug I'm closing this off until a review after 3.0...


We've made the decision to close of all currently open serializer tickets, pending the 3.0 release.

The 3.0 release will involve a major redesign and improvement of the current serializer implementation and should invalidate the large majority of these outstanding tickets.

We will be reviewing the closed tickets prior to the 3.0 launch and try to ensure that we have fully covered any long-standing issues, and adequately dealt with the outstanding problems.

If your issue does not appear to be addressed by the upcoming 3.0 release please do comment on your ticket with the current status, and we can reopen any valid tickets as required. In particular, if your ticket represents a long-standing or fundamental problem please do make sure to follow the mailing list and review any 3.0 pre-release candidates where possible.

Note that you can review the closed tickets with the following searches:

Serializer tickets: https://github.com/tomchristie/django-rest-framework/issues?q=label%3ASerializers
Writable nested serializer tickets: https://github.com/tomchristie/django-rest-framework/issues?q=label%3A%22Writable+nested+serializers%22

Many thanks for your understanding and here's looking forward to a new and improved version, and a cleaner more actionable issue list.

All the best,

Tom

@tomchristie tomchristie added this to the 3.0 pre-release reassessment milestone Aug 19, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants