Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increased test coverage. #244

Merged
merged 3 commits into from
Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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