Skip to content

Commit

Permalink
Merge pull request #244 from tj-django/increase-test-coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
jackton1 authored Mar 24, 2021
2 parents df2595e + 08f6a3c commit 772d7ea
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 25 deletions.
8 changes: 7 additions & 1 deletion model_clone/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@

from model_clone.admin import CloneModelAdmin, CloneModelAdminMixin
from model_clone.mixins.clone import CloneMixin
from model_clone.utils import create_copy_of_instance

__all__ = ["CloneMixin", "CloneModelAdmin", "CloneModelAdminMixin"]
__all__ = [
"CloneMixin",
"CloneModelAdmin",
"CloneModelAdminMixin",
"create_copy_of_instance",
]
Empty file added model_clone/tests/__init__.py
Empty file.
File renamed without changes.
42 changes: 42 additions & 0 deletions model_clone/tests/test_create_copy_of_instance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.test import TestCase
from django.utils.text import slugify

from model_clone import create_copy_of_instance
from sample.models import Library, Book

User = get_user_model()


class CreateCopyOfInstanceTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.user1 = User.objects.create(username="user 1")
cls.user2 = User.objects.create(username="user 2")

def test_cloning_model_with_custom_id(self):
instance = Library.objects.create(name="First library", user=self.user1)
clone = create_copy_of_instance(instance, attrs={"user": self.user2})

self.assertNotEqual(instance.pk, clone.pk)
self.assertEqual(clone.user, self.user2)

def test_cloning_unique_fk_field_without_a_fallback_value_is_invalid(self):
name = "New Library"
instance = Library.objects.create(name=name, user=self.user1)

with self.assertRaises(ValidationError):
create_copy_of_instance(instance)

def test_cloning_excluded_field_without_a_fallback_value_is_invalid(self):
name = "New Library"
instance = Book.objects.create(
name=name, created_by=self.user1, slug=slugify(name)
)

with self.assertRaises(IntegrityError):
create_copy_of_instance(
instance, exclude={"slug"}, attrs={"created_by": self.user2}
)
60 changes: 36 additions & 24 deletions model_clone/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,30 @@ def create_copy_of_instance(instance, exclude=(), save_new=True, attrs=None):
"""
Clone an instance of `django.db.models.Model`.
Args:
instance(django.db.models.Model): The model instance to clone.
exclude(list|set): List or set of fields to exclude from unique validation.
save_new(bool): Save the model instance after duplication calling .save().
attrs(dict): Kwargs of field and value to set on the duplicated instance.
Returns:
(django.db.models.Model): The new duplicated instance.
Examples:
>>> from django.contrib.auth import get_user_model
>>> from sample.models import Book
>>> instance = Book.objects.create(name='The Beautiful Life')
>>> instance.pk
1
>>> instance.name
"The Beautiful Life"
>>> duplicate = instance.make_clone(attrs={'name': 'Duplicate Book 2'})
>>> duplicate.pk
2
>>> duplicate.name
"Duplicate Book 2"
:param instance: The model instance to clone.
:type instance: django.db.models.Model
:param exclude: List or set of fields to exclude from unique validation.
:type exclude: list|set
:param save_new: Save the model instance after duplication calling .save().
:type save_new: bool
:param attrs: Kwargs of field and value to set on the duplicated instance.
:type attrs: dict
:return: The new duplicated instance.
:rtype: django.db.models.Model
:example:
>>> from django.contrib.auth import get_user_model
>>> from sample.models import Book
>>> instance = Book.objects.create(name='The Beautiful Life')
>>> instance.pk
1
>>> instance.name
"The Beautiful Life"
>>> duplicate = instance.make_clone(attrs={'name': 'Duplicate Book 2'})
>>> duplicate.pk
2
>>> duplicate.name
"Duplicate Book 2"
"""

defaults = {}
Expand All @@ -52,21 +54,31 @@ def create_copy_of_instance(instance, exclude=(), save_new=True, attrs=None):
if all(
[
not f.auto_created,
not f.primary_key,
f.concrete,
f.editable,
f not in instance.__class__._meta.related_objects,
f not in instance.__class__._meta.many_to_many,
]
):
defaults[f.attname] = getattr(instance, f.attname, f.get_default())
# Prevent duplicates
if f.name not in attrs:
defaults[f.attname] = getattr(instance, f.attname, f.get_default())

defaults.update(attrs)

new_obj = instance.__class__(**defaults)

exclude = exclude or [
f.name
for f in instance._meta.fields
if any([f.name not in defaults, f.has_default(), f.null])
if any(
[
all([f.name not in defaults, f.attname not in defaults]),
f.has_default(),
f.null,
]
)
]

try:
Expand Down

0 comments on commit 772d7ea

Please sign in to comment.