django-clone
pypi i django-clone

django-clone

Controlled Django model instance replication.

by tj-django

3.0.3 (see all)License:MIT/Apache-2.0
pypi i django-clone
Readme
PythonDjangoDownloadsCode Style
PyPI - Python VersionPyPI - Django VersionDownloadsCode style: black
PyPITestVulnerabilitiesCoverageCode QualityDependenciesPre-Commit
PyPI versionTestKnown VulnerabilitiesCodacy Badge
codecov
Codacy BadgeUpdatespre-commit.ci status

django-clone

Create copies of a model instance with explicit control on how the instance should be duplicated (limiting fields or related objects copied) with unique field detection.

This solves the problem introduced by using instance.pk = None and instance.save() which results in copying more object state than required.

Features

  • 100% test coverage.
  • More control over how a model instance should be duplicated
  • Multi Database support i.e Create a duplicate on one or more databases.
  • Restrict fields used for creating a duplicate instance.
  • Detect unique fields and naively adds a suffix (copy {count}) to each new instance.

Table of Contents

Installation

Run

pip install django-clone

Usage

Subclassing the CloneModel

CHANGE

from django.db import models

class MyModel(models.Model):
    title = models.CharField(max_length=200)

TO

from django.db import models
from model_clone import CloneModel

class MyModel(CloneModel):
    title = models.CharField(max_length=200)

Example

from django.db import models
from django.utils.translation import gettext_lazy as _
from model_clone import CloneModel

class TestModel(CloneModel):
    title = models.CharField(max_length=200)
    tags =  models.ManyToManyField('Tags')

    _clone_m2m_fields = ['tags']


class Tags(models.Model):  #  To enable cloning tags directly use `CloneModel` as shown above.
    name = models.CharField(max_length=255)

    def __str__(self):
        return _(self.name)

Using the CloneMixin

from django.db import models
from django.utils.translation import gettext_lazy as _
from model_clone import CloneMixin

class TestModel(CloneMixin, models.Model):
    title = models.CharField(max_length=200)
    tags =  models.ManyToManyField('Tags')

    _clone_m2m_fields = ['tags']


class Tags(models.Model):  #  To enable cloning tags directly use `CloneMixin` as shown above.
    name = models.CharField(max_length=255)

    def __str__(self):
        return _(self.name)

Duplicating a model instance

In [1]: test_obj = TestModel.objects.create(title='New')

In [2]: test_obj.pk
Out[2]: 1

In [3]: test_obj.title
Out[3]: 'New'

In [4]: test_obj.tags.create(name='men')

In [4]: test_obj.tags.create(name='women')

In [5]: test_obj.tags.all()
Out[5]: <QuerySet [<Tag: men>, <Tag: women>]>

In [6]: test_obj_clone = test_obj.make_clone()

In [7]: test_obj_clone.pk
Out[7]: 2

In [8]: test_obj_clone.title
Out[8]: 'New copy 1'

In [9]: test_obj_clone.tags.all()
Out[9]: <QuerySet [<Tag: men>, <Tag: women>]>

In [6]: test_obj_clone = test_obj.make_clone(attrs={'title': 'Updated title'})

In [7]: test_obj_clone.pk
Out[7]: 3

In [8]: test_obj_clone.title
Out[8]: 'Updated title'

In [9]: test_obj_clone.tags.all()
Out[9]: <QuerySet [<Tag: men>, <Tag: women>]>

Bulk cloning a model

In [1]: test_obj = TestModel.objects.create(title='New')

In [2]: test_obj.pk
Out[2]: 1

In [3]: test_obj.title
Out[3]: 'New'

In [4]: test_obj.tags.create(name='men')

In [4]: test_obj.tags.create(name='women')

In [5]: test_obj.tags.all()
Out[5]: <QuerySet [<Tag: men>, <Tag: women>]>

In [6]: test_obj_clones = test_obj.bulk_clone(1000)

In [7]: len(test_obj_clones)
Out[7]: 1000

In [8]: test_obj_clone = test_obj_clones[0]

In [9]: test_obj_clone.pk
Out[9]: 2

In [10]: test_obj_clone.title
Out[10]: 'New copy 1'

In [11]: test_obj_clone.tags.all()
Out[11]: <QuerySet [<Tag: men>, <Tag: women>]>

Multi database support


In [6]: test_obj_clone = test_obj.make_clone(using='replica')  # Replicate test object to a different database.

In [7]: test_obj_clone.pk
Out[7]: 1

In [8]: test_obj_clone.title
Out[8]: 'New'

In [9]: test_obj_clone.tags.all()
Out[9]: <QuerySet [<Tag: men>, <Tag: women>]>

In [10]: test_obj_clone._state.db
Out[10]: 'replica'

CloneMixin attributes

