8

I'm using Django 1.7 and I have a problem with my fixtures.

I would like Django to use the default value or use the save() method to create unspecified values.

Here are my current objects:

# File: uuidable.py
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _


class Uuidable(models.Model):
    uuid = models.CharField(_('uuid'), blank=True,
                            null=False, unique=True,
                            max_length=64, default=uuid.uuid4())  # Tried here

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        if self.pk is None:
            self.uuid = uuid.uuid4()  # Tried here also
        super().save(*args, **kwargs)

# File: timestampable.py
from django.db import models
from django.utils.translation import ugettext_lazy as _


class Timestampable(models.Model):
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)

    class Meta:
        abstract = True

# File: post.py
from project.lib.models.timestampable import Timestampable
from project.lib.models.uuidable import Uuidable

class Post(Timestampable, Uuidable):
    title = models.CharField(_('title'), max_length=250, blank=False)
    content = models.TextField(_('content'))

    def __str__(self):
        return self.title

As you can see, when I generate a new Post(), the created_at, updated_at and uuid values are automatically created on save(). But when I use fixtures, I get the following error:

[...]initial_data.yaml': Could not load post.Post(pk=None): UNIQUE constraint failed: post_post.uuid

If I specify a uuid in my fixture file, then I get an error on created_at and then on updated_at. So I have to specify the content of each field, even though I want it to be "automatic".

From the documentation (why is this in the django admin docs ?!), I know that the save() method is not called so this is why everything I put into the save() method doesn't work. But shouldn't the default or auto_now* features be enables/used ?

When fixture files are processed, the data is saved to the database as is. Model defined save() methods are not called, and any pre_save or post_save signals will be called with raw=True since the instance only contains attributes that are local to the model. You may, for example, want to disable handlers that access related fields that aren’t present during fixture loading and would otherwise raise an exception

Is there a way to "force" Django to automatically use the default or auto_now* features for fixtures ? I'm using manage.py syncdb to create all the tables etc.

I have searched on google and stack overflow but couldn't seem to find the right search keywords.

UPDATE-1: The following google group discussion says that objects are saved in raw mode, meaning that auto_now* features are not taken into account. I'm still searching to see if there is a way to hook some model function to the Django fixture saving.

achedeuzot
  • 4,164
  • 4
  • 41
  • 56
  • Can you use a data migration instead of a fixture? That also has the advantage that they keep working if you change the model with a schema migration later. – RemcoGerlich Sep 08 '14 at 13:43
  • I have a bit too much data at the moment to easily switch to migrations. But I know it's a better approach. – achedeuzot Sep 08 '14 at 13:51
  • A good side question about migrations during initial development http://stackoverflow.com/questions/5021800/why-use-south-during-initial-development – achedeuzot Sep 08 '14 at 14:11
  • IIUC, the idea with fixtures is that you save some records FROM database to a fixture, and then load it again to another (or newly created) database. Therefore what you're asking about is going against the design of fixtures, and that's why you're running into issues. If you want to create _new_ records, the standard approach is to have a (very simple) script that grabs data from somewhere and creates and saves records in the standard way, i.e. MyModel.create(**data). – Rainy Sep 08 '14 at 14:18
  • @Rainy Isn't it also to pre-populate a database with initial data ? (in my case that's why I'm using them) – achedeuzot Sep 08 '14 at 14:20
  • @achedeuzot yes, but initial data that was originally created using normal record creation method; e.g. loading initial data on production system which was first created on dev system; of course if you can create a fixture that already has all the needed fields manually in the right format, that would also work because django doesn't know where data comes from. – Rainy Sep 08 '14 at 14:23

3 Answers3

7

The solution was to use django signals:

import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.db.models.signals import pre_save
from django.dispatch import receiver

class Uuidable(models.Model):
    uuid = models.CharField(_('uuid'), blank=True,
                            null=False, unique=True,
                            max_length=64, default=uuid.uuid4())

    class Meta:
        abstract = True

    @receiver(pre_save)
    def set_uuid_on_save(sender, instance, *args, **kwargs):
        if instance.pk is None:
            instance.uuid = uuid.uuid4()

That way, the model/data is populated whatever way you create the model (via shell, fixtures, whatever).

achedeuzot
  • 4,164
  • 4
  • 41
  • 56
2

Automatically loading initial data fixtures is deprecated in Django 1.7. One solution is via signals as you mentioned. Another one that I prefer is to create a python script where you create all the needed data, and execute it in the shell:

python manage.py shell < create_initial_data.py
ferrangb
  • 2,012
  • 2
  • 20
  • 34
  • 2
    I love the idea of the python script that you suggest ! But for the moment, I have a bit too much data to easily convert it to that format. – achedeuzot Sep 08 '14 at 13:45
  • That's incorrect: **automatic loading** of fixtures was deprecated in 1.7. – Rainy Sep 08 '14 at 14:15
  • This would make more sense as a [Django Admin command](https://docs.djangoproject.com/en/1.10/howto/custom-management-commands/) – Julien Feb 18 '17 at 18:58
1

I think that the problem is when you put default=uuid.uuid4(). The parenthesis are too much, because they imply that you pass the result of uuid.uuid4() to default argument and not the function itself, so you should put default=uuid.uuid4.

Tiago Silva
  • 2,299
  • 14
  • 18
Wassel
  • 11
  • 1