Search code examples
pythondjangounit-testing

DJANGO non nullable field migration issue


Im a DJANGO/python novice. I have added two fields so that changes in enterprise and DJANGO admin are logged as who updated them and what date it was updated.

the constraint is that these fields cannot be null.

my changes are as follows:

   class Enterprise(models.Model):
    last_updated = models.DateTimeField(
        null=False,
        auto_now=True,
        verbose_name='Last Updated Date',
        help_text="Points to time this was last modified",
    )

    last_updated_by = models.CharField(
        null=False,
        max_length=100,
        default='N/A',
    )

AND

class EnterpriseAdmin(admin.ModelAdmin):
    model = Enterprise

    readonly_fields = (
        "last_updated",
        "last_updated_by",)

    fieldsets = ('last_updated','last_updated_by',),

however when I run pytest, I'm receiving an error: null value in column "last_updated" violates not-null constraint.

when I ran makemigrations, Django prompted me to add a default for last_updated:

You are trying to add a non-nullable field 'last_updated' to enterprise without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows)

so I chose option '1' and default=datetime.datetime(1970, 1, 1, 0, 0),preserve_default=False, is added to last_updated in my migration file.

    migrations.AddField(
            model_name='enterprise',
            name='last_updated',
            field=models.DateTimeField(auto_now=True, default=datetime.datetime(1970, 1, 1, 0, 0), help_text='Points to time this was last modified', verbose_name='Last Updated Date'),
            preserve_default=False,
        ),

I was under the impression that the Unix epoch would be added as default in all existing columns.


Solution

  • I was under the impression that the Unix epoch would be added as default in all existing columns.

    I tested this on and it does, it of course might be the case that for some (very) old versions it is not the case.

    Then you will need to provide a default value yourself, and the lucky part is, you can use timezone.now

    Indeed, you can specify the current datetime with:

    It is impossible to add a non-nullable field 'last_updated' to enterprise without specifying a default. This is because the database needs something to populate existing rows.
    Please select a fix:
     1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
     2) Quit and manually define a default value in models.py.
    Select an option: 1
    Please enter the default value as valid Python.
    The datetime and django.utils.timezone modules are available, so it is possible to provide e.g. timezone.now as a value.
    Type 'exit' to exit this prompt
    >>> timezone.now

    This will then set the field to the timestamp at the moment the migration runs. That being said, it might be odd, since that is not the time the record got modified last, so perhaps null=True is a more sensical decision.