[Python-modules-commits] [python-model-mommy] 01/03: Import python-model-mommy_1.3.1.orig.tar.gz

Edward Betts edward at moszumanska.debian.org
Tue Jan 3 21:03:30 UTC 2017


This is an automated email from the git hooks/post-receive script.

edward pushed a commit to branch master
in repository python-model-mommy.

commit 9a25ea96845ae9e157f8919dc3ecb4f49024f7a5
Author: Edward Betts <edward at 4angle.com>
Date:   Tue Jan 3 19:28:54 2017 +0000

    Import python-model-mommy_1.3.1.orig.tar.gz
---
 PKG-INFO                                     |   9 +-
 README.rst                                   |   7 +
 model_mommy.egg-info/PKG-INFO                |   9 +-
 model_mommy.egg-info/SOURCES.txt             |   1 +
 model_mommy/__init__.py                      |   2 +-
 model_mommy/generators.py                    | 243 +++++++--------------
 model_mommy/mommy.py                         | 305 +++++++++------------------
 model_mommy/{generators.py => random_gen.py} |  52 +++++
 tox.ini                                      |   3 +-
 9 files changed, 264 insertions(+), 367 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 9f7e340..c3e844f 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: model_mommy
-Version: 1.3.0
+Version: 1.3.1
 Summary: Smart object creation facility for Django.
 Home-page: http://github.com/vandersonmota/model_mommy
 Author: vandersonmota
@@ -41,6 +41,13 @@ Description: ============================================
         
         *     http://model-mommy.readthedocs.org/
         
