Skip to content
Advertisement

Django 2.2 cannot serialize default values once migration has been done

I have a model which is refered to as a foreignkey with on_delete set to SET_DEFAULT. Because of that, I need to provide this model with a default item. I created a static method which does just that.

class ScheduleChoice(models.Model):
    """
    This model allows users to define crontab schedules.
    """
    label = models.CharField(max_length=256, verbose_name="Label", unique=True)
    job_schedule = models.CharField(
        max_length=256,
        default="0 0 * * *", verbose_name="Crontab"
    )

    @staticmethod
    def get_default_schedule_choice():
        """
        Because some models rely on ScheduleChoice and need a default value,
        we need to give them a default ScheduleChoice.
        """
        try:
            choice = ScheduleChoice.objects.get_or_create(
                label="Every minute",
                job_schedule="* * * * *"
            )
        except ProgrammingError:
            choice = None
        return choice

    @classmethod
    def get_choice_count(cls):
        """
        Returns how many schedule choices have been defined.
        """
        return len(cls.objects.all())

    class Meta:
        verbose_name = "schedule choice"
        verbose_name_plural = "schedule choices"

    def __str__(self):
        return self.label


class MyOtherModel(models.Model):
    """
    Model using ScheduleChoices.
    """
    job_schedule = models.ForeignKey(
        "ScheduleChoice",
        on_delete=models.SET_DEFAULT,
        default=ScheduleChoice.get_default_schedule_choice(),
        verbose_name="Schedule"
    )
    activated = models.BooleanField(default=False, verbose_name="activated")

I am able to run makemigrations and migrate without issue.

My problem starts when I modifiy my models and try to use makemigrations again in order to update the migrations files. I get the error :

ValueError: Cannot serialize: <ScheduleChoice: Every minute> There are some values Django cannot serialize into migration files. For more, see https://docs.djangoproject.com/en/2.2/topics/migrations/#migration-serializing

I tried to apply this answer, but it didn’t help. Why does Django need to serialize my default value ? Why does it only do so after the first migration has successfully ended ?

I can always use reset_db and do my migration, but it is not acceptable in my production environment.

How can I fix this ?

Advertisement

Answer

Django needs to serialize your models to make migration files for them. Hence it also needs to serialize most of the attributes you set on the model fields (default included). Currently you define a method and directly call that instead of providing the method as the default, also your method returns a tuple with ScheduleChoice and a boolean.

Django can serialize booleans but not the model instance (for the migration) hence you get an error, not to mention the tuple would have caused an error anyway. You should not call the method and instead just pass the method as the default, also instead of returning the instance return only the pk, also ideally this method should simply be a function:

class ScheduleChoice(models.Model):
    """
    This model allows users to define crontab schedules.
    """
    label = models.CharField(max_length=256, verbose_name="Label", unique=True)
    job_schedule = models.CharField(
        max_length=256,
        default="0 0 * * *", verbose_name="Crontab"
    )
    
    @classmethod
    def get_choice_count(cls):
        """
        Returns how many schedule choices have been defined.
        """
        return len(cls.objects.all())

    class Meta:
        verbose_name = "schedule choice"
        verbose_name_plural = "schedule choices"

    def __str__(self):
        return self.label


def get_default_schedule_choice():
    choice, _created = ScheduleChoice.objects.get_or_create(
        label="Every minute",
        job_schedule="* * * * *"
    )
    # Why would programming error occur?
    return choice.pk # Return the pk

class MyOtherModel(models.Model):
    """
    Model using ScheduleChoices.
    """
    job_schedule = models.ForeignKey(
        "ScheduleChoice",
        on_delete=models.SET_DEFAULT,
        default=get_default_schedule_choice, # Don't call the function, only pass it
        verbose_name="Schedule"
    )
    activated = models.BooleanField(default=False, verbose_name="activated")
Advertisement