diff --git a/.travis.yml b/.travis.yml index b272e63..62786c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/autofixture/base.py b/autofixture/base.py index b4cb4b8..fe70540 100644 --- a/autofixture/base.py +++ b/autofixture/base.py @@ -1,7 +1,6 @@ # -*- 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 @@ -9,9 +8,8 @@ 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): @@ -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), @@ -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): @@ -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: @@ -298,8 +297,8 @@ 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( @@ -307,24 +306,24 @@ def get_generator(self, field): 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) @@ -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, )) @@ -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: @@ -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] @@ -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, @@ -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( diff --git a/autofixture/compat.py b/autofixture/compat.py index c21fe5d..ac95521 100644 --- a/autofixture/compat.py +++ b/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 @@ -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 diff --git a/autofixture/constraints.py b/autofixture/constraints.py index 8421ea5..145f1ee 100644 --- a/autofixture/constraints.py +++ b/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): @@ -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 diff --git a/autofixture/management/commands/loadtestdata.py b/autofixture/management/commands/loadtestdata.py index 94c1f0d..7ea1e99 100644 --- a/autofixture/management/commands/loadtestdata.py +++ b/autofixture/management/commands/loadtestdata.py @@ -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) @@ -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])) @@ -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( diff --git a/autofixture_tests/compat.py b/autofixture_tests/compat.py index a0b040e..e8652eb 100644 --- a/autofixture_tests/compat.py +++ b/autofixture_tests/compat.py @@ -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 diff --git a/autofixture_tests/models.py b/autofixture_tests/models.py index 10a6502..62bda38 100644 --- a/autofixture_tests/models.py +++ b/autofixture_tests/models.py @@ -6,9 +6,13 @@ from django.contrib.contenttypes.models import ContentType from django.utils.timezone import utc -from autofixture.compat import GenericForeignKey -from autofixture.compat import GenericRelation +from autofixture.compat import get_GenericForeignKey +from autofixture.compat import get_GenericRelation +try: + from django.db.models import GenericIPAddressField as IPAddressField +except ImportError: + from django.models import IPAddressField filepath = os.path.dirname(os.path.abspath(__file__)) @@ -100,7 +104,7 @@ class BasicModel(models.Model): decimalfield = models.DecimalField(max_digits=10, decimal_places=4) emailfield = models.EmailField() - ipaddressfield = models.IPAddressField() + ipaddressfield = IPAddressField() urlfield = models.URLField() rfilepathfield = models.FilePathField(path=filepath, recursive=True) filepathfield = models.FilePathField(path=filepath) @@ -179,11 +183,11 @@ class M2MModelThrough(models.Model): class GFKModel(models.Model): content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() - content_object = GenericForeignKey('content_type', 'object_id') + content_object = get_GenericForeignKey()('content_type', 'object_id') class GRModel(models.Model): - gr = GenericRelation('GFKModel') + gr = get_GenericRelation()('GFKModel') class DummyStorage(FileSystemStorage): diff --git a/autofixture_tests/tests/test_base.py b/autofixture_tests/tests/test_base.py index 65f5547..5f43dc2 100644 --- a/autofixture_tests/tests/test_base.py +++ b/autofixture_tests/tests/test_base.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import sys import autofixture +from django.core.management import call_command from decimal import Decimal from datetime import date, datetime from autofixture import generators, constraints @@ -621,61 +622,42 @@ def test_values_class(self): class TestManagementCommand(FileSystemCleanupTestCase): def setUp(self): - from autofixture.management.commands.loadtestdata import Command - self.command = Command() - self.options = { - 'overwrite_defaults': None, - 'no_follow_fk': None, - 'no_follow_m2m': None, - 'generate_fk': None, - 'follow_m2m': None, - 'generate_m2m': None, - 'verbosity': '0', - 'use': '', - } self.original_registry = autofixture.REGISTRY autofixture.REGISTRY = {} + def call(self, *args, **kwargs): + return call_command('loadtestdata', *args, verbosity=0, **kwargs) + def tearDown(self): autofixture.REGISTRY = self.original_registry def test_basic(self): - models = () - # empty attributes are allowed - self.command.handle(*models, **self.options) - self.assertEqual(SimpleModel.objects.count(), 0) - - models = ('autofixture_tests.SimpleModel:1',) - self.command.handle(*models, **self.options) + self.call('autofixture_tests.SimpleModel:1') self.assertEqual(SimpleModel.objects.count(), 1) - models = ('autofixture_tests.SimpleModel:5',) - self.command.handle(*models, **self.options) + self.call('autofixture_tests.SimpleModel:5') self.assertEqual(SimpleModel.objects.count(), 6) def test_generate_fk(self): - models = ('autofixture_tests.DeepLinkModel2:1',) - self.options['generate_fk'] = 'related,related__related' - self.command.handle(*models, **self.options) + self.call('autofixture_tests.DeepLinkModel2:1', + generate_fk='related,related__related') obj = DeepLinkModel2.objects.get() self.assertTrue(obj.related) self.assertTrue(obj.related.related) self.assertEqual(obj.related.related2, obj.related.related) def test_generate_fk_with_no_follow(self): - models = ('autofixture_tests.DeepLinkModel2:1',) - self.options['generate_fk'] = 'related,related__related' - self.options['no_follow_fk'] = True - self.command.handle(*models, **self.options) + self.call('autofixture_tests.DeepLinkModel2:1', + generate_fk='related,related__related', + no_follow_fk=True) obj = DeepLinkModel2.objects.get() self.assertTrue(obj.related) self.assertTrue(obj.related.related) self.assertEqual(obj.related.related2, None) def test_generate_fk_with_ALL(self): - models = ('autofixture_tests.DeepLinkModel2:1',) - self.options['generate_fk'] = 'ALL' - self.command.handle(*models, **self.options) + self.call('autofixture_tests.DeepLinkModel2:1', + generate_fk='ALL') obj = DeepLinkModel2.objects.get() self.assertTrue(obj.related) self.assertTrue(obj.related.related) @@ -685,9 +667,8 @@ def test_generate_fk_with_ALL(self): def test_no_follow_m2m(self): AutoFixture(SimpleModel).create(1) - models = ('autofixture_tests.NullableFKModel:1',) - self.options['no_follow_m2m'] = True - self.command.handle(*models, **self.options) + self.call('autofixture_tests.NullableFKModel:1', + no_follow_m2m=True) obj = NullableFKModel.objects.get() self.assertEqual(obj.m2m.count(), 0) @@ -695,18 +676,16 @@ def test_follow_m2m(self): AutoFixture(SimpleModel).create(10) AutoFixture(OtherSimpleModel).create(10) - models = ('autofixture_tests.M2MModel:25',) - self.options['follow_m2m'] = 'm2m:3:3,secondm2m:0:10' - self.command.handle(*models, **self.options) + self.call('autofixture_tests.M2MModel:25', + follow_m2m='m2m:3:3,secondm2m:0:10') for obj in M2MModel.objects.all(): self.assertEqual(obj.m2m.count(), 3) self.assertTrue(0 <= obj.secondm2m.count() <= 10) def test_generate_m2m(self): - models = ('autofixture_tests.M2MModel:10',) - self.options['generate_m2m'] = 'm2m:1:1,secondm2m:2:5' - self.command.handle(*models, **self.options) + self.call('autofixture_tests.M2MModel:10', + generate_m2m='m2m:1:1,secondm2m:2:5') all_m2m, all_secondm2m = set(), set() for obj in M2MModel.objects.all(): @@ -721,22 +700,20 @@ def test_generate_m2m(self): def test_using_registry(self): autofixture.register(SimpleModel, SimpleAutoFixture) - models = ('autofixture_tests.SimpleModel:10',) - self.command.handle(*models, **self.options) + self.call('autofixture_tests.SimpleModel:10') for obj in SimpleModel.objects.all(): self.assertEqual(obj.name, 'foo') def test_use_option(self): - self.options['use'] = 'autofixture_tests.tests.test_base.SimpleAutoFixture' - models = ('autofixture_tests.SimpleModel:10',) - self.command.handle(*models, **self.options) + self.call('autofixture_tests.SimpleModel:10', + use='autofixture_tests.tests.test_base.SimpleAutoFixture') for obj in SimpleModel.objects.all(): self.assertEqual(obj.name, 'foo') class TestGenericRelations(FileSystemCleanupTestCase): def assertNotRaises(self, exc_type, func, msg=None, - args=None, kwargs=None): + args=None, kwargs=None): args = args or [] kwargs = kwargs or {} try: diff --git a/autofixture_tests/urls.py b/autofixture_tests/urls.py index 64c00f6..2339ad0 100644 --- a/autofixture_tests/urls.py +++ b/autofixture_tests/urls.py @@ -1,14 +1,11 @@ # -*- coding: utf-8 -*- -from django.conf.urls.defaults import * -from django.contrib import admin -from django.conf import settings from django.http import HttpResponse -admin.autodiscover() - def handle404(request): return HttpResponse('404') + + def handle500(request): return HttpResponse('500') @@ -16,7 +13,5 @@ def handle500(request): handler500 = 'autofixture_tests.urls.handle500' -urlpatterns = patterns('', - url(r'^media/(.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}), - url(r'^admin/', include(admin.site.urls), name="admin"), -) +urlpatterns = [ +] diff --git a/manage.py b/manage.py old mode 100644 new mode 100755 diff --git a/tox.ini b/tox.ini index 198328b..0def9e3 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = docs, py26-{14,15,16}, py27-{14,15,16,17,master}, - py33-{15,16,17,master}, + py33-{15,16,17}, py34-{15,16,17,master}, pypy-{14,15,16,17,master} @@ -15,7 +15,7 @@ deps = 15: Django >= 1.5, < 1.6 16: Django >= 1.6, < 1.7 17: Django >= 1.7, < 1.8 - master: https://github.com/django/django/tarball/master#egg=Django + master: https://github.com/django/django/archive/master.tar.gz -r{toxinidir}/requirements/tests.txt [testenv:docs]