├── tests ├── urls.py ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0002_auto_20210712_1723.py │ └── 0001_initial.py ├── test_custom_fields.py ├── blueprints.py ├── test_utils.py ├── settings.py ├── test_gis.py ├── test_shortcuts.py ├── runtests.py ├── test_postgres.py ├── test_blueprints.py ├── models.py └── test_factory.py ├── VERSION ├── django_fakery ├── py.typed ├── rels.py ├── exceptions.py ├── lazy.py ├── plugin.py ├── __init__.py ├── types.py ├── compat.py ├── utils.py ├── values.py ├── shortcuts.py ├── fakes.py ├── blueprint.py ├── field_mappings.py └── faker_factory.py ├── setup.cfg ├── .coveragerc ├── MANIFEST.in ├── .bumpversion.cfg ├── ISSUE_TEMPLATE.md ├── mypy.ini ├── .isort.cfg ├── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── Makefile ├── docs ├── seeding.rst ├── custom_fields.rst ├── hooks.rst ├── lazies.rst ├── get_or_make.rst ├── get_or_update.rst ├── nonpersistantinstances.rst ├── blueprints.rst ├── index.rst ├── relationships.rst ├── quickstart.rst ├── shortcuts.rst ├── Makefile ├── make.bat └── conf.py ├── LICENSE ├── setup.py ├── CODE_OF_CONDUCT.md ├── .github └── workflows │ └── python-package.yml └── README.rst /tests/urls.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 4.1.3 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_fakery/py.typed: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_fakery/rels.py: -------------------------------------------------------------------------------- 1 | SELECT = "SELECT" 2 | -------------------------------------------------------------------------------- /django_fakery/exceptions.py: -------------------------------------------------------------------------------- 1 | class ForeignKeyError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [metadata] 5 | license_file = LICENSE 6 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = django_fakery 3 | omit = 4 | django_fakery/compat.py 5 | tests/* 6 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include VERSION 3 | include LICENSE 4 | prune tests 5 | prune docs 6 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 4.1.3 3 | files = VERSION django_fakery/__init__.py docs/conf.py 4 | commit = True 5 | tag = True 6 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Brief summary of the issue goes here. 2 | 3 | ### Steps to reproduce 4 | 5 | 1. step 1 6 | 1. step 2 7 | 1. step 3 8 | 9 | ### Expected behavior 10 | 11 | X should be ... 12 | 13 | ### Actual behavior 14 | 15 | X is ... 16 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | 3 | [mypy-faker.*] 4 | ignore_missing_imports = True 5 | 6 | [mypy-django.*] 7 | ignore_missing_imports = True 8 | 9 | [mypy-psycopg2.*] 10 | ignore_missing_imports = True 11 | 12 | [mypy-pytest.*] 13 | ignore_missing_imports = True 14 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | line_length=88 3 | multi_line_output=3 4 | known_first_party=avo 5 | known_django=django 6 | sections=FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER 7 | use_parentheses=true 8 | include_trailing_comma=true 9 | lines_between_types=1 10 | -------------------------------------------------------------------------------- /django_fakery/lazy.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | 4 | class Lazy(object): 5 | def __init__(self, name: str, *args, **kwargs): 6 | self.name = name 7 | self.args = args 8 | self.kwargs = kwargs 9 | super(Lazy, self).__init__() 10 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes #... 2 | 3 | ### What does this changes 4 | 5 | Brief summary of the changes 6 | 7 | ### What was wrong 8 | 9 | Description of what was the root cause of the issue. 10 | 11 | ### How this fixes it 12 | 13 | Description of how the changes fix the issue. 14 | -------------------------------------------------------------------------------- /django_fakery/plugin.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from django_fakery import factory 4 | from django_fakery import shortcuts as _shortcuts 5 | 6 | 7 | @pytest.fixture 8 | def fakery(): 9 | return factory 10 | 11 | 12 | @pytest.fixture 13 | def fakery_shortcuts(): 14 | return _shortcuts 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pot 3 | *.py[co] 4 | *.out 5 | .eggs 6 | .cache 7 | .coverage 8 | parsetab.py 9 | __pycache__ 10 | MANIFEST 11 | dist/ 12 | docs/_build/ 13 | docs/locale/ 14 | node_modules/ 15 | tests/coverage_html/ 16 | tests/.coverage 17 | build/ 18 | tests/report/ 19 | htmlcov 20 | .idea/ 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | python -Wd setup.py test 3 | 4 | mypy: 5 | mypy django_fakery 6 | 7 | isort: 8 | isort --atomic . 9 | 10 | black: 11 | black . 12 | 13 | release: 14 | rm -rf dist 15 | rm -rf build 16 | rm -rf django_fakery.egg-info 17 | python setup.py sdist bdist_wheel 18 | twine upload dist/* 19 | -------------------------------------------------------------------------------- /docs/seeding.rst: -------------------------------------------------------------------------------- 1 | .. ref-seeding: 2 | 3 | Seeding the faker 4 | ----------------- 5 | 6 | .. code-block:: python 7 | 8 | from django.contrib.auth.models import User 9 | from django_fakery import factory 10 | 11 | factory.m(User, seed=1234, quantity=4)( 12 | username='regularuser_{}' 13 | ) 14 | -------------------------------------------------------------------------------- /django_fakery/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "4.1.3" 2 | 3 | from django.utils.functional import SimpleLazyObject 4 | from django.utils.module_loading import import_string 5 | 6 | from .blueprint import Blueprint 7 | from .lazy import Lazy 8 | from .utils import get_model as M 9 | 10 | factory = SimpleLazyObject(lambda: import_string("django_fakery.faker_factory.factory")) 11 | -------------------------------------------------------------------------------- /tests/test_custom_fields.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from django_fakery import factory 4 | 5 | from .models import CustomIntegerField, Inventory 6 | 7 | 8 | class CustomFieldsTest(TestCase): 9 | def test_custom_field(self): 10 | factory.field_types.add(CustomIntegerField, (lambda faker, field: 3, [], {})) 11 | inventory = factory.m(Inventory)() 12 | assert inventory.in_stock == 3 13 | -------------------------------------------------------------------------------- /django_fakery/types.py: -------------------------------------------------------------------------------- 1 | from typing import Any, AnyStr, Callable, Dict, List, Tuple, Type, TypeVar, Union 2 | 3 | from django.db import models 4 | 5 | T = TypeVar("T", bound=models.Model) 6 | Seed = Union[AnyStr, bytearray, int] 7 | FieldMap = Dict[str, Any] 8 | Lookup = Dict[str, Any] 9 | SaveHooks = List[Callable[[T], None]] 10 | LazySaveHooks = List[Callable[[models.Model], Any]] 11 | Built = Tuple[T, Dict[str, List[models.Model]]] 12 | LazyBuilt = Tuple[models.Model, Dict[str, List[models.Model]]] 13 | -------------------------------------------------------------------------------- /tests/blueprints.py: -------------------------------------------------------------------------------- 1 | from django_fakery import factory, shortcuts 2 | 3 | chef = factory.blueprint("tests.Chef", fields={"first_name": "Chef {}"}) 4 | 5 | 6 | pizza = factory.blueprint( 7 | "tests.Pizza", 8 | fields={ 9 | "chef": chef, 10 | "thickness": 1, 11 | "expiration": shortcuts.future_date("+7d"), 12 | "baked_on": shortcuts.past_datetime("-1h"), 13 | }, 14 | ) 15 | 16 | 17 | chef_short = factory.blueprint("tests.Chef").fields(first_name="Chef {}") 18 | 19 | pizza_short = factory.blueprint("tests.Pizza").fields(chef=chef_short, thickness=1) 20 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from django_fakery.utils import get_model, language_to_locale 4 | from tests.models import Chef 5 | 6 | 7 | class UtilsTest(TestCase): 8 | def test_language_to_locale(self): 9 | locale = language_to_locale("en") 10 | self.assertEqual(locale, "en") 11 | 12 | locale = language_to_locale("en-us") 13 | self.assertEqual(locale, "en_US") 14 | 15 | def test_model(self): 16 | Model = get_model(Chef) 17 | self.assertEqual(Chef, Model) 18 | 19 | Model = get_model("tests.Chef") 20 | self.assertEqual(Chef, Model) 21 | -------------------------------------------------------------------------------- /django_fakery/compat.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ImproperlyConfigured 2 | 3 | try: 4 | import psycopg2 5 | 6 | HAS_PSYCOPG2 = True 7 | 8 | try: 9 | from django.contrib.postgres.fields import DecimalRangeField 10 | except ImportError: 11 | from django.contrib.postgres.fields import FloatRangeField as DecimalRangeField 12 | except ImportError: 13 | psycopg2 = None 14 | HAS_PSYCOPG2 = False 15 | 16 | try: 17 | from django.contrib.gis.geos.libgeos import geos_version_tuple 18 | 19 | HAS_GEOS = geos_version_tuple() >= (3, 3, 0) 20 | except (ImportError, OSError, ImproperlyConfigured): 21 | HAS_GEOS = False 22 | -------------------------------------------------------------------------------- /docs/custom_fields.rst: -------------------------------------------------------------------------------- 1 | .. ref-custom_fields 2 | 3 | Custom fields 4 | ------------- 5 | 6 | You can add support for custom fields by adding your 7 | custom field class and a function in ``factory.field_types``: 8 | 9 | .. code-block:: python 10 | 11 | from django_fakery import factory 12 | 13 | from my_fields import CustomField 14 | 15 | def func(faker, field, count, *args, **kwargs): 16 | return 43 17 | 18 | 19 | factory.field_types.add( 20 | CustomField, (func, [], {}) 21 | ) 22 | 23 | 24 | .. code-block:: python 25 | 26 | 27 | As a shortcut, you can specified any Faker function by its name: 28 | 29 | .. code-block:: python 30 | 31 | from django_fakery import factory 32 | 33 | from my_fields import CustomField 34 | 35 | 36 | factory.field_types.add( 37 | CustomField, ("random_int", [], {"min": 0, "max": 60}) 38 | ) 39 | -------------------------------------------------------------------------------- /docs/hooks.rst: -------------------------------------------------------------------------------- 1 | .. ref-hooks: 2 | 3 | Pre-save and Post-save hooks 4 | ---------------------------- 5 | 6 | You can define functions to be called right before the instance is saved or right after: 7 | 8 | .. code-block:: python 9 | 10 | from django.contrib.auth.models import User 11 | from django_fakery import factory 12 | 13 | factory.m( 14 | User, 15 | pre_save=[ 16 | lambda i: i.set_password('password') 17 | ], 18 | )(username='username') 19 | 20 | Since settings a user's password is such a common case, we special-cased that scenario, so you can just pass it as a field: 21 | 22 | .. code-block:: python 23 | 24 | from django.contrib.auth.models import User 25 | from django_fakery import factory 26 | 27 | factory.m(User)( 28 | username='username', 29 | password='password', 30 | ) 31 | -------------------------------------------------------------------------------- /docs/lazies.rst: -------------------------------------------------------------------------------- 1 | .. ref-lazies: 2 | 3 | Lazies 4 | ------ 5 | 6 | You can refer to the created instance's own attributes or method by using ``Lazy`` objects. 7 | 8 | For example, if you'd like to create user with email as username, and have them always match, you could do: 9 | 10 | .. code-block:: python 11 | 12 | from django.contrib.auth.models import User 13 | from django_fakery import factory, Lazy 14 | 15 | factory.m(User)( 16 | username=Lazy('email'), 17 | ) 18 | 19 | 20 | If you want to assign a value returned by a method on the instance, you can pass the method's arguments to the ``Lazy`` object: 21 | 22 | .. code-block:: python 23 | 24 | from django_fakery import factory, Lazy 25 | from myapp.models import MyModel 26 | 27 | factory.make(MyModel) 28 | myfield=Lazy('model_method', 'argument', keyword='keyword value'), 29 | ) 30 | -------------------------------------------------------------------------------- /docs/get_or_make.rst: -------------------------------------------------------------------------------- 1 | .. ref-get_or_make 2 | 3 | Get or Make 4 | ----------- 5 | 6 | You can check for existence of a model instance and create it if necessary by using the ``g_m`` (short for ``get_or_make``) method: 7 | 8 | .. code-block:: python 9 | 10 | from myapp.models import MyModel 11 | 12 | myinstance, created = factory.g_m( 13 | MyModel, 14 | lookup={ 15 | 'myfield': 'myvalue', 16 | } 17 | )(myotherfield='somevalue') 18 | 19 | If you're looking for a more explicit API, you can use the ``.get_or_make()`` method: 20 | 21 | .. code-block:: python 22 | 23 | from myapp.models import MyModel 24 | 25 | myinstance, created = factory.get_or_make( 26 | MyModel, 27 | lookup={ 28 | 'myfield': 'myvalue', 29 | }, 30 | fields={ 31 | 'myotherfield': 'somevalue', 32 | }, 33 | ) 34 | -------------------------------------------------------------------------------- /docs/get_or_update.rst: -------------------------------------------------------------------------------- 1 | .. ref-get_or_update 2 | 3 | Get or Update 4 | ------------- 5 | 6 | You can check for existence of a model instance and update it by using the ``g_u`` (short for ``get_or_update``) method: 7 | 8 | .. code-block:: python 9 | 10 | from myapp.models import MyModel 11 | 12 | myinstance, created = factory.g_u( 13 | MyModel, 14 | lookup={ 15 | 'myfield': 'myvalue', 16 | } 17 | )(myotherfield='somevalue') 18 | 19 | If you're looking for a more explicit API, you can use the ``.get_or_update()`` method: 20 | 21 | .. code-block:: python 22 | 23 | from myapp.models import MyModel 24 | 25 | myinstance, created = factory.get_or_update( 26 | MyModel, 27 | lookup={ 28 | 'myfield': 'myvalue', 29 | }, 30 | fields={ 31 | 'myotherfield': 'somevalue', 32 | }, 33 | ) 34 | -------------------------------------------------------------------------------- /docs/nonpersistantinstances.rst: -------------------------------------------------------------------------------- 1 | .. ref-nonpersistantinstances: 2 | 3 | Non-persistant Instances 4 | ------------------------ 5 | 6 | You can build instances that are not saved to the database by using the ``.b()`` method, just like you'd use ``.m()``: 7 | 8 | .. code-block:: python 9 | 10 | from django_fakery import factory 11 | from myapp.models import MyModel 12 | 13 | factory.b(MyModel)( 14 | field='value', 15 | ) 16 | 17 | Note that since the instance is not saved to the database, ``.build()`` does not support ManyToManies or post-save hooks. 18 | 19 | If you're looking for a more explicit API, you can use the ``.build()`` method: 20 | 21 | .. code-block:: python 22 | 23 | from django_fakery import factory 24 | from myapp.models import MyModel 25 | 26 | factory.build( 27 | MyModel, 28 | fields={ 29 | 'field': 'value', 30 | } 31 | ) 32 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from django_fakery.compat import HAS_GEOS 5 | 6 | if hasattr(sys, "pypy_version_info"): 7 | from psycopg2cffi import compat 8 | 9 | compat.register() 10 | 11 | 12 | DISABLE_SERVER_SIDE_CURSORS = False 13 | if os.environ.get("PYTHON_VERSION", "").startswith("pypy"): 14 | DISABLE_SERVER_SIDE_CURSORS = True 15 | 16 | DEFAULT_AUTO_FIELD = "django.db.models.AutoField" 17 | 18 | DATABASES = { 19 | "default": { 20 | "ENGINE": "django.contrib.gis.db.backends.postgis" 21 | if HAS_GEOS 22 | else "django.db.backends.postgresql_psycopg2", 23 | "NAME": "django_fakery", 24 | "USER": "postgres", 25 | "PASSWORD": os.environ.get("POSTGRES_PASSWORD", None), 26 | "DISABLE_SERVER_SIDE_CURSORS": True, 27 | "HOST": os.environ.get("POSTGRES_HOST", None), 28 | } 29 | } 30 | USE_TZ = True 31 | 32 | TIMEZONE = "America/Chicago" 33 | 34 | INSTALLED_APPS = ["django.contrib.auth", "django.contrib.contenttypes", "tests"] 35 | 36 | SILENCED_SYSTEM_CHECKS = ["1_7.W001"] 37 | 38 | SECRET_KEY = "itsasecret" 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2015 Flavio Curella 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /tests/migrations/0002_auto_20210712_1723.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-07-12 22:23 2 | 3 | import django.db.models.deletion 4 | 5 | from django.db import migrations, models 6 | 7 | import tests.models 8 | 9 | 10 | class Migration(migrations.Migration): 11 | dependencies = [ 12 | ("tests", "0001_initial"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="Inventory", 18 | fields=[ 19 | ( 20 | "id", 21 | models.AutoField( 22 | auto_created=True, 23 | primary_key=True, 24 | serialize=False, 25 | verbose_name="ID", 26 | ), 27 | ), 28 | ("in_stock", tests.models.CustomIntegerField()), 29 | ( 30 | "pizza", 31 | models.ForeignKey( 32 | on_delete=django.db.models.deletion.CASCADE, to="tests.Pizza" 33 | ), 34 | ), 35 | ], 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /tests/test_gis.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from unittest import skipIf, skipUnless 4 | 5 | from django.test import TestCase 6 | 7 | from django_fakery import factory 8 | from django_fakery.compat import HAS_GEOS 9 | 10 | PYPY3 = hasattr(sys, "pypy_version_info") and sys.version_info.major >= 3 11 | 12 | 13 | @skipUnless(HAS_GEOS, "Requires GEOS") 14 | @skipIf(PYPY3, "Psycopg2cffi does not support Python3") 15 | class GisTest(TestCase): 16 | def test_gis_fields(self): 17 | from django.contrib.gis import gdal, geos 18 | 19 | gigis = factory.make("tests.Pizzeria") 20 | self.assertTrue(isinstance(gigis.hq, geos.Point)) 21 | self.assertTrue(isinstance(gigis.directions, geos.LineString)) 22 | self.assertTrue(isinstance(gigis.floor_plan, geos.Polygon)) 23 | self.assertTrue(isinstance(gigis.locations, geos.MultiPoint)) 24 | self.assertTrue(isinstance(gigis.routes, geos.MultiLineString)) 25 | self.assertTrue(isinstance(gigis.delivery_areas, geos.MultiPolygon)) 26 | self.assertTrue(isinstance(gigis.all_the_things, geos.GeometryCollection)) 27 | self.assertTrue(isinstance(gigis.rast, gdal.GDALRaster)) 28 | -------------------------------------------------------------------------------- /docs/blueprints.rst: -------------------------------------------------------------------------------- 1 | .. ref-blueprints: 2 | 3 | Blueprints 4 | ---------- 5 | 6 | Use a blueprint: 7 | 8 | .. code-block:: python 9 | 10 | from django.contrib.auth.models import User 11 | from django_fakery import factory 12 | 13 | user = factory.blueprint(User) 14 | 15 | user.make(quantity=10) 16 | 17 | Blueprints can refer other blueprints: 18 | 19 | .. code-block:: python 20 | 21 | from food.models import Pizza 22 | 23 | pizza = factory.blueprint(Pizza).fields( 24 | chef=user, 25 | ) 26 | ) 27 | 28 | You can also override the field values you previously specified: 29 | 30 | .. code-block:: python 31 | 32 | from food.models import Pizza 33 | 34 | pizza = factory.blueprint(Pizza).fields( 35 | chef=user, 36 | thickness=1 37 | ) 38 | ) 39 | 40 | pizza.m(quantity=10)(thickness=2) 41 | 42 | Or, if you'd rather use the explicit api: 43 | 44 | .. code-block:: python 45 | 46 | from food.models import Pizza 47 | 48 | pizza = factory.blueprint(Pizza).fields( 49 | chef=user, 50 | thickness=1 51 | ) 52 | ) 53 | 54 | thicker_pizza = pizza.fields(thickness=2) 55 | thicker_pizza.make(quantity=10) 56 | -------------------------------------------------------------------------------- /django_fakery/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List, Type, overload 2 | 3 | from django.apps import apps 4 | from django.db import models 5 | 6 | from six import string_types 7 | 8 | from .types import T 9 | 10 | 11 | def language_to_locale(language: str) -> str: 12 | """ 13 | Converts django's `LANGUAGE_CODE` settings to a proper locale code. 14 | """ 15 | tokens = language.split("-") 16 | if len(tokens) == 1: 17 | return tokens[0] 18 | return "%s_%s" % (tokens[0], tokens[1].upper()) 19 | 20 | 21 | @overload 22 | def get_model(model: str) -> models.Model: 23 | pass 24 | 25 | 26 | @overload 27 | def get_model(model: T) -> T: 28 | pass 29 | 30 | 31 | def get_model(model): 32 | if isinstance(model, string_types): 33 | model = apps.get_model(*model.split(".")) 34 | return model 35 | 36 | 37 | def get_model_fields(model: Type[models.Model]) -> List[models.fields.Field]: 38 | fields = list(model._meta._forward_fields_map.items()) 39 | for m2m in model._meta.many_to_many: 40 | fields.append((m2m.name, m2m)) 41 | return fields 42 | 43 | 44 | def set_related(instance: models.Model, attr: str, value: Any): 45 | field = getattr(instance, attr) 46 | field.set(value) 47 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. django-fakery documentation master file, created by 2 | sphinx-quickstart on Tue Oct 13 12:43:11 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to django-fakery's documentation! 7 | ========================================= 8 | 9 | An easy-to-use implementation of `Creation Methods`_ (aka Object Factory) for Django, backed by ``Faker``. 10 | 11 | .. _Creation Methods: http://xunitpatterns.com/Creation%20Method.html 12 | 13 | ``django_fakery`` will try to guess the field's value based on the field's name and type. 14 | 15 | Installation 16 | ------------ 17 | 18 | Install with:: 19 | 20 | $ pip install django-fakery 21 | 22 | QuickStart 23 | ---------- 24 | 25 | .. code-block:: python 26 | 27 | from django_fakery import factory 28 | from myapp.models import MyModel 29 | 30 | factory.make( 31 | MyModel, 32 | fields={ 33 | 'field': 'value', 34 | } 35 | ) 36 | 37 | Contents: 38 | 39 | .. toctree:: 40 | :maxdepth: 2 41 | 42 | quickstart 43 | shortcuts 44 | lazies 45 | custom_fields 46 | relationships 47 | hooks 48 | get_or_make 49 | get_or_update 50 | nonpersistantinstances 51 | blueprints 52 | seeding 53 | 54 | 55 | 56 | Indices and tables 57 | ================== 58 | 59 | * :ref:`genindex` 60 | * :ref:`modindex` 61 | * :ref:`search` 62 | 63 | -------------------------------------------------------------------------------- /tests/test_shortcuts.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.utils import timezone 3 | 4 | from faker import Faker 5 | 6 | from django_fakery import shortcuts 7 | 8 | fake = Faker() 9 | 10 | 11 | class ShortcutsTest(TestCase): 12 | def test_future_datetime(self): 13 | fn = shortcuts.future_datetime() 14 | date = fn(1, fake) 15 | self.assertTrue(date > timezone.now()) 16 | 17 | fn = shortcuts.future_datetime("+2d") 18 | date = fn(1, fake) 19 | self.assertTrue(date > timezone.now()) 20 | 21 | def test_future_date(self): 22 | fn = shortcuts.future_date() 23 | date = fn(1, fake) 24 | self.assertTrue(date > timezone.now().date()) 25 | 26 | fn = shortcuts.future_date("+2d") 27 | date = fn(1, fake) 28 | self.assertTrue(date > timezone.now().date()) 29 | 30 | def test_past_datetime(self): 31 | fn = shortcuts.past_datetime() 32 | date = fn(1, fake) 33 | self.assertTrue(date < timezone.now()) 34 | 35 | fn = shortcuts.past_datetime("-2d") 36 | date = fn(1, fake) 37 | self.assertTrue(date < timezone.now()) 38 | 39 | def test_past_date(self): 40 | fn = shortcuts.past_date() 41 | date = fn(1, fake) 42 | self.assertTrue(date < timezone.now().date()) 43 | 44 | fn = shortcuts.past_date("-2d") 45 | date = fn(1, fake) 46 | self.assertTrue(date < timezone.now().date()) 47 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | 4 | from setuptools import find_packages, setup 5 | 6 | here = os.path.abspath(os.path.dirname(__file__)) 7 | with io.open(os.path.join(here, "README.rst"), encoding="utf-8") as fp: 8 | README = fp.read() 9 | 10 | with io.open(os.path.join(here, "VERSION")) as version_file: 11 | VERSION = version_file.read().strip() 12 | 13 | 14 | setup( 15 | name="django-fakery", 16 | version=VERSION, 17 | url="https://github.com/fcurella/django-fakery/", 18 | author="Flavio Curella", 19 | author_email="flavio.curella@gmail.com", 20 | description="A model instances generator for Django", 21 | long_description=README, 22 | license="MIT", 23 | packages=find_packages(exclude=["docs", "tests", "tests.*"]), 24 | package_data={"django_fakery": ["py.typed"]}, 25 | platforms=["any"], 26 | classifiers=[ 27 | "Development Status :: 4 - Beta", 28 | "Framework :: Django", 29 | "License :: OSI Approved :: MIT License", 30 | "Operating System :: OS Independent", 31 | "Programming Language :: Python", 32 | "Programming Language :: Python :: 3", 33 | "Programming Language :: Python :: 3.8", 34 | "Programming Language :: Python :: 3.9", 35 | "Programming Language :: Python :: 3.10", 36 | "Programming Language :: Python :: 3.11", 37 | ], 38 | entry_points={"pytest11": ["django_fakery = django_fakery.plugin"]}, 39 | install_requires=["Faker>=10.0,<11.0", "Django>=3.2", "six>=1.10.1"], 40 | python_requires=">=3.8", 41 | test_suite="tests.runtests.runtests", 42 | ) 43 | -------------------------------------------------------------------------------- /tests/runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | import warnings 5 | 6 | from django.conf import settings 7 | 8 | from django_fakery.compat import HAS_GEOS 9 | 10 | warnings.simplefilter("error", RuntimeWarning) 11 | 12 | 13 | if hasattr(sys, "pypy_version_info"): 14 | from psycopg2cffi import compat 15 | 16 | compat.register() 17 | 18 | 19 | DISABLE_SERVER_SIDE_CURSORS = False 20 | if os.environ.get("PYTHON_VERSION", "").startswith("pypy"): 21 | DISABLE_SERVER_SIDE_CURSORS = True 22 | 23 | 24 | SETTINGS = { 25 | "DATABASES": { 26 | "default": { 27 | "ENGINE": "django.contrib.gis.db.backends.postgis" 28 | if HAS_GEOS 29 | else "django.db.backends.postgresql_psycopg2", 30 | "NAME": "django_fakery", 31 | "USER": "postgres", 32 | "PASSWORD": os.environ.get("POSTGRES_PASSWORD", None), 33 | "DISABLE_SERVER_SIDE_CURSORS": DISABLE_SERVER_SIDE_CURSORS, 34 | "HOST": os.environ.get("POSTGRES_HOST", None), 35 | } 36 | }, 37 | "USE_TZ": True, 38 | "TIMEZONE": "America/Chicago", 39 | "INSTALLED_APPS": ["django.contrib.auth", "django.contrib.contenttypes", "tests"], 40 | "SILENCED_SYSTEM_CHECKS": ["1_7.W001"], 41 | "DEFAULT_AUTO_FIELD": "django.db.models.AutoField", 42 | } 43 | 44 | 45 | def runtests(*test_args): 46 | import django.test.utils 47 | 48 | settings.configure(**SETTINGS) 49 | 50 | django.setup() 51 | 52 | runner_class = django.test.utils.get_runner(settings) 53 | test_runner = runner_class(verbosity=1, interactive=True, failfast=False) 54 | failures = test_runner.run_tests(["tests"]) 55 | sys.exit(failures) 56 | 57 | 58 | if __name__ == "__main__": 59 | runtests() 60 | -------------------------------------------------------------------------------- /docs/relationships.rst: -------------------------------------------------------------------------------- 1 | .. ref-relathionships: 2 | 3 | Relationship Fields 4 | ------------------- 5 | 6 | Foreign keys 7 | ============ 8 | 9 | Non-nullable ``ForeignKey`` s create related objects automatically. 10 | 11 | If you want to explicitly create a related object, you can pass it like any other value: 12 | 13 | .. code-block:: python 14 | 15 | from django.contrib.auth.models import User 16 | from django_fakery import factory 17 | from food.models import Pizza 18 | 19 | pizza = factory.m(Pizza)( 20 | chef=factory.m(User)(username='Gusteau'), 21 | ) 22 | 23 | 24 | If you'd rather not create related objects and reuse the same value for a foreign key, you can use the special value ``django_fakery.rels.SELECT``: 25 | 26 | .. code-block:: python 27 | 28 | from django_fakery import factory, rels 29 | from food.models import Pizza 30 | 31 | pizza = factory.m(Pizza, quantity=5)( 32 | chef=rels.SELECT, 33 | ) 34 | 35 | ``django-fakery`` will always use the first instance of the related model, creating one if necessary. 36 | 37 | 38 | ManyToManies 39 | ============ 40 | 41 | Because ``ManyToManyField`` s are implicitly nullable (ie: they're always allowed to have their ``.count()`` equal to ``0``), related objects on those fields are not automatically created for you. 42 | 43 | If you want to explicitly create a related objects, you can pass a list as the field's value: 44 | 45 | .. code-block:: python 46 | 47 | from food.models import Pizza, Topping 48 | 49 | pizza = factory.m(Pizza)( 50 | toppings=[ 51 | factory.m(Topping)(name='Anchovies') 52 | ], 53 | ) 54 | 55 | You can also pass a factory, to create multiple objects: 56 | 57 | .. code-block:: python 58 | 59 | from food.models import Pizza, Topping 60 | 61 | pizza = factory.m(Pizza)( 62 | toppings=factory.m(Topping, quantity=5), 63 | ) 64 | -------------------------------------------------------------------------------- /django_fakery/values.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from django.db import models 4 | 5 | from six import string_types 6 | 7 | from . import field_mappings 8 | 9 | 10 | class Evaluator(object): 11 | def __init__( 12 | self, 13 | faker, 14 | factory, 15 | iteration, 16 | mappings_types=field_mappings.mappings_types, 17 | mappings_names=field_mappings.mappings_names, 18 | ): 19 | self.faker = faker 20 | self.factory = factory 21 | self.iteration = iteration 22 | self.mappings_types = mappings_types 23 | self.mappings_names = mappings_names 24 | super(Evaluator, self).__init__() 25 | 26 | def evaluate(self, value): 27 | from .blueprint import Blueprint 28 | 29 | if isinstance(value, Blueprint): 30 | return value.make_one(iteration=self.iteration) 31 | if callable(value): 32 | if value.__name__ == "": 33 | return value(self.iteration, self.faker) 34 | else: 35 | return value() 36 | if isinstance(value, string_types): 37 | try: 38 | return value.format(self.iteration, self.faker) 39 | except KeyError: 40 | return value 41 | return value 42 | 43 | def evaluate_fake(self, resolver, field): 44 | if callable(resolver[0]): 45 | func = partial(resolver[0], self.faker, field) 46 | else: 47 | func = getattr(self.faker, resolver[0]) 48 | return func(*resolver[1], **resolver[2]) 49 | 50 | def fake_value(self, model, field): 51 | if field.blank and isinstance(field, field_mappings.STRING_FIELDS): 52 | return "" 53 | 54 | if isinstance(field, models.ForeignKey): 55 | return self.factory.make_one(field.related_model, iteration=self.iteration) 56 | 57 | if field.name in self.mappings_names: 58 | return self.evaluate_fake(self.mappings_names[field.name], field) 59 | 60 | for field_class, fake in self.mappings_types.items(): 61 | if isinstance(field, field_class): 62 | return self.evaluate_fake(fake, field) 63 | 64 | model_name = "%s.%s" % (model._meta.app_label, model._meta.model_name) 65 | raise ValueError( 66 | "Cant generate a value for model `%s` field `%s`" % (model_name, field.name) 67 | ) 68 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | .. ref-quickstart: 2 | 3 | Installation 4 | ------------ 5 | 6 | Install with:: 7 | 8 | $ pip install django-fakery 9 | 10 | QuickStart 11 | ---------- 12 | 13 | .. code-block:: python 14 | 15 | from django_fakery import factory 16 | from myapp.models import MyModel 17 | 18 | factory.m('app.Model')(field='value') 19 | 20 | If you're having issues with circular imports, you can also reference a model by using the ``M`` utility function: 21 | 22 | .. code-block:: python 23 | 24 | from django_fakery import factory, M 25 | 26 | factory.m(M("myapp.MyModel"))(field="value") 27 | 28 | 29 | If you really don't want to import things, you could also just reference a model by using the ``.`` syntax. This is not encouraged, as it will likely break type-hinting: 30 | 31 | .. code-block:: python 32 | 33 | from django_fakery import factory 34 | 35 | factory.m("myapp.MyModel")(field="value") 36 | 37 | 38 | If you use ``pytest``, you can use the ``fakery`` fixture (requires ``pytest`` and ``pytest-django``): 39 | 40 | .. code-block:: python 41 | 42 | import pytest 43 | from myapp.models import MyModel 44 | 45 | @pytest.mark.django_db 46 | def test_mymodel(fakery): 47 | fakery.m(MyModel)(field='value') 48 | 49 | If you'd rather, you can use a more wordy API: 50 | 51 | .. code-block:: python 52 | 53 | from django_fakery import factory 54 | from myapp.models import MyModel 55 | 56 | factory.make( 57 | 'app.Model', 58 | fields={ 59 | 'field': 'value', 60 | } 61 | ) 62 | 63 | We will use the short API throught the documentation. 64 | 65 | The value of a field can be any python object, a callable, or a lambda: 66 | 67 | .. code-block:: python 68 | 69 | from django.utils import timezone 70 | from django_fakery import factory 71 | from myapp.models import MyModel 72 | 73 | factory.m(MyModel)(created=timezone.now) 74 | 75 | When using a lambda, it will receive two arguments: ``n`` is the iteration number, and ``f`` is an instance of ``faker``: 76 | 77 | .. code-block:: python 78 | 79 | from django.contrib.auth.models import User 80 | 81 | user = factory.m(User)( 82 | username=lambda n, f: 'user_{}'.format(n), 83 | ) 84 | 85 | 86 | ``django-fakery`` includes some pre-built lambdas for common needs. See :doc:`shortcuts` for more info. 87 | 88 | You can create multiple objects by using the ``quantity`` parameter: 89 | 90 | .. code-block:: python 91 | 92 | from django_fakery import factory 93 | from myapp.models import MyModel 94 | 95 | factory.m(MyModel, quantity=4) 96 | 97 | For convenience, when the value of a field is a string, it will be interpolated with the iteration number: 98 | 99 | .. code-block:: python 100 | 101 | from django.contrib.auth.models import User 102 | 103 | user = factory.m(User, quantity=4)( 104 | username='user_{}', 105 | ) 106 | -------------------------------------------------------------------------------- /docs/shortcuts.rst: -------------------------------------------------------------------------------- 1 | .. ref-shortcuts: 2 | 3 | Shortcuts 4 | --------- 5 | 6 | ``django-fakery`` includes some shortcut functions to generate commonly needed values. 7 | 8 | 9 | ``future_datetime(end='+30d')`` 10 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 11 | 12 | Returns a ``datetime`` object in the future (that is, 1 second from now) up to the specified ``end``. ``end`` can be a string, anotther datetime, or a timedelta. If it's a string, it must start with `+`, followed by and integer and a unit, Eg: ``'+30d'``. Defaults to ``'+30d'`` 13 | 14 | Valid units are: 15 | 16 | * ``'years'``, ``'y'`` 17 | * ``'weeks'``, ``'w'`` 18 | * ``'days'``, ``'d'`` 19 | * ``'hours'``, ``'hours'`` 20 | * ``'minutes'``, ``'m'`` 21 | * ``'seconds'``, ``'s'`` 22 | 23 | Example:: 24 | 25 | from django_fakery import factory, shortcuts 26 | from myapp.models import MyModel 27 | 28 | factory.m(MyModel)(field=shortcuts.future_datetime('+1w')) 29 | 30 | 31 | ``future_date(end='+30d')`` 32 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 33 | 34 | Returns a ``date`` object in the future (that is, 1 day from now) up to the specified ``end``. ``end`` can be a string, another date, or a timedelta. If it's a string, it must start with `+`, followed by and integer and a unit, Eg: ``'+30d'``. Defaults to ``'+30d'`` 35 | 36 | Valid units are: 37 | 38 | * ``'years'``, ``'y'`` 39 | * ``'weeks'``, ``'w'`` 40 | * ``'days'``, ``'d'`` 41 | * ``'hours'``, ``'hours'`` 42 | * ``'minutes'``, ``'m'`` 43 | * ``'seconds'``, ``'s'`` 44 | 45 | Example:: 46 | 47 | from django_fakery import factory, shortcuts 48 | from myapp.models import MyModel 49 | 50 | factory.m(MyModel)(field=shortcuts.future_date('+1w')) 51 | 52 | 53 | ``past_datetime(start='-30d')`` 54 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 55 | 56 | Returns a ``datetime`` object in the past between 1 second ago and the specified ``start``. ``start`` can be a string, another datetime, or a timedelta. If it's a string, it must start with `-`, followed by and integer and a unit, Eg: ``'-30d'``. Defaults to ``'-30d'`` 57 | 58 | Valid units are: 59 | 60 | * ``'years'``, ``'y'`` 61 | * ``'weeks'``, ``'w'`` 62 | * ``'days'``, ``'d'`` 63 | * ``'hours'``, ``'h'`` 64 | * ``'minutes'``, ``'m'`` 65 | * ``'seconds'``, ``'s'`` 66 | 67 | Example:: 68 | 69 | from django_fakery import factory, shortcuts 70 | from myapp.models import MyModel 71 | 72 | factory.m(MyModel)(field=shortcuts.past_datetime('-1w')) 73 | 74 | 75 | ``past_date(start='-30d')`` 76 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 77 | 78 | Returns a ``date`` object in the past between 1 day ago and the specified ``start``. ``start`` can be a string, another date, or a timedelta. If it's a string, it must start with `-`, followed by and integer and a unit, Eg: ``'-30d'``. Defaults to ``'-30d'`` 79 | 80 | Valid units are: 81 | 82 | * ``'years'``, ``'y'`` 83 | * ``'weeks'``, ``'w'`` 84 | * ``'days'``, ``'d'`` 85 | * ``'hours'``, ``'h'`` 86 | * ``'minutes'``, ``'m'`` 87 | * ``'seconds'``, ``'s'`` 88 | 89 | Example:: 90 | 91 | from django_fakery import factory, shortcuts 92 | from myapp.models import MyModel 93 | 94 | factory.m(MyModel)(field=shortcuts.past_date('-1w')) 95 | -------------------------------------------------------------------------------- /django_fakery/shortcuts.py: -------------------------------------------------------------------------------- 1 | from datetime import date, datetime, timedelta, tzinfo 2 | from typing import Callable, Optional, Union 3 | 4 | from django.conf import settings 5 | from django.utils import timezone 6 | 7 | from faker import Faker 8 | 9 | ParsableDate = Union[str, int, datetime, timedelta] 10 | 11 | 12 | def get_timezone() -> Optional[tzinfo]: 13 | return timezone.get_current_timezone() if settings.USE_TZ else None 14 | 15 | 16 | def future_datetime(end: ParsableDate = "+30d") -> Callable[[int, Faker], datetime]: 17 | """ 18 | Returns a ``datetime`` object in the future (that is, 1 second from now) up 19 | to the specified ``end``. ``end`` can be a string, another datetime, or a 20 | timedelta. If it's a string, it must start with `+`, followed by and integer 21 | and a unit, Eg: ``'+30d'``. Defaults to `'+30d'` 22 | 23 | Valid units are: 24 | * ``'years'``, ``'y'`` 25 | * ``'weeks'``, ``'w'`` 26 | * ``'days'``, ``'d'`` 27 | * ``'hours'``, ``'hours'`` 28 | * ``'minutes'``, ``'m'`` 29 | * ``'seconds'``, ``'s'`` 30 | """ 31 | return lambda n, f: f.future_datetime(end_date=end, tzinfo=get_timezone()) 32 | 33 | 34 | def future_date(end: ParsableDate = "+30d") -> Callable[[int, Faker], date]: 35 | """ 36 | Returns a ``date`` object in the future (that is, 1 day from now) up to 37 | the specified ``end``. ``end`` can be a string, another date, or a 38 | timedelta. If it's a string, it must start with `+`, followed by and integer 39 | and a unit, Eg: ``'+30d'``. Defaults to `'+30d'` 40 | 41 | Valid units are: 42 | * ``'years'``, ``'y'`` 43 | * ``'weeks'``, ``'w'`` 44 | * ``'days'``, ``'d'`` 45 | * ``'hours'``, ``'hours'`` 46 | * ``'minutes'``, ``'m'`` 47 | * ``'seconds'``, ``'s'`` 48 | """ 49 | return lambda n, f: f.future_date(end_date=end, tzinfo=get_timezone()) 50 | 51 | 52 | def past_datetime(start: ParsableDate = "-30d") -> Callable[[int, Faker], datetime]: 53 | """ 54 | Returns a ``datetime`` object in the past between 1 second ago and the 55 | specified ``start``. ``start`` can be a string, another datetime, or a 56 | timedelta. If it's a string, it must start with `-`, followed by and integer 57 | and a unit, Eg: ``'-30d'``. Defaults to `'-30d'` 58 | 59 | Valid units are: 60 | * ``'years'``, ``'y'`` 61 | * ``'weeks'``, ``'w'`` 62 | * ``'days'``, ``'d'`` 63 | * ``'hours'``, ``'h'`` 64 | * ``'minutes'``, ``'m'`` 65 | * ``'seconds'``, ``'s'`` 66 | """ 67 | return lambda n, f: f.past_datetime(start_date=start, tzinfo=get_timezone()) 68 | 69 | 70 | def past_date(start: ParsableDate = "-30d") -> Callable[[int, Faker], date]: 71 | """ 72 | Returns a ``date`` object in the past between 1 day ago and the 73 | specified ``start``. ``start`` can be a string, another date, or a 74 | timedelta. If it's a string, it must start with `-`, followed by and integer 75 | and a unit, Eg: ``'-30d'``. Defaults to `'-30d'` 76 | 77 | Valid units are: 78 | * ``'years'``, ``'y'`` 79 | * ``'weeks'``, ``'w'`` 80 | * ``'days'``, ``'d'`` 81 | * ``'hours'``, ``'h'`` 82 | * ``'minutes'``, ``'m'`` 83 | * ``'seconds'``, ``'s'`` 84 | """ 85 | return lambda n, f: f.past_date(start_date=start, tzinfo=get_timezone()) 86 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at community@curella.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ["3.8", "3.9", "3.10", "3.11", 'pypy-3.9'] 20 | django: 21 | - "Django>=3.2,<3.3" 22 | - "Django>=4.0,<4.1" 23 | - "Django>=4.1,<4.2" 24 | services: 25 | # Label used to access the service container 26 | postgres: 27 | # Docker Hub image 28 | image: postgis/postgis 29 | # Provide the password for postgres 30 | env: 31 | POSTGRES_USER: postgres 32 | POSTGRES_PASSWORD: postgres 33 | POSTGRES_DB: django_fakery 34 | # Set health checks to wait until postgres has started 35 | options: >- 36 | --health-cmd pg_isready 37 | --health-interval 10s 38 | --health-timeout 5s 39 | --health-retries 5 40 | ports: 41 | # Maps tcp port 5432 on service container to the host 42 | - 5432:5432 43 | steps: 44 | - uses: actions/checkout@v3 45 | - name: Set up Python ${{ matrix.python-version }} 46 | uses: actions/setup-python@v4 47 | with: 48 | python-version: ${{ matrix.python-version }} 49 | - name: Install dependencies 50 | env: 51 | DJANGO: ${{ matrix.django }} 52 | PYTHON_VERSION: ${{ matrix.python-version }} 53 | run: | 54 | python -m pip install --upgrade pip 55 | pip install wheel 56 | sudo apt-add-repository ppa:ubuntugis/ubuntugis-unstable 57 | sudo apt-get update 58 | pip install -q $DJANGO 59 | python -m pip install psycopg2cffi coveralls 60 | if [[ "$PYTHON_VERSION" != pypy* ]]; then pip install "psycopg2-binary"; fi 61 | if [[ "$PYTHON_VERSION" != pypy* ]]; then sudo apt-get install gdal-bin libgdal-dev libgeos-dev; fi 62 | if [[ "$PYTHON_VERSION" != pypy* ]]; then pip install "numpy"; fi 63 | if [[ "$PYTHON_VERSION" != pypy* ]]; then pip install "GDAL==3.6.2"; fi 64 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 65 | - name: Test 66 | run: | 67 | coverage run --source django_fakery setup.py test 68 | env: 69 | DJANGO: ${{ matrix.django }} 70 | PYTHON_VERSION: ${{ matrix.python-version }} 71 | POSTGRES_HOST: "127.0.0.1" 72 | POSTGRES_PASSWORD: postgres 73 | - name: Upload coverage data to coveralls.io 74 | run: coveralls 75 | env: 76 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 77 | COVERALLS_FLAG_NAME: ${{ matrix.test-name }} 78 | COVERALLS_PARALLEL: true 79 | COVERALLS_SERVICE_NAME: github 80 | 81 | finish: 82 | needs: build 83 | runs-on: ubuntu-latest 84 | container: python:3-slim 85 | steps: 86 | - name: Finished 87 | run: | 88 | pip3 install --upgrade coveralls 89 | coveralls --finish 90 | env: 91 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 92 | COVERALLS_SERVICE_NAME: github 93 | -------------------------------------------------------------------------------- /tests/test_postgres.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from datetime import datetime 4 | from unittest import skipIf 5 | 6 | from django.test import TestCase 7 | 8 | from six import text_type 9 | 10 | from django_fakery import factory 11 | from django_fakery.compat import HAS_PSYCOPG2 12 | 13 | if HAS_PSYCOPG2: 14 | from psycopg2.extras import DateTimeTZRange, NumericRange 15 | 16 | PYPY3 = hasattr(sys, "pypy_version_info") and sys.version_info.major >= 3 17 | 18 | 19 | @skipIf(not HAS_PSYCOPG2, "Psycopg2 not installed") 20 | @skipIf(PYPY3, "Psycopg2cffi does not support Python3") 21 | class PostgresTest(TestCase): 22 | def test_postgres_fields(self): 23 | gigis_special = factory.make("tests.SpecialtyPizza") 24 | self.assertTrue(isinstance(gigis_special.toppings, list)) 25 | for topping in gigis_special.toppings: 26 | self.assertTrue(isinstance(topping, text_type)) 27 | 28 | self.assertTrue(isinstance(gigis_special.metadata, dict)) 29 | self.assertTrue(isinstance(gigis_special.price_range, NumericRange)) 30 | self.assertTrue(isinstance(gigis_special.price_range.lower, int)) 31 | self.assertTrue(isinstance(gigis_special.sales, NumericRange)) 32 | self.assertTrue(isinstance(gigis_special.sales.lower, int)) 33 | self.assertTrue(isinstance(gigis_special.available_on, DateTimeTZRange)) 34 | self.assertTrue(isinstance(gigis_special.available_on.lower, datetime)) 35 | self.assertNotEqual(gigis_special.available_on.lower.tzinfo, None) 36 | self.assertTrue(isinstance(gigis_special.nutritional_values, dict)) 37 | 38 | def test_json(self): 39 | gigis_special = factory.make( 40 | "tests.SpecialtyPizza", fields={"nutritional_values": {"Calories": 310}} 41 | ) 42 | self.assertEqual(gigis_special.nutritional_values, {"Calories": 310}) 43 | 44 | gigis_special, created = factory.update_or_make( 45 | "tests.SpecialtyPizza", 46 | lookup={"id": gigis_special.id}, 47 | fields={ 48 | "nutritional_values": {"Calories": {"Total": 310, "From Fat": 120}} 49 | }, 50 | ) 51 | self.assertFalse(created) 52 | self.assertEqual( 53 | gigis_special.nutritional_values, 54 | {"Calories": {"Total": 310, "From Fat": 120}}, 55 | ) 56 | 57 | summer_special, created = factory.update_or_make( 58 | "tests.SpecialtyPizza", 59 | lookup={"name": "Summer Special"}, 60 | fields={ 61 | "nutritional_values": {"Calories": {"Total": 310, "From Fat": 120}} 62 | }, 63 | ) 64 | 65 | self.assertTrue(created) 66 | self.assertEqual( 67 | summer_special.nutritional_values, 68 | {"Calories": {"Total": 310, "From Fat": 120}}, 69 | ) 70 | 71 | def test_array(self): 72 | gigis_special = factory.make( 73 | "tests.SpecialtyPizza", 74 | fields={"toppings": ["black olives", "green olives"]}, 75 | ) 76 | self.assertEqual(gigis_special.toppings, ["black olives", "green olives"]) 77 | 78 | gigis_special, created = factory.update_or_make( 79 | "tests.SpecialtyPizza", 80 | lookup={"id": gigis_special.id}, 81 | fields={"toppings": ["black olives", "green olives", "cremini mushrooms"]}, 82 | ) 83 | self.assertFalse(created) 84 | self.assertEqual( 85 | gigis_special.toppings, 86 | ["black olives", "green olives", "cremini mushrooms"], 87 | ) 88 | 89 | summer_special, created = factory.update_or_make( 90 | "tests.SpecialtyPizza", 91 | lookup={"name": "Summer Special"}, 92 | fields={"toppings": ["black olives", "green olives", "cremini mushrooms"]}, 93 | ) 94 | 95 | self.assertTrue(created) 96 | self.assertEqual( 97 | summer_special.toppings, 98 | ["black olives", "green olives", "cremini mushrooms"], 99 | ) 100 | -------------------------------------------------------------------------------- /tests/test_blueprints.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.utils import timezone 3 | from django.utils.timezone import get_current_timezone 4 | 5 | from tests.blueprints import pizza, pizza_short 6 | 7 | 8 | class BlueprintTest(TestCase): 9 | def test_blueprint(self): 10 | movie_night = pizza.make(quantity=10) 11 | self.assertEqual(len(movie_night), 10) 12 | self.assertEqual(movie_night[0].chef.first_name, "Chef 0") 13 | self.assertEqual(movie_night[1].chef.first_name, "Chef 1") 14 | 15 | now = timezone.now() 16 | baked_pizza = pizza.make() 17 | difference = now - baked_pizza.baked_on 18 | self.assertTrue(difference.total_seconds() <= 3600) 19 | 20 | def test_blueprint_build(self): 21 | movie_night = pizza.build(quantity=10) 22 | self.assertEqual(len(movie_night), 10) 23 | self.assertEqual(movie_night[0].chef.first_name, "Chef 0") 24 | self.assertEqual(movie_night[1].chef.first_name, "Chef 1") 25 | self.assertEqual(movie_night[0].thickness, 1) 26 | self.assertEqual(movie_night[1].thickness, 1) 27 | 28 | movie_night = pizza.build(quantity=10, make_fks=True) 29 | self.assertEqual(len(movie_night), 10) 30 | self.assertEqual(movie_night[0].chef.first_name, "Chef 0") 31 | self.assertEqual(movie_night[1].chef.first_name, "Chef 1") 32 | self.assertEqual(movie_night[0].thickness, 1) 33 | self.assertEqual(movie_night[1].thickness, 1) 34 | 35 | def test_blueprint_build_override(self): 36 | movie_night = pizza.build(quantity=10, fields={"thickness": 2}) 37 | self.assertEqual(len(movie_night), 10) 38 | self.assertEqual(movie_night[0].thickness, 2) 39 | self.assertEqual(movie_night[1].thickness, 2) 40 | 41 | def test_blueprint_fields_returns_copy_with_updated_fields(self): 42 | thick_pizzas = pizza.fields(thickness=3).build(quantity=3) 43 | self.assertEqual(thick_pizzas[0].thickness, 3, "thickness should be udpated") 44 | self.assertEqual( 45 | thick_pizzas[1].chef.first_name, 46 | "Chef 1", 47 | "chef should still be set from the previous blueprint", 48 | ) 49 | 50 | def test_blueprint_fields_make_with_fields(self): 51 | """check that blueprint.fields(...).make(fields=...) works as expected""" 52 | p = pizza.fields(thickness=3).make(fields={"thickness": 5}) 53 | self.assertEqual(p.thickness, 5) 54 | 55 | def test_blueprint_fields_make_one_with_fields(self): 56 | """check that blueprint.fields(...).make_one(fields=...) works as expected""" 57 | p = pizza.fields(thickness=3).make_one(fields={"thickness": 5}) 58 | self.assertEqual(p.thickness, 5) 59 | 60 | 61 | class BlueprintShortTest(TestCase): 62 | def test_blueprint(self): 63 | movie_night = pizza_short.m(quantity=10)() 64 | self.assertEqual(len(movie_night), 10) 65 | self.assertEqual(movie_night[0].chef.first_name, "Chef 0") 66 | self.assertEqual(movie_night[1].chef.first_name, "Chef 1") 67 | 68 | def test_blueprint_build(self): 69 | movie_night = pizza_short.b(quantity=10)() 70 | self.assertEqual(len(movie_night), 10) 71 | self.assertEqual(movie_night[0].chef.first_name, "Chef 0") 72 | self.assertEqual(movie_night[1].chef.first_name, "Chef 1") 73 | self.assertEqual(movie_night[0].thickness, 1) 74 | self.assertEqual(movie_night[1].thickness, 1) 75 | 76 | movie_night = pizza_short.b(quantity=10, make_fks=True)() 77 | self.assertEqual(len(movie_night), 10) 78 | self.assertEqual(movie_night[0].chef.first_name, "Chef 0") 79 | self.assertEqual(movie_night[1].chef.first_name, "Chef 1") 80 | self.assertEqual(movie_night[0].thickness, 1) 81 | self.assertEqual(movie_night[1].thickness, 1) 82 | 83 | def test_blueprint_build_override(self): 84 | movie_night = pizza.b(quantity=10)(thickness=2) 85 | self.assertEqual(len(movie_night), 10) 86 | self.assertEqual(movie_night[0].thickness, 2) 87 | self.assertEqual(movie_night[1].thickness, 2) 88 | 89 | movie_night = pizza_short.b(quantity=10)(thickness=2) 90 | self.assertEqual(len(movie_night), 10) 91 | self.assertEqual(movie_night[0].thickness, 2) 92 | self.assertEqual(movie_night[1].thickness, 2) 93 | -------------------------------------------------------------------------------- /tests/models.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from django import VERSION as django_version 4 | from django.contrib.postgres import fields as postgres_fields 5 | from django.db import models 6 | 7 | from django_fakery.compat import HAS_GEOS 8 | 9 | try: 10 | from django.db.models import JSONField 11 | except ImportError: 12 | from django.contrib.postgres.fields import JSONField 13 | 14 | 15 | class Chef(models.Model): 16 | slug = models.SlugField() 17 | first_name = models.CharField(max_length=60) 18 | last_name = models.CharField(max_length=60) 19 | uuid_id = models.UUIDField() 20 | email_address = models.EmailField() 21 | twitter_profile = models.URLField() 22 | 23 | def __str__(self): 24 | return "Chef {} {}".format(self.first_name, self.last_name) 25 | 26 | 27 | class Topping(models.Model): 28 | name = models.CharField(max_length=60) 29 | 30 | 31 | class Pizza(models.Model): 32 | THICKNESSES = ((0, "thin"), (1, "thick"), (2, "deep dish")) 33 | 34 | name = models.CharField(max_length=50) 35 | price = models.DecimalField(null=True, decimal_places=2, max_digits=4) 36 | gluten_free = models.BooleanField(default=False) 37 | vegan = models.BooleanField() 38 | description = models.TextField(blank=True) 39 | thickness = models.CharField(max_length=50, choices=THICKNESSES) 40 | baked_on = models.DateTimeField() 41 | expiration = models.DateField() 42 | rating = models.PositiveSmallIntegerField() 43 | 44 | chef = models.ForeignKey( 45 | Chef, on_delete=models.CASCADE, related_name="invented_pizzas" 46 | ) 47 | critic = models.ForeignKey( 48 | Chef, null=True, on_delete=models.CASCADE, related_name="reviewed_pizzas" 49 | ) 50 | toppings = models.ManyToManyField(Topping, related_name="pizzas") 51 | unique_comment = models.TextField() 52 | 53 | def get_price(self, tax): 54 | return (Decimal("7.99") + (Decimal("7.99") * Decimal(tax))).quantize( 55 | Decimal("0.01") 56 | ) 57 | 58 | 59 | if HAS_GEOS: 60 | from django.contrib.gis.db import models as geo_models 61 | 62 | if django_version < (1, 9, 0): 63 | 64 | class Pizzeria(geo_models.Model): 65 | hq = geo_models.PointField() 66 | directions = geo_models.LineStringField() 67 | floor_plan = geo_models.PolygonField() 68 | locations = geo_models.MultiPointField() 69 | routes = geo_models.MultiLineStringField() 70 | delivery_areas = geo_models.MultiPolygonField() 71 | all_the_things = geo_models.GeometryCollectionField() 72 | 73 | else: 74 | 75 | class Pizzeria(geo_models.Model): 76 | hq = geo_models.PointField() 77 | directions = geo_models.LineStringField() 78 | floor_plan = geo_models.PolygonField() 79 | locations = geo_models.MultiPointField() 80 | routes = geo_models.MultiLineStringField() 81 | delivery_areas = geo_models.MultiPolygonField() 82 | all_the_things = geo_models.GeometryCollectionField() 83 | rast = geo_models.RasterField() 84 | 85 | 86 | if django_version < (1, 9, 0): 87 | 88 | class SpecialtyPizza(models.Model): 89 | toppings = postgres_fields.ArrayField(models.CharField(max_length=20), size=4) 90 | metadata = postgres_fields.HStoreField() 91 | price_range = postgres_fields.IntegerRangeField() 92 | sales = postgres_fields.BigIntegerRangeField() 93 | available_on = postgres_fields.DateTimeRangeField() 94 | season = postgres_fields.DateRangeField() 95 | 96 | else: 97 | 98 | class SpecialtyPizza(models.Model): 99 | name = models.CharField(max_length=50) 100 | toppings = postgres_fields.ArrayField(models.CharField(max_length=20), size=4) 101 | metadata = postgres_fields.HStoreField() 102 | price_range = postgres_fields.IntegerRangeField() 103 | sales = postgres_fields.BigIntegerRangeField() 104 | available_on = postgres_fields.DateTimeRangeField() 105 | season = postgres_fields.DateRangeField() 106 | nutritional_values = JSONField() 107 | 108 | 109 | class CustomIntegerField(models.IntegerField): 110 | def db_type(self, connection): 111 | return "integer" 112 | 113 | def rel_db_type(self, connection): 114 | return "integer" 115 | 116 | 117 | class Inventory(models.Model): 118 | pizza = models.ForeignKey(Pizza, on_delete=models.CASCADE) 119 | in_stock = CustomIntegerField() 120 | -------------------------------------------------------------------------------- /django_fakery/fakes.py: -------------------------------------------------------------------------------- 1 | from django.utils import text, timezone 2 | 3 | from faker.generator import random 4 | 5 | from .compat import HAS_GEOS, HAS_PSYCOPG2 6 | 7 | 8 | def comma_sep_integers(faker, field, *args, **kwargs): 9 | return ",".join([faker.random_int() for _ in range(10)]) 10 | 11 | 12 | def decimal(faker, field, *args, **kwargs): 13 | right_digits = field.decimal_places 14 | left_digits = field.max_digits - right_digits 15 | return faker.pydecimal( 16 | left_digits=left_digits, right_digits=right_digits, positive=True 17 | ) 18 | 19 | 20 | def slug(faker, field, count, *args, **kwargs): 21 | return text.slugify("-".join(faker.words(nb=count)))[: field.max_length] 22 | 23 | 24 | def random_dict(faker, field, *args, **kwargs): 25 | return faker.pydict( 26 | nb_elements=10, 27 | variable_nb_elements=True, 28 | value_types=(int,), 29 | allowed_types=(str,), 30 | ) 31 | 32 | 33 | if HAS_GEOS: 34 | from django.contrib.gis import gdal, geos 35 | 36 | def point(faker, field, srid): 37 | lat = random.uniform(-180.0, 180.0) 38 | lng = random.uniform(-90, 90) 39 | 40 | if field.dim == 2: 41 | return geos.Point(lat, lng, srid=srid) 42 | else: 43 | alt = random.uniform(-4000.0, 9000.0) 44 | return geos.Point(lat, lng, alt, srid=srid) 45 | 46 | def linestring(faker, field, srid, *args, **kwargs): 47 | point_count = faker.random_int(min=2, max=10) 48 | 49 | points = [point(faker, field, srid) for _ in range(point_count)] 50 | return geos.LineString(*points) 51 | 52 | def linearring(faker, field, srid, *args, **kwargs): 53 | point_0 = point(faker, field, srid) 54 | point_1 = point(faker, field, srid) 55 | point_2 = point(faker, field, srid) 56 | point_3 = geos.Point(point_0.x, point_0.y) 57 | points = [point_0, point_1, point_2, point_3] 58 | return geos.LinearRing(*points) 59 | 60 | def polygon(faker, field, srid, *args, **kwargs): 61 | ring = linearring(faker, field, srid) 62 | return geos.Polygon(ring) 63 | 64 | def collection(faker, field, srid, element_func, geometry_class, *args, **kwargs): 65 | element_count = faker.random_int(min=1, max=10) 66 | elements = [element_func(faker, field, srid) for _ in range(element_count)] 67 | 68 | return geometry_class(*elements) 69 | 70 | def multipoint(faker, field, srid, *args, **kwargs): 71 | return collection(faker, field, srid, point, geos.MultiPoint) 72 | 73 | def multilinestring(faker, field, srid, *args, **kwargs): 74 | return collection(faker, field, srid, linestring, geos.MultiLineString) 75 | 76 | def multipolygon(faker, field, srid, *args, **kwargs): 77 | return collection(faker, field, srid, polygon, geos.MultiPolygon) 78 | 79 | def geometrycollection(faker, field, srid, *args, **kwargs): 80 | single_point = point(faker, field, srid) 81 | points = collection(faker, field, srid, point, geos.MultiPoint) 82 | geometries = [single_point] + points 83 | return geos.GeometryCollection(*geometries) 84 | 85 | def gdal_raster(faker, field, srid, *args, **kwargs): 86 | scale = faker.pyfloat(positive=True) 87 | return gdal.GDALRaster( 88 | { 89 | "width": faker.random_int(min=1, max=1024), 90 | "height": faker.random_int(min=1, max=1024), 91 | "name": faker.word(), 92 | "srid": srid, 93 | "scale": [scale, -scale], 94 | "bands": [{"data": range(faker.random_int(min=1, max=1024))}], 95 | } 96 | ) 97 | 98 | 99 | if HAS_PSYCOPG2: 100 | from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange 101 | 102 | def array(faker, field, *args, **kwargs): 103 | from .field_mappings import mappings_types 104 | from .values import Evaluator 105 | 106 | if field.size: 107 | size = field.size 108 | else: 109 | size = 10 110 | element_count = faker.random_int(min=1, max=size) 111 | 112 | evaluator = Evaluator(faker=faker, factory=None, iteration=None) 113 | 114 | fake = mappings_types[field.base_field.__class__] 115 | return [evaluator.evaluate_fake(fake, field) for _ in range(element_count)] 116 | 117 | def integerrange(faker, field, min, max, *args, **kwargs): 118 | lower = faker.random_int(min=min, max=max - 1) 119 | upper = faker.random_int(min=lower, max=max) 120 | return NumericRange(lower, upper) 121 | 122 | def floatrange(faker, field, *args, **kwargs): 123 | lower = random.uniform(-2147483647, 2147483646) 124 | upper = random.uniform(lower, 2147483647) 125 | return NumericRange(lower, upper) 126 | 127 | def datetimerange(faker, field, *args, **kwargs): 128 | lower = timezone.make_aware(faker.date_time()) 129 | upper = timezone.make_aware(faker.date_time_between_dates(datetime_start=lower)) 130 | return DateTimeTZRange(lower, upper) 131 | 132 | def daterange(faker, field, *args, **kwargs): 133 | lower = faker.date_time().date() 134 | upper = faker.date_time_between_dates(datetime_start=lower).date() 135 | return DateRange(lower, upper) 136 | -------------------------------------------------------------------------------- /django_fakery/blueprint.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | from typing import Any, Callable, Generic, List 3 | from typing import Optional as Opt 4 | from typing import overload 5 | 6 | from .types import Built, FieldMap, SaveHooks, Seed, T 7 | 8 | 9 | class Blueprint(Generic[T]): 10 | def __init__( 11 | self, 12 | model: T, 13 | fields: Opt[FieldMap] = None, 14 | pre_save: Opt[SaveHooks] = None, 15 | post_save: Opt[SaveHooks] = None, 16 | seed: Opt[Seed] = None, 17 | ): 18 | from .faker_factory import factory 19 | 20 | self.factory = factory 21 | 22 | self._model = model 23 | self._fields = fields or {} 24 | self.seed = seed 25 | self.pre_save = pre_save 26 | self.post_save = post_save 27 | 28 | self.pk = -1 29 | 30 | def fields(self, **kwargs) -> "Blueprint": 31 | return Blueprint( 32 | model=self._model, 33 | fields=dict(self._fields, **kwargs), 34 | pre_save=self.pre_save, 35 | post_save=self.post_save, 36 | seed=self.seed, 37 | ) 38 | 39 | def make_one( 40 | self, 41 | fields: Opt[FieldMap] = None, 42 | pre_save: Opt[SaveHooks] = None, 43 | post_save: Opt[SaveHooks] = None, 44 | seed: Opt[Seed] = None, 45 | iteration: Opt[int] = None, 46 | ) -> T: 47 | _fields = self._fields.copy() 48 | if fields: 49 | _fields.update(fields) 50 | if seed is None: 51 | seed = self.seed 52 | 53 | if pre_save is None: 54 | pre_save = self.pre_save 55 | 56 | if post_save is None: 57 | post_save = self.post_save 58 | 59 | return self.factory.make_one( 60 | self._model, _fields, pre_save, post_save, seed, iteration 61 | ) 62 | 63 | @overload 64 | def make( 65 | self, 66 | fields: Opt[FieldMap], 67 | pre_save: Opt[SaveHooks], 68 | post_save: Opt[SaveHooks], 69 | seed: Opt[Seed], 70 | quantity: None, 71 | ) -> T: # pragma: no cover 72 | pass 73 | 74 | @overload 75 | def make( 76 | self, 77 | fields: Opt[FieldMap], 78 | pre_save: Opt[SaveHooks], 79 | post_save: Opt[SaveHooks], 80 | seed: Opt[Seed], 81 | quantity: int, 82 | ) -> List[T]: # pragma: no cover 83 | pass 84 | 85 | def make( 86 | self, fields=None, pre_save=None, post_save=None, seed=None, quantity=None 87 | ): 88 | _fields = self._fields.copy() 89 | if fields: 90 | _fields.update(fields) 91 | if seed is None: 92 | seed = self.seed 93 | 94 | if pre_save is None: 95 | pre_save = self.pre_save 96 | 97 | if post_save is None: 98 | post_save = self.post_save 99 | 100 | return self.factory.make( 101 | self._model, _fields, pre_save, post_save, seed, quantity 102 | ) 103 | 104 | @overload 105 | def build( 106 | self, 107 | fields: Opt[FieldMap], 108 | pre_save: Opt[SaveHooks], 109 | seed: Opt[Seed], 110 | quantity: None, 111 | make_fks: bool, 112 | ) -> Built: # pragma: no cover 113 | pass 114 | 115 | @overload 116 | def build( 117 | self, 118 | fields: Opt[FieldMap], 119 | pre_save: Opt[SaveHooks], 120 | seed: Opt[Seed], 121 | quantity: int, 122 | make_fks: bool, 123 | ) -> List[Built]: # pragma: no cover 124 | pass 125 | 126 | def build( 127 | self, fields=None, pre_save=None, seed=None, quantity=None, make_fks=False 128 | ): 129 | _fields = self._fields.copy() 130 | if fields: 131 | _fields.update(fields) 132 | if seed is None: 133 | seed = self.seed 134 | 135 | if pre_save is None: 136 | pre_save = self.pre_save 137 | 138 | return self.factory.build( 139 | self._model, _fields, pre_save, seed, quantity, make_fks 140 | ) 141 | 142 | @overload 143 | def m( 144 | self, 145 | pre_save: Opt[SaveHooks], 146 | post_save: Opt[SaveHooks], 147 | seed: Opt[Seed], 148 | quantity: None, 149 | ) -> Callable[..., T]: # pragma: no cover 150 | pass 151 | 152 | @overload 153 | def m( 154 | self, 155 | pre_save: Opt[SaveHooks], 156 | post_save: Opt[SaveHooks], 157 | seed: Opt[Seed], 158 | quantity: int, 159 | ) -> Callable[..., List[T]]: # pragma: no cover 160 | pass 161 | 162 | def m(self, pre_save=None, post_save=None, seed=None, quantity=None): 163 | make = partial( 164 | self.make, 165 | pre_save=pre_save, 166 | post_save=post_save, 167 | seed=seed, 168 | quantity=quantity, 169 | ) 170 | 171 | def fn(**kwargs): 172 | return make(fields=kwargs) 173 | 174 | return fn 175 | 176 | @overload 177 | def b( 178 | self, pre_save: Opt[SaveHooks], seed: Opt[Seed], quantity: None, make_fks: bool 179 | ) -> Callable[..., Built]: # pragma: no cover 180 | pass 181 | 182 | @overload 183 | def b( 184 | self, pre_save: Opt[SaveHooks], seed: Opt[Seed], quantity: int, make_fks: bool 185 | ) -> Callable[..., List[Built]]: # pragma: no cover 186 | pass 187 | 188 | def b(self, pre_save=None, seed=None, quantity=None, make_fks=False): 189 | build = partial( 190 | self.build, 191 | pre_save=pre_save, 192 | seed=seed, 193 | quantity=quantity, 194 | make_fks=make_fks, 195 | ) 196 | 197 | def fn(**kwargs): 198 | return build(fields=kwargs) 199 | 200 | return fn 201 | -------------------------------------------------------------------------------- /tests/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | import django.contrib.postgres.fields 5 | import django.contrib.postgres.fields.hstore 6 | 7 | from django.contrib.postgres.operations import CreateExtension, HStoreExtension 8 | from django.db import migrations, models 9 | 10 | try: 11 | from django.db.models import JSONField 12 | except ImportError: 13 | from django.contrib.postgres.fields import JSONField 14 | 15 | try: 16 | from django.contrib.gis.geos.libgeos import geos_version_tuple 17 | 18 | HAS_GEOS = geos_version_tuple() >= (3, 3, 0) 19 | except (ImportError, OSError): 20 | HAS_GEOS = False 21 | 22 | if HAS_GEOS: 23 | import django.contrib.gis.db.models.fields 24 | 25 | 26 | class Migration(migrations.Migration): 27 | dependencies = [] 28 | 29 | operations = [HStoreExtension()] 30 | operations += [ 31 | migrations.CreateModel( 32 | name="Chef", 33 | fields=[ 34 | ( 35 | "id", 36 | models.AutoField( 37 | verbose_name="ID", 38 | serialize=False, 39 | auto_created=True, 40 | primary_key=True, 41 | ), 42 | ), 43 | ("slug", models.SlugField()), 44 | ("first_name", models.CharField(max_length=60)), 45 | ("last_name", models.CharField(max_length=60)), 46 | ("uuid_id", models.UUIDField()), 47 | ("email_address", models.EmailField(max_length=60)), 48 | ("twitter_profile", models.URLField(max_length=60)), 49 | ], 50 | ), 51 | migrations.CreateModel( 52 | name="Topping", 53 | fields=[ 54 | ( 55 | "id", 56 | models.AutoField( 57 | verbose_name="ID", 58 | serialize=False, 59 | auto_created=True, 60 | primary_key=True, 61 | ), 62 | ), 63 | ("name", models.CharField(max_length=60)), 64 | ], 65 | ), 66 | migrations.CreateModel( 67 | name="Pizza", 68 | fields=[ 69 | ( 70 | "id", 71 | models.AutoField( 72 | verbose_name="ID", 73 | serialize=False, 74 | auto_created=True, 75 | primary_key=True, 76 | ), 77 | ), 78 | ("name", models.CharField(max_length=50)), 79 | ( 80 | "price", 81 | models.DecimalField(null=True, max_digits=4, decimal_places=2), 82 | ), 83 | ("gluten_free", models.BooleanField(default=False)), 84 | ("vegan", models.BooleanField()), 85 | ("description", models.TextField(blank=True)), 86 | ( 87 | "thickness", 88 | models.CharField( 89 | max_length=50, 90 | choices=[(0, b"thin"), (1, b"thick"), (2, b"deep dish")], 91 | ), 92 | ), 93 | ("baked_on", models.DateTimeField()), 94 | ("expiration", models.DateField()), 95 | ( 96 | "chef", 97 | models.ForeignKey( 98 | to="tests.Chef", 99 | on_delete=models.CASCADE, 100 | related_name="invented_pizzas", 101 | ), 102 | ), 103 | ( 104 | "critic", 105 | models.ForeignKey( 106 | to="tests.Chef", 107 | null=True, 108 | on_delete=models.CASCADE, 109 | related_name="reviewed_pizzas", 110 | ), 111 | ), 112 | ("toppings", models.ManyToManyField(to="tests.Topping")), 113 | ("rating", models.PositiveSmallIntegerField()), 114 | ("unique_comment", models.TextField()), 115 | ], 116 | ), 117 | ] 118 | 119 | if HAS_GEOS: 120 | operations += [CreateExtension("postgis_raster")] 121 | 122 | pizzeria_fields = [ 123 | ( 124 | "id", 125 | models.AutoField( 126 | verbose_name="ID", 127 | serialize=False, 128 | auto_created=True, 129 | primary_key=True, 130 | ), 131 | ), 132 | ("hq", django.contrib.gis.db.models.fields.PointField(srid=4326)), 133 | ( 134 | "directions", 135 | django.contrib.gis.db.models.fields.LineStringField(srid=4326), 136 | ), 137 | ("floor_plan", django.contrib.gis.db.models.fields.PolygonField(srid=4326)), 138 | ( 139 | "locations", 140 | django.contrib.gis.db.models.fields.MultiPointField(srid=4326), 141 | ), 142 | ( 143 | "routes", 144 | django.contrib.gis.db.models.fields.MultiLineStringField(srid=4326), 145 | ), 146 | ( 147 | "delivery_areas", 148 | django.contrib.gis.db.models.fields.MultiPolygonField(srid=4326), 149 | ), 150 | ( 151 | "all_the_things", 152 | django.contrib.gis.db.models.fields.GeometryCollectionField(srid=4326), 153 | ), 154 | ("rast", django.contrib.gis.db.models.fields.RasterField(srid=4326)), 155 | ] 156 | operations += [migrations.CreateModel(name="Pizzeria", fields=pizzeria_fields)] 157 | 158 | specialtypizza_fields = [ 159 | ( 160 | "id", 161 | models.AutoField( 162 | verbose_name="ID", serialize=False, auto_created=True, primary_key=True 163 | ), 164 | ), 165 | ("name", models.CharField(max_length=50)), 166 | ( 167 | "toppings", 168 | django.contrib.postgres.fields.ArrayField( 169 | base_field=models.CharField(max_length=20), size=4 170 | ), 171 | ), 172 | ("metadata", django.contrib.postgres.fields.hstore.HStoreField()), 173 | ("price_range", django.contrib.postgres.fields.IntegerRangeField()), 174 | ("sales", django.contrib.postgres.fields.BigIntegerRangeField()), 175 | ("available_on", django.contrib.postgres.fields.DateTimeRangeField()), 176 | ("season", django.contrib.postgres.fields.DateRangeField()), 177 | ("nutritional_values", JSONField()), 178 | ] 179 | 180 | operations += [ 181 | migrations.CreateModel(name="SpecialtyPizza", fields=specialtypizza_fields) 182 | ] 183 | -------------------------------------------------------------------------------- /django_fakery/field_mappings.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from collections import OrderedDict 4 | from typing import Any, Tuple 5 | 6 | from django.conf import settings 7 | from django.db import models 8 | from django.utils import timezone 9 | 10 | from . import fakes 11 | from .compat import HAS_GEOS, HAS_PSYCOPG2 12 | 13 | 14 | class PrependOrderedDict(OrderedDict): 15 | "Store items in the order the keys were last added" 16 | 17 | def add(self, key, value): 18 | super().__setitem__(key, value) 19 | self.move_to_end(key, last=False) 20 | 21 | 22 | STRING_FIELDS: Tuple[Any, ...] = ( 23 | models.CharField, 24 | models.TextField, 25 | ) 26 | 27 | if HAS_PSYCOPG2: 28 | from django.contrib.postgres import fields as pg_fields 29 | 30 | STRING_FIELDS += (pg_fields.CICharField, pg_fields.CITextField) 31 | """ 32 | This module maps fields to functions generating values. 33 | 34 | It first tries by looking at the field's class, then falls back to some 35 | special-cased names. 36 | 37 | Values are 3-tuples composed of ``(, , )``. 38 | 39 | When ```` is a string, it's assumed to be a faker provider. Whenever 40 | ``faker`` doesn't provide a suitable provider, we ship our own function. They 41 | are defined in ``django_fakery.fakes``. 42 | """ 43 | 44 | 45 | TZINFO = timezone.get_current_timezone() if settings.USE_TZ else None 46 | 47 | 48 | mappings_types = PrependOrderedDict( 49 | [ 50 | ( 51 | models.BigIntegerField, 52 | ("random_int", (), {"min": -sys.maxsize, "max": sys.maxsize}), 53 | ), 54 | (models.BinaryField, ("binary", (), {"length": 1024})), 55 | (models.BooleanField, ("pybool", (), {})), 56 | (models.DateTimeField, ("date_time", (), {"tzinfo": TZINFO})), 57 | # ``DateField`` must come after ``DateTimeField`` because it's its superclass 58 | (models.DateField, (lambda faker, field: faker.date_time().date(), (), {})), 59 | (models.DecimalField, (fakes.decimal, (), {})), 60 | (models.EmailField, ("email", (), {})), 61 | (models.FileField, ("file_name", (), {})), 62 | (models.FilePathField, ("file_name", (), {})), 63 | (models.FloatField, ("pyfloat", (), {})), 64 | (models.ImageField, ("file_name", (), {"extension": "jpg"})), 65 | (models.IntegerField, ("pyint", (), {})), 66 | (models.IPAddressField, ("ipv4", (), {})), 67 | (models.GenericIPAddressField, ("ipv4", (), {})), 68 | (models.PositiveIntegerField, ("random_int", (), {"max": 2147483647})), 69 | (models.PositiveSmallIntegerField, ("random_int", (), {"max": 32767})), 70 | (models.SlugField, (fakes.slug, (), {"count": 3})), 71 | (models.SmallIntegerField, ("random_int", (), {"min": -32768, "max": 32767})), 72 | ( 73 | models.TextField, 74 | ( 75 | lambda faker, field: field.unique 76 | and faker.pystr(max_chars=2700) 77 | or faker.paragraph(), 78 | (), 79 | {}, 80 | ), 81 | ), 82 | ( 83 | models.TimeField, 84 | (lambda faker, field: faker.date_time(tzinfo=TZINFO).time(), (), {}), 85 | ), 86 | (models.URLField, ("url", (), {})), 87 | ( 88 | models.CharField, 89 | ( 90 | lambda faker, field: field.unique 91 | and faker.pystr(max_chars=field.max_length) 92 | or faker.word()[: field.max_length], 93 | (), 94 | {}, 95 | ), 96 | ), 97 | (models.DurationField, ("time_delta", (), {})), 98 | (models.UUIDField, ("uuid4", (), {})), 99 | ] 100 | ) 101 | 102 | try: 103 | from django.db.models import JSONField 104 | 105 | mappings_types[JSONField] = (fakes.random_dict, (), {}) 106 | except ImportError: 107 | pass 108 | 109 | 110 | if HAS_GEOS: 111 | from django.contrib.gis.db import models as geo_models 112 | 113 | mappings_types.update( 114 | { 115 | geo_models.PointField: (fakes.point, (), {"srid": 4326}), 116 | geo_models.LineStringField: (fakes.linestring, (), {"srid": 4326}), 117 | geo_models.PolygonField: (fakes.polygon, (), {"srid": 4326}), 118 | geo_models.MultiPointField: (fakes.multipoint, (), {"srid": 4326}), 119 | geo_models.MultiLineStringField: ( 120 | fakes.multilinestring, 121 | (), 122 | {"srid": 4326}, 123 | ), 124 | geo_models.MultiPolygonField: (fakes.multipolygon, (), {"srid": 4326}), 125 | geo_models.GeometryCollectionField: ( 126 | fakes.geometrycollection, 127 | (), 128 | {"srid": 4326}, 129 | ), 130 | geo_models.RasterField: (fakes.gdal_raster, (), {"srid": 4326}), 131 | } 132 | ) 133 | 134 | 135 | if HAS_PSYCOPG2: 136 | from .compat import DecimalRangeField 137 | 138 | mappings_types.update( 139 | { 140 | pg_fields.CICharField: mappings_types[models.CharField], 141 | pg_fields.CIEmailField: mappings_types[models.EmailField], 142 | pg_fields.CITextField: mappings_types[models.TextField], 143 | pg_fields.ArrayField: (fakes.array, (), {}), 144 | pg_fields.HStoreField: ( 145 | "pydict", 146 | (), 147 | { 148 | "nb_elements": 10, 149 | "variable_nb_elements": True, 150 | "value_types": (str,), 151 | }, 152 | ), 153 | pg_fields.IntegerRangeField: ( 154 | fakes.integerrange, 155 | (), 156 | {"min": -2147483647, "max": 2147483647}, 157 | ), 158 | pg_fields.BigIntegerRangeField: ( 159 | fakes.integerrange, 160 | (), 161 | {"min": -sys.maxsize, "max": sys.maxsize}, 162 | ), 163 | DecimalRangeField: (fakes.floatrange, (), {}), 164 | pg_fields.DateTimeRangeField: (fakes.datetimerange, (), {}), 165 | pg_fields.DateRangeField: (fakes.daterange, (), {}), 166 | } 167 | ) 168 | try: 169 | from django.db.models import JSONField 170 | except ImportError: 171 | from django.contrib.postgres.fields import JSONField 172 | 173 | mappings_types[JSONField] = (fakes.random_dict, (), {}) 174 | 175 | 176 | mappings_names = { 177 | "name": ( 178 | lambda faker, field: field.unique 179 | and faker.pystr(max_chars=field.max_length or 2700) 180 | or faker.word()[: field.max_length], 181 | (), 182 | {}, 183 | ), # `name` is too generic to assume it's a person 184 | "slug": (fakes.slug, (), {"count": 3}), 185 | "first_name": ("first_name", (), {}), 186 | "last_name": ("last_name", (), {}), 187 | "full_name": ("full_name", (), {}), 188 | "email": ("email", (), {}), 189 | "created": ( 190 | "date_time_between", 191 | (), 192 | {"start_date": "-30d", "end_date": "+30d", "tzinfo": TZINFO}, 193 | ), 194 | "created_at": ( 195 | "date_time_between", 196 | (), 197 | {"start_date": "-30d", "end_date": "+30d", "tzinfo": TZINFO}, 198 | ), 199 | "updated": ( 200 | "date_time_between", 201 | (), 202 | {"start_date": "-30d", "end_date": "+30d", "tzinfo": TZINFO}, 203 | ), 204 | "updated_at": ( 205 | "date_time_between", 206 | (), 207 | {"start_date": "-30d", "end_date": "+30d", "tzinfo": TZINFO}, 208 | ), 209 | } 210 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-fakery.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-fakery.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-fakery" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-fakery" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-fakery.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-fakery.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-fakery documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Oct 13 12:43:11 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import os 16 | import shlex 17 | import sys 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = ["sphinx.ext.todo"] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ["_templates"] 36 | 37 | # The suffix(es) of source filenames. 38 | # You can specify multiple suffix as a list of string: 39 | # source_suffix = ['.rst', '.md'] 40 | source_suffix = ".rst" 41 | 42 | # The encoding of source files. 43 | # source_encoding = 'utf-8-sig' 44 | 45 | # The master toctree document. 46 | master_doc = "index" 47 | 48 | # General information about the project. 49 | project = "django-fakery" 50 | copyright = "2015, Flavio Curella" 51 | author = "Flavio Curella" 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = "4.1.3" 59 | # The full version, including alpha/beta/rc tags. 60 | release = "4.1.3" 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # There are two options for replacing |today|: either, you set today to some 70 | # non-false value, then it is used: 71 | # today = '' 72 | # Else, today_fmt is used as the format for a strftime call. 73 | # today_fmt = '%B %d, %Y' 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | exclude_patterns = ["_build"] 78 | 79 | # The reST default role (used for this markup: `text`) to use for all 80 | # documents. 81 | # default_role = None 82 | 83 | # If true, '()' will be appended to :func: etc. cross-reference text. 84 | # add_function_parentheses = True 85 | 86 | # If true, the current module name will be prepended to all description 87 | # unit titles (such as .. function::). 88 | # add_module_names = True 89 | 90 | # If true, sectionauthor and moduleauthor directives will be shown in the 91 | # output. They are ignored by default. 92 | # show_authors = False 93 | 94 | # The name of the Pygments (syntax highlighting) style to use. 95 | pygments_style = "sphinx" 96 | 97 | # A list of ignored prefixes for module index sorting. 98 | # modindex_common_prefix = [] 99 | 100 | # If true, keep warnings as "system message" paragraphs in the built documents. 101 | # keep_warnings = False 102 | 103 | # If true, `todo` and `todoList` produce output, else they produce nothing. 104 | todo_include_todos = True 105 | 106 | 107 | # -- Options for HTML output ---------------------------------------------- 108 | 109 | # The theme to use for HTML and HTML Help pages. See the documentation for 110 | # a list of builtin themes. 111 | html_theme = "alabaster" 112 | 113 | # Theme options are theme-specific and customize the look and feel of a theme 114 | # further. For a list of options available for each theme, see the 115 | # documentation. 116 | # html_theme_options = {} 117 | 118 | # Add any paths that contain custom themes here, relative to this directory. 119 | # html_theme_path = [] 120 | 121 | # The name for this set of Sphinx documents. If None, it defaults to 122 | # " v documentation". 123 | # html_title = None 124 | 125 | # A shorter title for the navigation bar. Default is the same as html_title. 126 | # html_short_title = None 127 | 128 | # The name of an image file (relative to this directory) to place at the top 129 | # of the sidebar. 130 | # html_logo = None 131 | 132 | # The name of an image file (within the static path) to use as favicon of the 133 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 134 | # pixels large. 135 | # html_favicon = None 136 | 137 | # Add any paths that contain custom static files (such as style sheets) here, 138 | # relative to this directory. They are copied after the builtin static files, 139 | # so a file named "default.css" will overwrite the builtin "default.css". 140 | html_static_path = ["_static"] 141 | 142 | # Add any extra paths that contain custom files (such as robots.txt or 143 | # .htaccess) here, relative to this directory. These files are copied 144 | # directly to the root of the documentation. 145 | # html_extra_path = [] 146 | 147 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 148 | # using the given strftime format. 149 | # html_last_updated_fmt = '%b %d, %Y' 150 | 151 | # If true, SmartyPants will be used to convert quotes and dashes to 152 | # typographically correct entities. 153 | # html_use_smartypants = True 154 | 155 | # Custom sidebar templates, maps document names to template names. 156 | # html_sidebars = {} 157 | 158 | # Additional templates that should be rendered to pages, maps page names to 159 | # template names. 160 | # html_additional_pages = {} 161 | 162 | # If false, no module index is generated. 163 | # html_domain_indices = True 164 | 165 | # If false, no index is generated. 166 | # html_use_index = True 167 | 168 | # If true, the index is split into individual pages for each letter. 169 | # html_split_index = False 170 | 171 | # If true, links to the reST sources are added to the pages. 172 | # html_show_sourcelink = True 173 | 174 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 175 | # html_show_sphinx = True 176 | 177 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 178 | # html_show_copyright = True 179 | 180 | # If true, an OpenSearch description file will be output, and all pages will 181 | # contain a tag referring to it. The value of this option must be the 182 | # base URL from which the finished HTML is served. 183 | # html_use_opensearch = '' 184 | 185 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 186 | # html_file_suffix = None 187 | 188 | # Language to be used for generating the HTML full-text search index. 189 | # Sphinx supports the following languages: 190 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 191 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 192 | # html_search_language = 'en' 193 | 194 | # A dictionary with options for the search language support, empty by default. 195 | # Now only 'ja' uses this config value 196 | # html_search_options = {'type': 'default'} 197 | 198 | # The name of a javascript file (relative to the configuration directory) that 199 | # implements a search results scorer. If empty, the default will be used. 200 | # html_search_scorer = 'scorer.js' 201 | 202 | # Output file base name for HTML help builder. 203 | htmlhelp_basename = "django-fakerydoc" 204 | 205 | # -- Options for LaTeX output --------------------------------------------- 206 | 207 | latex_elements = { 208 | # The paper size ('letterpaper' or 'a4paper'). 209 | #'papersize': 'letterpaper', 210 | # The font size ('10pt', '11pt' or '12pt'). 211 | #'pointsize': '10pt', 212 | # Additional stuff for the LaTeX preamble. 213 | #'preamble': '', 214 | # Latex figure (float) alignment 215 | #'figure_align': 'htbp', 216 | } 217 | 218 | # Grouping the document tree into LaTeX files. List of tuples 219 | # (source start file, target name, title, 220 | # author, documentclass [howto, manual, or own class]). 221 | latex_documents = [ 222 | ( 223 | master_doc, 224 | "django-fakery.tex", 225 | "django-fakery Documentation", 226 | "Flavio Curella", 227 | "manual", 228 | ) 229 | ] 230 | 231 | # The name of an image file (relative to this directory) to place at the top of 232 | # the title page. 233 | # latex_logo = None 234 | 235 | # For "manual" documents, if this is true, then toplevel headings are parts, 236 | # not chapters. 237 | # latex_use_parts = False 238 | 239 | # If true, show page references after internal links. 240 | # latex_show_pagerefs = False 241 | 242 | # If true, show URL addresses after external links. 243 | # latex_show_urls = False 244 | 245 | # Documents to append as an appendix to all manuals. 246 | # latex_appendices = [] 247 | 248 | # If false, no module index is generated. 249 | # latex_domain_indices = True 250 | 251 | 252 | # -- Options for manual page output --------------------------------------- 253 | 254 | # One entry per manual page. List of tuples 255 | # (source start file, name, description, authors, manual section). 256 | man_pages = [(master_doc, "django-fakery", "django-fakery Documentation", [author], 1)] 257 | 258 | # If true, show URL addresses after external links. 259 | # man_show_urls = False 260 | 261 | 262 | # -- Options for Texinfo output ------------------------------------------- 263 | 264 | # Grouping the document tree into Texinfo files. List of tuples 265 | # (source start file, target name, title, author, 266 | # dir menu entry, description, category) 267 | texinfo_documents = [ 268 | ( 269 | master_doc, 270 | "django-fakery", 271 | "django-fakery Documentation", 272 | author, 273 | "django-fakery", 274 | "One line description of project.", 275 | "Miscellaneous", 276 | ) 277 | ] 278 | 279 | # Documents to append as an appendix to all manuals. 280 | # texinfo_appendices = [] 281 | 282 | # If false, no module index is generated. 283 | # texinfo_domain_indices = True 284 | 285 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 286 | # texinfo_show_urls = 'footnote' 287 | 288 | # If true, do not generate a @detailmenu in the "Top" node's menu. 289 | # texinfo_no_detailmenu = False 290 | -------------------------------------------------------------------------------- /tests/test_factory.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from django.test import TestCase 4 | from django.utils import timezone 5 | 6 | from django_fakery import Lazy, factory, rels 7 | from django_fakery.exceptions import ForeignKeyError 8 | from tests.models import Chef, Pizza 9 | 10 | 11 | class FactoryTest(TestCase): 12 | def test_model(self): 13 | margherita = factory.make("tests.Pizza") 14 | 15 | # field.default 16 | self.assertFalse(margherita.gluten_free) 17 | # field.null 18 | self.assertFalse(margherita.price) 19 | 20 | # field.blank 21 | self.assertEqual(margherita.description, "") 22 | 23 | # field.choices 24 | self.assertIn(margherita.thickness, [0, 1, 2]) 25 | 26 | # required field 27 | self.assertNotEqual(margherita.name, "") 28 | 29 | self.assertNotEqual(margherita.chef.first_name, "") 30 | 31 | def test_fields(self): 32 | margherita = factory.make("tests.Pizza", fields={"name": "four cheeses"}) 33 | self.assertEqual(margherita.name, "four cheeses") 34 | self.assertEqual(margherita.description, "") 35 | 36 | def test_short_api_m(self): 37 | margherita = factory.m("tests.Pizza")(name="four cheeses") 38 | self.assertEqual(margherita.name, "four cheeses") 39 | 40 | pizzas = factory.m("tests.Pizza", quantity=2)(name="pizza {}") 41 | self.assertEqual(len(pizzas), 2) 42 | self.assertEqual(pizzas[0].name, "pizza 0") 43 | self.assertEqual(pizzas[1].name, "pizza 1") 44 | 45 | def test_short_api_b(self): 46 | gusteau = factory.b("tests.Chef")(first_name="Gusteau") 47 | self.assertEqual(gusteau.first_name, "Gusteau") 48 | 49 | chefs = factory.b("tests.Chef", quantity=2)(first_name="Chef {}") 50 | self.assertEqual(len(chefs), 2) 51 | self.assertEqual(chefs[0].first_name, "Chef 0") 52 | self.assertEqual(chefs[1].first_name, "Chef 1") 53 | 54 | def test_blank(self): 55 | margherita = factory.make( 56 | "tests.Pizza", 57 | fields={"name": "four cheeses", "description": lambda n, f: f.sentence()}, 58 | ) 59 | self.assertEqual(margherita.name, "four cheeses") 60 | self.assertNotEqual(margherita.description, "") 61 | 62 | def test_sequence(self): 63 | margheritas = factory.make( 64 | "tests.Pizza", fields={"name": "pizza {}"}, quantity=10 65 | ) 66 | self.assertEqual(len(margheritas), 10) 67 | self.assertEqual(margheritas[0].name, "pizza 0") 68 | self.assertEqual(margheritas[1].name, "pizza 1") 69 | 70 | def test_sequence_callable_lambda(self): 71 | margheritas = factory.make( 72 | "tests.Pizza", 73 | fields={"name": lambda n, f: "pizza {}".format(n)}, 74 | quantity=10, 75 | ) 76 | self.assertEqual(len(margheritas), 10) 77 | self.assertEqual(margheritas[0].name, "pizza 0") 78 | self.assertEqual(margheritas[1].name, "pizza 1") 79 | 80 | def test_sequence_callable(self): 81 | margheritas = factory.make( 82 | "tests.Pizza", fields={"baked_on": timezone.now}, quantity=10 83 | ) 84 | self.assertEqual(len(margheritas), 10) 85 | self.assertNotEqual(margheritas[0].baked_on, None) 86 | self.assertNotEqual(margheritas[0].baked_on, margheritas[1].baked_on) 87 | 88 | def test_lazy_field(self): 89 | chef_masters = factory.make( 90 | "tests.Chef", 91 | fields={"first_name": "Chef {}", "last_name": Lazy("first_name")}, 92 | quantity=10, 93 | ) 94 | self.assertEqual(len(chef_masters), 10) 95 | self.assertEqual(chef_masters[0].first_name, "Chef 0") 96 | self.assertEqual(chef_masters[0].first_name, chef_masters[0].last_name) 97 | self.assertEqual(chef_masters[1].first_name, "Chef 1") 98 | self.assertEqual(chef_masters[0].first_name, chef_masters[0].last_name) 99 | 100 | margherita = factory.make( 101 | "tests.pizza", fields={"price": Lazy("get_price", tax=0.07)} 102 | ) 103 | self.assertEqual(margherita.price, Decimal("8.55")) 104 | 105 | def test_foreign_keys(self): 106 | chef_gusteau = factory.make("tests.Chef", fields={"first_name": "Gusteau"}) 107 | 108 | self.assertEqual(Chef.objects.count(), 1) 109 | 110 | pizza = factory.make("tests.Pizza", fields={"chef": chef_gusteau}) 111 | self.assertEqual(pizza.chef, chef_gusteau) 112 | self.assertEqual(Chef.objects.count(), 1) 113 | 114 | def test_foreign_keys_explicit(self): 115 | chef_gusteau = factory.make("tests.Chef", fields={"first_name": "Gusteau"}) 116 | 117 | self.assertEqual(Chef.objects.count(), 1) 118 | 119 | pizza = factory.make("tests.Pizza", fields={"chef_id": chef_gusteau.pk}) 120 | self.assertEqual(pizza.chef, chef_gusteau) 121 | self.assertEqual(Chef.objects.count(), 1) 122 | 123 | def test_foreign_keys_fail(self): 124 | chef_gusteau = factory.make("tests.Chef", fields={"last_name": "Gusteau"}) 125 | 126 | self.assertRaises(ForeignKeyError, factory.build, "tests.Pizza") 127 | factory.build("tests.Pizza", fields={"chef": chef_gusteau}) 128 | 129 | chef_skinner = factory.build("tests.Chef", fields={"last_name": "Skinner"}) 130 | self.assertRaises( 131 | ForeignKeyError, factory.build, "tests.Pizza", fields={"chef": chef_skinner} 132 | ) 133 | 134 | factory.build("tests.Pizza", fields={"chef": chef_gusteau, "critic": None}) 135 | 136 | def test_manytomany(self): 137 | pizza = factory.make("tests.Pizza") 138 | self.assertEqual(pizza.toppings.count(), 0) 139 | 140 | pizza = factory.make( 141 | "tests.Pizza", fields={"toppings": [factory.make("tests.Topping")]} 142 | ) 143 | self.assertEqual(pizza.toppings.count(), 1) 144 | 145 | pizza = factory.make( 146 | "tests.Pizza", 147 | fields={"toppings": factory.make("tests.Topping", quantity=5)}, 148 | ) 149 | self.assertEqual(pizza.toppings.count(), 5) 150 | 151 | def test_save_hooks(self): 152 | user = factory.make( 153 | "auth.User", 154 | fields={"username": "username"}, 155 | pre_save=[lambda i: i.set_password("password")], 156 | ) 157 | self.assertTrue(user.check_password("password")) 158 | 159 | def test_password(self): 160 | user = factory.make( 161 | "auth.User", fields={"username": "username", "password": "password"} 162 | ) 163 | self.assertTrue(user.check_password("password")) 164 | 165 | user, created = factory.update_or_make( 166 | "auth.User", 167 | lookup={"username": "username"}, 168 | fields={"password": "new_password"}, 169 | ) 170 | self.assertFalse(created) 171 | self.assertTrue(user.check_password("new_password")) 172 | 173 | def test_build(self): 174 | chef_masters = factory.build( 175 | "tests.Chef", 176 | fields={"first_name": "Chef {}", "last_name": Lazy("first_name")}, 177 | quantity=10, 178 | ) 179 | for chef in chef_masters: 180 | self.assertEqual(chef.id, None) 181 | 182 | def test_field_inheritance(self): 183 | chef_gusteau = factory.make("tests.Chef", fields={"last_name": "Gusteau"}) 184 | self.assertTrue("@" in chef_gusteau.email_address) 185 | self.assertTrue( 186 | chef_gusteau.twitter_profile.startswith("http://") 187 | or chef_gusteau.twitter_profile.startswith("https://") 188 | ) 189 | 190 | def test_get_or_make(self): 191 | already_there = factory.make( 192 | "tests.Chef", fields={"first_name": "Auguste", "last_name": "Gusteau"} 193 | ) 194 | 195 | self.assertEqual(Chef.objects.count(), 1) 196 | chef_gusteau, created = factory.get_or_make( 197 | "tests.Chef", lookup={"last_name": "Gusteau"}, fields={"first_name": "Remi"} 198 | ) 199 | self.assertEqual(Chef.objects.count(), 1) 200 | self.assertEqual(already_there, chef_gusteau) 201 | self.assertFalse(created) 202 | 203 | chef_linguini, created = factory.get_or_make( 204 | "tests.Chef", 205 | lookup={"last_name": "Linguini"}, 206 | fields={"first_name": "Alfredo"}, 207 | ) 208 | 209 | self.assertEqual(Chef.objects.count(), 2) 210 | self.assertTrue(created) 211 | self.assertEqual(chef_linguini.first_name, "Alfredo") 212 | self.assertEqual(chef_linguini.last_name, "Linguini") 213 | 214 | def test_g_m(self): 215 | already_there = factory.make( 216 | "tests.Chef", fields={"first_name": "Auguste", "last_name": "Gusteau"} 217 | ) 218 | 219 | self.assertEqual(Chef.objects.count(), 1) 220 | chef_gusteau, created = factory.g_m( 221 | "tests.Chef", lookup={"last_name": "Gusteau"} 222 | )(first_name="Remi") 223 | self.assertEqual(Chef.objects.count(), 1) 224 | self.assertEqual(already_there, chef_gusteau) 225 | self.assertFalse(created) 226 | 227 | chef_linguini, created = factory.g_m( 228 | "tests.Chef", lookup={"last_name": "Linguini"} 229 | )(first_name="Alfredo") 230 | 231 | self.assertEqual(Chef.objects.count(), 2) 232 | self.assertTrue(created) 233 | self.assertEqual(chef_linguini.first_name, "Alfredo") 234 | self.assertEqual(chef_linguini.last_name, "Linguini") 235 | 236 | def test_update_or_make(self): 237 | already_there = factory.make( 238 | "tests.Chef", fields={"first_name": "Auguste", "last_name": "Gusteau"} 239 | ) 240 | 241 | self.assertEqual(Chef.objects.count(), 1) 242 | chef_gusteau, created = factory.update_or_make( 243 | "tests.Chef", lookup={"last_name": "Gusteau"}, fields={"first_name": "Remi"} 244 | ) 245 | self.assertEqual(Chef.objects.count(), 1) 246 | self.assertEqual(already_there, chef_gusteau) 247 | self.assertEqual(chef_gusteau.first_name, "Remi") 248 | 249 | self.assertFalse(created) 250 | 251 | chef_linguini, created = factory.update_or_make( 252 | "tests.Chef", 253 | lookup={"last_name": "Linguini"}, 254 | fields={"first_name": "Alfredo"}, 255 | ) 256 | 257 | self.assertEqual(Chef.objects.count(), 2) 258 | self.assertTrue(created) 259 | self.assertEqual(chef_linguini.first_name, "Alfredo") 260 | self.assertEqual(chef_linguini.last_name, "Linguini") 261 | 262 | def test_u_m(self): 263 | already_there = factory.make( 264 | "tests.Chef", fields={"first_name": "Auguste", "last_name": "Gusteau"} 265 | ) 266 | 267 | self.assertEqual(Chef.objects.count(), 1) 268 | chef_gusteau, created = factory.u_m( 269 | "tests.Chef", lookup={"last_name": "Gusteau"} 270 | )(first_name="Remi") 271 | self.assertEqual(Chef.objects.count(), 1) 272 | self.assertEqual(already_there, chef_gusteau) 273 | self.assertEqual(chef_gusteau.first_name, "Remi") 274 | 275 | self.assertFalse(created) 276 | 277 | chef_linguini, created = factory.u_m( 278 | "tests.Chef", lookup={"last_name": "Linguini"} 279 | )(first_name="Alfredo") 280 | 281 | self.assertEqual(Chef.objects.count(), 2) 282 | self.assertTrue(created) 283 | self.assertEqual(chef_linguini.first_name, "Alfredo") 284 | self.assertEqual(chef_linguini.last_name, "Linguini") 285 | 286 | def test_rel(self): 287 | pizzas = factory.make("tests.Pizza", quantity=5, fields={"chef": rels.SELECT}) 288 | self.assertEqual(Pizza.objects.count(), 5) 289 | self.assertEqual(Chef.objects.count(), 1) 290 | 291 | self.assertEqual(pizzas[0].chef, pizzas[1].chef) 292 | 293 | def test_format(self): 294 | chef = factory.make(Chef, fields={"last_name": "{Linguini}"}) 295 | 296 | self.assertEqual(chef.last_name, "{Linguini}") 297 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django-fakery 2 | ============= 3 | 4 | .. image:: https://badge.fury.io/py/django-fakery.svg 5 | :target: https://badge.fury.io/py/django-fakery 6 | 7 | .. image:: https://travis-ci.org/fcurella/django-fakery.svg?branch=master 8 | :target: https://travis-ci.org/fcurella/django-fakery 9 | 10 | 11 | .. image:: https://coveralls.io/repos/fcurella/django-fakery/badge.svg?branch=master&service=github 12 | :target: https://coveralls.io/github/fcurella/django-fakery?branch=master 13 | 14 | An easy-to-use implementation of `Creation Methods`_ (aka Object Factory) for Django, backed by ``Faker``. 15 | 16 | .. _Creation Methods: http://xunitpatterns.com/Creation%20Method.html 17 | 18 | ``django_fakery`` will try to guess the field's value based on the field's name and type. 19 | 20 | Installation 21 | ------------ 22 | 23 | Install with:: 24 | 25 | $ pip install django-fakery 26 | 27 | QuickStart 28 | ---------- 29 | 30 | .. code-block:: python 31 | 32 | from django_fakery import factory 33 | from myapp.models import MyModel 34 | 35 | factory.m(MyModel)(field='value') 36 | 37 | If you're having issues with circular imports, you can also reference a model by using the ``M`` utility function: 38 | 39 | .. code-block:: python 40 | 41 | from django_fakery import factory, M 42 | 43 | factory.m(M("myapp.MyModel"))(field="value") 44 | 45 | 46 | If you really don't want to import things, you could also just reference a model by using the ``.`` syntax. This is not encouraged, as it will likely break type-hinting: 47 | 48 | .. code-block:: python 49 | 50 | from django_fakery import factory 51 | 52 | factory.m("myapp.MyModel")(field="value") 53 | 54 | 55 | If you use ``pytest``, you can use the ``fakery`` and ``fakery_shortcuts` fixtures 56 | (requires ``pytest`` and ``pytest-django``): 57 | 58 | .. code-block:: python 59 | 60 | import pytest 61 | from myapp.models import MyModel 62 | 63 | @pytest.mark.django_db 64 | def test_mymodel(fakery, fakery_shortcuts): 65 | fakery.m(MyModel)(field=fakery_shortcuts.future_datetime()) 66 | 67 | 68 | 69 | If you'd rather, you can use a more wordy API: 70 | 71 | .. code-block:: python 72 | 73 | from django_fakery import factory 74 | from myapp.models import MyModel 75 | 76 | factory.make( 77 | MyModel, 78 | fields={ 79 | 'field': 'value', 80 | } 81 | ) 82 | 83 | We will use the short API thorough the documentation. 84 | 85 | The value of a field can be any python object, a callable, or a lambda: 86 | 87 | .. code-block:: python 88 | 89 | from django.utils import timezone 90 | from django_fakery import factory 91 | from myapp.models import MyModel 92 | 93 | factory.m(MyModel)(created=timezone.now) 94 | 95 | When using a lambda, it will receive two arguments: ``n`` is the iteration number, and ``f`` is an instance of ``faker``: 96 | 97 | .. code-block:: python 98 | 99 | from django.contrib.auth.models import User 100 | 101 | user = factory.m(User)( 102 | username=lambda n, f: 'user_{}'.format(n), 103 | ) 104 | 105 | ``django-fakery`` includes some pre-built lambdas for common needs. See shortcuts_ for more info. 106 | 107 | You can create multiple objects by using the ``quantity`` parameter: 108 | 109 | .. code-block:: python 110 | 111 | from django_fakery import factory 112 | from django.contrib.auth.models import User 113 | 114 | factory.m(User, quantity=4) 115 | 116 | For convenience, when the value of a field is a string, it will be interpolated with the iteration number: 117 | 118 | .. code-block:: python 119 | 120 | from myapp.models import MyModel 121 | 122 | user = factory.m(User, quantity=4)( 123 | username='user_{}', 124 | ) 125 | 126 | Custom fields 127 | ------------- 128 | 129 | You can add support for custom fields by adding your 130 | custom field class and a function in ``factory.field_types``: 131 | 132 | .. code-block:: python 133 | 134 | from django_fakery import factory 135 | 136 | from my_fields import CustomField 137 | 138 | def func(faker, field, count, *args, **kwargs): 139 | return 43 140 | 141 | 142 | factory.field_types.add( 143 | CustomField, (func, [], {}) 144 | ) 145 | 146 | 147 | As a shortcut, you can specified any Faker function by its name: 148 | 149 | .. code-block:: python 150 | 151 | from django_fakery import factory 152 | 153 | from my_fields import CustomField 154 | 155 | 156 | factory.field_types.add( 157 | CustomField, ("random_int", [], {"min": 0, "max": 60}) 158 | ) 159 | 160 | Foreign keys 161 | ------------ 162 | 163 | Non-nullable ``ForeignKey`` s create related objects automatically. 164 | 165 | If you want to explicitly create a related object, you can pass a factory like any other value: 166 | 167 | .. code-block:: python 168 | 169 | from django.contrib.auth.models import User 170 | from food.models import Pizza 171 | 172 | pizza = factory.m(Pizza)( 173 | chef=factory.m(User)(username='Gusteau'), 174 | ) 175 | 176 | If you'd rather not create related objects and reuse the same value for a foreign key, you can use the special value ``django_fakery.rels.SELECT``: 177 | 178 | .. code-block:: python 179 | 180 | from django_fakery import factory, rels 181 | from food.models import Pizza 182 | 183 | pizza = factory.m(Pizza, quantity=5)( 184 | chef=rels.SELECT, 185 | ) 186 | 187 | ``django-fakery`` will always use the first instance of the related model, creating one if necessary. 188 | 189 | ManyToManies 190 | ------------ 191 | 192 | Because ``ManyToManyField`` s are implicitly nullable (ie: they're always allowed to have their ``.count()`` equal to ``0``), related objects on those fields are not automatically created for you. 193 | 194 | If you want to explicitly create a related objects, you can pass a list as the field's value: 195 | 196 | .. code-block:: python 197 | 198 | from food.models import Pizza, Topping 199 | 200 | pizza = factory.m(Pizza)( 201 | toppings=[ 202 | factory.m(Topping)(name='Anchovies') 203 | ], 204 | ) 205 | 206 | You can also pass a factory, to create multiple objects: 207 | 208 | .. code-block:: python 209 | 210 | from food.models import Pizza, Topping 211 | 212 | pizza = factory.m(Pizza)( 213 | toppings=factory.m(Topping, quantity=5), 214 | ) 215 | 216 | .. _shortcuts: 217 | 218 | Shortcuts 219 | --------- 220 | 221 | ``django-fakery`` includes some shortcut functions to generate commonly needed values. 222 | 223 | 224 | ``future_datetime(end='+30d')`` 225 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 226 | 227 | Returns a ``datetime`` object in the future (that is, 1 second from now) up to the specified ``end``. ``end`` can be a string, anotther datetime, or a timedelta. If it's a string, it must start with `+`, followed by and integer and a unit, Eg: ``'+30d'``. Defaults to ``'+30d'`` 228 | 229 | Valid units are: 230 | 231 | * ``'years'``, ``'y'`` 232 | * ``'weeks'``, ``'w'`` 233 | * ``'days'``, ``'d'`` 234 | * ``'hours'``, ``'hours'`` 235 | * ``'minutes'``, ``'m'`` 236 | * ``'seconds'``, ``'s'`` 237 | 238 | Example: 239 | 240 | .. code-block:: python 241 | 242 | from django_fakery import factory, shortcuts 243 | from myapp.models import MyModel 244 | 245 | factory.m(MyModel)(field=shortcuts.future_datetime('+1w')) 246 | 247 | 248 | ``future_date(end='+30d')`` 249 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 250 | 251 | Returns a ``date`` object in the future (that is, 1 day from now) up to the specified ``end``. ``end`` can be a string, another date, or a timedelta. If it's a string, it must start with `+`, followed by and integer and a unit, Eg: ``'+30d'``. Defaults to ``'+30d'`` 252 | 253 | ``past_datetime(start='-30d')`` 254 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 255 | 256 | Returns a ``datetime`` object in the past between 1 second ago and the specified ``start``. ``start`` can be a string, another datetime, or a timedelta. If it's a string, it must start with `-`, followed by and integer and a unit, Eg: ``'-30d'``. Defaults to ``'-30d'`` 257 | 258 | ``past_date(start='-30d')`` 259 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 260 | 261 | Returns a ``date`` object in the past between 1 day ago and the specified ``start``. ``start`` can be a string, another date, or a timedelta. If it's a string, it must start with `-`, followed by and integer and a unit, Eg: ``'-30d'``. Defaults to ``'-30d'`` 262 | 263 | 264 | Lazies 265 | ------ 266 | 267 | You can refer to the created instance's own attributes or method by using `Lazy` objects. 268 | 269 | For example, if you'd like to create user with email as username, and have them always match, you could do: 270 | 271 | .. code-block:: python 272 | 273 | from django_fakery import factory, Lazy 274 | from django.contrib.auth.models import User 275 | 276 | factory.m(auth.User)( 277 | username=Lazy('email'), 278 | ) 279 | 280 | 281 | If you want to assign a value returned by a method on the instance, you can pass the method's arguments to the ``Lazy`` object: 282 | 283 | .. code-block:: python 284 | 285 | from django_fakery import factory, Lazy 286 | from myapp.models import MyModel 287 | 288 | factory.m(MyModel)( 289 | myfield=Lazy('model_method', 'argument', keyword='keyword value'), 290 | ) 291 | 292 | Pre-save and Post-save hooks 293 | ---------------------------- 294 | 295 | You can define functions to be called right before the instance is saved or right after: 296 | 297 | .. code-block:: python 298 | 299 | from django.contrib.auth.models import User 300 | from django_fakery import factory 301 | 302 | factory.m( 303 | User, 304 | pre_save=[ 305 | lambda u: u.set_password('password') 306 | ], 307 | )(username='username') 308 | 309 | Since settings a user's password is such a common case, we special-cased that scenario, so you can just pass it as a field: 310 | 311 | .. code-block:: python 312 | 313 | from django.contrib.auth.models import User 314 | from django_fakery import factory 315 | 316 | factory.m(User)( 317 | username='username', 318 | password='password', 319 | ) 320 | 321 | Get or Make 322 | ----------- 323 | 324 | You can check for existance of a model instance and create it if necessary by using the ``g_m`` (short for ``get_or_make``) method: 325 | 326 | .. code-block:: python 327 | 328 | from myapp.models import MyModel 329 | 330 | myinstance, created = factory.g_m( 331 | MyModel, 332 | lookup={ 333 | 'myfield': 'myvalue', 334 | } 335 | )(myotherfield='somevalue') 336 | 337 | If you're looking for a more explicit API, you can use the ``.get_or_make()`` method: 338 | 339 | .. code-block:: python 340 | 341 | from myapp.models import MyModel 342 | 343 | myinstance, created = factory.get_or_make( 344 | MyModel, 345 | lookup={ 346 | 'myfield': 'myvalue', 347 | }, 348 | fields={ 349 | 'myotherfield': 'somevalue', 350 | }, 351 | ) 352 | 353 | Get or Update 354 | ------------- 355 | 356 | You can check for existence of a model instance and update it by using the ``g_u`` (short for ``get_or_update``) method: 357 | 358 | .. code-block:: python 359 | 360 | from myapp.models import MyModel 361 | 362 | myinstance, created = factory.g_u( 363 | MyModel, 364 | lookup={ 365 | 'myfield': 'myvalue', 366 | } 367 | )(myotherfield='somevalue') 368 | 369 | If you're looking for a more explicit API, you can use the ``.get_or_update()`` method: 370 | 371 | .. code-block:: python 372 | 373 | from myapp.models import MyModel 374 | 375 | myinstance, created = factory.get_or_update( 376 | MyModel, 377 | lookup={ 378 | 'myfield': 'myvalue', 379 | }, 380 | fields={ 381 | 'myotherfield': 'somevalue', 382 | }, 383 | ) 384 | 385 | Non-persistent instances 386 | ------------------------ 387 | 388 | You can build instances that are not saved to the database by using the ``.b()`` method, just like you'd use ``.m()``: 389 | 390 | .. code-block:: python 391 | 392 | from django_fakery import factory 393 | from myapp.models import MyModel 394 | 395 | factory.b(MyModel)( 396 | field='value', 397 | ) 398 | 399 | Note that since the instance is not saved to the database, ``.build()`` does not support ManyToManies or post-save hooks. 400 | 401 | If you're looking for a more explicit API, you can use the ``.build()`` method: 402 | 403 | .. code-block:: python 404 | 405 | from django_fakery import factory 406 | from myapp.models import MyModel 407 | 408 | factory.build( 409 | MyModel, 410 | fields={ 411 | 'field': 'value', 412 | } 413 | ) 414 | 415 | 416 | Blueprints 417 | ---------- 418 | 419 | Use a blueprint: 420 | 421 | .. code-block:: python 422 | 423 | from django.contrib.auth.models import User 424 | from django_fakery import factory 425 | 426 | user = factory.blueprint(User) 427 | 428 | user.make(quantity=10) 429 | 430 | Blueprints can refer other blueprints: 431 | 432 | .. code-block:: python 433 | 434 | from food.models import Pizza 435 | 436 | pizza = factory.blueprint(Pizza).fields( 437 | chef=user, 438 | ) 439 | ) 440 | 441 | You can also override the field values you previously specified: 442 | 443 | .. code-block:: python 444 | 445 | from food.models import Pizza 446 | 447 | pizza = factory.blueprint(Pizza).fields( 448 | chef=user, 449 | thickness=1 450 | ) 451 | ) 452 | 453 | pizza.m(quantity=10)(thickness=2) 454 | 455 | Or, if you'd rather use the explicit api: 456 | 457 | .. code-block:: python 458 | 459 | from food.models import Pizza 460 | 461 | pizza = factory.blueprint(Pizza).fields( 462 | chef=user, 463 | thickness=1 464 | ) 465 | ) 466 | 467 | thicker_pizza = pizza.fields(thickness=2) 468 | thicker_pizza.make(quantity=10) 469 | 470 | 471 | Seeding the faker 472 | ----------------- 473 | 474 | .. code-block:: python 475 | 476 | from django.contrib.auth.models import User 477 | from django_fakery import factory 478 | 479 | factory.m(User, seed=1234, quantity=4)( 480 | username='regularuser_{}' 481 | ) 482 | 483 | Credits 484 | ------- 485 | 486 | The API is heavily inspired by `model_mommy`_. 487 | 488 | .. _model_mommy: https://github.com/vandersonmota/model_mommy 489 | 490 | License 491 | ------- 492 | 493 | This software is released under the MIT License. 494 | -------------------------------------------------------------------------------- /django_fakery/faker_factory.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from functools import partial 4 | from typing import Any, Callable, Dict, Generic, List 5 | from typing import Optional as Opt 6 | from typing import Tuple, Union, overload 7 | 8 | from django.conf import settings 9 | from django.contrib.auth import get_user_model 10 | from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation 11 | from django.db import models 12 | from django.db.models.fields import NOT_PROVIDED 13 | from django.forms.models import model_to_dict 14 | 15 | from faker import Factory as FakerFactory 16 | 17 | from . import field_mappings, rels 18 | from .blueprint import Blueprint 19 | from .exceptions import ForeignKeyError 20 | from .lazy import Lazy 21 | from .types import Built, FieldMap, LazyBuilt, LazySaveHooks, Lookup, SaveHooks, Seed, T 22 | from .utils import get_model, get_model_fields, language_to_locale, set_related 23 | from .values import Evaluator 24 | 25 | user_model = get_user_model() 26 | 27 | locale = language_to_locale(settings.LANGUAGE_CODE) 28 | 29 | 30 | class Empty(object): 31 | pass 32 | 33 | 34 | fks_cache: Dict[str, Any] = {} 35 | 36 | 37 | class Factory(Generic[T]): 38 | def __init__(self, fake: Opt[FakerFactory] = None): 39 | self.fake = fake or FakerFactory.create(locale) 40 | self.field_types = field_mappings.mappings_types 41 | self.field_names = field_mappings.mappings_names 42 | 43 | def _serialize_instance(self, instance: models.Model) -> FieldMap: 44 | model_fields = dict(get_model_fields(instance)) 45 | attrs: FieldMap = {} 46 | for k, v in model_to_dict(instance).items(): 47 | if k == instance._meta.pk.name: # type: ignore 48 | continue 49 | 50 | if isinstance(v, (list, models.QuerySet)): 51 | continue 52 | 53 | if isinstance(model_fields[k], models.ForeignKey) and not isinstance( 54 | v, models.Model 55 | ): 56 | attrs[k + "_id"] = v 57 | continue 58 | 59 | attrs[k] = v 60 | 61 | return attrs 62 | 63 | def seed(self, seed: Seed, set_global: bool = False) -> None: 64 | self.fake.seed(seed) 65 | if set_global: 66 | random.seed(seed) 67 | 68 | def blueprint(self, model: Union[str, models.Model], *args, **kwargs) -> Blueprint: 69 | return Blueprint(get_model(model), *args, **kwargs) 70 | 71 | @overload 72 | def build_one( 73 | self, 74 | model: str, 75 | fields: Opt[FieldMap], 76 | pre_save: Opt[LazySaveHooks], 77 | seed: Opt[Seed], 78 | make_fks: bool, 79 | iteration: Opt[int], 80 | ) -> LazyBuilt: # pragma: no cover 81 | pass 82 | 83 | @overload 84 | def build_one( 85 | self, 86 | model: T, 87 | fields: Opt[FieldMap], 88 | pre_save: Opt[SaveHooks[T]], 89 | seed: Opt[Seed], 90 | make_fks: bool, 91 | iteration: Opt[int], 92 | ) -> Built: # pragma: no cover 93 | pass 94 | 95 | def build_one( 96 | self, 97 | model, 98 | fields=None, 99 | pre_save=None, 100 | seed=None, 101 | make_fks=False, 102 | iteration=None, 103 | ): 104 | if fields is None: 105 | fields = {} 106 | 107 | if pre_save is None: 108 | pre_save = [] 109 | 110 | if seed: 111 | fake = FakerFactory.create(locale) 112 | fake.seed(seed) 113 | else: 114 | fake = self.fake 115 | 116 | evaluator = Evaluator( 117 | fake, 118 | factory=self, 119 | iteration=iteration, 120 | mappings_types=self.field_types, 121 | mappings_names=self.field_names, 122 | ) 123 | 124 | model = get_model(model) 125 | instance = model() 126 | m2ms = {} 127 | lazies = [] 128 | 129 | model_fields = get_model_fields(model) 130 | for _field_name, model_field in model_fields: 131 | value = Empty 132 | field_name = _field_name 133 | 134 | if _field_name.endswith("_id") and model_field.is_relation: 135 | continue 136 | 137 | if isinstance(model_field, models.AutoField): 138 | continue 139 | 140 | if isinstance(model_field, (GenericForeignKey, GenericRelation)): 141 | continue 142 | 143 | if field_name not in fields and ( 144 | model_field.null or model_field.default != NOT_PROVIDED 145 | ): 146 | continue 147 | 148 | if field_name not in fields and isinstance( 149 | model_field, models.ManyToManyField 150 | ): 151 | continue 152 | 153 | value = fields.get(field_name, Empty) 154 | if isinstance(model_field, models.ForeignKey): 155 | if value is Empty: 156 | value = fields.get(field_name + "_id", Empty) 157 | 158 | if value == rels.SELECT: 159 | model = model_field.related_model 160 | qs = model.objects.all() 161 | cache_key = model 162 | 163 | value = fks_cache.get(cache_key, Empty) 164 | 165 | if value is Empty: 166 | try: 167 | value = qs[0] 168 | except IndexError: 169 | value = evaluator.fake_value(model, model_field) 170 | 171 | fks_cache[cache_key] = value 172 | 173 | if not make_fks and ((value is Empty) or (value and value.pk is None)): 174 | raise ForeignKeyError( 175 | "Field {} is a required ForeignKey, but the related {}.{} model" 176 | " doesn't have the necessary primary key.".format( 177 | field_name, 178 | model_field.related_model._meta.app_label, 179 | model_field.related_model._meta.model_name, 180 | ) 181 | ) 182 | 183 | field_name += "_id" 184 | 185 | if value is not Empty: 186 | value = evaluator.evaluate(value) 187 | else: 188 | if model_field.choices: 189 | value = fake.random_element(model_field.choices)[0] 190 | else: 191 | value = evaluator.fake_value(model, model_field) 192 | 193 | if isinstance(value, Lazy): 194 | lazies.append((field_name, value)) 195 | continue 196 | 197 | if isinstance(model_field, models.ForeignKey): 198 | value = value.pk if hasattr(value, "pk") else value 199 | 200 | if isinstance(model_field, models.ManyToManyField): 201 | m2ms[field_name] = value 202 | # special case for user passwords 203 | if model == user_model and field_name == "password": 204 | instance.set_password(value) 205 | else: 206 | if field_name not in m2ms: 207 | setattr(instance, field_name, value) 208 | 209 | for field_name, lazy in lazies: 210 | value = getattr(instance, lazy.name) 211 | if callable(value): 212 | value = value(*lazy.args, **lazy.kwargs) 213 | setattr(instance, field_name, value) 214 | 215 | for func in pre_save: 216 | func(instance) 217 | 218 | return instance, m2ms 219 | 220 | @overload 221 | def build( 222 | self, 223 | model: str, 224 | fields: Opt[FieldMap], 225 | pre_save: Opt[LazySaveHooks], 226 | seed: Opt[Seed], 227 | quantity: None, 228 | make_fks: bool, 229 | ) -> LazyBuilt: # pragma: no cover 230 | pass 231 | 232 | @overload 233 | def build( 234 | self, 235 | model: T, 236 | fields: Opt[FieldMap], 237 | pre_save: Opt[SaveHooks[T]], 238 | seed: Opt[SaveHooks[T]], 239 | quantity: None, 240 | make_fks: bool, 241 | ) -> Built: # pragma: no cover 242 | pass 243 | 244 | @overload 245 | def build( 246 | self, 247 | model: str, 248 | fields: Opt[FieldMap], 249 | pre_save: Opt[LazySaveHooks], 250 | seed: Opt[Seed], 251 | quantity: int, 252 | make_fks: bool, 253 | ) -> List[LazyBuilt]: # pragma: no cover 254 | pass 255 | 256 | @overload 257 | def build( 258 | self, 259 | model: T, 260 | fields: Opt[FieldMap], 261 | pre_save: Opt[SaveHooks[T]], 262 | seed: Opt[Seed], 263 | quantity: Opt[int], 264 | make_fks: bool, 265 | ) -> List[Built]: # pragma: no cover 266 | pass 267 | 268 | def build( 269 | self, 270 | model, 271 | fields=None, 272 | pre_save=None, 273 | seed=None, 274 | quantity=None, 275 | make_fks=False, 276 | ): 277 | if fields is None: 278 | fields = {} 279 | 280 | if quantity is None: 281 | return self.build_one(model, fields, pre_save, seed, make_fks)[0] 282 | else: 283 | return [ 284 | self.build_one(model, fields, pre_save, seed, make_fks, i)[0] 285 | for i in range(quantity) 286 | ] 287 | 288 | @overload 289 | def make_one( 290 | self, 291 | model: str, 292 | fields: Opt[FieldMap], 293 | pre_save: Opt[LazySaveHooks], 294 | post_save: Opt[LazySaveHooks], 295 | seed: Opt[Seed], 296 | iteration: Opt[int], 297 | ) -> models.Model: # pragma: no cover 298 | pass 299 | 300 | @overload 301 | def make_one( 302 | self, 303 | model: T, 304 | fields: Opt[FieldMap], 305 | pre_save: Opt[SaveHooks[T]], 306 | post_save: Opt[SaveHooks[T]], 307 | seed: Opt[Seed], 308 | iteration: Opt[int], 309 | ) -> T: # pragma: no cover 310 | pass 311 | 312 | def make_one( 313 | self, 314 | model, 315 | fields=None, 316 | pre_save=None, 317 | post_save=None, 318 | seed=None, 319 | iteration=None, 320 | ): 321 | if fields is None: 322 | fields = {} 323 | 324 | if post_save is None: 325 | post_save = [] 326 | 327 | instance, m2ms = self.build_one( 328 | model, fields, pre_save, seed, make_fks=True, iteration=iteration 329 | ) 330 | 331 | # Sometimes the model's field for the primary key as a default, which 332 | # means ``instance.pk`` is already set. We pass ``force_insert`` as a 333 | # way to tell downstream code that this is a new model. 334 | instance.save(force_insert=True) 335 | 336 | for field, relateds in m2ms.items(): 337 | set_related(instance, field, relateds) 338 | 339 | for func in post_save: 340 | func(instance) 341 | return instance 342 | 343 | @overload 344 | def get_or_make( 345 | self, 346 | model: str, 347 | lookup: Opt[Lookup], 348 | fields: Opt[FieldMap], 349 | pre_save: Opt[LazySaveHooks], 350 | post_save: Opt[LazySaveHooks], 351 | seed: Opt[Seed], 352 | ) -> Tuple[models.Model, bool]: # pragma: no cover 353 | pass 354 | 355 | @overload 356 | def get_or_make( 357 | self, 358 | model: T, 359 | lookup: Opt[Lookup], 360 | fields: Opt[FieldMap], 361 | pre_save: Opt[SaveHooks[T]], 362 | post_save: Opt[SaveHooks[T]], 363 | seed: Opt[Seed], 364 | ) -> Tuple[T, bool]: # pragma: no cover 365 | pass 366 | 367 | def get_or_make( 368 | self, model, lookup=None, fields=None, pre_save=None, post_save=None, seed=None 369 | ): 370 | if lookup is None: 371 | lookup = {} 372 | if fields is None: 373 | fields = {} 374 | if post_save is None: 375 | post_save = [] 376 | 377 | instance, m2ms = self.build_one(model, fields, pre_save, seed, make_fks=True) 378 | 379 | attrs = self._serialize_instance(instance) 380 | for k in lookup: 381 | attrs.pop(k, None) 382 | instance, created = get_model(model).objects.get_or_create( 383 | defaults=attrs, **lookup 384 | ) 385 | 386 | for field, relateds in m2ms.items(): 387 | set_related(instance, field, relateds) 388 | 389 | for func in post_save: 390 | func(instance) 391 | return instance, created 392 | 393 | @overload 394 | def g_m( 395 | self, 396 | model: str, 397 | lookup: Opt[Lookup], 398 | pre_save: Opt[LazySaveHooks], 399 | post_save: Opt[LazySaveHooks], 400 | seed: Opt[Seed], 401 | ) -> Callable[..., models.Model]: # pragma: no cover 402 | pass 403 | 404 | @overload 405 | def g_m( 406 | self, 407 | model: T, 408 | lookup: Opt[Lookup], 409 | pre_save: Opt[SaveHooks[T]], 410 | post_save: Opt[SaveHooks[T]], 411 | seed: Opt[Seed], 412 | ) -> Callable[..., T]: # pragma: no cover 413 | pass 414 | 415 | def g_m(self, model, lookup=None, pre_save=None, post_save=None, seed=None): 416 | build = partial( 417 | self.get_or_make, 418 | model=model, 419 | lookup=lookup, 420 | pre_save=pre_save, 421 | post_save=post_save, 422 | seed=seed, 423 | ) 424 | 425 | def fn(**kwargs): 426 | return build(fields=kwargs) 427 | 428 | return fn 429 | 430 | @overload 431 | def update_or_make( 432 | self, 433 | model: str, 434 | lookup: Opt[Lookup], 435 | fields: Opt[FieldMap], 436 | pre_save: Opt[LazySaveHooks], 437 | post_save: Opt[LazySaveHooks], 438 | seed: Opt[Seed], 439 | ) -> Tuple[models.Model, bool]: # pragma: no cover 440 | pass 441 | 442 | @overload 443 | def update_or_make( 444 | self, 445 | model: T, 446 | lookup: Opt[Lookup], 447 | fields: Opt[FieldMap], 448 | pre_save: Opt[SaveHooks[T]], 449 | post_save: Opt[SaveHooks[T]], 450 | seed: Opt[Seed], 451 | ) -> Tuple[T, bool]: # pragma: no cover 452 | pass 453 | 454 | def update_or_make( 455 | self, model, lookup=None, fields=None, pre_save=None, post_save=None, seed=None 456 | ): 457 | if lookup is None: 458 | lookup = {} 459 | if fields is None: 460 | fields = {} 461 | if post_save is None: 462 | post_save = [] 463 | 464 | model_class = get_model(model) 465 | 466 | try: 467 | instance = model_class.objects.get(**lookup) 468 | except model_class.DoesNotExist: 469 | created = True 470 | params = {k: v for k, v in lookup.items() if "__" not in k} 471 | params.update(fields) 472 | instance = self.make(model, params, pre_save, post_save, seed) 473 | else: 474 | created = False 475 | for k, v in fields.items(): 476 | # special case for user passwords 477 | if model_class == user_model and k == "password": 478 | instance.set_password(v) 479 | else: 480 | setattr(instance, k, v) 481 | instance.save() 482 | 483 | for func in post_save: 484 | func(instance) 485 | 486 | return instance, created 487 | 488 | @overload 489 | def u_m( 490 | self, 491 | model: str, 492 | lookup: Opt[Lookup], 493 | pre_save: Opt[LazySaveHooks], 494 | post_save: Opt[LazySaveHooks], 495 | seed: Opt[Seed], 496 | ) -> Callable[..., models.Model]: # pragma: no cover 497 | pass 498 | 499 | @overload 500 | def u_m( 501 | self, 502 | model: T, 503 | lookup: Opt[Lookup], 504 | pre_save: Opt[SaveHooks[T]], 505 | post_save: Opt[SaveHooks[T]], 506 | seed: Opt[Seed], 507 | ) -> Callable[..., T]: # pragma: no cover 508 | pass 509 | 510 | def u_m(self, model, lookup=None, pre_save=None, post_save=None, seed=None): 511 | build = partial( 512 | self.update_or_make, 513 | model=model, 514 | lookup=lookup, 515 | pre_save=pre_save, 516 | post_save=post_save, 517 | seed=seed, 518 | ) 519 | 520 | def fn(**kwargs): 521 | return build(fields=kwargs) 522 | 523 | return fn 524 | 525 | @overload 526 | def make( 527 | self, 528 | model: str, 529 | fields: Opt[FieldMap], 530 | pre_save: Opt[LazySaveHooks], 531 | post_save: Opt[LazySaveHooks], 532 | seed: Opt[Seed], 533 | quantity: None, 534 | ) -> models.Model: # pragma: no cover 535 | pass 536 | 537 | @overload 538 | def make( 539 | self, 540 | model: T, 541 | fields: Opt[FieldMap], 542 | pre_save: Opt[SaveHooks[T]], 543 | post_save: Opt[SaveHooks[T]], 544 | seed: Opt[Seed], 545 | quantity: None, 546 | ) -> T: # pragma: no cover 547 | pass 548 | 549 | @overload 550 | def make( 551 | self, 552 | model: str, 553 | fields: Opt[FieldMap], 554 | pre_save: Opt[LazySaveHooks], 555 | post_save: Opt[LazySaveHooks], 556 | seed: Opt[Seed], 557 | quantity: Opt[int], 558 | ) -> List[models.Model]: # pragma: no cover 559 | pass 560 | 561 | @overload 562 | def make( 563 | self, 564 | model: T, 565 | fields: Opt[FieldMap], 566 | pre_save: Opt[SaveHooks[T]], 567 | post_save: Opt[SaveHooks[T]], 568 | seed: Opt[Seed], 569 | quantity: Opt[int], 570 | ) -> List[T]: # pragma: no cover 571 | pass 572 | 573 | def make( 574 | self, 575 | model, 576 | fields=None, 577 | pre_save=None, 578 | post_save=None, 579 | seed=None, 580 | quantity=None, 581 | ): 582 | if fields is None: 583 | fields = {} 584 | if quantity is None: 585 | return self.make_one(model, fields, pre_save, post_save, seed) 586 | else: 587 | return [ 588 | self.make_one(model, fields, pre_save, post_save, seed, i) 589 | for i in range(quantity) 590 | ] 591 | 592 | @overload 593 | def m( 594 | self, 595 | model: str, 596 | pre_save: Opt[LazySaveHooks], 597 | post_save: Opt[LazySaveHooks], 598 | seed: Opt[Seed], 599 | quantity: None, 600 | ) -> Callable[..., models.Model]: # pragma: no cover 601 | pass 602 | 603 | @overload 604 | def m( 605 | self, 606 | model: T, 607 | pre_save: Opt[SaveHooks[T]], 608 | post_save: Opt[SaveHooks[T]], 609 | seed: Opt[Seed], 610 | quantity: None, 611 | ) -> Callable[..., T]: # pragma: no cover 612 | pass 613 | 614 | @overload 615 | def m( 616 | self, 617 | model: str, 618 | pre_save: Opt[LazySaveHooks], 619 | post_save: Opt[LazySaveHooks], 620 | seed: Opt[Seed], 621 | quantity: Opt[int], 622 | ) -> Callable[..., List[models.Model]]: # pragma: no cover 623 | pass 624 | 625 | @overload 626 | def m( 627 | self, 628 | model: T, 629 | pre_save: Opt[SaveHooks[T]], 630 | post_save: Opt[SaveHooks[T]], 631 | seed: Opt[Seed], 632 | quantity: Opt[int], 633 | ) -> Callable[..., List[T]]: # pragma: no cover 634 | pass 635 | 636 | def m(self, model, pre_save=None, post_save=None, seed=None, quantity=None): 637 | make = partial( 638 | self.make, 639 | model=model, 640 | pre_save=pre_save, 641 | post_save=post_save, 642 | seed=seed, 643 | quantity=quantity, 644 | ) 645 | 646 | def fn(**kwargs): 647 | return make(fields=kwargs) 648 | 649 | return fn 650 | 651 | @overload 652 | def b( 653 | self, 654 | model: str, 655 | pre_save: Opt[LazySaveHooks], 656 | seed: Opt[Seed], 657 | quantity: None, 658 | make_fks: bool, 659 | ) -> Callable[..., LazyBuilt]: # pragma: no cover 660 | pass 661 | 662 | @overload 663 | def b( 664 | self, 665 | model: T, 666 | pre_save: Opt[SaveHooks[T]], 667 | seed: Opt[Seed], 668 | quantity: None, 669 | make_fks: bool, 670 | ) -> Callable[..., Built]: # pragma: no cover 671 | pass 672 | 673 | @overload 674 | def b( 675 | self, 676 | model: str, 677 | pre_save: Opt[LazySaveHooks], 678 | seed: Opt[Seed], 679 | quantity: Opt[int], 680 | make_fks: bool, 681 | ) -> Callable[..., List[LazyBuilt]]: # pragma: no cover 682 | pass 683 | 684 | @overload 685 | def b( 686 | self, 687 | model: T, 688 | pre_save: Opt[SaveHooks[T]], 689 | seed: Opt[Seed], 690 | quantity: Opt[int], 691 | make_fks: bool, 692 | ) -> Callable[..., List[Built]]: # pragma: no cover 693 | pass 694 | 695 | def b(self, model, pre_save=None, seed=None, quantity=None, make_fks=False): 696 | build = partial( 697 | self.build, 698 | model=model, 699 | pre_save=pre_save, 700 | seed=seed, 701 | quantity=quantity, 702 | make_fks=make_fks, 703 | ) 704 | 705 | def fn(**kwargs): 706 | return build(fields=kwargs) 707 | 708 | return fn 709 | 710 | 711 | factory: Factory = Factory() 712 | --------------------------------------------------------------------------------