1

I was searching for the fastest and simplest way to duplicate a model instance and related model. I found some techniques here and here but I think they are based on old django versions.

I have achieved it with loops, but is there any better way to do it? I tryed the django-forkit plugin but encounter a problem with model that have a foreignkey field with auth.User.

Models:

class Book(Folder):
    filename = models.CharField(max_length=255)
    created_by = models.ForeignKey(User)

class Chapter(models.Model):
    book = models.ForeignKey(Book)
    name = models.CharField(max_length=255, db_index=True)

class Page(models.Model):
    book = models.ForeignKey(Book)
    attribute_value = hstore.DictionaryField(db_index=True)

Duplicate function:

    book = Book.objects.get(pk=node_id, created_by=request.user)
    chapters = Chapter.objects.filter(book=book)
    pages = Page.objects.filter(book=book)
    book.pk = None
    book.id = None
    book.filename = book.filename + "_copy"
    book.save()
    for chapter in chapters:
        chapter.book= book
        chapter.pk = None
        chapter.save()
    for page in pages:
        page.book= book
        page.pk = None
        page.save()
Community
  • 1
  • 1
Below the Radar
  • 7,321
  • 11
  • 63
  • 142
  • Given the simplicity of your models, I'd stick with your solution, but if you are looking for a more general way to do that, have a look at the `NestedObjects` class: http://stackoverflow.com/questions/12158714/how-to-show-related-items-using-deleteview-in-django/12162619#12162619 – Germano Aug 04 '14 at 13:28
  • @Germano, thanks for the link. I wonder how you duplicate all the nested objects at final. – Below the Radar Aug 04 '14 at 13:34
  • Hello, this solution seems ok to me; you can also take a look at the ORM's `bulk_create` method and see if you can save chapters and pages without hitting the db multiple times. https://docs.djangoproject.com/en/dev/ref/models/querysets/#bulk-create. (That is if no weird staff is happening in Chapter.save() and Page.save() ofcourse) – ppetrid Aug 04 '14 at 16:17

1 Answers1

1

I'd keep it exactly like you have it if the models really are that simple.

If not, or if you want to over-engineer it ;), then my personal favorite design pattern is to explicitly declare the fields affected by a copy operation and loop through those. This keeps you from hacking around on model APIs and (potentially) making your code non-upgradeable. Your models also self-describe what happens in a copy, which is handy for certain fields that you may not want to translate across. For instance:

class CopyableModel(models.Model):
    # these fields are persisted across copies
    _copy_concerns = ['title', 'author', 'body']

    def create_copy(self):
        attrs = {}
        for concern in self._copy_concerns:
           attrs[concern] = getattr(self, concern)

        # does this work?  Might have to access the CopyableModel definition in a different way...
        # Like self.__class__.objects.create( ...
        return CopyableModel.objects.create(**attrs)

And you can jiggle that around a bit to work with m2m and other relationships. Again, not sure its worth the trouble in your particular case, but this is a nice expandable pattern when you need it.

Casey Kinsey
  • 1,451
  • 9
  • 16