Quickstart

Quickstart#

This pages aims to get you going with django-copyist as quickly as possible.

Installation#

To install with pip:

pip install django-copyist

To install with poetry:

poetry add django-copyist

Basic usage#

Assuming you have following models in your Django app:

from django.db import models


class Company(models.Model):
    name = models.CharField(max_length=100)
    address = models.CharField(max_length=100)


class Project(models.Model):
    name = models.CharField(max_length=100)
    company = models.ForeignKey(
        Company, related_name="projects", on_delete=models.CASCADE
    )


class Employee(models.Model):
    name = models.CharField(max_length=100)
    company = models.ForeignKey(
        Company, related_name="employees", on_delete=models.CASCADE
    )


class Counterpart(models.Model):
    name = models.CharField(max_length=100)
    external_id = models.IntegerField()
    project = models.ForeignKey(
        Project, related_name="counterparts", on_delete=models.CASCADE
    )


class Task(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField()

    assignee = models.ForeignKey(
        Employee, related_name="tasks", on_delete=models.CASCADE
    )
    project = models.ForeignKey(Project, related_name="tasks", on_delete=models.CASCADE)
    counterparts = models.ManyToManyField(Counterpart, related_name="tasks")

And you want to create full copy of company with all nested data, but also want it to be created with different name and address. In this case you should write following ModelCopyConfig

from django_copyist.config import (
    ModelCopyConfig,
    TAKE_FROM_ORIGIN,
    MakeCopy,
    UpdateToCopied,
    FieldCopyConfig,
    CopyActions,
)
from example.demo.models import (
    Project,
    Counterpart,
    Task,
    Company,
    Employee,
)


config = ModelCopyConfig(
    model=Company,
    filter_field_to_input_key={"id": "company_id"},
    field_copy_actions={
        "name": FieldCopyConfig(
            action=CopyActions.TAKE_FROM_INPUT,
            input_key="new_company_name",
        ),
        "address": FieldCopyConfig(
            action=CopyActions.TAKE_FROM_INPUT,
            input_key="new_company_address",
        ),
        "projects": MakeCopy(
            ModelCopyConfig(
                model=Project,
                field_copy_actions={
                    "name": TAKE_FROM_ORIGIN,
                    "counterparts": MakeCopy(
                        ModelCopyConfig(
                            model=Counterpart,
                            field_copy_actions={
                                "name": TAKE_FROM_ORIGIN,
                                "external_id": TAKE_FROM_ORIGIN,
                            },
                        )
                    ),
                },
            )
        ),
        "employees": MakeCopy(
            ModelCopyConfig(
                model=Employee,
                field_copy_actions={
                    "name": TAKE_FROM_ORIGIN,
                },
            )
        ),
    },
    compound_copy_actions=[
        ModelCopyConfig(
            model=Task,
            field_copy_actions={
                "name": TAKE_FROM_ORIGIN,
                "description": TAKE_FROM_ORIGIN,
                "counterparts": UpdateToCopied(Counterpart),
                "project": UpdateToCopied(Project),
                "assignee": UpdateToCopied(Employee),
            },
        )
    ],
)

And then you can execute copy action like this:

from django_copyist.copy_request import CopyRequest
from django_copyist.copyist import CopyistConfig, Copyist

copy_request = CopyRequest(
    config=CopyistConfig([config]),
    input_data={
        "company_id": company_id,
        "new_company_name": new_company_name,
        "new_company_address": new_company_address,
    },
    confirm_write=False,
)
result = Copyist(copy_request).execute_copy_request()

With this, all company data should be copied. That seems like a lot to take in, so let’s break it down to what exactly happens here:

  1. We define a ModelCopyConfig for the Company model.

config = ModelCopyConfig(
    model=Company,
    filter_field_to_input_key={"id": "company_id"},
...

ModelCopyConfig is a class that defines how to copy a model. It takes the model class as the first argument and a dictionary that maps the filter field to the input key. This is used to find the object to copy.

  1. Next we define ModelCopyConfig.field_copy_actions for the Company model.

field_copy_actions={
    "name": FieldCopyConfig(
        action=CopyActions.TAKE_FROM_INPUT,
        input_key="new_company_name",
    ),
    "address": FieldCopyConfig(
        action=CopyActions.TAKE_FROM_INPUT,
        input_key="new_company_address",
    ),
    "projects": MakeCopy(
        ModelCopyConfig(
            model=Project,
            field_copy_actions={
                "name": TAKE_FROM_ORIGIN,
                "counterparts": MakeCopy(
                    ModelCopyConfig(
                        model=Counterpart,
                        field_copy_actions={
                            "name": TAKE_FROM_ORIGIN,
                            "external_id": TAKE_FROM_ORIGIN,
                        },
                    )
                ),
            },
        )
    ),
    "employees": MakeCopy(
        ModelCopyConfig(
            model=Employee,
            field_copy_actions={
                "name": TAKE_FROM_ORIGIN,
            },
        )
    ),
...

ModelCopyConfig.field_copy_actions is a dictionary that maps the field name to a FieldCopyConfig object.

The FieldCopyConfig object defines how to copy the field. In this case, we take the name and address fields from the input data.

TAKE_FROM_ORIGIN is a shortcut for creating FieldCopyConfig with TAKE_FROM_ORIGIN action, which takes value for new object from original object.

We also define how to copy the projects and employees fields.

We use the MakeCopy action to copy the related objects. MakeCopy is a shortcut for creating FieldCopyConfig with CopyActions.MAKE_COPY action and reference to given model. Nested MakeCopy automatically propagate parent id to child object.

  1. We define compound_copy_actions for the Company model.

compound_copy_actions=[
    ModelCopyConfig(
        model=Task,
        field_copy_actions={
            "name": TAKE_FROM_ORIGIN,
            "description": TAKE_FROM_ORIGIN,
            "counterparts": UpdateToCopied(Counterpart),
            "project": UpdateToCopied(Project),
            "assignee": UpdateToCopied(Employee),
        },
    )
...

compound_copy_actions is a list of ModelCopyConfig objects that define how to copy related objects that are not directly related to the model, or related through multiple relations that need to be created beforehand.

compound_copy_actions are executed after all fields are copied.

In this case, we define how to copy the Task model. We take the name and description fields from the original object. We also define how to copy the counterparts, project, and assignee fields.

UpdateToCopied() is a shortcut for creating FieldCopyConfig with CopyActions.UPDATE_TO_COPIED action and reference to given model. It will search mapping of previously copied objects and update reference to copied object.

  1. We create a CopyRequest object with the CopyistConfig and input data.

copy_request = CopyRequest(
    config=CopyistConfig([config]),
    input_data={
        "company_id": company_id,
        "new_company_name": new_company_name,
        "new_company_address": new_company_address,
    },
    confirm_write=False,
)
...

CopyRequest is a class that defines the copy request. It takes the CopyistConfig object, input data, and a boolean flag that indicates whether to confirm the write operation.

CopyistConfig is a class that defines the configuration for the copy operation. It takes a list of ModelCopyConfig objects.

CopyResult.input_data is a dictionary that contains the input data for the copy operation. It is later used in filtering or TAKE_FROM_INPUT actions.

CopyResult.confirm_write is a boolean flag that indicates whether to confirm the write operation, even if there are issues with matching objects in origin location with objects in target destination. It is not used in this example, but you can read more about it in overview section of this documentation.

  1. We execute the copy request.

result = Copyist(copy_request).execute_copy_request()

Copyist is a class that executes the copy request. It takes the CopyRequest object as an argument.

CopyResult.execute_copy_request method returns CopyResult object that contains information about the copy operation. Read more about it in overview section.

And like this you have copied the company with all related data and can see and edit configuration in one place.

Next steps#

This is just a basic example of how to use django-copyist. It can do much more granular control on how it should execute copy, and you can read more about it in the documentation.