Skip to content

Instantly share code, notes, and snippets.

@avalanchy
Created August 28, 2024 08:26
Show Gist options
  • Save avalanchy/acddf191bf7145ef1564f8d9968c7025 to your computer and use it in GitHub Desktop.
Save avalanchy/acddf191bf7145ef1564f8d9968c7025 to your computer and use it in GitHub Desktop.
factory boy override django auto_now_add
from typing import Generic, TypeVar, get_args
import factory
from factory.base import FactoryMetaClass
def _override_auto_now_add_fields(obj, kwargs):
"""
That's a built-in "feature" of Django: when a field has auto_now_add=True, Django will override any provided value
with django.utils.timezone.now() ; this happens "after" factory_boy's code runs.
To overcome this we can re-save the object if any of those fields were passed.
Source: https://github.com/FactoryBoy/factory_boy/issues/102#issuecomment-28010862
"""
auto_now_add_fields = {f.name for f in obj.__class__._meta.fields if getattr(f, "auto_now_add", False)}
update_fields = auto_now_add_fields.intersection(kwargs)
if update_fields:
for field in update_fields:
setattr(obj, field, kwargs[field])
obj.save(update_fields=update_fields)
class BaseFactoryMeta(FactoryMetaClass):
"""
Source: https://github.com/FactoryBoy/factory_boy/issues/468#issuecomment-1536373442
Modified for ruff
"""
def __new__(cls, class_name, bases: list[type], attrs):
orig_bases = attrs.get("__orig_bases__", [])
for t in orig_bases:
if t.__name__ == "BaseFactory" and t.__module__ == __name__:
type_args = get_args(t)
if len(type_args) == 1:
if "Meta" not in attrs:
attrs["Meta"] = type("Meta", (), {})
attrs["Meta"].model = type_args[0]
return super().__new__(cls, class_name, bases, attrs)
T = TypeVar("T", bound=factory.django.DjangoModelFactory)
class BaseFactory(Generic[T], factory.django.DjangoModelFactory, metaclass=BaseFactoryMeta):
"""
Source: https://github.com/FactoryBoy/factory_boy/issues/468#issuecomment-1536373442
Modified for DjangoModelFactory and typing clues.
"""
class Meta:
abstract = True
def __new__(cls, *args, **kwargs) -> T:
return super().__new__(*args, **kwargs)
@classmethod
def create(cls, **kwargs) -> T:
obj = super().create(**kwargs)
_override_auto_now_add_fields(obj, kwargs)
return obj
@classmethod
def build(cls, **kwargs) -> T:
return super().build(**kwargs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment