Django & South: Custom field methods are not executed when doing obj.save() in a data migration
Asked Answered
M

2

6

In my Django model I have two fields, name (a regular CharField) and slug, a custom field that generates the slug based on a field name passed in the field definition, in this case I use name for this.

First, the model only had the name field, with it's corresponding migrations and all. Then I needed to add the slug field, so following South conventions, I added the slug field with unique=False, create the schema migration, then created a data migration, set the unique=True and create another migration for this last change.

Since the value of the slug is generated on model save, in the forwards method of the data migration what I did was to iterate over the queryset returned by orm['myapp.MyModel'].objects.all() and calling the save() method on each instance.

But the value of the slug field is never generated. Under an IPython session I did the same thing, but referencing the model as from myapp.models import MyModel, and worked. Using some debug statements, printing the type of the model returned by South's orm dict shows the real class, it doesn't appear to be an fake model constructed on the fly by South.

The slug field creates it's value when the pre_save method. How to force it to be called during a data migration? I need to ensure the uniqueness of the value so when the index is applied in a later schema migration, the columns doesn't contains duplicate values.

Thanks!

BTW: The slug field class does define the south_field_triple so South plays nice with it.

EDIT: Please see my answer. But more like an answer, it feels more like a hack. Is there a better/right way to do this?

Mycorrhiza answered 16/7, 2012 at 13:34 Comment(2)
Is everything ok with your content types ?Chert
Yes, I haven't added or deleted any model nor changed any model's name. Just added the new field.Aardvark
M
4

Generally you should explicitly replicate the code that generates the field's contents as closely as possible in the migration (A rare example of purposeful code duplication). The code in your approach, even if it worked, would call pre_save as defined at the time of executing the migration, which may have changed or even fail with the models state at the time the migration was created (It may depend on other fields not being present at an earlier time etc.).

So the correct approach in you example would be to use slugify() directly, as it is done in the SlugField's pre_save method:

from django.template.defaultfilters import slugify

class Migration(DataMigration):

    def forwards(self, orm):
        "Write your forwards methods here."

        for myobj in orm['myapp.MyModel'].objects.all():
            myobj.slug = slugify(myobj.otherfield)
            myobj.save()
Messapian answered 13/11, 2012 at 9:12 Comment(0)
M
1

I solved this temporarily by obtaining the model field instance and calling it's pre_save directly:

class Migration(DataMigration):

    def forwards(self, orm):
        "Write your forwards methods here."
        # Note: Remember to use orm['appname.ModelName'] rather than "from appname.models..."
        for myobj in orm['myapp.MyModel'].objects.all():
            slug_field = myobj._meta.get_field_by_name('slug')[0]
            myobj.slug = slug_field.pre_save(myobj, add=False)
            myobj.save()

However it feels cumbersome to take this into account for custom fields...

Mycorrhiza answered 16/7, 2012 at 14:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.