Skip to content

Commit

Permalink
Merge pull request #75 from ad-m/django19
Browse files Browse the repository at this point in the history
Add django 1.9 compatibility
  • Loading branch information
gregmuellegger committed Nov 25, 2015
2 parents 5607e0d + 139151b commit b9a9780
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 149 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Expand Up @@ -24,6 +24,9 @@ env:
- TOX_ENV=pypy-15
- TOX_ENV=pypy-16
- TOX_ENV=pypy-17
- TOX_ENV=py27-master
- TOX_ENV=py34-master
- TOX_ENV=pypy-master

install:
- pip install pip wheel -U
Expand Down
47 changes: 23 additions & 24 deletions autofixture/base.py
@@ -1,17 +1,15 @@
# -*- coding: utf-8 -*-
import inspect
import warnings
from django.db import models
from django.db.models import fields, ImageField
from django.db.models.fields import related
from django.utils.six import with_metaclass

import autofixture
from autofixture import constraints, generators, signals
from autofixture.values import Values
from .compat import GenericRelation
from .compat import OrderedDict

from .compat import OrderedDict, get_GenericRelation, get_remote_field, get_remote_field_to


class CreateInstanceError(Exception):
Expand Down Expand Up @@ -109,6 +107,7 @@ class IGNORE_FIELD(object):
(fields.IntegerField, generators.IntegerGenerator),
(fields.FloatField, generators.FloatGenerator),
(fields.IPAddressField, generators.IPAddressGenerator),
(fields.GenericIPAddressField, generators.IPAddressGenerator),
(fields.TextField, generators.LoremGenerator),
(fields.TimeField, generators.TimeGenerator),
(ImageField, generators.ImageGenerator),
Expand Down Expand Up @@ -240,7 +239,7 @@ def is_inheritance_parent(self, field):
return (
isinstance(field, related.OneToOneField) and
field.primary_key and
issubclass(field.model, field.rel.to)
issubclass(field.model, get_remote_field_to(field))
)

def get_generator(self, field):
Expand Down Expand Up @@ -277,18 +276,18 @@ def get_generator(self, field):
return generators.ChoicesGenerator(choices=field.choices, **kwargs)
if isinstance(field, related.ForeignKey):
# if generate_fk is set, follow_fk is ignored.
is_self_fk = (field.rel.to().__class__ == self.model)
is_self_fk = (get_remote_field_to(field)().__class__ == self.model)
if field.name in self.generate_fk and not is_self_fk:
return generators.InstanceGenerator(
autofixture.get(
field.rel.to,
get_remote_field_to(field),
follow_fk=self.follow_fk.get_deep_links(field.name),
generate_fk=self.generate_fk.get_deep_links(field.name)),
limit_choices_to=field.rel.limit_choices_to)
limit_choices_to=get_remote_field(field).limit_choices_to)
if field.name in self.follow_fk:
selected = generators.InstanceSelector(
field.rel.to,
limit_choices_to=field.rel.limit_choices_to)
get_remote_field_to(field),
limit_choices_to=get_remote_field(field).limit_choices_to)
if selected.get_value() is not None:
return selected
if field.blank or field.null:
Expand All @@ -298,33 +297,33 @@ def get_generator(self, field):
u'Cannot resolve self referencing field "%s" to "%s" without null=True' % (
field.name,
'%s.%s' % (
field.rel.to._meta.app_label,
field.rel.to._meta.object_name,
get_remote_field_to(field)._meta.app_label,
get_remote_field_to(field)._meta.object_name,
)
))
raise CreateInstanceError(
u'Cannot resolve ForeignKey "%s" to "%s". Provide either '
u'"follow_fk" or "generate_fk" parameters.' % (
field.name,
'%s.%s' % (
field.rel.to._meta.app_label,
field.rel.to._meta.object_name,
get_remote_field_to(field)._meta.app_label,
get_remote_field_to(field)._meta.object_name,
)
))
if isinstance(field, related.ManyToManyField):
if field.name in self.generate_m2m:
min_count, max_count = self.generate_m2m[field.name]
return generators.MultipleInstanceGenerator(
autofixture.get(field.rel.to),
limit_choices_to=field.rel.limit_choices_to,
autofixture.get(get_remote_field_to(field)),
limit_choices_to=get_remote_field(field).limit_choices_to,
min_count=min_count,
max_count=max_count,
**kwargs)
if field.name in self.follow_m2m:
min_count, max_count = self.follow_m2m[field.name]
return generators.InstanceSelector(
field.rel.to,
limit_choices_to=field.rel.limit_choices_to,
get_remote_field_to(field),
limit_choices_to=get_remote_field(field).limit_choices_to,
min_count=min_count,
max_count=max_count,
**kwargs)
Expand All @@ -334,8 +333,8 @@ def get_generator(self, field):
u'Cannot assign instances of "%s" to ManyToManyField "%s". '
u'Provide either "follow_m2m" or "generate_m2m" argument.' % (
'%s.%s' % (
field.rel.to._meta.app_label,
field.rel.to._meta.object_name,
get_remote_field_to(field)._meta.app_label,
get_remote_field_to(field)._meta.object_name,
),
field.name,
))
Expand Down Expand Up @@ -399,7 +398,7 @@ def process_m2m(self, instance, field):
# check django's version number to determine how intermediary models
# are checked if they are auto created or not.
auto_created_through_model = False
through = field.rel.through
through = get_remote_field(field).through
auto_created_through_model = through._meta.auto_created

