Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated to work with Django 1.11/2 + python 2/3 #28

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
[paths]
source = django_faker/
omit = django_faker/tests.py
source = django_faker/
31 changes: 7 additions & 24 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,24 @@
language: python
sudo: false
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
env:
matrix:
- DJANGO=Django==1.4
- DJANGO=Django==1.5
- DJANGO=Django==1.6
- DJANGO=Django==1.7
- DJANGO=Django==1.8
- DJANGO=Django==1.11
- DJANGO=Django==2.0
install:
- pip install $DJANGO --use-mirrors
- pip install . --use-mirrors
- pip install coverage
- pip install $DJANGO
- pip install -e .
branches:
only:
- master
- develop
script: coverage run --source=django_faker setup.py test
script: python setup.py test
matrix:
exclude:
- python: "2.6"
env: DJANGO=Django==1.7
- python: "2.6"
env: DJANGO=Django==1.8
- python: "3.3"
env: DJANGO=Django==1.4
- python: "3.3"
env: DJANGO=Django==1.5
- python: "3.4"
env: DJANGO=Django==1.4
- python: "3.4"
env: DJANGO=Django==1.5
- python: "2.7"
env: DJANGO=Django==2.0
after_success:
- coverage report
- pip install --quiet python-coveralls
- coveralls
66 changes: 45 additions & 21 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Django-faker
============

*Django-faker* uses `fake-factory`_ package to generate test data for Django models and templates.
*Django-faker* uses `faker`_ package to generate test data for Django models and templates.

|pypi| |unix_build| |windows_build| |coverage| |downloads| |license|

Expand All @@ -19,9 +19,9 @@ Configuration
In django application `settings.py`::

INSTALLED_APPS = (

# ...
'django_faker',
'django_faker_tests',
)

FAKER_LOCALE = None # settings.LANGUAGE_CODE is loaded
Expand All @@ -39,25 +39,25 @@ call `execute()` method.
Here is an example showing how to populate 5 `Game` and 10 `Player` objects::

from django_faker import Faker
# this Populator is only a function thats return a django_faker.populator.Populator instance
# this Populator is only a function that returns a django_faker.populator.Populator instance
# correctly initialized with a faker.generator.Generator instance, configured as above
populator = Faker.getPopulator()
populator = Faker.get_populator()

from myapp.models import Game, Player
populator.addEntity(Game,5)
populator.addEntity(Player,10)
populator.add_entity(Game,5)
populator.add_entity(Player,10)

insertedPks = populator.execute()

The populator uses name and column type guessers to populate each column with relevant data.
For instance, Django-faker populates a column named `first_name` using the `firstName` formatter, and a column with
a `datetime` instance using the `dateTime`.
For instance, Django-faker populates a column named `first_name` using the `first_name` formatter, and a column with
a `datetime` instance using `date_time`.
The resulting entities are therefore coherent. If Django-faker misinterprets a column name, you can still specify a custom
function to be used for populating a particular column, using the third argument to `addEntity()`::
function to be used for populating a particular column, using the third argument to `add_entity()`::


