Skip to content

Instantly share code, notes, and snippets.

@mariocesar
Last active February 10, 2025 20:14
Show Gist options
  • Save mariocesar/6cfebb877da34c61204ef31888fd0bc0 to your computer and use it in GitHub Desktop.
Save mariocesar/6cfebb877da34c61204ef31888fd0bc0 to your computer and use it in GitHub Desktop.
TimestampStateField custom Django field that creates a timestamp-boolean pair

The TimestampStateField creates two related fields in your Django models:

  1. A timestamp field (DateTimeField) that records when a state changed
  2. A corresponding boolean field that indicates the current state

The goal is to do state-tracking where you want to know both IF something is in a state and WHEN it entered that state. For example, tracking if a user is active and when they became active. And with that also gain the benefits of faster DB access and creating more efficient db indexes.

Here's how it works through an example:

class User(models.Model):
    active_at = TimestampStateField()  # Creates two fields:
    # 1. active_at: DateTimeField - stores when user became active
    # 2. is_active: BooleanField - automatically true if active_at has a value

How to query:

# Find active users (using the generated boolean field)
User.objects.filter(is_active=True)
# Find users who became active in 2021 (using the timestamp field)
User.objects.filter(active_at__year=2021)
from django.db import models
class TimestampStateField(models.Field):
"""
An state timestamp field that automatically creates a generated boolean field.
The boolean field name is automatically derived from the timestamp field name.
Example usage:
class User(models.Model):
active_at = TimestampStateField() # Creates 'is_active' bool field
verified_at = TimestampStateField() # Creates 'is_verified' bool field
User.objects.filter(is_active=True) # Filter active users
User.objects.filter(active_at__year=2021) # Filter users activated in 2021
"""
boolean_field_prefix: str
boolean_field_name: str | None
datetime_kwargs: dict
def __init__(
self,
*args,
boolean_field_prefix: str = "is",
boolean_field_name: str | None = None,
**kwargs,
):
self.boolean_field_prefix = boolean_field_prefix
self.boolean_field_name = boolean_field_name
kwargs.setdefault("null", True)
kwargs.setdefault("blank", True)
if "help_text" not in kwargs:
kwargs["help_text"] = (
"Timestamp when this state was set to true. A null value indicates the state is false."
)
self.datetime_kwargs = kwargs
def contribute_to_class(self, cls: type[models.Model], name):
self._add_datetime_field(cls, name)
self._add_generated_field(cls, name)
def _add_datetime_field(self, cls: type[models.Model], name: str) -> None:
field = models.DateTimeField(**self.datetime_kwargs)
field.contribute_to_class(cls, name)
def _add_generated_field(self, cls: type[models.Model], name: str) -> None:
field_name = (
self.boolean_field_name
if self.boolean_field_name
else f"{self.boolean_field_prefix}_{name.removesuffix("_at")}"
)
field = models.GeneratedField(
expression=models.Q((f"{name}__isnull", True)),
output_field=models.BooleanField(),
db_persist=True,
help_text=f"Generated from {name}",
)
field.contribute_to_class(cls, field_name)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if "null" in kwargs:
del kwargs["null"]
if "blank" in kwargs:
del kwargs["blank"]
if "help_text" in kwargs:
del kwargs["help_text"]
return name, path, args, kwargs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment