4

I have a django abstract model that goes like this:

class AbstractAppendOnly(models.Model):
    created_at = models.DateTimeField(auto_now=True, editable=False)
    db_note = models.TextField(default=None, null=True)

    class Meta:
        abstract = True

This model is extended to multiple other models so that a create_at field is automatically added to all of those. Now when an object is created and saved from django server end the created_at timestamp field is automatically produced, as expected. But it does not enforce it in database level, so anyone can insert a row with fake created_at value.

As far as I am concerned django does not let user to set database level default value for model issue-470.

What I have found that may solve the issue -

  • I have found an way to customize the migration files using tool like django-add-default-value . But since migrations may sometimes needed to be pruned in big systems and we will have to write customized migrations every time we create a new model, it seems of huge error-prone.

  • Another way I have thought of is to add trigger using django-pgtrigger like this

@pgtrigger.register(
    pgtrigger.Trigger(
        name='insert_auto_created_at_timestamp',
        operation=pgtrigger.Insert,
        when=pgtrigger.Before,
        func='''
            new.created_at = now();
            return new;
        '''
    )
)
class AbstractAppendOnly(models.Model):
    created_at = models.DateTimeField(auto_now=True, editable=False)
    db_note = models.TextField(default=None, null=True)

    class Meta:
        abstract = True

But doing so throws error while running migrations

Traceback (most recent call last):
  File "/run/media/shafi/Codes/Python/tkdc/./manage.py", line 22, in <module>
    main()
  File "/run/media/shafi/Codes/Python/tkdc/./manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/core/management/__init__.py", line 395, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/core/management/base.py", line 330, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/core/management/base.py", line 371, in execute
    output = self.handle(*args, **options)
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/core/management/base.py", line 85, in wrapped
    res = handle_func(*args, **kwargs)
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/core/management/commands/migrate.py", line 267, in handle
    emit_post_migrate_signal(
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/core/management/sql.py", line 48, in emit_post_migrate_signal
    models.signals.post_migrate.send(
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/dispatch/dispatcher.py", line 177, in send
    return [
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/dispatch/dispatcher.py", line 178, in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/pgtrigger/apps.py", line 9, in install
    pgtrigger.install()
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/pgtrigger/core.py", line 846, in install
    trigger.install(model)
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/pgtrigger/core.py", line 621, in install
    cursor.execute(rendered_comment)
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/db/backends/utils.py", line 98, in execute
    return super().execute(sql, params)
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/db/backends/utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/django/db/backends/utils.py", line 82, in _execute
    return self.cursor.execute(sql)
  File "/run/media/shafi/Codes/Python/tkdc/venv/lib/python3.9/site-packages/pgconnection/core.py", line 85, in execute
    return super().execute(sql, args)
django.db.utils.ProgrammingError: relation "utils_abstractappendonly" does not exist

Which I think is because AbstractAppendOnly is an abstract class, and the trigger translates directly to the Model it is attached to and not to the inherited class. Therefore one solution would to add the trigger to every model that extends the class.

I would be grateful if someone could point me out a solution.

exilour
  • 516
  • 5
  • 12
  • Too bad currently it has to be done manually https://stackoverflow.com/a/65327092 – xjlin0 Dec 17 '21 at 04:12
  • @xjlin0 so, I came up with a solution manipulating Django's manage.py command. I made a command class that looks at all of my models (application-specific ones) and run custom ALTER SQL to add a default. I know it's a bit of a hackish solution but it works. – exilour Dec 19 '21 at 04:01

0 Answers0