populator.addEntity(Player, 10, {
'score': lambda x: populator.generator.randomInt(0,1000),
populator.add_entity(Player, 10, {
'score': lambda x: populator.generator.random_int(0,1000),
'nickname': lambda x: populator.generator.email(),
})
populator.execute()
Expand All @@ -75,12 +75,36 @@ In the previous example, the `Player` and `Game` models share a relationship. Si
Faker is smart enough to relate the populated `Player` entities to one of populated `Game` entities.


More on Relational Fields
~~~~~~~~~~~~~~~~~
Django-faker will attempt to populate relational fields in the following manner:

#. From model instances added through `add_entity()`
#. From pre-existing values in the db

If there aren't available values and the field can't be null, an `AttributeError` is thrown.

**One-to-one fields**:
The Populator keeps track of what values have been used already so it doesn't violate the one-to-one constraint.

**Many-to-many fields**:
The Populator randomly selects between 1-n values to assign to the object.

**Foreign key fields**:
The Populator randomly selects 1 value to assign.

**unique/unique_together constraints**:
Currently, django-faker tries to populate the field(s) and then if a constraint is violated it tries again.
This happens up to 1000 times and then an InvalidConstraint exception is thrown.
Future iterations will hopefully pick values from a generated set of options to guarantee correctness.


Template tags and filter
~~~~~~~~~~~~~~~~~~~~~~~~

Django-faker offers a useful template tags and filters for interact with `PyFaker`_::
Django-faker offers a useful template tags and filters to interact with `faker`_::

{% fake 'name' as myname %}{% fake 'dateTimeBetween' '-10d' as mydate %}
{% fake 'name' as myname %}{% fake 'date_time_between' '-10d' as mydate %}

{{ myname|title }} - {{ mydate|date:"M Y" }}

Expand All @@ -90,20 +114,20 @@ Django-faker offers a useful template tags and filters for interact with `PyFake

<?xml version="1.0" encoding="UTF-8"?>
<contacts>
{% fake 'randomInt' 10 20 as times %}
{% fake 'random_int' 10 20 as times %}
{% for i in 10|get_range %}
<contact firstName="{% fakestr 'firstName' %}" lastName="{% fakestr 'lastName' %}" email="{% fakestr 'email' %}"/>
<phone number="{% fakestr 'phoneNumber' %}"/>
<contact first_name="{% fakestr 'first_name' %}" last_name="{% fakestr 'last_name' %}" email="{% fakestr 'email' %}"/>
<phone number="{% fakestr 'phone_number' %}"/>
{% if 'boolean'|fake:25 %}
<birth date="{{ 'dateTimeThisCentury'|fake|date:"D d M Y" }}" place="{% fakestr 'city' %}"/>
<birth date="{{ 'date_time_this_century'|fake|date:"D d M Y" }}" place="{% fakestr 'city' %}"/>
{% endif %}
<address>
<street>{% fakestr 'streetAddress' %}</street>
<street>{% fakestr 'street_address' %}</street>
<city>{% fakestr 'city' %}</city>
<postcode>{% fakestr 'postcode' %}</postcode>
<state>{% fakestr 'state' %}</state>
</address>
<company name="{% fakestr 'company' %}" catchPhrase="{% fakestr 'catchPhrase' %}">
<company name="{% fakestr 'company' %}" catch_phrase="{% fakestr 'catch_phrase' %}">
{% if 'boolean'|fake:25 %}
<offer>{% fakestr 'bs' %}</offer>
{% endif %}
Expand All @@ -129,7 +153,7 @@ Open `url.py` in your main application and add this url::

urlpatterns = patterns('',
...
url(r'', include('django_faker.urls')),
url(r'', include('django_faker_tests.urls')),
...
)

Expand Down Expand Up @@ -168,7 +192,7 @@ Changelog
- Add django template tag and filter


.. _fake-factory: https://www.github.com/joke2k/faker/
.. _faker: https://www.github.com/joke2k/faker/

.. |pypi| image:: https://img.shields.io/pypi/v/django-faker.svg?style=flat-square&label=version
:target: https://pypi.python.org/pypi/django-faker
Expand Down
70 changes: 24 additions & 46 deletions django_faker/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
"""

Django-faker uses python-faker to generate test data for Django models and templates.

Django-faker uses a generator (eg faker) to generate test data for Django models and templates.
"""

__version__ = "0.2.1"


__version__ = '0.2.1'

class Faker(object):
class DjangoFaker(object):

instance = None
populators = {}
Expand All @@ -17,24 +14,22 @@ class Faker(object):
@classmethod
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super(Faker, cls).__new__(*args, **kwargs)
cls.instance = super(DjangoFaker, cls).__new__(*args, **kwargs)
return cls.instance

def __init__(self):
# assert False, "Cannot create a instance of Faker"
pass


@staticmethod
def getCodename(locale=None, providers=None):
def get_codename(locale=None, providers=None):
"""
codename = locale[-Provider]*
"""
from django.conf import settings
# language
locale = locale or getattr(settings,'FAKER_LOCALE', getattr(settings,'LANGUAGE_CODE', None))
locale = locale or getattr(settings, 'FAKER_LOCALE', getattr(settings, 'LANGUAGE_CODE', None))
# providers
providers = providers or getattr(settings,'FAKER_PROVIDERS', None)
providers = providers or getattr(settings, 'FAKER_PROVIDERS', None)

codename = locale or 'default'

Expand All @@ -43,68 +38,51 @@ def getCodename(locale=None, providers=None):

return codename


@classmethod
def getGenerator(cls, locale=None, providers=None, codename=None):
def get_generator(cls, locale=None, providers=None, codename=None):
"""
use a codename to cache generators
"""

codename = codename or cls.getCodename(locale, providers)
codename = codename or cls.get_codename(locale, providers)

if codename not in cls.generators:
from faker import Faker as FakerGenerator
# initialize with faker.generator.Generator instance
# and remember in cache
cls.generators[codename] = FakerGenerator( locale, providers )
cls.generators[codename].seed( cls.generators[codename].randomInt() )
from faker import Faker as FakerGenerator
cls.generators[codename] = FakerGenerator(locale, providers)
cls.generators[codename].seed(cls.generators[codename].random_int())

return cls.generators[codename]



@classmethod
def getPopulator(cls, locale=None, providers=None):
def get_populator(cls, locale=None, providers=None):
"""

uses:

from django_faker import Faker
pop = Faker.getPopulator()
from django_faker import DjangoFaker
pop = DjangoFaker.get_populator()

from myapp import models
pop.addEntity(models.MyModel, 10)
pop.addEntity(models.MyOtherModel, 10)
pop.add_entity(models.MyModel, 10)
pop.add_entity(models.MyOtherModel, 10)
pop.execute()

pop = Faker.getPopulator('it_IT')
pop = Faker.get_populator('it_IT')

pop.addEntity(models.MyModel, 10)
pop.addEntity(models.MyOtherModel, 10)
pop.add_entity(models.MyModel, 10)
pop.add_entity(models.MyOtherModel, 10)
pop.execute()

"""

codename = cls.getCodename(locale, providers)
codename = cls.get_codename(locale, providers)

if codename not in cls.populators:
generator = cls.generators.get(codename, None) or cls.get_generator(codename=codename)

generator = cls.generators.get(codename, None) or cls.getGenerator(codename=codename)

from django_faker import populator

cls.populators[codename] = populator.Populator( generator )
from .populator import Populator
cls.populators[codename] = Populator(generator)

return cls.populators[codename]

# if not cls.populator:
# cls.populator= populators.Populator(
# # initialize with faker.generator.Generator instance
# FakerGenerator(
#
# getattr(settings,'FAKER_LOCALE', getattr(settings,'LANGUAGE_CODE', locale)),
#
# getattr(settings,'FAKER_PROVIDERS', providers)
# )
# )

65 changes: 65 additions & 0 deletions django_faker/constraints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# coding=utf-8
# coding=UTF-8
# Credit for constraints goes to django-autofixture:
# https://github.com/gregmuellegger/django-autofixture/blob/master/autofixture/constraints.py
# Copyright (c) 2010, Gregor Müllegger
# All rights reserved.

from django.db.models.fields import related


class InvalidConstraint(Exception):
def __init__(self, fields, *args, **kwargs):
self.fields = fields
super(InvalidConstraint, self).__init__(*args, **kwargs)


def _is_unique_field(field):
if not field.unique:
return False
if field.primary_key:
# Primary key fields should not generally be checked for unique constraints, except when the
# primary key is a OneToOne mapping to an external table not via table inheritance, in which
# case we don't want to create new objects which will overwrite existing objects.
return (isinstance(field, related.OneToOneField) and
not issubclass(field.model, field.remote_field.model))
return True


def validate_unique_constraint(instance):
error_fields = []
for field in instance._meta.fields: # pylint: disable=protected-access
if _is_unique_field(field):
value = getattr(instance, field.name)

# If the value is none and the field allows nulls, skip it
if value is None and field.null:
continue

check = {field.name: value}

if instance._meta.default_manager.filter(**check).exists(): # pylint: disable=protected-access
error_fields.append(field)
if error_fields:
raise InvalidConstraint(error_fields)


def validate_unique_together_constraint(instance):
if not instance._meta.unique_together: # pylint: disable=protected-access
return
error_fields = []
for unique_fields in instance._meta.unique_together: # pylint: disable=protected-access
check = {}
for field_name in unique_fields:
if not instance._meta.get_field(field_name).primary_key: # pylint: disable=protected-access
check[field_name] = getattr(instance, field_name)
if all(e is None for e in check.values()):
continue

if instance._meta.default_manager.filter(**check).exists(): # pylint: disable=protected-access
error_fields.extend([
instance._meta.get_field(field_name) # pylint: disable=protected-access
for field_name in unique_fields
])
if error_fields:
raise InvalidConstraint(error_fields)
Loading