diff --git a/.coveragerc b/.coveragerc index 31a39370..d03a0f5e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,3 +3,18 @@ source = model_clone omit = model_clone/test*, model_clone/admin* + +[report] +precision = 1 +exclude_lines = + pragma: no cover + pass + def __repr__ + if self.debug: + if settings.DEBUG + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + except ImportError + # Don't complain if non-runnable code isn't run: + if __name__ == .__main__.: diff --git a/model_clone/mixins/clone.py b/model_clone/mixins/clone.py index a351cafd..972296f7 100644 --- a/model_clone/mixins/clone.py +++ b/model_clone/mixins/clone.py @@ -211,7 +211,6 @@ def make_clone(self, attrs=None, sub_clone=False): for name, value in attrs.items(): setattr(duplicate, name, value) - duplicate.full_clean() duplicate.save() duplicate = self.__duplicate_o2o_fields(duplicate) diff --git a/model_clone/tests/test_clone_mixin.py b/model_clone/tests/test_clone_mixin.py index cc19a336..da752f42 100644 --- a/model_clone/tests/test_clone_mixin.py +++ b/model_clone/tests/test_clone_mixin.py @@ -6,6 +6,7 @@ from django.db.transaction import TransactionManagementError from django.test import TestCase, TransactionTestCase from django.utils.text import slugify +from django.db.utils import IntegrityError from django.utils.timezone import make_naive from mock import patch, PropertyMock @@ -20,6 +21,8 @@ Furniture, Cover, BackCover, + BookTag, + Tag, ) User = get_user_model() @@ -34,7 +37,7 @@ def setUpTestData(cls): def test_cloning_a_transient_instance_with_pk_is_invalid(self): instance = Library() - with self.assertRaises(ValidationError): + with self.assertRaises(IntegrityError): instance.make_clone() def test_cloning_a_transient_instance_is_invalid(self): @@ -201,16 +204,34 @@ def test_cloning_with_explicit_clone_m2m_fields( ) _clone_m2m_fields_mock.return_value = ["authors"] - book = Book.objects.create( - name="New Book", created_by=self.user1, slug=slugify("New Book") + book_1 = Book.objects.create( + name="New Book 1", created_by=self.user1, slug=slugify("New Book 1") ) - book.authors.set([author_1, author_2]) + book_1.authors.set([author_1, author_2]) - book_clone = book.make_clone() + book_clone_1 = book_1.make_clone() self.assertEqual( - list(book.authors.values_list("first_name", "last_name")), - list(book_clone.authors.values_list("first_name", "last_name")), + list(book_1.authors.values_list("first_name", "last_name")), + list(book_clone_1.authors.values_list("first_name", "last_name")), + ) + + tag_1 = Tag.objects.create(name="test-tag-1") + tag_2 = Tag.objects.create(name="test-tag-2") + + _clone_m2m_fields_mock.return_value = ["tags"] + + book_2 = Book.objects.create( + name="New Book 2", created_by=self.user1, slug=slugify("New Book 2") + ) + BookTag.objects.create(book=book_2, tag=tag_1) + BookTag.objects.create(book=book_2, tag=tag_2) + + book_clone_2 = book_2.make_clone() + + self.assertEqual( + list(book_2.tags.values_list("name")), + list(book_clone_2.tags.values_list("name")), ) @patch("sample.models.Author._clone_excluded_fields", new_callable=PropertyMock) @@ -289,7 +310,7 @@ def test_cloning_unique_field_with_use_unique_duplicate_suffix_set_to_False( sex="F", created_by=self.user1, ) - with self.assertRaises(ValidationError): + with self.assertRaises(IntegrityError): author.make_clone() use_unique_duplicate_suffix_mock.assert_called() diff --git a/sample/migrations/0014_auto_20210422_1449.py b/sample/migrations/0014_auto_20210422_1449.py new file mode 100644 index 00000000..8a654c16 --- /dev/null +++ b/sample/migrations/0014_auto_20210422_1449.py @@ -0,0 +1,138 @@ +# Generated by Django 3.2 on 2021-04-22 14:49 + +from django.db import migrations, models +import django.db.models.deletion +import model_clone.mixins.clone + + +class Migration(migrations.Migration): + + dependencies = [ + ("sample", "0013_edition"), + ] + + operations = [ + migrations.CreateModel( + name="Tag", + 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.AlterField( + model_name="assignment", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + migrations.AlterField( + model_name="author", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + migrations.AlterField( + model_name="backcover", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + migrations.AlterField( + model_name="book", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + migrations.AlterField( + model_name="cover", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + migrations.AlterField( + model_name="edition", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + migrations.AlterField( + model_name="furniture", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + migrations.AlterField( + model_name="house", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + migrations.AlterField( + model_name="page", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + migrations.AlterField( + model_name="room", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + migrations.CreateModel( + name="BookTag", + 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" + ), + ), + ( + "tag", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="sample.tag" + ), + ), + ], + options={ + "unique_together": {("book", "tag")}, + }, + ), + migrations.AddField( + model_name="book", + name="tags", + field=models.ManyToManyField(through="sample.BookTag", to="sample.Tag"), + ), + ] diff --git a/sample/models.py b/sample/models.py index e95e52b2..cae56783 100644 --- a/sample/models.py +++ b/sample/models.py @@ -35,6 +35,13 @@ class Meta: unique_together = (("first_name", "last_name", "sex"),) +class Tag(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) @@ -44,11 +51,22 @@ class Book(CloneModel): on_delete=models.PROTECT, ) created_at = models.DateTimeField(auto_now_add=True) + tags = models.ManyToManyField(Tag, through="BookTag") def __str__(self): return _(self.name) +class BookTag(models.Model): + book = models.ForeignKey(Book, on_delete=models.CASCADE) + tag = models.ForeignKey(Tag, on_delete=models.PROTECT) + + class Meta: + unique_together = [ + ("book", "tag"), + ] + + class Page(CloneModel): content = models.CharField(max_length=20000) book = models.ForeignKey(Book, on_delete=models.CASCADE) diff --git a/sample_assignment/migrations/0002_alter_contract_id.py b/sample_assignment/migrations/0002_alter_contract_id.py new file mode 100644 index 00000000..7f270aa9 --- /dev/null +++ b/sample_assignment/migrations/0002_alter_contract_id.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2 on 2021-04-22 14:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("sample_assignment", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="contract", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ] diff --git a/sample_company/migrations/0002_alter_companydepot_id.py b/sample_company/migrations/0002_alter_companydepot_id.py new file mode 100644 index 00000000..fcb3b587 --- /dev/null +++ b/sample_company/migrations/0002_alter_companydepot_id.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2 on 2021-04-22 14:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("sample_company", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="companydepot", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ] diff --git a/sample_driver/migrations/0002_alter_driver_id.py b/sample_driver/migrations/0002_alter_driver_id.py new file mode 100644 index 00000000..db08106e --- /dev/null +++ b/sample_driver/migrations/0002_alter_driver_id.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2 on 2021-04-22 14:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("sample_driver", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="driver", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ]