Opened 15 months ago
Last modified 3 weeks ago
#35659 assigned Cleanup/optimization
Document db_default behaviour before instances are saved
| Reported by: | Markus Andresen | Owned by: | Jerome Cagado |
|---|---|---|---|
| Component: | Documentation | Version: | 5.0 |
| Severity: | Normal | Keywords: | db_default |
| Cc: | Ülgen Sarıkavak, Lily | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | yes |
| Easy pickings: | no | UI/UX: | no |
Description
There seems to be an unexpected behavior when using the db_default attribute on a BooleanField. The bool() evaluation when the instance is not saved to the database does not reflect the specified db_default value.
Steps to Reproduce:
from django.db import models
class MyModel(models.Model):
my_field = models.BooleanField(db_default=False)
my_obj = MyModel()
bool(my_obj.my_field) # True, which is unexpected.
my_obj.save()
bool(my_obj.my_field) # False, as expected.
I know this can be worked around by adding a default attribute in addition to db_default, but in my opinion its not clear enough from the documentation that you have to.
Change History (9)
comment:1 by , 15 months ago
| Component: | Database layer (models, ORM) → Documentation |
|---|---|
| Summary: | Unexpected Behavior(footgun) with db_default on a BooleanField → Document db_default behaviour before instances are saved |
| Triage Stage: | Unreviewed → Accepted |
| Type: | Uncategorized → Cleanup/optimization |
comment:2 by , 15 months ago
Hello, I would like to take a shot on this issue as a new contributor. Could you please assign it to me? Thanks!
comment:3 by , 15 months ago
| Has patch: | set |
|---|---|
| Owner: | set to |
| Status: | new → assigned |
Hi team,
I’ve submitted a pull request to address this issue. You can review the changes here: https://github.com/django/django/pull/18454
Please let me know if there are any further updates needed.
Thanks!
comment:4 by , 15 months ago
Hey, thanks for accepting the ticket!
It's not safe to randomly cast things without at least checking what the underlying type of the variable is. Eg bool("false") == True. In this case my_field is an instance of DatabaseDefault.
I'm aware, the reason this behaviour is problematic (if not properly documented) is because my_field is assumed to be a bool since that is the case when a BooleanField has a default attribute, would people assume that db_default behaves differently? They might have code that looks something like this:
my_model = MyModel
if MyModel.my_field:
do_something
# later in code
my_model.save()
Docs currently looks like this
If both db_default and Field.default are set, default will take precedence when creating instances in Python code. db_default will still be set at the database level and will be used when inserting rows outside of the ORM or when adding a new field in a migration.
default taking precedence in Python code is somewhat misleading IMO, adding a proper "disclaimer" here might be good?
comment:5 by , 15 months ago
| Patch needs improvement: | set |
|---|
comment:6 by , 12 months ago
| Cc: | added |
|---|
comment:7 by , 3 weeks ago
Another possible way forward with this would be to override __bool__ and other special methods of DatabaseDefault to raise an error instead of silently doing an unexpected thing.
comment:8 by , 3 weeks ago
| Cc: | added |
|---|
comment:9 by , 3 weeks ago
My concerns with specializing DatabaseDefault.__bool__ (or even just for conditional output_field = BooleanField() expressions) would be that this would create an asymmetry with other expression assignments which are refreshed on Model.save now.
from django.db import models class MyModel(models.Model): my_field = models.BooleanField() my_obj = MyModel() my_obj.my_field = Value(False) # Or any expression really such as `GreaterThan(Now(), some_datetime)) bool(my_obj.my_field) # True my_obj.save() bool(my_obj.my_field) # False
To me it is just bad practice to expect model fields assignments to always be literal values as assigning expressions is just as valid hence why I think it's more of a documentation issue.
Thanks for the report, though I disagree with the analysis of it being unexpected 😊
It's not safe to randomly cast things without at least checking what the underlying type of the variable is. Eg
bool("false") == True. In this casemy_fieldis an instance ofDatabaseDefault.The documentation of
DatabaseDefaultis missing. The docs could probably be improved to make mention of it and that field values will be of this type before saving to the database.Accepting the ticket as a docs improvement 👍