Once in a while you might find yourself in the situation in which a django CMS plugin you are using in a project is no longer supported or no longer suited for your goals.
But then, you have hundreds (or thousands) of instances of the old plugin and you need a quick way to migrate all the data to a new one.
Luckily, it's possible to achieve this using a rather simple data migration.
Sample code used referenced below: https://gist.github.com/yakky/31466588da712e25e1f2c200d56d3776
How it works
In order to have a successful migration we must achieve the following:
- keep the plugins tree consistent
- keep the reference to the plugin valid (eg: the tag embedded in text fields when the plugin is embeddable in a text field)
- keep custom data intact (at least the ones supported by the new plugin model)
To achieve this we can use the fact that Django models instances are python classes disconnected from the database until an explicit database update is run.
deceiving Django ORM and the database
So the first step (lines 9-10) is to create an instance of the new plugin class without saving it to the database, then set the id of the new plugin to the one that's being removed; at the end (21-22) we delete the old instance before saving the new one to free any constraint on unique fields.
By keeping the same id, plugin instances embedded in text plugins (whose reference is kept by hardcoding the id in the text body) are moved to the new model.
Migrating tree data
Django CMS plugins are stored in the database using the materialized path algorithm by Django-treebeard, and the plugins hierarchy information is stored in a few fields which neees to be copied verbatim (lines 11-18). This bypass the tree algorithm and we effectively replace a single node in the tree without affecting the tree balancement or triggering the automatic tree rebalance.
Migrating custom data
We now can save the custom model data to the new one. To achieve this we can add the lists of assignments from the field on the old plugin to the one the new plugin: new_plugin.field_name = old_plugin.field_name
. If the plugin has any many to many relationship we can copy those as well, after saving our new plugin, using new_plugin.many2many.set(old_plugin.many2many.all())
.
Closing
The above snippet has still room for improvements (like using _meta.get_fields
to copy the fields data without writing every single field, but should provide you a good starting point to customise them your needs.
Top comments (1)
Another example is this gist, which migrates deprecated plugin instances to new plugins.