Search code examples
django

Django migration from 3.1 to 3.2 error while copying models instances


I'm trying to upgrade Django from 3.1 to 3.2, and one of the error that I encountered was endpoints that I have that duplicate resources and their related models.

In this example:

@action(
    detail=True,
    methods=['POST'],
    name='Duplicate one resource',
    url_name='duplicate',
    url_path='duplicate',
)
@transaction.atomic
def duplicate(self, request, pk=None):
    foo = self.get_object() # gets foo
    foo_bars = foo.foo_bars.all() # gets a queryset of FooBar

    new_name = self.append_copy_suffix(foo.name)
    new_foo = foo.copy_instance(name=new_name) # code below

    print(foo, new_foo)

    FooBar.objects.bulk_create(
        [FooBar(foo=new_foo, bar=foo_bar.bar, stuff=foo_bar.stuff) for foo_bar in foo_bars]
    )

    return Response(self.get_serializer(new_foo).data, status=status.HTTP_201_CREATED)


def copy_instance(self, **kwargs):
    """
    Method to copy a model instance
    More info here: https://docs.djangoproject.com/en/3.1/topics/db/queries/#copying-model-instances
    :param kwargs: additional parameters to update
    :return: the duplicated resource
    """

    new_resource = self
    new_resource.pk = None
    for k, v in kwargs.items():
        setattr(new_resource, k, v)
    new_resource._state.adding = True
    new_resource.save()

    return new_resource

This works pretty well in Django 3.1, but in Django 3.2, the related resources are not created for some reason.

Things that I noticed in debug and printing the values:

  • When I print the foo and new_foo, they are the same one (new_foo).
  • foo_bars is empty. Maybe because the foo and new_foo are the same one before the bulk_create and the foo_bars is just a queryset that was yet to be executed.
  • I need to cast the queryset to a list() in order to get the existent ones and create new FooBars.

What do I need to fix in order for this to work?

EDIT:

Tried the Serhii Fomenko approach, and it worked. Also I've implemented another way that it works (might be the same).

def copy_instance(self, **kwargs):
    new_resource = copy.deepcopy(self)
    new_resource.pk = None
    for k, v in kwargs.items():
        setattr(new_resource, k, v)
    new_resource._state.adding = True
    new_resource.save()

    return new_resource

def copy_instance(self, **kwargs):
    new_resource = self.__class__()
    for attr, value in self.__dict__.items():
        setattr(new_resource, attr, value)
    new_resource.pk = None
    new_resource.id = None
    for k, v in kwargs.items():
        setattr(new_resource, k, v)
    new_resource._state.adding = True
    new_resource.save()
    return new_resource

Solution

  • Regarding this problem, I've tried the Serhii Fomenko approach and it worked. Thanks for the help :)

    def copy_instance(self, **kwargs):
        new_resource = copy.deepcopy(self)
        new_resource.pk = None
        for k, v in kwargs.items():
            setattr(new_resource, k, v)
        new_resource._state.adding = True
        new_resource.save()
    
        return new_resource