Skip to content

Instantly share code, notes, and snippets.

@avalanchy
Created August 28, 2024 08:26
Show Gist options
  • Select an option

  • Save avalanchy/acddf191bf7145ef1564f8d9968c7025 to your computer and use it in GitHub Desktop.

Select an option

Save avalanchy/acddf191bf7145ef1564f8d9968c7025 to your computer and use it in GitHub Desktop.

Revisions

  1. avalanchy created this gist Aug 28, 2024.
    65 changes: 65 additions & 0 deletions __init__.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,65 @@
    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)