+        
+        Maintainers
+        ===========
+        
+        *     [Vanderson Mota (creator)](https://github.com/vandersonmota/)
+        *     [Bernardo Fontes](https://github.com/berinhard/)
+        
 Keywords: django testing factory python
 Platform: UNKNOWN
 Classifier: Framework :: Django
diff --git a/README.rst b/README.rst
index f65051f..1746807 100644
--- a/README.rst
+++ b/README.rst
@@ -32,3 +32,10 @@ Usage and Info
 ==============
 
 *     http://model-mommy.readthedocs.org/
+
+
+Maintainers
+===========
+
+*     [Vanderson Mota (creator)](https://github.com/vandersonmota/)
+*     [Bernardo Fontes](https://github.com/berinhard/)
diff --git a/model_mommy.egg-info/PKG-INFO b/model_mommy.egg-info/PKG-INFO
index 1aa1c36..e384673 100644
--- a/model_mommy.egg-info/PKG-INFO
+++ b/model_mommy.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: model-mommy
-Version: 1.3.0
+Version: 1.3.1
 Summary: Smart object creation facility for Django.
 Home-page: http://github.com/vandersonmota/model_mommy
 Author: vandersonmota
@@ -41,6 +41,13 @@ Description: ============================================
         
         *     http://model-mommy.readthedocs.org/
         
+        
+        Maintainers
+        ===========
+        
+        *     [Vanderson Mota (creator)](https://github.com/vandersonmota/)
+        *     [Bernardo Fontes](https://github.com/berinhard/)
+        
 Keywords: django testing factory python
 Platform: UNKNOWN
 Classifier: Framework :: Django
diff --git a/model_mommy.egg-info/SOURCES.txt b/model_mommy.egg-info/SOURCES.txt
index 803c2f4..bb2db79 100644
--- a/model_mommy.egg-info/SOURCES.txt
+++ b/model_mommy.egg-info/SOURCES.txt
@@ -9,6 +9,7 @@ model_mommy/generators.py
 model_mommy/mock-img.jpeg
 model_mommy/mock_file.txt
 model_mommy/mommy.py
+model_mommy/random_gen.py
 model_mommy/recipe.py
 model_mommy/timezone.py
 model_mommy/utils.py
diff --git a/model_mommy/__init__.py b/model_mommy/__init__.py
index 4687d80..73b3d5a 100644
--- a/model_mommy/__init__.py
+++ b/model_mommy/__init__.py
@@ -1,5 +1,5 @@
 #coding:utf-8
-__version__ = '1.3.0'
+__version__ = '1.3.1'
 __title__ = 'model_mommy'
 __author__ = 'Vanderson Mota'
 __license__ = 'Apache 2.0'
diff --git a/model_mommy/generators.py b/model_mommy/generators.py
index 1900d7b..30b28b5 100644
--- a/model_mommy/generators.py
+++ b/model_mommy/generators.py
@@ -1,186 +1,107 @@
-# -*- coding:utf-8 -*-
-"""
-Generators are callables that return a value used to populate a field.
-
-If this callable has a `required` attribute (a list, mostly), for each item in
-the list, if the item is a string, the field attribute with the same name will
-be fetched from the field and used as argument for the generator. If it is a
-callable (which will receive `field` as first argument), it should return a
-list in the format (key, value) where key is the argument name for generator
-and value is the value for that argument.
-"""
-
-import string
-import warnings
-from decimal import Decimal
-from os.path import abspath, join, dirname
-from random import randint, choice, random
-from django import VERSION
-from django.core.files.base import ContentFile
-import six
-
-from model_mommy.timezone import now
-
-# Map unicode to str in Python 2.x since bytes can be used
-try:
-    str = unicode
-except NameError:
-    pass
-
-
-MAX_LENGTH = 300
-# Using sys.maxint here breaks a bunch of tests when running against a
-# Postgres database.
-MAX_INT = 10000
-
-def get_content_file(content, name):
-    if VERSION < (1, 4):
-        return ContentFile(content)
-    else:
-        return ContentFile(content, name=name)
-
-def gen_file_field():
-    name = 'mock_file.txt'
-    file_path = abspath(join(dirname(__file__), name))
-    with open(file_path, 'rb') as f:
-        return get_content_file(f.read(), name=name)
-
-def gen_image_field():
-    name = 'mock-img.jpeg'
-    file_path = abspath(join(dirname(__file__), name))
-    with open(file_path, 'rb') as f:
-        return get_content_file(f.read(), name=name)
-
-
-def gen_from_list(L):
-    '''Makes sure all values of the field are generated from the list L
-    Usage:
-    from mommy import Mommy
-    class KidMommy(Mommy):
-      attr_mapping = {'some_field':gen_from_list([A, B, C])}
-    '''
-    return lambda: choice(list(L))
-
-# -- DEFAULT GENERATORS --
-
-
-def gen_from_choices(C):
-    choice_list = []
-    for value, label in C:
-        if isinstance(label, (list, tuple)):
-            for val, lbl in label:
-                choice_list.append(val)
-        else:
-            choice_list.append(value)
-    return gen_from_list(choice_list)
-
-
-def gen_integer(min_int=-MAX_INT, max_int=MAX_INT):
-    return randint(min_int, max_int)
-
-
-def gen_float():
-    return random() * gen_integer()
-
-
-def gen_decimal(max_digits, decimal_places):
-    num_as_str = lambda x: ''.join([str(randint(0, 9)) for i in range(x)])
-    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))
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import (
+    CharField, EmailField, SlugField, TextField, URLField,
+    DateField, DateTimeField, TimeField,
+    IntegerField, SmallIntegerField, PositiveIntegerField,
+    PositiveSmallIntegerField, BooleanField, DecimalField,
+    FloatField, FileField, ImageField, IPAddressField,
+    ForeignKey, ManyToManyField, OneToOneField)
 
-gen_decimal.required = ['max_digits', 'decimal_places']
+from model_mommy.utils import import_if_str
 
+try:
+    from django.db.models import BigIntegerField
+except ImportError:
+    BigIntegerField = IntegerField
 
-def gen_date():
-    return now().date()
-
-
-def gen_datetime():
-    return now()
-
-
-def gen_time():
-    return now().time()
-
-
-def gen_string(max_length):
-    return str(''.join(choice(string.ascii_letters) for i in range(max_length)))
-gen_string.required = ['max_length']
-
-
-def gen_slug(max_length):
-    valid_chars = string.ascii_letters + string.digits + '_-'
-    return str(''.join(choice(valid_chars) for i in range(max_length)))
-gen_slug.required = ['max_length']
-
+try:
+    from django.db.models import GenericIPAddressField
+except ImportError:
+    GenericIPAddressField = IPAddressField
 
-def gen_text():
-    return gen_string(MAX_LENGTH)
+try:
+    from django.db.models import BinaryField
+except ImportError:
+    BinaryField = None
 
+try:
+    from django.db.models import DurationField
+except ImportError:
+    DurationField = None
 
-def gen_boolean():
-    return choice((True, False))
+try:
+    from django.db.models import UUIDField
+except ImportError:
+    UUIDField = None
 
+try:
+    from django.contrib.postgres.fields import ArrayField
+except ImportError:
+    ArrayField = None
 
-def gen_url():
-    return str('http://www.%s.com/' % gen_string(30))
+try:
+    from django.contrib.postgres.fields import JSONField
+except ImportError:
+    JSONField = None
 
+from . import random_gen
+default_mapping = {
+    ForeignKey: random_gen.gen_related,
+    OneToOneField: random_gen.gen_related,
+    ManyToManyField: random_gen.gen_m2m,
 
-def gen_email():
-    return "%s at example.com" % gen_string(10)
+    BooleanField: random_gen.gen_boolean,
+    IntegerField: random_gen.gen_integer,
+    BigIntegerField: random_gen.gen_integer,
+    SmallIntegerField: random_gen.gen_integer,
 
+    PositiveIntegerField: lambda: random_gen.gen_integer(0),
+    PositiveSmallIntegerField: lambda: random_gen.gen_integer(0),
 
-def gen_ipv6():
-    return ":".join(format(randint(1, 65535), 'x') for i in range(8))
+    FloatField: random_gen.gen_float,
+    DecimalField: random_gen.gen_decimal,
 
+    CharField: random_gen.gen_string,
+    TextField: random_gen.gen_text,
+    SlugField: random_gen.gen_slug,
 
-def gen_ipv4():
-    return ".".join(str(randint(1, 255)) for i in range(4))
+    DateField: random_gen.gen_date,
+    DateTimeField: random_gen.gen_datetime,
+    TimeField: random_gen.gen_time,
 
+    URLField: random_gen.gen_url,
+    EmailField: random_gen.gen_email,
+    IPAddressField: random_gen.gen_ipv4,
+    GenericIPAddressField: random_gen.gen_ip,
+    FileField: random_gen.gen_file_field,
+    ImageField: random_gen.gen_image_field,
 
-def gen_ipv46():
-    ip_gen = choice([gen_ipv4, gen_ipv6])
-    return ip_gen()
+    ContentType: random_gen.gen_content_type,
+}
 
+if BinaryField:
+    default_mapping[BinaryField] = random_gen.gen_byte_string
+if DurationField:
+    default_mapping[DurationField] = random_gen.gen_interval
+if UUIDField:
+    default_mapping[UUIDField] = random_gen.gen_uuid
+if ArrayField:
+    default_mapping[ArrayField] = random_gen.gen_array
+if JSONField:
+    default_mapping[JSONField] = random_gen.gen_json
 
-def gen_byte_string(max_length=16):
-    generator = (randint(0, 255) for x in range(max_length))
-    if six.PY2:
-        return "".join(map(chr, generator))
-    elif six.PY3:
-        return bytes(generator)
 
-def gen_interval(interval_key='milliseconds'):
-    from datetime import timedelta
-    interval = gen_integer()
-    kwargs = {interval_key: interval}
-    return timedelta(**kwargs)
+def get_type_mapping():
+    mapping = default_mapping.copy()
+    return mapping.copy()
 
-def gen_content_type():
-    from django.contrib.contenttypes.models import ContentType
-    try:
-        # for >= 1.7
-        from django.apps import apps
-        get_models = apps.get_models
-    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()
+user_mapping = {}
 
 
-def gen_array():
-    return []
+def add(field, func):
+    user_mapping[import_if_str(field)] = import_if_str(func)
 
 
-def gen_json():
-    return {}
+def get(field):
+    return user_mapping.get(field)
diff --git a/model_mommy/mommy.py b/model_mommy/mommy.py
index 76b4577..0862590 100644
--- a/model_mommy/mommy.py
+++ b/model_mommy/mommy.py
@@ -1,120 +1,64 @@
 # -*- coding: utf-8 -*-
-import warnings
+from os.path import dirname, join
+
+import django
 
 from django.conf import settings
 from django.contrib.contenttypes.models import ContentType
-import django
 if django.VERSION >= (1, 7):
     from django.apps import apps
     get_model = apps.get_model
     from django.contrib.contenttypes.fields import GenericRelation
 else:
-    from django.db.models.loading import get_model
-    from django.db.models.loading import cache
+    from django.db.models.loading import get_model, cache
     from django.contrib.contenttypes.generic import GenericRelation
+
 from django.db.models.base import ModelBase
-from django.db.models import (
-    CharField, EmailField, SlugField, TextField, URLField,
-    DateField, DateTimeField, TimeField,
-    AutoField, IntegerField, SmallIntegerField,
-    PositiveIntegerField, PositiveSmallIntegerField,
-    BooleanField, DecimalField, FloatField,
-    FileField, ImageField, Field, IPAddressField,
-    ForeignKey, ManyToManyField, OneToOneField)
+from django.db.models import ForeignKey, ManyToManyField, OneToOneField, Field, AutoField, BooleanField
 if django.VERSION >= (1, 9):
     from django.db.models.fields.related import ReverseManyToOneDescriptor as ForeignRelatedObjectsDescriptor
 else:
     from django.db.models.fields.related import ForeignRelatedObjectsDescriptor
 from django.db.models.fields.proxy import OrderWrt
-try:
-    from django.db.models import BigIntegerField
-except ImportError:
-    BigIntegerField = IntegerField
-
-try:
-    from django.db.models import GenericIPAddressField
-except ImportError:
-    GenericIPAddressField = IPAddressField
-
-try:
-    from django.db.models import BinaryField
-except ImportError:
-    BinaryField = None
-
-try:
-    from django.db.models import DurationField
-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:
-    from django.core.validators import validate_ipv6_address, validate_ipv46_address
-except ImportError:
-    def validate_ipv6_address(v):
-        raise ValidationError()
-    validate_ipv46_address = validate_ipv6_address
 
 from . import generators
+from . import random_gen
 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
 
 recipes = None
 
 # FIXME: use pkg_resource
-from os.path import dirname, join
 mock_file_jpeg = join(dirname(__file__), 'mock-img.jpeg')
 mock_file_txt = join(dirname(__file__), 'mock_file.txt')
 
-
-#TODO: improve related models handling
-def _fk_model(field):
-    try:
-        return ('model', field.related_model)
-    except AttributeError:
-        return ('model', field.related.parent_model)
-foreign_key_required = [_fk_model]
-
 MAX_MANY_QUANTITY = 5
 
+
 def _valid_quantity(quantity):
     return quantity is not None and (not isinstance(quantity, int) or quantity < 1)
 
-def make(model, _quantity=None, make_m2m=False, **attrs):
+
+def make(model, _quantity=None, make_m2m=False, _save_kwargs=None, **attrs):
     """
     Creates a persisted instance from a given model its associated models.
     It fill the fields with random values or you can specify
     which fields you want to define its values by yourself.
     """
+    _save_kwargs = _save_kwargs or {}
     mommy = Mommy.create(model, make_m2m=make_m2m)
     if _valid_quantity(_quantity):
         raise InvalidQuantityException
 
     if _quantity:
-        return [mommy.make(**attrs) for i in range(_quantity)]
+        return [mommy.make(_save_kwargs=_save_kwargs, **attrs) for i in range(_quantity)]
     else:
-        return mommy.make(**attrs)
+        return mommy.make(_save_kwargs=_save_kwargs, **attrs)
 
 
-def prepare(model, _quantity=None, **attrs):
+def prepare(model, _quantity=None, _save_related=False, **attrs):
     """
     Creates BUT DOESN'T persist an instance from a given model its
     associated models.
@@ -126,73 +70,21 @@ def prepare(model, _quantity=None, **attrs):
         raise InvalidQuantityException
 
     if _quantity:
-        return [mommy.prepare(**attrs) for i in range(_quantity)]
+        return [mommy.prepare(_save_related=_save_related, **attrs) for i in range(_quantity)]
     else:
-        return mommy.prepare(**attrs)
+        return mommy.prepare(_save_related=_save_related, **attrs)
 
-make.prepare = prepare
 
 def _recipe(name):
     app, recipe_name = name.rsplit('.', 1)
     return import_from_str('.'.join((app, 'mommy_recipes', recipe_name)))
 
+
 def make_recipe(mommy_recipe_name, _quantity=None, **new_attrs):
     return _recipe(mommy_recipe_name).make(_quantity=_quantity, **new_attrs)
 
-def prepare_recipe(mommy_recipe_name, _quantity=None, **new_attrs):
-    return _recipe(mommy_recipe_name).prepare(_quantity=_quantity, **new_attrs)
-
-
-def __m2m_generator(model, **attrs):
-    return make(model, _quantity=MAX_MANY_QUANTITY, **attrs)
-
-make.required = foreign_key_required
-prepare.required = foreign_key_required
-__m2m_generator.required = foreign_key_required
-
-default_mapping = {
-    BooleanField: generators.gen_boolean,
-    IntegerField: generators.gen_integer,
-    BigIntegerField: generators.gen_integer,
-    SmallIntegerField: generators.gen_integer,
-
-    PositiveIntegerField: lambda: generators.gen_integer(0),
-    PositiveSmallIntegerField: lambda: generators.gen_integer(0),
-
-    FloatField: generators.gen_float,
-    DecimalField: generators.gen_decimal,
-
-    CharField: generators.gen_string,
-    TextField: generators.gen_text,
-    SlugField: generators.gen_slug,
-
-    ForeignKey: make,
-    OneToOneField: make,
-    ManyToManyField: __m2m_generator,
-
-    DateField: generators.gen_date,
-    DateTimeField: generators.gen_datetime,
-    TimeField: generators.gen_time,
-
-    URLField: generators.gen_url,
-    EmailField: generators.gen_email,
-    IPAddressField: generators.gen_ipv4,
-    FileField: generators.gen_file_field,
-    ImageField: generators.gen_image_field,
-
-    ContentType: generators.gen_content_type,
-}
-
-if BinaryField:
-    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
+def prepare_recipe(mommy_recipe_name, _quantity=None, _save_related=False, **new_attrs):
+    return _recipe(mommy_recipe_name).prepare(_quantity=_quantity, _save_related=_save_related, **new_attrs)
 
 class ModelFinder(object):
     '''
@@ -276,6 +168,7 @@ 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
@@ -296,6 +189,7 @@ def _custom_mommy_class():
     except ImportError:
         raise CustomMommyNotFound("Could not find custom mommy class '%s'" % custom_class_string)
 
+
 class Mommy(object):
     attr_mapping = {}
     type_mapping = None
@@ -315,6 +209,10 @@ class Mommy(object):
     def __init__(self, model, make_m2m=False):
         self.make_m2m = make_m2m
         self.m2m_dict = {}
+        self.iterator_attrs = {}
+        self.model_attrs = {}
+        self.rel_attrs = {}
+        self.rel_fields = []
 
         if isinstance(model, ModelBase):
             self.model = model
@@ -324,74 +222,61 @@ class Mommy(object):
         self.init_type_mapping()
 
     def init_type_mapping(self):
-        self.type_mapping = default_mapping.copy()
+        self.type_mapping = generators.get_type_mapping()
         generators_from_settings = getattr(settings, 'MOMMY_CUSTOM_FIELDS_GEN', {})
         for k, v in generators_from_settings.items():
             field_class = import_if_str(k)
             generator = import_if_str(v)
             self.type_mapping[field_class] = generator
 
-    def make(self, **attrs):
+    def make(self, _save_kwargs=None, **attrs):
         '''Creates and persists an instance of the model
         associated with Mommy instance.'''
-        return self._make(commit=True, **attrs)
+        return self._make(commit=True, commit_related=True, _save_kwargs=_save_kwargs, **attrs)
 
-    def prepare(self, **attrs):
+    def prepare(self, _save_related=False, **attrs):
         '''Creates, but does not persist, an instance of the model
         associated with Mommy instance.'''
-        return self._make(commit=False, **attrs)
+        return self._make(commit=False, commit_related=_save_related, **attrs)
 
     def get_fields(self):
         return self.model._meta.fields + self.model._meta.many_to_many
 
-    def _make(self, commit=True, **attrs):
-        fill_in_optional = attrs.pop('_fill_optional', False)
-        is_rel_field = lambda x: '__' in x
-        iterator_attrs = dict((k, v) for k, v in attrs.items() if is_iterator(v))
-        model_attrs = dict((k, v) for k, v in attrs.items() if not is_rel_field(k))
-        self.rel_attrs = dict((k, v) for k, v in attrs.items() if is_rel_field(k))
-        self.rel_fields = [x.split('__')[0] for x in self.rel_attrs.keys() if is_rel_field(x)]
-
-        for field in self.get_fields():
-            # check for fill optional argument
-            if isinstance(fill_in_optional, bool):
-                field.fill_optional = fill_in_optional
-            else:
-                field.fill_optional = field.name in fill_in_optional
-
-            # Skip links to parent so parent is not created twice.
-            if isinstance(field, OneToOneField) and field.rel.parent_link:
-                continue
+    def get_related(self):
+        if django.VERSION[:2] <= (1, 7):
+            return self.model._meta.get_all_related_objects()
+        else:
+            return [r for r in self.model._meta.related_objects if not r.many_to_many]
 
-            field_value_not_defined = field.name not in model_attrs
+    def _make(self, commit=True, commit_related=True, _save_kwargs=None, **attrs):
+        _save_kwargs = _save_kwargs or {}
 
-            if isinstance(field, (AutoField, GenericRelation, OrderWrt)):
+        self._clean_attrs(attrs)
+        for field in self.get_fields():
+            if self._skip_field(field):
                 continue
 
-            if all([field.name not in model_attrs, field.name not in self.rel_fields, field.name not in self.attr_mapping]):
-                # Django is quirky in that BooleanFields are always "blank", but have no default default.
-                if not field.fill_optional and (not issubclass(field.__class__, Field) or field.has_default() or (field.blank and not isinstance(field, BooleanField))):
-                    continue
-
             if isinstance(field, ManyToManyField):
-                if field.name not in model_attrs:
+                if field.name not in self.model_attrs:
                     self.m2m_dict[field.name] = self.m2m_value(field)
                 else:
-                    self.m2m_dict[field.name] = model_attrs.pop(field.name)
-            elif field_value_not_defined:
-                if field.name not in self.rel_fields and (field.null and not field.fill_optional):
-                    continue
-                else:
-                    model_attrs[field.name] = self.generate_value(field, commit)
-            elif callable(model_attrs[field.name]):
-                model_attrs[field.name] = model_attrs[field.name]()
-            elif field.name in iterator_attrs:
+                    self.m2m_dict[field.name] = self.model_attrs.pop(field.name)
+            elif field.name not in self.model_attrs:
+                self.model_attrs[field.name] = self.generate_value(field, commit_related)
+            elif callable(self.model_attrs[field.name]):
+                self.model_attrs[field.name] = self.model_attrs[field.name]()
+            elif field.name in self.iterator_attrs:
                 try:
-                    model_attrs[field.name] = advance_iterator(iterator_attrs[field.name])
+                    self.model_attrs[field.name] = advance_iterator(self.iterator_attrs[field.name])
                 except StopIteration:
                     raise RecipeIteratorEmpty('{0} iterator is empty.'.format(field.name))
 
-        return self.instance(model_attrs, _commit=commit)
+        instance = self.instance(self.model_attrs, _commit=commit, _save_kwargs=_save_kwargs)
+        if commit:
+            for related in self.get_related():
+                self.create_by_related_name(instance, related)
+
+        return instance
 
     def m2m_value(self, field):
         if field.name in self.rel_fields:
@@ -400,7 +285,7 @@ class Mommy(object):
             return []
         return self.generate_value(field)
 
-    def instance(self, attrs, _commit):
+    def instance(self, attrs, _commit, _save_kwargs):
         one_to_many_keys = {}
         for k in tuple(attrs.keys()):
             field = getattr(self.model, k, None)
@@ -410,11 +295,55 @@ class Mommy(object):
         instance = self.model(**attrs)
         # m2m only works for persisted instances
         if _commit:
-            instance.save()
+            instance.save(**_save_kwargs)
             self._handle_one_to_many(instance, one_to_many_keys)
             self._handle_m2m(instance)
         return instance
 
+    def create_by_related_name(self, instance, related):
+        rel_name = related.get_accessor_name()
+        if rel_name not in self.rel_fields:
+            return
+
+        kwargs = filter_rel_attrs(rel_name, **self.rel_attrs)
+        kwargs[related.field.name] = instance
+        kwargs['model'] = related.field.model
+
+        make(**kwargs)
+
+    def _clean_attrs(self, attrs):
+        self.fill_in_optional = attrs.pop('_fill_optional', False)
+        is_rel_field = lambda x: '__' in x
+        self.iterator_attrs = dict((k, v) for k, v in attrs.items() if is_iterator(v))
+        self.model_attrs = dict((k, v) for k, v in attrs.items() if not is_rel_field(k))
+        self.rel_attrs = dict((k, v) for k, v in attrs.items() if is_rel_field(k))
+        self.rel_fields = [x.split('__')[0] for x in self.rel_attrs.keys() if is_rel_field(x)]
+
+    def _skip_field(self, field):
+        # check for fill optional argument
+        if isinstance(self.fill_in_optional, bool):
+            field.fill_optional = self.fill_in_optional
+        else:
+            field.fill_optional = field.name in self.fill_in_optional
+
+        # Skip links to parent so parent is not created twice.
+        if isinstance(field, OneToOneField) and field.rel.parent_link:
+            return True
+
+        if isinstance(field, (AutoField, GenericRelation, OrderWrt)):
+            return True
+
+        if all([field.name not in self.model_attrs, field.name not in self.rel_fields, field.name not in self.attr_mapping]):
+            # Django is quirky in that BooleanFields are always "blank", but have no default default.
+            if not field.fill_optional and (not issubclass(field.__class__, Field) or field.has_default() or (field.blank and not isinstance(field, BooleanField))):
+                return True
+
+        if field.name not in self.model_attrs:
+            if field.name not in self.rel_fields and (field.null and not field.fill_optional):
+                return True
+
+        return False
+
     def _handle_one_to_many(self, instance, attrs):
         for k, v in attrs.items():
             if django.VERSION >= (1, 9):
@@ -442,33 +371,6 @@ class Mommy(object):
                     }
                     make(through_model, **base_kwargs)
 
-
-    def _ip_generator(self, field):
-        protocol = getattr(field, 'protocol', '').lower()
-
-        if not protocol:
-            field_validator = field.default_validators[0]
-            dummy_ipv4 = '1.1.1.1'
-            dummy_ipv6 = 'FE80::0202:B3FF:FE1E:8329'
-            try:
-                field_validator(dummy_ipv4)
-                field_validator(dummy_ipv6)
-                generator = generators.gen_ipv46
-            except ValidationError:
-                try:
-                    field_validator(dummy_ipv4)
-                    generator = generators.gen_ipv4
-                except ValidationError:
-                    generator = generators.gen_ipv6
-        elif protocol == 'ipv4':
-            generator = generators.gen_ipv4
-        elif protocol == 'ipv6':
-            generator = generators.gen_ipv6
-        else:
-            generator = generators.gen_ipv46
-
-        return generator
-
     def generate_value(self, field, commit=True):
         '''
         Calls the generator associated with a field passing all required args.
@@ -486,13 +388,13 @@ class Mommy(object):
         if field.name in self.attr_mapping:
             generator = self.attr_mapping[field.name]
         elif getattr(field, 'choices'):
-            generator = generators.gen_from_choices(field.choices)
+            generator = random_gen.gen_from_choices(field.choices)
         elif isinstance(field, ForeignKey) and issubclass(field.rel.to, ContentType):
             generator = self.type_mapping[ContentType]
         elif field.__class__ in self.type_mapping:
             generator = self.type_mapping[field.__class__]
-        elif isinstance(field, GenericIPAddressField):
-            generator = self._ip_generator(field)
+        elif generators.get(field.__class__):
+            generator = generators.get(field.__class__)
         else:
             raise TypeError('%s is not supported by mommy.' % field.__class__)
 
@@ -533,6 +435,7 @@ def get_required_values(generator, field):
 
     return rt
 
+
 def filter_rel_attrs(field_name, **rel_attrs):
     clean_dict = {}
 
diff --git a/model_mommy/generators.py b/model_mommy/random_gen.py
similarity index 76%
copy from model_mommy/generators.py
copy to model_mommy/random_gen.py
index 1900d7b..8676c19 100644
--- a/model_mommy/generators.py
+++ b/model_mommy/random_gen.py
@@ -17,6 +17,7 @@ from os.path import abspath, join, dirname
 from random import randint, choice, random
 from django import VERSION
 from django.core.files.base import ContentFile
+from django.core.exceptions import ValidationError
 import six
 
 from model_mommy.timezone import now
@@ -144,6 +145,32 @@ def gen_ipv46():
     ip_gen = choice([gen_ipv4, gen_ipv6])
     return ip_gen()
 
+def gen_ip(protocol, default_validators):
+    protocol = (protocol or '').lower()
+
+    if not protocol:
+        field_validator = default_validators[0]
+        dummy_ipv4 = '1.1.1.1'
+        dummy_ipv6 = 'FE80::0202:B3FF:FE1E:8329'
+        try:
+            field_validator(dummy_ipv4)
+            field_validator(dummy_ipv6)
+            generator = gen_ipv46
+        except ValidationError:
+            try:
+                field_validator(dummy_ipv4)
+                generator = gen_ipv4
+            except ValidationError:
+                generator = gen_ipv6
+    elif protocol == 'ipv4':
+        generator = gen_ipv4
+    elif protocol == 'ipv6':
+        generator = gen_ipv6
+    else:
+        generator = gen_ipv46
+
+    return generator()
+gen_ip.required = ['protocol', 'default_validators']
 
 def gen_byte_string(max_length=16):
     generator = (randint(0, 255) for x in range(max_length))
@@ -184,3 +211,28 @@ def gen_array():
 
 def gen_json():
     return {}
+
+
+def _fk_model(field):
+    try:
+        return ('model', field.related_model)
+    except AttributeError:
+        return ('model', field.related.parent_model)
+
+
+def _prepare_related(model, **attrs):
+    from .mommy import prepare
+    return prepare(model, **attrs)
+
+
+def gen_related(model, **attrs):
+    from .mommy import make
+    return make(model, **attrs)
+gen_related.required = [_fk_model]
+gen_related.prepare = _prepare_related
+
+
+def gen_m2m(model, **attrs):
+    from .mommy import make, MAX_MANY_QUANTITY
+    return make(model, _quantity=MAX_MANY_QUANTITY, **attrs)
+gen_m2m.required = [_fk_model]
diff --git a/tox.ini b/tox.ini
index 54a2442..f99b776 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = {py27}-django{15,16,17,18,19,110}-{postgresql,sqlite}, {py26}-django{15,16}, py34-django{15,16,17}-{postgresql,sqlite}, {py35,py34}-django{18,19,110}-{postgresql,sqlite}
+envlist = {py27}-django{16,17,18,19,110}-{postgresql,sqlite}, {py26}-django{16}, py34-django{16,17}-{postgresql,sqlite}, {py35,py34}-django{18,19,110}-{postgresql,sqlite}
 
 [testenv]
 setenv=
@@ -14,7 +14,6 @@ deps =
     mock==1.0.1
     six>=1.3.0
     django-test-without-migrations
-    django15: Django>=1.5,<1.6
     django16: Django>=1.6,<1.7
     django17: Django>=1.7,<1.8
     django18: Django>=1.8,<1.9

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-model-mommy.git



More information about the Python-modules-commits mailing list