Re: [Django] #35731: Extend documentation about db_default and DatabaseDefault (was: Fields with db_default, and without default, are initialized to instance of DatabaseDefault)

6 views
Skip to first unread message

Django

unread,
Sep 5, 2024, 8:15:35 AM9/5/24
to django-...@googlegroups.com
#35731: Extend documentation about db_default and DatabaseDefault
-------------------------------------+-------------------------------------
Reporter: Kyle Bebak | Owner:
Type: | YashRaj1506
Cleanup/optimization | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Natalia Bidart):

* stage: Unreviewed => Accepted
* summary:
Fields with db_default, and without default, are initialized to
instance of DatabaseDefault
=> Extend documentation about db_default and DatabaseDefault
* version: 5.0 => dev


Old description:

> For example, if client code creates a model `Foo` with `val =
> IntegerField(db_default=10)`, does `foo = Foo()`, and accesses `foo.val`,
> they get an instance of `django.db.models.expressions.DatabaseDefault`.
>
> This `DatabaseDefault` seems to be used for bookkeeping until the model
> instance is written to the DB, after which `foo.val` is an `int`. IMO
> this is not a good design, because it's a case of an implementation
> detail (setting a value for the field once it's saved to the DB) changing
> the model's public interface (IMO a model instance's field values are
> part of its public interface).
>
> If instead we do `val = IntegerField()`, and `foo = Foo()`, and access
> `foo.val`, we get `None`, s.t. the type of `foo.val` is `int | None`.
> Using `db_default` means that the type of `foo.val` is now `int |
> DatabaseDefault`. `DatabaseDefault` is a bookkeeping type that client
> code usually shouldn't interact with. If users aren't aware of
> `db_default`'s implementation, they might still write code like this,
> which would be broken: `if foo.val is not None: print(foo.val + 10)`.
>
> Because `DatabaseDefault` is for bookkeeping, it seems like there's no
> reason the model instance couldn't store its `DatabaseDefault` instances
> on a "private" field which wouldn't affect the model's public interface.
> This would be a lot cleaner IMO. Most users shouldn't know about
> `DatabaseDefault`, which unsurprisingly isn't mentioned here,
> https://docs.djangoproject.com/en/5.1/ref/models/fields/#db-default, or
> anywhere else in the docs AFAICT.

New description:

I would be helpful if the existing docs at `ref/models/fields.txt`, when
describing `db_default`, would mention `DatabaseDefault`.
Also, the docs `topics/db/models.txt` describe most of the `Field` options
but `db_default` is missing, so ideally we would add a section for it with
examples, including some that would show how and when `DatabaseDefault` is
returned/used.

==== Original report ====

For example, if client code creates a model `Foo` with `val =
IntegerField(db_default=10)`, does `foo = Foo()`, and accesses `foo.val`,
they get an instance of `django.db.models.expressions.DatabaseDefault`.

This `DatabaseDefault` seems to be used for bookkeeping until the model
instance is written to the DB, after which `foo.val` is an `int`. IMO this
is not a good design, because it's a case of an implementation detail
(setting a value for the field once it's saved to the DB) changing the
model's public interface (IMO a model instance's field values are part of
its public interface).

If instead we do `val = IntegerField()`, and `foo = Foo()`, and access
`foo.val`, we get `None`, s.t. the type of `foo.val` is `int | None`.
Using `db_default` means that the type of `foo.val` is now `int |
DatabaseDefault`. `DatabaseDefault` is a bookkeeping type that client code
usually shouldn't interact with. If users aren't aware of `db_default`'s
implementation, they might still write code like this, which would be
broken: `if foo.val is not None: print(foo.val + 10)`.

Because `DatabaseDefault` is for bookkeeping, it seems like there's no
reason the model instance couldn't store its `DatabaseDefault` instances
on a "private" field which wouldn't affect the model's public interface.
This would be a lot cleaner IMO. Most users shouldn't know about
`DatabaseDefault`, which unsurprisingly isn't mentioned here,
https://docs.djangoproject.com/en/5.1/ref/models/fields/#db-default, or
anywhere else in the docs AFAICT.

--
Comment:

Overall, I agree with Simon analysis. Specifically, the docs for
`db_default` say:

> The database-computed default value for this field.

So, when doing `val = IntegerField(db_default=10); foo = Foo(); foo.val`,
I would expect anything **but** the value that was set as `db_default`
(10), since the code "hasn't gone to the db yet". Intuitively I would have
expected an exception such as "you can't have a value since DB hasn't been
reached yet", but getting a instance of a "db value promise" is even
better and clearer.

OTOH, I do agree that we may be lacking docs about this. In particular, I
think we should:
1. add a small note to the existing `ref/models/fields.txt` docs about
`db_default`, and
2. add a richer section to the `topics/db/models.txt` for
`:attr:~Field.db_default` since most of the `Field` options are documented
there but `db_default` is missing.

Accepting and re-purposing this ticket with that goal in mind.
--
Ticket URL: <https://code.djangoproject.com/ticket/35731#comment:3>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
Reply all
Reply to author
Forward
0 new messages