Skip to content

Instantly share code, notes, and snippets.

@easherma
Forked from vkurup/generate_factories.py
Created April 16, 2021 20:03
Show Gist options
  • Save easherma/8b31da2f9d49678af33e6c95da01e81e to your computer and use it in GitHub Desktop.
Save easherma/8b31da2f9d49678af33e6c95da01e81e to your computer and use it in GitHub Desktop.
Django management command to generate factory-boy boilerplate factory definitions for all the models in a given app. The code is not intended to be usable right off the bat, it just provides a few sane defaults based on the structure of your models.
from django.core.management.base import BaseCommand
from django.db.models import fields
from django.db.models.fields import related
from django.apps import apps
class Command(BaseCommand):
help = """Generate factory-boy factories for the given app"""
def add_arguments(self, parser):
parser.add_argument("app", type=str)
def handle(self, *args, **options):
app = options["app"]
app = apps.get_app_config(app)
models = app.get_models(app)
app_label = app.label
print(
"""
import factory
import datetime
import {models} as {app_label}_models
# import other requried factories here
""".format(
models=app.get_models, app_label=app.label
)
)
needed_factories = set()
created_factories = set()
for model in models:
print(
"""
class {model_name}Factory(factory.django.DjangoModelFactory):
class Meta:
model = {app_label}_models.{model_name}
""".format(
app_label=app_label, model_name=model.__name__
)
)
for field in model._meta.fields:
# skip fields with default values or that can be blank
if isinstance(field, fields.AutoField):
continue
if field.default != fields.NOT_PROVIDED:
print(
" # skipped field {0} ({1}) with default value ({2})".format(
field.name, field.__class__.__name__, field.default
)
)
continue
if field.blank or field.null:
print(
" # skipped blank or nullable field {0} ({1})".format(
field.name, field.__class__.__name__
)
)
continue
args = []
model_slug = model._meta.verbose_name.replace(" ", "-")
if field.choices:
cls = "factory.fuzzy.FuzzyChoice"
args.append("** need choices **")
elif isinstance(field, fields.IntegerField):
cls = "factory.fuzzy.FuzzyInteger"
args.append("0")
elif isinstance(field, fields.EmailField):
cls = "factory.Sequence"
args.append(
'lambda n: "{0}-{1}-{{}}@example.com".format(n)'.format(
model_slug, field.name
)
)
elif isinstance(field, fields.CharField) or isinstance(
field, fields.TextField
):
cls = "factory.Sequence"
args.append(
'lambda n: "{0}-{1}-{{}}".format(n)'.format(
model_slug, field.name
)
)
elif isinstance(field, fields.DateTimeField):
cls = "factory.fuzzy.FuzzyNaiveDateTime"
args.append(
"datetime.datetime.now() - datetime.timedelta(days=365)"
)
elif isinstance(field, fields.DateField):
cls = "factory.fuzzy.FuzzyDate"
args.append("datetime.date.today() - datetime.timedelta(days=365)")
elif isinstance(field, related.ForeignKey) or isinstance(
field, related.OneToOneField
):
cls = "factory.SubFactory"
rel = field.rel.to.__name__
rel_factory = "{0}Factory".format(rel)
needed_factories.add(rel_factory)
args.append(rel_factory)
else:
cls = "factory.UNKNOWN"
args.append(field.__class__.__name__)
print(
" {name} = {cls}({args})".format(
name=field.name, cls=cls, args=", ".join(args)
)
)
created_factories.add(
"{model_name}Factory".format(model_name=model.__name__)
)
print("") # two lines between class definitions
print(
"# TODO: you also need to create and add imports for the "
"following related factories: {0}".format(
", ".join(needed_factories - created_factories)
)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment