model_mommy-1.5.1/0000775000175000017500000000000013230420736015450 5ustar bernardobernardo00000000000000model_mommy-1.5.1/setup.cfg0000664000175000017500000000010313230420736017263 0ustar bernardobernardo00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 model_mommy-1.5.1/PKG-INFO0000664000175000017500000000422513230420736016550 0ustar bernardobernardo00000000000000Metadata-Version: 1.1 Name: model_mommy Version: 1.5.1 Summary: Smart object creation facility for Django. Home-page: http://github.com/vandersonmota/model_mommy Author: vandersonmota Author-email: vandersonmota@gmail.com License: Apache 2.0 Description-Content-Type: UNKNOWN Description: ============================================ Model Mommy: Smart fixtures for better tests ============================================ *Model-mommy* offers you a smart way to create fixtures for testing in Django. With a simple and powerful API you can create many objects with a single line of code. .. image:: https://travis-ci.org/vandersonmota/model_mommy.svg?branch=master :target: https://travis-ci.org/vandersonmota/model_mommy :alt: Test Status .. image:: https://badge.fury.io/py/model_mommy.svg :target: https://badge.fury.io/py/model_mommy :alt: Latest PyPI version .. image:: https://img.shields.io/gratipay/vandersonmota.svg?style=social&label=Donate :target: https://www.gratipay.com/vandersonmota Install ======= .. code-block:: console pip install model_mommy Usage and Info ============== * http://model-mommy.readthedocs.org/ Maintainers =========== * Bernardo Fontes - https://github.com/berinhard/ Creator ======= * Vanderson Mota - https://github.com/vandersonmota/ Keywords: django testing factory python Platform: UNKNOWN Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 model_mommy-1.5.1/model_mommy/0000775000175000017500000000000013230420736017766 5ustar bernardobernardo00000000000000model_mommy-1.5.1/model_mommy/gis.py0000664000175000017500000000205213227463052021125 0ustar bernardobernardo00000000000000from __future__ import absolute_import from django.apps import apps MOMMY_GIS = apps.is_installed("django.contrib.gis") default_gis_mapping = {} __all__ = ['MOMMY_GIS', 'default_gis_mapping'] if MOMMY_GIS: from . import random_gen from django.contrib.gis.db.models import ( GeometryField, PointField, LineStringField, PolygonField, MultiPointField, MultiLineStringField, MultiPolygonField, GeometryCollectionField, ) default_gis_mapping[GeometryField] = random_gen.gen_geometry default_gis_mapping[PointField] = random_gen.gen_point default_gis_mapping[LineStringField] = random_gen.gen_line_string default_gis_mapping[PolygonField] = random_gen.gen_polygon default_gis_mapping[MultiPointField] = random_gen.gen_multi_point default_gis_mapping[MultiLineStringField] = random_gen.gen_multi_line_string default_gis_mapping[MultiPolygonField] = random_gen.gen_multi_polygon default_gis_mapping[GeometryCollectionField] = random_gen.gen_geometry_collection model_mommy-1.5.1/model_mommy/__init__.py0000664000175000017500000000016713227463070022107 0ustar bernardobernardo00000000000000#coding:utf-8 __version__ = '1.5.1' __title__ = 'model_mommy' __author__ = 'Vanderson Mota' __license__ = 'Apache 2.0' model_mommy-1.5.1/model_mommy/mock-img.jpeg0000664000175000017500000000357413213557066022361 0ustar bernardobernardo00000000000000JFIF  ! '(*%$%"/,#3)8,/ 25<,A27-5 2$$-,4./50,,/55)5.4*4*,,)),))-.4),)),,,,),),),,)))),)8">!1AQa"qt2Srs%46T#$5B !1AQa" ?sWN;TY!U3d?Vz|Vv&j*ȭ. T.:hAgn`(KNP= P*#t,\JwݽsߵB*h)CJRR(rVkiD.!~҅-|{ɱw8m*'JX' xW|E4vPB;L$N}9oW'PiJ!ư&)fvLWEAqC~Y zԾKݵ>yT  r>O]akMq nG+#ȟz| -55)W@#F =Xod;Z! MHQRH9#:T/C 4[c̆w}0ߨGxk 72*b6u$1ު>Z9HDeadbez xc~?2j̶9pAQ %=ۇ$w]:K(UI6PO^鎹2rZqS?u$š15c_5B*'a=aEIe❜pK*0R$~Pz{zd[t}xAӕŤDFqMf+}KRyiwp"c]ʤ;HCWF7[?67Hm+{}֏<\s$*\OVP;Phrg-Ѵ*e ]Y‡ ;=+a=gILH"4v/r*;:JMd2F^eK0luK4dsᝀGZ޽qX{o[ D<:bmJG,S) 0G=׌Hu B[W "5P}Ϲ5PÕxjVӴNVfPDiIv^'۽BnZYI͘^U9u)C3C?O:Hb,yRdy:|YR)JR)@~XHJI9>(S)JW+LҦGopW?{JPV^ "':vl]FF|OsJR⏣model_mommy-1.5.1/model_mommy/random_gen.py0000664000175000017500000001626613227463052022470 0ustar bernardobernardo00000000000000# -*- 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, uniform import six from django.core.exceptions import ValidationError from django.core.files.base import ContentFile 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): 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)) gen_decimal.required = ['max_digits', 'decimal_places'] 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'] def gen_text(): return gen_string(MAX_LENGTH) def gen_boolean(): return choice((True, False)) def gen_null_boolean(): return choice((True, False, None)) def gen_url(): return str('http://www.%s.com/' % gen_string(30)) def gen_email(): return "%s@example.com" % gen_string(10) def gen_ipv6(): return ":".join(format(randint(1, 65535), 'x') for i in range(8)) def gen_ipv4(): return ".".join(str(randint(1, 255)) for i in range(4)) 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)) 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 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() def gen_array(): return [] def gen_json(): return {} def gen_hstore(): 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] # GIS generators def gen_coord(): return uniform(0, 1) def gen_coords(): return '{x} {y}'.format(x=gen_coord(), y=gen_coord()) def gen_point(): return 'POINT ({})'.format( gen_coords(), ) def _gen_line_string_without_prefix(): return '({}, {})'.format( gen_coords(), gen_coords(), ) def gen_line_string(): return 'LINESTRING {}'.format( _gen_line_string_without_prefix() ) def _gen_polygon_without_prefix(): start = gen_coords() return '(({}, {}, {}, {}))'.format( start, gen_coords(), gen_coords(), start ) def gen_polygon(): return 'POLYGON {}'.format( _gen_polygon_without_prefix(), ) def gen_multi_point(): return 'MULTIPOINT (({}))'.format( gen_coords(), ) def gen_multi_line_string(): return 'MULTILINESTRING ({})'.format( _gen_line_string_without_prefix(), ) def gen_multi_polygon(): return 'MULTIPOLYGON ({})'.format( _gen_polygon_without_prefix(), ) def gen_geometry(): return gen_point() def gen_geometry_collection(): return 'GEOMETRYCOLLECTION ({})'.format( gen_point(), ) model_mommy-1.5.1/model_mommy/utils.py0000664000175000017500000000133013213557066021505 0ustar bernardobernardo00000000000000# -*- coding: utf-8 -*- import importlib from six import string_types def import_if_str(import_string_or_obj): """ Import and return an object defined as import string in the form of path.to.module.object_name or just return the object if it isn't a string. """ if isinstance(import_string_or_obj, string_types): return import_from_str(import_string_or_obj) return import_string_or_obj def import_from_str(import_string): """ Import and return an object defined as import string in the form of path.to.module.object_name """ path, field_name = import_string.rsplit('.', 1) module = importlib.import_module(path) return getattr(module, field_name) model_mommy-1.5.1/model_mommy/mock_file.txt0000664000175000017500000000001213213557066022460 0ustar bernardobernardo00000000000000mock file model_mommy-1.5.1/model_mommy/generators.py0000664000175000017500000000621113227463052022515 0ustar bernardobernardo00000000000000from 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, NullBooleanField) from .gis import default_gis_mapping from .utils import import_if_str 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 try: from django.contrib.postgres.fields import HStoreField except ImportError: HStoreField = None from . import random_gen default_mapping = { ForeignKey: random_gen.gen_related, OneToOneField: random_gen.gen_related, ManyToManyField: random_gen.gen_m2m, BooleanField: random_gen.gen_boolean, NullBooleanField: random_gen.gen_null_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), 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, 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, 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 if HStoreField: default_mapping[HStoreField] = random_gen.gen_hstore # Add GIS fields default_mapping.update(default_gis_mapping) def get_type_mapping(): mapping = default_mapping.copy() return mapping.copy() user_mapping = {} def add(field, func): user_mapping[import_if_str(field)] = import_if_str(func) def get(field): return user_mapping.get(field) model_mommy-1.5.1/model_mommy/timezone.py0000664000175000017500000000075713213557066022213 0ustar bernardobernardo00000000000000# coding: utf-8 ''' Add support for Django 1.4+ safe datetimes. https://docs.djangoproject.com/en/1.4/topics/i18n/timezones/ ''' from datetime import datetime from django.conf import settings try: from django.utils.timezone import now, utc except ImportError: now = lambda: datetime.now() def smart_datetime(*args): value = datetime(*args) return tz_aware(value) def tz_aware(d): value = d if settings.USE_TZ: value = d.replace(tzinfo=utc) return value model_mommy-1.5.1/model_mommy/mommy.py0000664000175000017500000004011713227463052021505 0ustar bernardobernardo00000000000000# -*- coding: utf-8 -*- from os.path import dirname, join import django from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.apps import apps get_model = apps.get_model from django.contrib.contenttypes.fields import GenericRelation from django.db.models.base import ModelBase from django.db.models import ForeignKey, ManyToManyField, OneToOneField, Field, AutoField, BooleanField, FileField 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 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 mock_file_jpeg = join(dirname(__file__), 'mock-img.jpeg') mock_file_txt = join(dirname(__file__), 'mock_file.txt') 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, _save_kwargs=None, _create_files=False, **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, create_files=_create_files) if _valid_quantity(_quantity): raise InvalidQuantityException if _quantity: return [mommy.make(_save_kwargs=_save_kwargs, **attrs) for i in range(_quantity)] else: return mommy.make(_save_kwargs=_save_kwargs, **attrs) def prepare(_model, _quantity=None, _save_related=False, **attrs): """ Creates BUT DOESN'T persist an 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. """ mommy = Mommy.create(_model) if _valid_quantity(_quantity): raise InvalidQuantityException if _quantity: return [mommy.prepare(_save_related=_save_related, **attrs) for i in range(_quantity)] else: return mommy.prepare(_save_related=_save_related, **attrs) 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, _save_related=False, **new_attrs): return _recipe(mommy_recipe_name).prepare(_quantity=_quantity, _save_related=_save_related, **new_attrs) class ModelFinder(object): ''' Encapsulates all the logic for finding a model to Mommy. ''' _unique_models = None _ambiguous_models = None def get_model(self, name): ''' Get a model. :param name String on the form 'applabel.modelname' or 'modelname'. :return a model class. ''' try: if '.' in name: app_label, model_name = name.split('.') model = get_model(app_label, model_name) else: model = self.get_model_by_name(name) except LookupError: model = None if not model: raise ModelNotFound("Could not find model '%s'." % name.title()) return model def get_model_by_name(self, name): ''' Get a model by name. If a model with that name exists in more than one app, raises AmbiguousModelName. ''' name = name.lower() if self._unique_models is None: self._populate() if name in self._ambiguous_models: raise AmbiguousModelName('%s is a model in more than one app. ' 'Use the form "app.model".' % name.title()) return self._unique_models.get(name) def _populate(self): ''' Cache models for faster self._get_model. ''' unique_models = {} ambiguous_models = [] all_models = apps.all_models for app_model in all_models.values(): for name, model in app_model.items(): if name not in unique_models: unique_models[name] = model else: ambiguous_models.append(name) for name in ambiguous_models: unique_models.pop(name, None) self._ambiguous_models = ambiguous_models self._unique_models = unique_models def is_iterator(value): if not hasattr(value, '__iter__'): return False if PY3: return hasattr(value, '__next__') 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 = {} type_mapping = None # Note: we're using one finder for all Mommy instances to avoid # rebuilding the model cache for every make_* or prepare_* call. finder = ModelFinder() @classmethod def create(cls, _model, make_m2m=False, create_files=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, create_files) def __init__(self, _model, make_m2m=False, create_files=False): self.make_m2m = make_m2m self.create_files = create_files self.m2m_dict = {} self.iterator_attrs = {} self.model_attrs = {} self.rel_attrs = {} self.rel_fields = [] if isinstance(_model, ModelBase): self.model = _model else: self.model = self.finder.get_model(_model) self.init_type_mapping() def init_type_mapping(self): 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, _save_kwargs=None, **attrs): '''Creates and persists an instance of the model associated with Mommy instance.''' return self._make(commit=True, commit_related=True, _save_kwargs=_save_kwargs, **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, commit_related=_save_related, **attrs) def get_fields(self): return self.model._meta.fields + self.model._meta.many_to_many def get_related(self): return [r for r in self.model._meta.related_objects if not r.many_to_many] def _make(self, commit=True, commit_related=True, _save_kwargs=None, **attrs): _save_kwargs = _save_kwargs or {} self._clean_attrs(attrs) for field in self.get_fields(): if self._skip_field(field): continue if isinstance(field, ManyToManyField): if field.name not in self.model_attrs: self.m2m_dict[field.name] = self.m2m_value(field) else: self.m2m_dict[field.name] = self.model_attrs.pop(field.name) elif field.name not in self.model_attrs: if not isinstance(field, ForeignKey) or '{0}_id'.format(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: self.model_attrs[field.name] = advance_iterator(self.iterator_attrs[field.name]) except StopIteration: raise RecipeIteratorEmpty('{0} iterator is empty.'.format(field.name)) 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: return self.generate_value(field) if not self.make_m2m or field.null and not field.fill_optional: return [] return self.generate_value(field) def instance(self, attrs, _commit, _save_kwargs): one_to_many_keys = {} for k in tuple(attrs.keys()): field = getattr(self.model, k, None) if isinstance(field, ForeignRelatedObjectsDescriptor): one_to_many_keys[k] = attrs.pop(k) instance = self.model(**attrs) # m2m only works for persisted instances if _commit: 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 if isinstance(field, FileField) and not self.create_files: return True # Skip links to parent so parent is not created twice. if isinstance(field, OneToOneField) and self._remote_field(field).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): manager = getattr(instance, k) manager.set(v, bulk=False, clear=True) else: setattr(instance, k, v) def _handle_m2m(self, instance): for key, values in self.m2m_dict.items(): for value in values: if not value.pk: value.save() m2m_relation = getattr(instance, key) through_model = m2m_relation.through # using related manager to fire m2m_changed signal if through_model._meta.auto_created: m2m_relation.add(*values) else: for value in values: base_kwargs = { m2m_relation.source_field_name: instance, m2m_relation.target_field_name: value } make(through_model, **base_kwargs) def _remote_field(self, field): if django.VERSION >= (1, 9): return field.remote_field return field.rel def generate_value(self, field, commit=True): ''' Calls the generator associated with a field passing all required args. Generator Resolution Precedence Order: -- attr_mapping - mapping per attribute name -- choices -- mapping from avaiable field choices -- type_mapping - mapping from user defined type associated generators -- default_mapping - mapping from pre-defined type associated generators `attr_mapping` and `type_mapping` can be defined easily overwriting the model. ''' if field.name in self.attr_mapping: generator = self.attr_mapping[field.name] elif getattr(field, 'choices'): generator = random_gen.gen_from_choices(field.choices) elif isinstance(field, ForeignKey) and issubclass(self._remote_field(field).model, ContentType): generator = self.type_mapping[ContentType] elif generators.get(field.__class__): generator = generators.get(field.__class__) elif field.__class__ in self.type_mapping: generator = self.type_mapping[field.__class__] else: raise TypeError('%s is not supported by mommy.' % field.__class__) # attributes like max_length, decimal_places are taken into account when # generating the value. generator_attrs = get_required_values(generator, field) if field.name in self.rel_fields: generator_attrs.update(filter_rel_attrs(field.name, **self.rel_attrs)) if not commit: generator = getattr(generator, 'prepare', generator) return generator(**generator_attrs) def get_required_values(generator, field): ''' Gets required values for a generator from the field. If required value is a function, calls it with field as argument. If required value is a string, simply fetch the value from the field and return. ''' #FIXME: avoid abreviations rt = {} if hasattr(generator, 'required'): for item in generator.required: if callable(item): # mommy can deal with the nasty hacking too! key, value = item(field) rt[key] = value elif isinstance(item, string_types): rt[item] = getattr(field, item) else: raise ValueError("Required value '%s' is of wrong type. \ Don't make mommy sad." % str(item)) return rt def filter_rel_attrs(field_name, **rel_attrs): clean_dict = {} for k, v in rel_attrs.items(): if k.startswith(field_name + '__'): splited_key = k.split('__') key = '__'.join(splited_key[1:]) clean_dict[key] = v else: clean_dict[k] = v return clean_dict model_mommy-1.5.1/model_mommy/exceptions.py0000664000175000017500000000053513213557066022534 0ustar bernardobernardo00000000000000#coding:utf-8 class RecipeNotFound(Exception): pass class RecipeIteratorEmpty(Exception): pass class ModelNotFound(Exception): pass class AmbiguousModelName(Exception): pass class InvalidQuantityException(Exception): pass class CustomMommyNotFound(Exception): pass class InvalidCustomMommy(Exception): pass model_mommy-1.5.1/model_mommy/recipe.py0000664000175000017500000001303313216207065021611 0ustar bernardobernardo00000000000000#coding: utf-8 from functools import wraps import inspect import itertools from . import mommy from .timezone import tz_aware from .exceptions import RecipeNotFound, RecipeIteratorEmpty from six import string_types import datetime # Python 2.6.x compatibility code itertools_count = itertools.count try: itertools_count(0, 1) except TypeError: def count(start=0, step=1): n = start while True: yield n n += step itertools_count = count finder = mommy.ModelFinder() class Recipe(object): def __init__(self, _model, **attrs): self.attr_mapping = attrs self._model = _model # _iterator_backups will hold values of the form (backup_iterator, usable_iterator). self._iterator_backups = {} def _mapping(self, new_attrs): _save_related = new_attrs.get('_save_related', True) rel_fields_attrs = dict((k, v) for k, v in new_attrs.items() if '__' in k) new_attrs = dict((k, v) for k, v in new_attrs.items() if not '__' in k) mapping = self.attr_mapping.copy() for k, v in self.attr_mapping.items(): # do not generate values if field value is provided if new_attrs.get(k): continue elif mommy.is_iterator(v): if isinstance(self._model, string_types): m = finder.get_model(self._model) else: m = self._model if k not in self._iterator_backups or m.objects.count() == 0: self._iterator_backups[k] = itertools.tee(self._iterator_backups.get(k, [v])[0]) mapping[k] = self._iterator_backups[k][1] elif isinstance(v, RecipeForeignKey): a={} for key, value in list(rel_fields_attrs.items()): if key.startswith('%s__' % k): a[key] = rel_fields_attrs.pop(key) recipe_attrs = mommy.filter_rel_attrs(k, **a) if _save_related: mapping[k] = v.recipe.make(**recipe_attrs) else: mapping[k] = v.recipe.prepare(**recipe_attrs) elif isinstance(v, related): mapping[k] = v.make() mapping.update(new_attrs) mapping.update(rel_fields_attrs) return mapping def make(self, **attrs): return mommy.make(self._model, **self._mapping(attrs)) def prepare(self, **attrs): defaults = {'_save_related': False} defaults.update(attrs) return mommy.prepare(self._model, **self._mapping(defaults)) def extend(self, **attrs): attr_mapping = self.attr_mapping.copy() attr_mapping.update(attrs) return Recipe(self._model, **attr_mapping) class RecipeForeignKey(object): def __init__(self, recipe): if isinstance(recipe, Recipe): self.recipe = recipe elif isinstance(recipe, string_types): frame = inspect.stack()[2] caller_module = inspect.getmodule(frame[0]) recipe = getattr(caller_module, recipe) if recipe: self.recipe = recipe else: raise RecipeNotFound else: raise TypeError('Not a recipe') def foreign_key(recipe): """ Returns the callable, so that the associated _model will not be created during the recipe definition. """ return RecipeForeignKey(recipe) def _total_secs(td): """ python 2.6 compatible timedelta total seconds calculation backport from https://docs.python.org/2.7/library/datetime.html#datetime.timedelta.total_seconds """ if hasattr(td, 'total_seconds'): return td.total_seconds() else: #py26 return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10.0**6 def seq(value, increment_by=1): if type(value) in [datetime.datetime, datetime.date, datetime.time]: if type(value) is datetime.date: date = datetime.datetime.combine(value, datetime.datetime.now().time()) elif type(value) is datetime.time: date = datetime.datetime.combine(datetime.date.today(), value) else: date = value # convert to epoch time start = _total_secs((date - datetime.datetime(1970, 1, 1))) increment_by = _total_secs(increment_by) for n in itertools_count(increment_by, increment_by): series_date = tz_aware(datetime.datetime.utcfromtimestamp(start + n)) if type(value) is datetime.time: yield series_date.time() elif type(value) is datetime.date: yield series_date.date() else: yield series_date else: for n in itertools_count(increment_by, increment_by): yield value + type(value)(n) class related(object): def __init__(self, *args): self.related = [] for recipe in args: if isinstance(recipe, Recipe): self.related.append(recipe) elif isinstance(recipe, string_types): frame = inspect.stack()[1] caller_module = inspect.getmodule(frame[0]) recipe = getattr(caller_module, recipe) if recipe: self.related.append(recipe) else: raise RecipeNotFound else: raise TypeError('Not a recipe') def make(self): """ Persists objects to m2m relation """ return [m.make() for m in self.related] model_mommy-1.5.1/requirements.txt0000664000175000017500000000002213216207065020730 0ustar bernardobernardo00000000000000django>=1.8.0 six model_mommy-1.5.1/README.rst0000664000175000017500000000201713230417143017135 0ustar bernardobernardo00000000000000============================================ Model Mommy: Smart fixtures for better tests ============================================ *Model-mommy* offers you a smart way to create fixtures for testing in Django. With a simple and powerful API you can create many objects with a single line of code. .. image:: https://travis-ci.org/vandersonmota/model_mommy.svg?branch=master :target: https://travis-ci.org/vandersonmota/model_mommy :alt: Test Status .. image:: https://badge.fury.io/py/model_mommy.svg :target: https://badge.fury.io/py/model_mommy :alt: Latest PyPI version .. image:: https://img.shields.io/gratipay/vandersonmota.svg?style=social&label=Donate :target: https://www.gratipay.com/vandersonmota Install ======= .. code-block:: console pip install model_mommy Usage and Info ============== * http://model-mommy.readthedocs.org/ Maintainers =========== * Bernardo Fontes - https://github.com/berinhard/ Creator ======= * Vanderson Mota - https://github.com/vandersonmota/ model_mommy-1.5.1/MANIFEST.in0000664000175000017500000000023513213557066017216 0ustar bernardobernardo00000000000000include model_mommy/mock_file.txt include model_mommy/mock-img.jpeg include README.rst include requirements.txt include tox.ini recursive-include tests *.py model_mommy-1.5.1/setup.py0000775000175000017500000000240513216207065017170 0ustar bernardobernardo00000000000000import setuptools from os.path import join, dirname import model_mommy setuptools.setup( name="model_mommy", version=model_mommy.__version__, packages=["model_mommy"], include_package_data=True, # declarations in MANIFEST.in install_requires=open(join(dirname(__file__), 'requirements.txt')).readlines(), tests_require=[ 'django>=1.8', 'pil', 'tox', 'mock' ], test_suite='runtests.runtests', author="vandersonmota", author_email="vandersonmota@gmail.com", url="http://github.com/vandersonmota/model_mommy", license="Apache 2.0", description="Smart object creation facility for Django.", long_description=open(join(dirname(__file__), "README.rst")).read(), keywords="django testing factory python", classifiers=[ 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Topic :: Software Development', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ], ) model_mommy-1.5.1/tox.ini0000664000175000017500000000130013227463052016761 0ustar bernardobernardo00000000000000[tox] envlist = py27-django{18,19,110,111}-{postgresql,sqlite} py35-django{18,19,110,111,20}-{postgresql,sqlite} py36-django{20}-{postgresql,sqlite} [testenv] setenv= PYTHONPATH = {toxinidir} basepython = py27: python2.7 py35: python3.5 py36: python3.6 deps = py26: Pillow>=2.3.1,<4.0.0 py{27,34,35}: Pillow>=2.3.1 mock==1.0.1 six>=1.3.0 django-test-without-migrations django18: Django>=1.8,<1.9 django19: Django>=1.9,<1.10 django110: Django>=1.10,<1.11 django111: Django>=1.11,<1.12 django20: Django>=2.0 postgresql: psycopg2 commands= postgresql: {toxinidir}/runtests.py --postgis sqlite: {toxinidir}/runtests.py --use-tz model_mommy-1.5.1/model_mommy.egg-info/0000775000175000017500000000000013230420736021460 5ustar bernardobernardo00000000000000model_mommy-1.5.1/model_mommy.egg-info/top_level.txt0000664000175000017500000000001413230420736024205 0ustar bernardobernardo00000000000000model_mommy model_mommy-1.5.1/model_mommy.egg-info/dependency_links.txt0000664000175000017500000000000113230420736025526 0ustar bernardobernardo00000000000000 model_mommy-1.5.1/model_mommy.egg-info/SOURCES.txt0000664000175000017500000000076513230420736023354 0ustar bernardobernardo00000000000000MANIFEST.in README.rst requirements.txt setup.cfg setup.py tox.ini model_mommy/__init__.py model_mommy/exceptions.py model_mommy/generators.py model_mommy/gis.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 model_mommy.egg-info/PKG-INFO model_mommy.egg-info/SOURCES.txt model_mommy.egg-info/dependency_links.txt model_mommy.egg-info/requires.txt model_mommy.egg-info/top_level.txtmodel_mommy-1.5.1/model_mommy.egg-info/PKG-INFO0000664000175000017500000000422513230420736022560 0ustar bernardobernardo00000000000000Metadata-Version: 1.1 Name: model-mommy Version: 1.5.1 Summary: Smart object creation facility for Django. Home-page: http://github.com/vandersonmota/model_mommy Author: vandersonmota Author-email: vandersonmota@gmail.com License: Apache 2.0 Description-Content-Type: UNKNOWN Description: ============================================ Model Mommy: Smart fixtures for better tests ============================================ *Model-mommy* offers you a smart way to create fixtures for testing in Django. With a simple and powerful API you can create many objects with a single line of code. .. image:: https://travis-ci.org/vandersonmota/model_mommy.svg?branch=master :target: https://travis-ci.org/vandersonmota/model_mommy :alt: Test Status .. image:: https://badge.fury.io/py/model_mommy.svg :target: https://badge.fury.io/py/model_mommy :alt: Latest PyPI version .. image:: https://img.shields.io/gratipay/vandersonmota.svg?style=social&label=Donate :target: https://www.gratipay.com/vandersonmota Install ======= .. code-block:: console pip install model_mommy Usage and Info ============== * http://model-mommy.readthedocs.org/ Maintainers =========== * Bernardo Fontes - https://github.com/berinhard/ Creator ======= * Vanderson Mota - https://github.com/vandersonmota/ Keywords: django testing factory python Platform: UNKNOWN Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 model_mommy-1.5.1/model_mommy.egg-info/requires.txt0000664000175000017500000000002213230420736024052 0ustar bernardobernardo00000000000000django>=1.8.0 six