Python | Django | Downloads | Code Style |
---|---|---|---|
PyPI | Test | Vulnerabilities | Coverage | Code Quality | Dependencies | Pre-Commit |
---|---|---|---|---|---|---|
|
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.
- 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.
- Installation
- Usage
- Compatibility
- Running locally
- Found a Bug?
- Contributors ✨
Run
pip install django-clone
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)
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)
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>]>
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>]>
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'
Attribute | Description |
---|---|
DUPLICATE_SUFFIX |
Suffix 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_FIELDS |
Enable appending the DUPLICATE_SUFFIX to new cloned instances. |
UNIQUE_DUPLICATE_SUFFIX |
Suffix to append to unique fields |
USE_UNIQUE_DUPLICATE_SUFFIX |
Enable appending the UNIQUE_DUPLICATE_SUFFIX to new cloned instances. |
MAX_UNIQUE_DUPLICATE_QUERY_ATTEMPTS |
The max query attempt while generating unique values for a case of unique conflicts. |
Attribute | Description |
---|---|
_clone_fields |
Restrict the list of fields to copy from the instance (By default: Copies all fields excluding auto-created/non editable model fields) |
_clone_m2m_fields |
Restricted Many to many fields (i.e Test.tags) |
_clone_m2o_or_o2m_fields |
Restricted Many to One/One to Many fields |
_clone_o2o_fields |
Restricted One to One fields |
Attribute | Description |
---|---|
_clone_excluded_fields |
Excluded model fields. |
_clone_excluded_m2m_fields |
Excluded many to many fields. |
_clone_excluded_m2o_or_o2m_fields |
Excluded Many to One/One to Many fields. |
_clone_excluded_o2o_fields |
Excluded one to one fields. |
NOTE:
⚠️
- Ensure to either set
_clone_excluded_*
or_clone_*
. Using both would raise errors.
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.
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
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 beforedjango.contrib.admin
INSTALLED_APPS = [
'model_clone',
'django.contrib.admin',
'...',
]
Python | Supported version |
---|---|
Python2.x | <=2.5.3 |
Python3.5 | <=2.9.6 |
Python3.6+ | All versions |
Django | Supported version |
---|---|
1.11 | <=2.7.2 |
2.x | All versions |
3.x | All versions |
$ git clone [email protected]: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
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 ⭐
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!