if auto_created_through_model:
Expand All @@ -414,11 +413,11 @@ def process_m2m(self, instance, field):
related_fks = [fk
for fk in through._meta.fields
if isinstance(fk, related.ForeignKey) and \
fk.rel.to is field.rel.to]
get_remote_field_to(fk) is get_remote_field_to(field)]
self_fks = [fk
for fk in through._meta.fields
if isinstance(fk, related.ForeignKey) and \
fk.rel.to is self.model]
get_remote_field_to(fk) is self.model]
assert len(related_fks) == 1
assert len(self_fks) == 1
related_fk = related_fks[0]
Expand All @@ -430,7 +429,7 @@ def process_m2m(self, instance, field):
field_values={
self_fk.name: instance,
related_fk.name: generators.InstanceGenerator(
autofixture.get(field.rel.to))
autofixture.get(get_remote_field_to(field)))
}),
min_count=min_count,
max_count=max_count,
Expand Down Expand Up @@ -514,7 +513,7 @@ def create_one(self, commit=True):
#to handle particular case of GenericRelation
#in Django pre 1.6 it appears in .many_to_many
many_to_many = [f for f in instance._meta.many_to_many
if not isinstance(f, GenericRelation)]
if not isinstance(f, get_GenericRelation())]
for field in many_to_many:
self.process_m2m(instance, field)
signals.instance_created.send(
Expand Down
39 changes: 28 additions & 11 deletions autofixture/compat.py
@@ -1,19 +1,22 @@
import django


try:
from django.contrib.contenttypes.fields import GenericForeignKey
# For Django 1.6 and earlier
except ImportError:
from django.contrib.contenttypes.generic import GenericForeignKey

def get_GenericForeignKey():
try:
from django.contrib.contenttypes.fields import GenericForeignKey
# For Django 1.6 and earlier
except ImportError:
from django.contrib.contenttypes.generic import GenericForeignKey
return GenericForeignKey

try:
from django.contrib.contenttypes.fields import GenericRelation
# For Django 1.6 and earlier
except ImportError:
from django.contrib.contenttypes.generic import GenericRelation

def get_GenericRelation():
try:
from django.contrib.contenttypes.fields import GenericRelation
# For Django 1.6 and earlier
except ImportError:
from django.contrib.contenttypes.generic import GenericRelation
return GenericRelation

try:
from collections import OrderedDict
Expand Down Expand Up @@ -46,3 +49,17 @@ def get_field(model, field_name):
return model._meta.get_field_by_name(field_name)[0]
else:
return model._meta.get_field(field_name)


def get_remote_field(field):
if django.VERSION < (1, 9):
return field.rel
else:
return field.remote_field


def get_remote_field_to(field):
if django.VERSION < (1, 9):
return field.rel.to
else:
return field.remote_field.model
5 changes: 3 additions & 2 deletions autofixture/constraints.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from django.db.models.fields import related
from .compat import get_field
from .compat import get_field, get_remote_field_to


class InvalidConstraint(Exception):
Expand All @@ -16,7 +16,8 @@ def _is_unique_field(field):
# 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.rel.to)
return (isinstance(field, related.OneToOneField) and
not issubclass(field.model, get_remote_field_to(field)))
else:
return True

Expand Down
103 changes: 53 additions & 50 deletions autofixture/management/commands/loadtestdata.py
Expand Up @@ -25,64 +25,70 @@
django-admin.py help loadtestdata
'''
import django
from django.utils.encoding import smart_text
from django.db import models
from django.core.management.base import BaseCommand, CommandError
from optparse import make_option

import autofixture
from autofixture import signals
from ...compat import atomic
from ...compat import importlib
from ...compat import get_model

if django.VERSION < (1, 9):
from optparse import make_option
else:
def make_option(*args, **kwargs):
return {'args': args, 'kwargs': kwargs}


class Command(BaseCommand):
help = (
u'Create random model instances for testing purposes.'
)
help = 'Create random model instances for testing purposes.'
args = 'app.Model:# [app.Model:# ...]'

option_list = BaseCommand.option_list + (
make_option('-d', '--overwrite-defaults', action='store_true',
dest='overwrite_defaults', default=None, help=
u'Generate values for fields with default values. Default is '
u'to use default values.'),
make_option('--no-follow-fk', action='store_true', dest='no_follow_fk',
default=None, help=
u'Ignore foreignkeys while creating model instances.'),
make_option('--generate-fk', action='store', dest='generate_fk',
default=None, help=
u'Do not use already existing instances for ForeignKey '
u'relations. Create new instances instead. You can specify a '
u'comma sperated list of field names or ALL to indicate that '
u'all foreignkeys should be generated automatically.'),
make_option('--no-follow-m2m', action='store_true',
dest='no_follow_m2m', default=None, help=
u'Ignore many to many fields while creating model '
u'instances.'),
make_option('--follow-m2m', action='store', dest='follow_m2m',
default=None, help=
u'Specify minimum and maximum number of instances that are '
u'assigned to a m2m relation. Use two, colon separated '
u'numbers in the form of: min,max. Default is 1,5.\n'
u'You can limit following of many to many relations to '
u'specific fields using the following format:\n'
u'field1:min:max,field2:min:max ...'),
make_option('--generate-m2m', action='store', dest='generate_m2m',
default=None, help=
u'Specify minimum and maximum number of instances that are '
u'newly created and assigned to a m2m relation. Use two, '
u'colon separated numbers in the form of: min:max. Default is '
u'to not generate many to many related models automatically. '
u'You can select specific of many to many fields which are '
u'automatically generated. Use the following format:\n'
u'field1:min:max,field2:min:max ...'),
make_option('-u', '--use', action='store', dest='use',
default='', help=
u'Specify a autofixture subclass that is used to create the '
u'test data. E.g. myapp.autofixtures.MyAutoFixture'),
)
def __init__(self, *args, **kwargs):
params = (
make_option('-d', '--overwrite-defaults', action='store_true',
dest='overwrite_defaults', default=None,
help=u'Generate values for fields with default values. Default is to use ' +
'default values.'),
make_option('--no-follow-fk', action='store_true', dest='no_follow_fk', default=None,
help=u'Ignore foreignkeys while creating model instances.'),
make_option('--generate-fk', action='store', dest='generate_fk', default=None,
help=u'Do not use already existing instances for ForeignKey relations. ' +
'Create new instances instead. You can specify a comma sperated list of ' +
'field names or ALL to indicate that all foreignkeys should be generated ' +
'automatically.'),
make_option('--no-follow-m2m', action='store_true', dest='no_follow_m2m', default=None,
help=u'Ignore many to many fields while creating model instances.'),
make_option('--follow-m2m', action='store', dest='follow_m2m', default=None,
help=u'Specify minimum and maximum number of instances that are assigned ' +
'to a m2m relation. Use two, colon separated numbers in the form of: ' +
'min,max. Default is 1,5.\nYou can limit following of many to many ' +
'relations to specific fields using the following format:\nfield1:min:max' +
',field2:min:max ...'),
make_option('--generate-m2m', action='store', dest='generate_m2m', default=None,
help=u'Specify minimum and maximum number of instances that are newly ' +
'created and assigned to a m2m relation. Use two, colon separated ' +
'numbers in the form of: min:max. Default is to not generate many to ' +
'many related models automatically. You can select specific of many to ' +
'many fields which are automatically generated. Use the following ' +
'format:\nfield1:min:max,field2:min:max ...'),
make_option('-u', '--use', action='store', dest='use', default='',
help=u'Specify a autofixture subclass that is used to create the test ' +
'data. E.g. myapp.autofixtures.MyAutoFixture')
)

if django.VERSION < (1, 9):
self.option_list = BaseCommand.option_list + params
else:
self.argument_params = params
super(Command, self).__init__(*args, **kwargs)

def add_arguments(self, parser):
parser.add_argument('args', nargs='+')
for option in self.argument_params:
parser.add_argument(*option['args'], **option['kwargs'])

def format_output(self, obj):
output = smart_text(obj)
Expand Down Expand Up @@ -171,8 +177,7 @@ def handle(self, *attrs, **options):

if error_option:
raise CommandError(
u'Invalid option {0}\n'
u'Expected: {1}=field:min:max,field2:min:max... (min and max must be numbers)'.format(
u'Invalid option {0}\nExpected: {1}=field:min:max,field2:min:max... (min and max must be numbers)'.format(
error_option,
error_option.split('=', 1)[0]))

Expand All @@ -192,9 +197,7 @@ def handle(self, *attrs, **options):
count = int(count)
except ValueError:
raise CommandError(
u'Invalid argument: {0}\n'
u'Expected: app_label.ModelName:count '
u'(count must be a number)'.format(attr))
u'Invalid argument: {0}\nExpected: app_label.ModelName:count (count must be a number)'.format(attr))
model = get_model(app_label, model_label)
if not model:
raise CommandError(
Expand Down
10 changes: 10 additions & 0 deletions autofixture_tests/compat.py
Expand Up @@ -2,3 +2,13 @@
from unittest import skipIf
except ImportError:
from django.utils.unittest import skipIf

try:
from django.conf.urls.defaults import url
except ImportError:
from django.conf.urls import url

try:
from django.conf.urls.defaults import include
except ImportError:
from django.conf.urls import include

0 comments on commit b9a9780

Please sign in to comment.