Django - How to duplicate a model instance that uses a m2m field with a through model

StackOverflow https://stackoverflow.com/questions/23117784

سؤال

I'm trying to write an admin action to duplicate the the Circus model below.

The problem with my code so far is I get an error like this when I try to run it:

Cannot set values on a ManyToManyField which specifies an intermediary model.

Here are my models:

class Clown(models.Model):
    name = models.CharField(max_length=255)

class Circus(models.Model):
    clowns = models.ManyToManyField(Clown, blank=True, through='WorkedAt')
    name = models.CharField(max_length=255)

class WorkedAt(models.Model):
    clown = models.ForeignKey(Clown)
    circus = models.ForeignKey(Circus)

Here's the code in admin.py so far: (trying to following the advice here)

class CircusAdmin(BaseAdmin):
    def duplicate(self, request, queryset):
        for obj in queryset:
            old_clowns = obj.clowns.all()
            obj.id = None
            obj.name += ' (copy)'
            obj.save()
            obj.clowns = old_clowns
    duplicate.short_description = "Duplicate Selected Circuses"
    actions = [duplicate]
هل كانت مفيدة؟

المحلول

When you use an intermediate through table your ability to use the related objects manager (i.e. obj.clowns) is limited. As the docs put it:

Unlike normal many-to-many fields, you can’t use add, create, or assignment (i.e., beatles.members = [...]) to create relationships.... The simple add, create and assignment calls don’t provide a way to specify this extra detail. As a result, they are disabled for many-to-many relationships that use an intermediate model. The only way to create this type of relationship is to create instances of the intermediate model.

So instead of assigning the related manager, just create the new intermediate objects yourself:

def duplicate(self, request, queryset): 
    for obj in queryset: 
        old_workedat = list(obj.workedat_set.all())

        obj.id = None 
        obj.name += ' (copy)' 
        obj.save() 

        new_workedat = [WorkedAt(circus_id=obj.id, clown_id=wa.clown_id)
                        for wa in old_workedat]
        WorkedAt.objects.bulk_create(new_workedat)
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top