AttributeDescription
DUPLICATE_SUFFIXSuffix to append to duplicates
(NOTE: This requires USE_DUPLICATE_SUFFIX_FOR_NON_UNIQUE_FIELDS
to be enabled and supports string fields).
USE_DUPLICATE_SUFFIX_FOR_NON_UNIQUE_FIELDSEnable appending the DUPLICATE_SUFFIX to new cloned instances.
UNIQUE_DUPLICATE_SUFFIXSuffix to append to unique fields
USE_UNIQUE_DUPLICATE_SUFFIXEnable appending the UNIQUE_DUPLICATE_SUFFIX to new cloned instances.
MAX_UNIQUE_DUPLICATE_QUERY_ATTEMPTSThe max query attempt while generating unique values for a case of unique conflicts.

Explicit (include only these fields)

AttributeDescription
_clone_fieldsRestrict the list of fields to copy from the instance (By default: Copies all fields excluding auto-created/non editable model fields)
_clone_m2m_fieldsRestricted Many to many fields (i.e Test.tags)
_clone_m2o_or_o2m_fieldsRestricted Many to One/One to Many fields
_clone_o2o_fieldsRestricted One to One fields

Implicit (include all except these fields)

AttributeDescription
_clone_excluded_fieldsExcluded model fields.
_clone_excluded_m2m_fieldsExcluded many to many fields.
_clone_excluded_m2o_or_o2m_fieldsExcluded Many to One/One to Many fields.
_clone_excluded_o2o_fieldsExcluded one to one fields.

NOTE: ⚠️

  • Ensure to either set _clone_excluded_* or _clone_*. Using both would raise errors.

Creating clones without subclassing CloneMixin.


In [1]: from model_clone.utils import create_copy_of_instance

In [2]: test_obj = TestModel.objects.create(title='New')

In [3]: test_obj.pk
Out[3]: 1

In [4]: test_obj.title
Out[4]: 'New'

In [5]: test_obj.tags.create(name='men')

In [6]: test_obj.tags.create(name='women')

In [7]: test_obj.tags.all()
Out[7]: <QuerySet [<Tag: men>, <Tag: women>]>

In [8]: test_obj_clone = create_copy_of_instance(test_obj, attrs={'title': 'Updated title'})

In [9]: test_obj_clone.pk
Out[9]: 2

In [10]: test_obj_clone.title
Out[10]: 'Updated title'

In [11]: test_obj_clone.tags.all()
Out[11]: <QuerySet []>

NOTE: ⚠️

  • This method won't copy over related objects like Many to Many/One to Many relationships.
  • Ensure that required fields skipped from being cloned are passed in using the attrs kwargs.

Django Admin

Duplicating Models from the Django Admin view.

Change

from django.contrib import admin
from django.contrib.admin import ModelAdmin

@admin.register(TestModel)
class TestModelAdmin(ModelAdmin):
    pass

to

from model_clone import CloneModelAdmin

@admin.register(TestModel)
class TestModelAdmin(CloneModelAdmin):
    pass
List View

Screenshot

Change View

Screenshot

CloneModelAdmin class attributes


from model_clone import CloneModelAdmin

@admin.register(TestModel)
class TestModelAdmin(CloneModelAdmin):
    # Enables/Disables the Duplicate action in the List view (Defaults to True)
    include_duplicate_action = True
    # Enables/Disables the Duplicate action in the Change view (Defaults to True)
    include_duplicate_object_link = True

NOTE: ⚠️

  • Ensure that model_clone is placed before django.contrib.admin
INSTALLED_APPS = [
    'model_clone',
    'django.contrib.admin',
    '...',
]

Compatibility

PythonSupported version
Python2.x<=2.5.3
Python3.5<=2.9.6
Python3.6+All versions
DjangoSupported version
1.11<=2.7.2
2.xAll versions
3.xAll versions

Running locally

$ git clone git@github.com:tj-django/django-clone.git
$ make default-user
$ make run

Spins up a django server running the demo app.

Visit http://127.0.0.1:8000

Found a Bug?

To file a bug or submit a patch, please head over to django-clone on github.

If you feel generous and want to show some extra appreciation:

Support me with a ⭐

Buy me a coffee

Contributors ✨

Thanks goes to these wonderful people:


Gerben Neven

🐛 ⚠️ 💻

Sebastian Kapunkt

💻 🐛 ⚠️

Andrés Portillo

🐛

WhiteSource Renovate

🚧

Yuekui

💻 🐛 ⚠️

Take Weiland

⚠️ 🐛 💻

Patrick

🐛 💻

This project follows the all-contributors specification. Contributions of any kind welcome!

GitHub Stars

72

LAST COMMIT

5mos ago

MAINTAINERS

1

CONTRIBUTORS

19

OPEN ISSUES

3

OPEN PRs

1
VersionTagPublished
3.0.3
2mos ago
3.0.2
2mos ago
3.0.1
7mos ago
3.0.0
7mos ago
No alternatives found
No tutorials found
Add a tutorial