Skip to content
This repository has been archived by the owner on Oct 22, 2019. It is now read-only.

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
  • Loading branch information
vandersonmota committed Sep 3, 2016
2 parents c7aa8d9 + f3e3d83 commit 6fad6f2
Show file tree
Hide file tree
Showing 17 changed files with 317 additions and 164 deletions.
84 changes: 43 additions & 41 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,50 +1,52 @@
sudo: true
language: python
python:
- "2.6"
- "2.7"
- "3.2"
- "3.4"
- "3.5"
env:
- DJANGO_VERSION=1.4.10
- DJANGO_VERSION=1.5.5
- DJANGO_VERSION=1.6
- DJANGO_VERSION=1.7
- DJANGO_VERSION=1.8.5
- DJANGO_VERSION=1.9
- DJANGO=django14
- DJANGO=django15
- DJANGO=django16
- DJANGO=django17
- DJANGO=django18
- DJANGO=django19
matrix:
include:
- python: 2.6
env: DJANGO_VERSION=1.4.10
- python: 2.6
env: DJANGO_VERSION=1.5.5
- python: 2.6
env: DJANGO_VERSION=1.6
- python: 3.2
env: DJANGO_VERSION=1.5.5
- python: 3.2
env: DJANGO_VERSION=1.6
- python: 3.2
env: DJANGO_VERSION=1.7
- python: 3.2
env: DJANGO_VERSION=1.8.5
- python: 3.4
env: DJANGO_VERSION=1.5.5
- python: 3.4
env: DJANGO_VERSION=1.6
- python: 3.4
env: DJANGO_VERSION=1.7
- python: 3.4
env: DJANGO_VERSION=1.8.5
- python: 3.4
env: DJANGO_VERSION=1.9
- python: 3.5
env: DJANGO_VERSION=1.8.5
- python: 3.5
env: DJANGO_VERSION=1.9
exclude:
- python: "2.6"
env: DJANGO=django17
- python: "2.6"
env: DJANGO=django18
- python: "2.6"
env: DJANGO=django19
- python: "3.2"
env: DJANGO=django14
- python: "3.2"
env: DJANGO=django19
- python: "3.4"
env: DJANGO=django14
- python: "3.5"
env: DJANGO=django14
- python: "3.5"
env: DJANGO=django15
- python: "3.5"
env: DJANGO=django16
- python: "3.5"
env: DJANGO=django17
before_install:
- sudo apt-get update && sudo apt-get build-dep python-imaging
install:
# Install whatever version of Django that's listed above
# Travis is currently working on
- pip install -q Django==$DJANGO_VERSION
- pip install -q Pillow
- pip install -r requirements.txt
script: python runtests.py
- pip install tox
addons:
postgresql: "9.4"
services:
- postgresql
before_script:
- psql -U postgres -c "create extension postgis"
- psql -c 'create database model_mommy;' -U postgres
- psql -c 'create database model_mommy_test;' -U postgres
script:
- TOX_TEST_PASSENV="TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH" tox -e py${TRAVIS_PYTHON_VERSION//[.]/}-$DJANGO-{postgresql}
- TOX_TEST_PASSENV="TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH" tox -e py${TRAVIS_PYTHON_VERSION//[.]/}-$DJANGO-{sqlite}
13 changes: 13 additions & 0 deletions Changelog.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
1.3.0
-----
- Django 1.10 support
- Added UUIDField, JSONField and ArrayField support
- Python 3.2 support dropped
- Fix nested m2m recipes
- Removes deprecated API
- Fix related() with nullable foreignkeys on django 1.9
- Custom Mommy class implementation support

1.2.6
-----
First changelog
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ include model_mommy/mock_file.txt
include model_mommy/mock-img.jpeg
include README.rst
include requirements.txt
include tox.ini
recursive-include tests *.py
23 changes: 23 additions & 0 deletions docs/source/how_mommy_behaves.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,26 @@ Examples:
MOMMY_CUSTOM_FIELDS_GEN = {
'test.generic.fields.CustomField': 'code.path.gen_func',
}
Customizing Mommy
-------------

In some rare cases, you might need to customize the way Mommy behaves.
This can be achieved by creating a new class and specifying it in your settings files. It is likely that you will want to extend Mommy, however the minimum requirement is that the custom class have `make` and `prepare` functions.
In order for the custom class to be used, make sure to use the `model_mommy.mommy.make` and `model_mommy.mommy.prepare` functions, and not `model_mommy.mommy.Mommy` directly.

Examples:

.. code-block:: python
# in the module code.path:
class CustomMommy(mommy.Mommy)
def get_fields(self):
return [
field
for field in super(CustomMommy, self).get_fields()
if not field isinstance CustomField
]
# in your settings.py file:
MOMMY_CUSTOM_CLASS = 'code.path.CustomMommy'
2 changes: 1 addition & 1 deletion model_mommy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#coding:utf-8
__version__ = '1.2.6'
__version__ = '1.3.0'
__title__ = 'model_mommy'
__author__ = 'Vanderson Mota'
__license__ = 'Apache 2.0'
8 changes: 8 additions & 0 deletions model_mommy/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@ class AmbiguousModelName(Exception):

class InvalidQuantityException(Exception):
pass


class CustomMommyNotFound(Exception):
pass


class InvalidCustomMommy(Exception):
pass
23 changes: 21 additions & 2 deletions model_mommy/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"""

import string
import warnings
from decimal import Decimal
from os.path import abspath, join, dirname
from random import randint, choice, random
Expand Down Expand Up @@ -84,8 +85,11 @@ def gen_float():

def gen_decimal(max_digits, decimal_places):
num_as_str = lambda x: ''.join([str(randint(0, 9)) for i in range(x)])
return Decimal("%s.%s" % (num_as_str(max_digits - decimal_places - 1),
if decimal_places:
return Decimal("%s.%s" % (num_as_str(max_digits - decimal_places - 1),
num_as_str(decimal_places)))
return Decimal(num_as_str(max_digits))

gen_decimal.required = ['max_digits', 'decimal_places']


Expand Down Expand Up @@ -163,5 +167,20 @@ def gen_content_type():
except ImportError:
# Deprecated
from django.db.models import get_models
try:
return ContentType.objects.get_for_model(choice(get_models()))
except AssertionError:
warnings.warn('Database access disabled, returning ContentType raw instance')
return ContentType()

def gen_uuid():
import uuid
return uuid.uuid4()


def gen_array():
return []


return ContentType.objects.get_for_model(choice(get_models()))
def gen_json():
return {}
86 changes: 52 additions & 34 deletions model_mommy/mommy.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@
except ImportError:
DurationField = None

try:
from django.db.models import UUIDField
except ImportError:
UUIDField = None

try:
from django.contrib.postgres.fields import ArrayField
except ImportError:
ArrayField = None

try:
from django.contrib.postgres.fields import JSONField
except ImportError:
JSONField = None

from django.core.exceptions import ValidationError
from django.core.validators import validate_ipv4_address
try:
Expand All @@ -56,7 +71,8 @@ def validate_ipv6_address(v):
validate_ipv46_address = validate_ipv6_address

from . import generators
from .exceptions import ModelNotFound, AmbiguousModelName, InvalidQuantityException, RecipeIteratorEmpty
from .exceptions import (ModelNotFound, AmbiguousModelName, InvalidQuantityException, RecipeIteratorEmpty,
CustomMommyNotFound, InvalidCustomMommy)
from .utils import import_from_str, import_if_str

from six import string_types, advance_iterator, PY3
Expand Down Expand Up @@ -88,7 +104,7 @@ def make(model, _quantity=None, make_m2m=False, **attrs):
It fill the fields with random values or you can specify
which fields you want to define its values by yourself.
"""
mommy = Mommy(model, make_m2m=make_m2m)
mommy = Mommy.create(model, make_m2m=make_m2m)
if _valid_quantity(_quantity):
raise InvalidQuantityException

Expand All @@ -105,7 +121,7 @@ def prepare(model, _quantity=None, **attrs):
It fill the fields with random values or you can specify
which fields you want to define its values by yourself.
"""
mommy = Mommy(model)
mommy = Mommy.create(model)
if _valid_quantity(_quantity):
raise InvalidQuantityException

Expand Down Expand Up @@ -171,7 +187,12 @@ def __m2m_generator(model, **attrs):
default_mapping[BinaryField] = generators.gen_byte_string
if DurationField:
default_mapping[DurationField] = generators.gen_interval

if UUIDField:
default_mapping[UUIDField] = generators.gen_uuid
if ArrayField:
default_mapping[ArrayField] = generators.gen_array
if JSONField:
default_mapping[JSONField] = generators.gen_json

class ModelFinder(object):
'''
Expand Down Expand Up @@ -255,6 +276,25 @@ def is_iterator(value):
else:
return hasattr(value, 'next')

def _custom_mommy_class():
"""
Returns custom mommy class specified by MOMMY_CUSTOM_CLASS in the django
settings, or None if no custom class is defined
"""
custom_class_string = getattr(settings, 'MOMMY_CUSTOM_CLASS', None)
if custom_class_string is None:
return None

try:
mommy_class = import_from_str(custom_class_string)

for required_function_name in ['make', 'prepare']:
if not hasattr(mommy_class, required_function_name):
raise InvalidCustomMommy('Custom Mommy classes must have a "%s" function' % required_function_name)

return mommy_class
except ImportError:
raise CustomMommyNotFound("Could not find custom mommy class '%s'" % custom_class_string)

class Mommy(object):
attr_mapping = {}
Expand All @@ -264,6 +304,14 @@ class Mommy(object):
# rebuilding the model cache for every make_* or prepare_* call.
finder = ModelFinder()

@classmethod
def create(cls, model, make_m2m=False):
"""
Factory which creates the mommy class defined by the MOMMY_CUSTOM_CLASS setting
"""
mommy_class = _custom_mommy_class() or cls
return mommy_class(model, make_m2m)

def __init__(self, model, make_m2m=False):
self.make_m2m = make_m2m
self.m2m_dict = {}
Expand Down Expand Up @@ -497,33 +545,3 @@ def filter_rel_attrs(field_name, **rel_attrs):
clean_dict[k] = v

return clean_dict


### DEPRECATED METHODS (should be removed in the future)
def make_many(model, quantity=None, **attrs):
msg = "make_many is deprecated. You should use make with _quantity parameter."
warnings.warn(msg, DeprecationWarning)
quantity = quantity or MAX_MANY_QUANTITY
mommy = Mommy(model)
return [mommy.make(**attrs) for i in range(quantity)]


def make_one(model, make_m2m=False, **attrs):
msg = "make_one is deprecated. You should use the method make instead."
warnings.warn(msg, DeprecationWarning)
mommy = Mommy(model, make_m2m=make_m2m)
return mommy.make(**attrs)


def prepare_one(model, **attrs):
msg = "prepare_one is deprecated. You should use the method prepare instead."
warnings.warn(msg, DeprecationWarning)
mommy = Mommy(model)
return mommy.prepare(**attrs)


def make_many_from_recipe(mommy_recipe_name, quantity=None, **new_attrs):
msg = "make_many_from_recipe is deprecated. You should use the method make_recipe with the _quantity parameter instead."
warnings.warn(msg, DeprecationWarning)
quantity = quantity or MAX_MANY_QUANTITY
return [make_recipe(mommy_recipe_name, **new_attrs) for x in range(quantity)]
9 changes: 4 additions & 5 deletions model_mommy/recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def _mapping(self, new_attrs):
recipe_attrs = mommy.filter_rel_attrs(k, **a)
mapping[k] = v.recipe.make(**recipe_attrs)
elif isinstance(v, related):
mapping[k] = v.prepare()
mapping[k] = v.make()
mapping.update(new_attrs)
mapping.update(rel_fields_attrs)
return mapping
Expand Down Expand Up @@ -151,10 +151,9 @@ def __init__(self, *args):
else:
raise TypeError('Not a recipe')

def prepare(self):
def make(self):
"""
Django related manager saves related set.
No need to persist at first
Persists objects to m2m relation
"""
return [m.prepare() for m in self.related]
return [m.make() for m in self.related]

Loading

0 comments on commit 6fad6f2

Please sign in to comment.