Skip to content

Instantly share code, notes, and snippets.

@vkurup
Forked from tobiasmcnulty/generate_factories.py
Last active April 16, 2021 20:03
Show Gist options
  • Save vkurup/7b5d4d37de7ce82c42e8 to your computer and use it in GitHub Desktop.
Save vkurup/7b5d4d37de7ce82c42e8 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 get_models, get_app, fields
from django.db.models.fields import related
class Command(BaseCommand):
help = """Generate factory-boy factories for the given app"""
def handle(self, *args, **options):
assert len(args) == 1, 'Must specify app name as first and only argument'
app = get_app(args[0])
models = get_models(app)
app_label = models[0]._meta.app_label
print("""
import factory
import datetime
import {models} as {app_label}_models
# import other requried factories here
""".format(models=app.__name__, 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