diff --git a/model_clone/mixins/clone.py b/model_clone/mixins/clone.py index 972296f7..3adfe952 100644 --- a/model_clone/mixins/clone.py +++ b/model_clone/mixins/clone.py @@ -508,7 +508,12 @@ def __duplicate_m2m_fields(self, duplicate, sub_clone): objs = through.objects.filter(**{field_name: self.pk}) for item in objs: if hasattr(through, "make_clone"): - item.make_clone(attrs={field_name: duplicate}, sub_clone=True) + try: + item.make_clone(attrs={field_name: duplicate}) + except IntegrityError: + item.make_clone( + attrs={field_name: duplicate}, sub_clone=True + ) else: item.pk = None setattr(item, field_name, duplicate) diff --git a/model_clone/tests/test_clone_mixin.py b/model_clone/tests/test_clone_mixin.py index da752f42..4ce69ef1 100644 --- a/model_clone/tests/test_clone_mixin.py +++ b/model_clone/tests/test_clone_mixin.py @@ -22,7 +22,9 @@ Cover, BackCover, BookTag, + BookSaleTag, Tag, + SaleTag, ) User = get_user_model() @@ -234,6 +236,24 @@ def test_cloning_with_explicit_clone_m2m_fields( list(book_clone_2.tags.values_list("name")), ) + sale_tag_1 = SaleTag.objects.create(name="test-tag-3") + sale_tag_2 = SaleTag.objects.create(name="test-tag-4") + + _clone_m2m_fields_mock.return_value = ["sale_tags"] + + book_3 = Book.objects.create( + name="New Book 3", created_by=self.user1, slug=slugify("New Book 3") + ) + BookSaleTag.objects.create(book=book_3, sale_tag=sale_tag_1) + BookSaleTag.objects.create(book=book_3, sale_tag=sale_tag_2) + + book_clone_3 = book_3.make_clone() + + self.assertEqual( + list(book_3.sale_tags.values_list("name")), + list(book_clone_3.sale_tags.values_list("name")), + ) + @patch("sample.models.Author._clone_excluded_fields", new_callable=PropertyMock) def test_cloning_with_clone_excluded_fields( self, diff --git a/sample/migrations/0015_auto_20210423_0935.py b/sample/migrations/0015_auto_20210423_0935.py new file mode 100644 index 00000000..f464dc8b --- /dev/null +++ b/sample/migrations/0015_auto_20210423_0935.py @@ -0,0 +1,71 @@ +# Generated by Django 3.2 on 2021-04-23 09:35 + +from django.db import migrations, models +import django.db.models.deletion +import model_clone.mixins.clone + + +class Migration(migrations.Migration): + + dependencies = [ + ("sample", "0014_auto_20210422_1449"), + ] + + operations = [ + migrations.CreateModel( + name="SaleTag", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ], + options={ + "abstract": False, + }, + bases=(model_clone.mixins.clone.CloneMixin, models.Model), + ), + migrations.CreateModel( + name="BookSaleTag", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "book", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="sample.book" + ), + ), + ( + "sale_tag", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="sample.saletag" + ), + ), + ], + options={ + "unique_together": {("book", "sale_tag")}, + }, + bases=(model_clone.mixins.clone.CloneMixin, models.Model), + ), + migrations.AddField( + model_name="book", + name="sale_tags", + field=models.ManyToManyField( + through="sample.BookSaleTag", to="sample.SaleTag" + ), + ), + ] diff --git a/sample/models.py b/sample/models.py index cae56783..0e0b88e8 100644 --- a/sample/models.py +++ b/sample/models.py @@ -42,6 +42,13 @@ def __str__(self): return _(self.name) +class SaleTag(CloneModel): + name = models.CharField(max_length=255) + + def __str__(self): + return _(self.name) + + class Book(CloneModel): name = models.CharField(max_length=2000) slug = models.SlugField(unique=True) @@ -52,6 +59,7 @@ class Book(CloneModel): ) created_at = models.DateTimeField(auto_now_add=True) tags = models.ManyToManyField(Tag, through="BookTag") + sale_tags = models.ManyToManyField(SaleTag, through="BookSaleTag") def __str__(self): return _(self.name) @@ -67,6 +75,16 @@ class Meta: ] +class BookSaleTag(CloneModel): + book = models.ForeignKey(Book, on_delete=models.CASCADE) + sale_tag = models.ForeignKey(SaleTag, on_delete=models.PROTECT) + + class Meta: + unique_together = [ + ("book", "sale_tag"), + ] + + class Page(CloneModel): content = models.CharField(max_length=20000) book = models.ForeignKey(Book, on_delete=models.CASCADE)