├── tests ├── __init__.py ├── media │ └── sample_subdir │ │ └── sample_file.txt ├── requirements.pip ├── settings.py └── manage.py ├── django_any ├── contrib │ ├── __init__.py │ └── auth.py ├── __init__.py ├── tests │ ├── test_custom_seed.py │ ├── model_field_validatiors.py │ ├── model_qobjects_spec.py │ ├── __init__.py │ ├── model_foreign_key.py │ ├── contrib_any_user.py │ ├── model_redefine_creation.py │ ├── model_creation_constraint.py │ ├── field_attr_choices.py │ ├── test_client.py │ ├── model_creation_simple.py │ └── model_oneto_one.py ├── functions.py ├── xunit.py ├── test.py ├── forms.py └── models.py ├── MANIFEST.in ├── .gitignore ├── setup.py ├── README ├── LICENSE └── docs └── quickstart.txt /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_any/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/media/sample_subdir/sample_file.txt: -------------------------------------------------------------------------------- 1 | Sample content 2 | -------------------------------------------------------------------------------- /tests/requirements.pip: -------------------------------------------------------------------------------- 1 | django>=1.2 2 | 3 | ipython 4 | ipdb 5 | django-jenkins 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README 3 | include MANIFEST.in 4 | recursive-include docs * 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .coverage 3 | .ve 4 | .cache 5 | reports 6 | coverage.xml 7 | nosetests.xml 8 | pylint.out 9 | distribute-*.tar.gz -------------------------------------------------------------------------------- /django_any/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django_any.forms import any_form_field, any_form 3 | from django_any.models import any_field, any_model 4 | 5 | -------------------------------------------------------------------------------- /django_any/tests/test_custom_seed.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: django -*- 2 | from django.db import models 3 | from django.test import TestCase 4 | from django_any import any_field 5 | from django_any.test import WithTestDataSeed, with_seed, without_random_seed 6 | 7 | 8 | class CustomSeed(TestCase): 9 | __metaclass__ = WithTestDataSeed 10 | 11 | @without_random_seed 12 | @with_seed(1) 13 | def test_deterministic_string(self): 14 | media = models.CharField(max_length=25) 15 | result = any_field(media) 16 | self.assertEqual('SNnz', result) 17 | -------------------------------------------------------------------------------- /django_any/tests/model_field_validatiors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: django -*- 2 | """ 3 | Test model creation with custom field validation 4 | """ 5 | from django.core.exceptions import ValidationError 6 | from django.db import models 7 | from django.test import TestCase 8 | from django_any import any_model 9 | 10 | 11 | def validate_even(value): 12 | if value % 2 != 0: 13 | raise ValidationError(u'%s is not an even number' % value) 14 | 15 | 16 | class ModelWithValidatedField(models.Model): 17 | even_field = models.PositiveIntegerField(validators=[validate_even]) 18 | 19 | class Meta: 20 | app_label = 'django_any' 21 | 22 | 23 | class PassFieldValidation(TestCase): 24 | def test_created_value_pass_validation(self): 25 | result = any_model(ModelWithValidatedField) 26 | validate_even(result.even_field) 27 | 28 | -------------------------------------------------------------------------------- /django_any/tests/model_qobjects_spec.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: django -*- 2 | """ 3 | Allow partial specifications with q objects 4 | """ 5 | from django.db import models 6 | from django.db.models import Q 7 | from django.test import TestCase 8 | from django_any import any_model 9 | 10 | 11 | class QObjectRelated(models.Model): 12 | class Meta: 13 | app_label = 'django_any' 14 | 15 | 16 | class RelatedToQObject(models.Model): 17 | related = models.ForeignKey(QObjectRelated) 18 | 19 | class Meta: 20 | app_label = 'django_any' 21 | 22 | 23 | class QObjectsSupport(TestCase): 24 | def setUp(self): 25 | self.related = any_model(QObjectRelated) 26 | 27 | def test_qobject_specification(self): 28 | result = any_model(RelatedToQObject, related=Q(pk=self.related.pk)) 29 | self.assertEqual(self.related, result.related) 30 | 31 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='django-any', 5 | version='0.2.0', 6 | description='Unobtrusive test models creation for django.', 7 | author='Mikhail Podgurskiy', 8 | author_email='kmmbvnr@gmail.com', 9 | url='http://github.com/kmmbvnr/django-any', 10 | keywords = "django", 11 | packages=['django_any', 'django_any.contrib', 'django_any.tests'], 12 | include_package_data=True, 13 | zip_safe=False, 14 | license='MIT License', 15 | platforms = ['any'], 16 | classifiers=[ 17 | 'Development Status :: 3 - Alpha', 18 | 'Environment :: Web Environment', 19 | 'Intended Audience :: Developers', 20 | 'License :: OSI Approved :: MIT License', 21 | 'Operating System :: OS Independent', 22 | 'Programming Language :: Python', 23 | 'Framework :: Django', 24 | ] 25 | ) 26 | 27 | -------------------------------------------------------------------------------- /django_any/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: django -*- 2 | import os 3 | import sys 4 | import doctest 5 | from glob import glob 6 | from unittest import TestSuite, defaultTestLoader 7 | 8 | 9 | TESTS_ROOT = os.path.abspath(os.path.dirname(__file__)) 10 | 11 | 12 | def suite(): 13 | result = TestSuite() 14 | 15 | result.addTest(doctest.DocTestSuite('django_any.xunit')) 16 | result.addTest(doctest.DocTestSuite('django_any.forms')) 17 | 18 | for filename in glob(os.path.join(TESTS_ROOT, '*.py')): 19 | if filename.endswith('__init__.py'): 20 | continue 21 | 22 | module_name = 'django_any.tests.%s' % \ 23 | os.path.splitext(os.path.basename(filename))[0] 24 | __import__(module_name) 25 | 26 | result.addTest( 27 | defaultTestLoader.loadTestsFromModule(sys.modules[module_name])) 28 | 29 | return result 30 | 31 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) 4 | 5 | SITE_ID=1 6 | PROJECT_APPS = ('django_any',) 7 | INSTALLED_APPS = ( 'django.contrib.auth', 8 | 'django.contrib.contenttypes', 9 | 'django.contrib.sessions', 10 | 'django.contrib.sites', 11 | 'django.contrib.admin', 12 | 'django_jenkins',) + PROJECT_APPS 13 | DATABASE_ENGINE = 'sqlite3' 14 | TEMPLATE_LOADERS = ( 15 | 'django.template.loaders.app_directories.load_template_source', 16 | ) 17 | ROOT_URLCONF = 'tests.test_runner' 18 | MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'media') 19 | 20 | if __name__ == "__main__": 21 | import sys, test_runner as settings 22 | from django.core.management import execute_manager 23 | if len(sys.argv) == 1: 24 | sys.argv += ['test'] + list(PROJECT_APPS) 25 | execute_manager(settings) 26 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Unobtrusive test models creation for django 2 | =========================================== 3 | 4 | django-any the explicit replacement for old-style, big and error-prone 5 | implicit fixture files. 6 | 7 | django-any allows to specify only fields important for test, 8 | and fill rest by random with acceptable values. 9 | 10 | It makes tests clean and easy to undestood, without reading fixture files. 11 | 12 | 13 | from django_any import any_model, WithTestDataSeed 14 | 15 | class TestMyShop(TestCase): 16 | def test_order_updates_user_account(self): 17 | account = any_model(Account, amount=25, user__is_active=True) 18 | order = any_model(Order, user=account.user, amount=10) 19 | order.proceed() 20 | 21 | account = Account.objects.get(pk=account.pk) 22 | self.assertEquals(15, account.amount) 23 | 24 | 25 | The same approach available for forms also (django_any.any_form) 26 | 27 | See docs/quickstart.txt for more details 28 | -------------------------------------------------------------------------------- /django_any/tests/model_foreign_key.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: django -*- 2 | """ 3 | Auto-create foreingkey referencies 4 | """ 5 | from django.db import models 6 | from django.test import TestCase 7 | from django_any import any_model 8 | 9 | 10 | class RelatedModel(models.Model): 11 | name = models.CharField(max_length=5) 12 | 13 | class Meta: 14 | app_label = 'django_any' 15 | 16 | 17 | class BaseModel(models.Model): 18 | related = models.ForeignKey(RelatedModel) 19 | 20 | class Meta: 21 | app_label = 'django_any' 22 | 23 | 24 | class ForeignKeyCreation(TestCase): 25 | def test_fk_relation_autocreate(self): 26 | result = any_model(BaseModel) 27 | 28 | self.assertEqual(type(result), BaseModel) 29 | 30 | self.assertEqual(type(result.related), RelatedModel) 31 | self.assertTrue(result.related.name is not None) 32 | 33 | def test_nested_models_specification(self): 34 | result = any_model(BaseModel, related__name='test') 35 | self.assertEqual(result.related.name, 'test') 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | copyright (c) 2010 Mikhail Podgurskiy 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /django_any/tests/contrib_any_user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: django -*- 2 | from django.db import models 3 | from django.contrib.auth.models import User 4 | from django.test import TestCase 5 | from django_any import any_model 6 | from django_any.contrib.auth import any_user 7 | 8 | 9 | class CustomPermission(models.Model): 10 | name = models.CharField(max_length=5) 11 | 12 | class Meta: 13 | app_label = 'django_any' 14 | 15 | 16 | class AnyUser(TestCase): 17 | def test_raw_user_creation(self): 18 | result = any_model(User) 19 | self.assertEqual(type(result), User) 20 | 21 | def test_create_superuser(self): 22 | user = any_user(is_superuser=True) 23 | self.assertTrue(user.is_superuser) 24 | 25 | def test_create_with_permissions(self): 26 | user = any_user(permissions= ['django_any.add_custompermission', 27 | 'django_any.delete_custompermission']) 28 | 29 | self.assertTrue(user.has_perm('django_any.add_custompermission')) 30 | self.assertTrue(user.has_perm('django_any.delete_custompermission')) 31 | self.assertFalse(user.has_perm('django_any.change_custompermission')) 32 | 33 | -------------------------------------------------------------------------------- /django_any/contrib/auth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.contrib.auth.models import User, Permission, Group 3 | from django_any import any_model 4 | 5 | def any_user(password=None, permissions=[], groups=[], **kwargs): 6 | """ 7 | Shortcut for creating Users 8 | 9 | Permissions could be a list of permission names 10 | 11 | If not specified, creates active, non superuser 12 | and non staff user 13 | """ 14 | 15 | is_active = kwargs.pop('is_active', True) 16 | is_superuser = kwargs.pop('is_superuser', False) 17 | is_staff = kwargs.pop('is_staff', False) 18 | 19 | user = any_model(User, is_active = is_active, is_superuser = is_superuser, 20 | is_staff = is_staff, **kwargs) 21 | 22 | for group_name in groups : 23 | group = Group.objects.get(name=group_name) 24 | user.groups.add(group) 25 | 26 | for permission_name in permissions: 27 | app_label, codename = permission_name.split('.') 28 | permission = Permission.objects.get( 29 | content_type__app_label=app_label, 30 | codename=codename) 31 | user.user_permissions.add(permission) 32 | 33 | if password: 34 | user.set_password(password) 35 | 36 | user.save() 37 | return user 38 | 39 | -------------------------------------------------------------------------------- /django_any/tests/model_redefine_creation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: django -*- 2 | from django.db import models 3 | from django.test import TestCase 4 | from django_any import any_model 5 | 6 | 7 | class Redefined(models.Model): 8 | name = models.CharField(max_length=5) 9 | 10 | class Meta: 11 | app_label = 'django_any' 12 | 13 | 14 | class RelatedToRedefined(models.Model): 15 | related = models.ForeignKey(Redefined) 16 | 17 | class Meta: 18 | app_label = 'django_any' 19 | 20 | 21 | @any_model.register(Redefined) 22 | def any_redefined_model(model_cls, **kwargs): 23 | kwargs['name'] = kwargs.get('name', 'test') 24 | return any_model.default(model_cls, **kwargs) 25 | 26 | 27 | class RedefinedCreation(TestCase): 28 | def test_redefined_creation(self): 29 | result = any_model(Redefined) 30 | self.assertEqual(result.name, 'test') 31 | 32 | def test_redefined_creation_partial_specification(self): 33 | result = any_model(Redefined, name="test2") 34 | self.assertEqual(result.name, 'test2') 35 | 36 | # TODO Fix model factory registration 37 | def _test_create_related_redefied(self): 38 | result = any_model(RelatedToRedefined) 39 | self.assertEqual(result.related.name, 'test') 40 | -------------------------------------------------------------------------------- /django_any/tests/model_creation_constraint.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: django -*- 2 | """ 3 | Models that have custom validation checks 4 | """ 5 | from django.core.exceptions import ValidationError 6 | from django.db import models 7 | from django.test import TestCase 8 | from django_any import any_model 9 | 10 | 11 | class ModelWithConstraint(models.Model): 12 | """ 13 | Validates that start_time is always before end_time 14 | """ 15 | start_time = models.DateTimeField() 16 | end_time = models.DateTimeField() 17 | 18 | def clean(self): 19 | if self.start_time > self.end_time: 20 | raise ValidationError('start_time could not be after end_time') 21 | 22 | class Meta: 23 | app_label = 'django_any' 24 | 25 | 26 | class ModelWithConstraintOnForeignKey(models.Model): 27 | timestamp = models.ForeignKey(ModelWithConstraint) 28 | 29 | class Meta: 30 | app_label = 'django_any' 31 | 32 | 33 | class PassModelValidation(TestCase): 34 | def test_model_creation_succeed(self): 35 | result = any_model(ModelWithConstraint) 36 | self.assertTrue(result.start_time <= result.end_time) 37 | 38 | def test_foreignkey_constraint_succeed(self): 39 | result = any_model(ModelWithConstraintOnForeignKey) 40 | self.assertTrue(result.timestamp.start_time <= result.timestamp.end_time) 41 | 42 | -------------------------------------------------------------------------------- /django_any/tests/field_attr_choices.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: django -*- 2 | """ 3 | https://docs.djangoproject.com/en/1.3/ref/models/fields/#choices 4 | """ 5 | from django.test import TestCase 6 | from django.db import models 7 | from django_any import any_field 8 | 9 | class AttrChoices(TestCase): 10 | def test_one_case_selection(self): 11 | """ 12 | Even there is only one choice, it should be returned always 13 | """ 14 | field = models.BooleanField(choices=[ 15 | (False, 'This sentence is wrong')]) 16 | 17 | result = any_field(field) 18 | 19 | self.assertEqual(bool, type(result)) 20 | self.assertEqual(False, result) 21 | 22 | def test_choices_named_groups_support(self): 23 | """ 24 | Group names completely ignored 25 | """ 26 | MEDIA_CHOICES = ( 27 | ('Audio', ( 28 | ('vinyl', 'Vinyl'), 29 | ('cd', 'CD'), 30 | )), 31 | ('Video', ( 32 | ('vhs', 'VHS Tape'), 33 | ('dvd', 'DVD'), 34 | )), 35 | ('unknown', 'Unknown')) 36 | media = models.CharField(max_length=25, choices=MEDIA_CHOICES) 37 | 38 | result = any_field(media) 39 | 40 | self.assertTrue(result in ['vinyl', 'cd', 'vhs', 'dvd', 'unknown']) 41 | 42 | -------------------------------------------------------------------------------- /django_any/tests/test_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: django -*- 2 | from django.conf.urls.defaults import patterns, include 3 | from django.contrib import admin 4 | from django.test import TestCase 5 | from django_any.test import Client 6 | 7 | def view(request): 8 | """ 9 | Test view that returning form 10 | """ 11 | from django import forms 12 | from django.http import HttpResponse 13 | from django.shortcuts import redirect 14 | from django.template import Context, Template 15 | 16 | class TestForm(forms.Form): 17 | name = forms.CharField() 18 | 19 | if request.POST: 20 | form = TestForm(request.POST) 21 | if form.is_valid(): 22 | return redirect('/view/') 23 | else: 24 | form = TestForm() 25 | 26 | template = Template("{{ form }}") 27 | context = Context({'form' : form}) 28 | 29 | return HttpResponse(template.render(context)) 30 | 31 | 32 | urlpatterns = patterns('', 33 | (r'^admin/', include(admin.site.urls)), 34 | (r'^view/', view), 35 | ) 36 | 37 | 38 | class DjangoAnyClient(TestCase): 39 | urls = 'django_any.tests.test_client' 40 | 41 | def setUp(self): 42 | self.client = Client() 43 | 44 | def test_login_as_super_user(self): 45 | self.assertTrue(self.client.login_as(is_superuser=True)) 46 | 47 | response = self.client.get('/admin/') 48 | self.assertEquals(200, response.status_code) 49 | 50 | def test_post_any_data(self): 51 | response = self.client.post_any_data('/view/') 52 | self.assertRedirects(response, '/view/') 53 | 54 | -------------------------------------------------------------------------------- /django_any/tests/model_creation_simple.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: django -*- 2 | """ 3 | Create models will all fields with simply to generate values 4 | """ 5 | from django.db import models 6 | from django.test import TestCase 7 | from django_any import any_model 8 | 9 | class SimpleModel(models.Model): 10 | big_integer_field = models.BigIntegerField() 11 | char_field = models.CharField(max_length=5) 12 | boolean_field = models.BooleanField() 13 | comma_separated_field = models.CommaSeparatedIntegerField(max_length=50) 14 | date_field = models.DateField() 15 | datetime_field = models.DateTimeField() 16 | decimal_field = models.DecimalField(decimal_places=2, max_digits=10) 17 | email_field = models.EmailField() 18 | float_field = models.FloatField() 19 | integer_field = models.IntegerField() 20 | ip_field = models.IPAddressField() 21 | null_boolead_field = models.NullBooleanField() 22 | positive_integer_field = models.PositiveIntegerField() 23 | small_integer = models.PositiveSmallIntegerField() 24 | slig_field = models.SlugField() 25 | text_field = models.TextField() 26 | time_field = models.TimeField() 27 | url_field = models.URLField(verify_exists=False) 28 | 29 | class Meta: 30 | app_label = 'django_any' 31 | 32 | 33 | class SimpleCreation(TestCase): 34 | def test_model_creation_succeed(self): 35 | result = any_model(SimpleModel) 36 | 37 | self.assertEqual(type(result), SimpleModel) 38 | 39 | for field in result._meta.fields: 40 | value = getattr(result, field.name) 41 | self.assertTrue(value is not None, "%s is uninitialized" % field.name) 42 | 43 | def _test_partial_specification(self): 44 | result = any_model(SimpleModel, char_field='test') 45 | self.assertEqual(result.char_field, 'test') 46 | -------------------------------------------------------------------------------- /django_any/tests/model_oneto_one.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: django -*- 2 | """ 3 | Shortcuts for onetoone model fields 4 | """ 5 | from django.db import models 6 | from django.test import TestCase 7 | from django_any import any_model 8 | 9 | 10 | class OneToOneRelated(models.Model): 11 | name = models.CharField(max_length=5) 12 | 13 | class Meta: 14 | app_label = 'django_any' 15 | 16 | 17 | class ModelWithOneToOneField(models.Model): 18 | name = models.CharField(max_length=5) 19 | related = models.OneToOneField(OneToOneRelated) 20 | 21 | class Meta: 22 | app_label = 'django_any' 23 | 24 | 25 | class OneToOneCreation(TestCase): 26 | def test_oneto_one_autocreate(self): 27 | result = any_model(ModelWithOneToOneField) 28 | self.assertEqual(type(result), ModelWithOneToOneField) 29 | self.assertTrue(result.name is not None) 30 | 31 | self.assertEqual(type(result.related), OneToOneRelated) 32 | self.assertTrue(result.related.name is not None) 33 | 34 | def test_related_onetoone_not_created_by_default(self): 35 | simple_model = any_model(OneToOneRelated) 36 | self.assertRaises(ModelWithOneToOneField.DoesNotExist, 37 | lambda : simple_model.modelwithonetoonefield) 38 | 39 | def test_related_specification_succeed(self): 40 | related = any_model(OneToOneRelated) 41 | result = any_model(ModelWithOneToOneField, related=related) 42 | self.assertEqual(related, result.related) 43 | 44 | def test_partial_specification_succeed(self): 45 | result = any_model(ModelWithOneToOneField, related__name='test') 46 | self.assertEqual(result.related.name, 'test') 47 | 48 | # TODO Create model for reverse relation 49 | def _test_reverse_relation_spec_succeed(self): 50 | related = any_model(OneToOneRelated, modelwithonetoonefield__name='test') 51 | self.assertEqual(related.modelwithonetoonefield.name, 'test') 52 | 53 | -------------------------------------------------------------------------------- /docs/quickstart.txt: -------------------------------------------------------------------------------- 1 | Quickstart 2 | ========== 3 | 4 | django-any the explicit replacement for old-style, big and error-prone 5 | implicit fixture files. 6 | 7 | django-any allows to specify only fields important for test, 8 | and fill rest by random with acceptable values. 9 | 10 | 11 | Basic features 12 | -------------- 13 | 14 | You could get saved in db model instance without specify 15 | any model fields 16 | 17 | from django_any import any_model 18 | user = any_model(User) 19 | 20 | django-any will preserve all field constrants, such as max_length, 21 | and choices when filling models with random data. 22 | 23 | django-any supports the same `double-underscore` syntax as django orm, 24 | for setting subfields values 25 | 26 | order = any_model(Order, user__is_active = True) 27 | 28 | You could use Q objects, for selection values for fields from fixtures 29 | 30 | order = any_model(Order, customer__location=Q(country='US')) 31 | 32 | 33 | Debugging 34 | --------- 35 | 36 | It is recomended to specify django_any.WithTestDataSeed as metaclass 37 | for your TestCase 38 | 39 | from django_any import any_model, WithTestDataSeed 40 | 41 | class SiteTests(TestCase): 42 | __metaclass__ = WithTestDataSeed 43 | 44 | def test_something(self): 45 | .... 46 | 47 | If you test sometimes fails, in error log, you could found used 48 | random seed 49 | 50 | 51 | ====================================================================== 52 | FAIL: test__something (mysite.SiteTests) With seed 1434556623 53 | 54 | 55 | You could use this seed, to repeat and debug you tests, with exactly 56 | the same random data 57 | 58 | 59 | from django_any import any_model, WithTestDataSeed, with_seed, without_random_seed 60 | 61 | class SiteTests(TestCase): 62 | __metaclass__ = WithTestDataSeed 63 | 64 | @without_random_seed 65 | @with_seed(1434556623) 66 | def test_something(self): 67 | .... 68 | 69 | 70 | `without_random_seed` decorator disables test run with random seed, and 71 | `with_seed` runs test with selected seed. 72 | -------------------------------------------------------------------------------- /tests/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.6 2 | # -*- coding: utf-8 -*- 3 | from os import path 4 | import shutil, sys, virtualenv, subprocess 5 | 6 | PROJECT_ROOT = path.dirname(path.abspath(path.dirname(__file__))) 7 | REQUIREMENTS = path.join(PROJECT_ROOT, 'tests', 'requirements.pip') 8 | 9 | VE_ROOT = path.join(PROJECT_ROOT, '.ve') 10 | VE_TIMESTAMP = path.join(VE_ROOT, 'timestamp') 11 | 12 | envtime = path.exists(VE_ROOT) and path.getmtime(VE_ROOT) or 0 13 | envreqs = path.exists(VE_TIMESTAMP) and path.getmtime(VE_TIMESTAMP) or 0 14 | envspec = path.getmtime(REQUIREMENTS) 15 | 16 | def go_to_ve(): 17 | # going into ve 18 | if not sys.prefix == VE_ROOT: 19 | if sys.platform == 'win32': 20 | python = path.join(VE_ROOT, 'Scripts', 'python.exe') 21 | else: 22 | python = path.join(VE_ROOT, 'bin', 'python') 23 | 24 | retcode = subprocess.call([python, __file__] + sys.argv[1:]) 25 | sys.exit(retcode) 26 | 27 | update_ve = 'update_ve' in sys.argv 28 | if update_ve or envtime < envspec or envreqs < envspec: 29 | if update_ve: 30 | # install ve 31 | if envtime < envspec: 32 | if path.exists(VE_ROOT): 33 | shutil.rmtree(VE_ROOT) 34 | virtualenv.logger = virtualenv.Logger(consumers=[]) 35 | virtualenv.create_environment(VE_ROOT, site_packages=True) 36 | 37 | go_to_ve() 38 | 39 | # check requirements 40 | if update_ve or envreqs < envspec: 41 | import pip 42 | pip.main(initial_args=['install', '-r', REQUIREMENTS]) 43 | file(VE_TIMESTAMP, 'w').close() 44 | sys.exit(0) 45 | else: 46 | print "VirtualEnv need to be updated" 47 | print "Run ./manage.py update_ve" 48 | sys.exit(1) 49 | 50 | go_to_ve() 51 | 52 | sys.path.insert(0, PROJECT_ROOT) 53 | 54 | # run django 55 | from django.core.management import execute_manager 56 | try: 57 | import tests.settings 58 | except ImportError: 59 | import sys 60 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory") 61 | sys.exit(1) 62 | 63 | if __name__ == "__main__": 64 | if len(sys.argv) == 1: 65 | sys.argv += ['test'] + list(tests.settings.PROJECT_APPS) 66 | execute_manager(tests.settings) 67 | -------------------------------------------------------------------------------- /django_any/functions.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | """ 3 | Additional functions for django-any 4 | """ 5 | 6 | def valid_choices(choices): 7 | """ 8 | Return list of choices's keys 9 | """ 10 | for key, value in choices: 11 | if isinstance(value, (list, tuple)): 12 | for key, _ in value: 13 | yield key 14 | else: 15 | yield key 16 | 17 | 18 | def split_model_kwargs(kw): 19 | """ 20 | django_any birds language parser 21 | """ 22 | from collections import defaultdict 23 | 24 | model_fields = {} 25 | fields_agrs = defaultdict(lambda : {}) 26 | 27 | for key in kw.keys(): 28 | if '__' in key: 29 | field, _, subfield = key.partition('__') 30 | fields_agrs[field][subfield] = kw[key] 31 | else: 32 | model_fields[key] = kw[key] 33 | 34 | return model_fields, fields_agrs 35 | 36 | 37 | class ExtensionMethod(object): 38 | """ 39 | Works like one parameter multimethod 40 | """ 41 | def __init__(self, by_instance=False): 42 | self.registry = {} 43 | self.by_instance = by_instance 44 | self.default = None 45 | 46 | def register(self, field_type, impl=None): 47 | """ 48 | Register form field data function. 49 | 50 | Could be used as decorator 51 | """ 52 | def _wrapper(func): 53 | self.registry[field_type] = func 54 | return func 55 | 56 | if impl: 57 | return _wrapper(impl) 58 | return _wrapper 59 | 60 | def register_default(self, func): 61 | self.default = func 62 | return func 63 | 64 | def decorator(self, impl): 65 | """ 66 | Decorator for register decorators 67 | """ 68 | self._create_value = impl(self._create_value) 69 | return impl 70 | 71 | def _create_value(self, *args, **kwargs): 72 | """ 73 | Lowest value generator. 74 | 75 | Separated from __call__, because it seems that python 76 | cache __call__ reference on module import 77 | """ 78 | if not len(args): 79 | raise TypeError('Object instance is not provided') 80 | 81 | if self.by_instance: 82 | field_type = args[0] 83 | else: 84 | field_type = args[0].__class__ 85 | 86 | function = self.registry.get(field_type, self.default) 87 | 88 | if function is None: 89 | raise TypeError("no match %s" % field_type) 90 | 91 | return function(*args, **kwargs) 92 | 93 | def __call__(self, *args, **kwargs): 94 | return self._create_value(*args, **kwargs) 95 | 96 | -------------------------------------------------------------------------------- /django_any/xunit.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | """ 3 | The python basic types generators 4 | """ 5 | import random 6 | from string import ascii_letters 7 | from datetime import date, datetime, timedelta 8 | from decimal import Decimal 9 | 10 | def weighted_choice(choices): 11 | """ 12 | Supposes that choices is sequence of two elements items, 13 | where first one is the probability and second is the 14 | result object or callable 15 | 16 | >>> result = weighted_choice([(20,'x'), (100, 'y')]) 17 | >>> result in ['x', 'y'] 18 | True 19 | """ 20 | total = sum([weight for (weight, _) in choices]) 21 | i = random.randint(0, total - 1) 22 | for weight, choice in choices: 23 | i -= weight 24 | if i < 0: 25 | if callable(choice): 26 | return choice() 27 | return choice 28 | raise Exception('Bug') 29 | 30 | 31 | def any_boolean(): 32 | """ 33 | Returns True or False 34 | 35 | >>> result = any_boolean() 36 | >>> type(result) 37 | 38 | """ 39 | return random.choice([True, False]) 40 | 41 | 42 | def any_int(min_value=0, max_value=100, **kwargs): 43 | """ 44 | Return random integer from the selected range 45 | 46 | >>> result = any_int(min_value=0, max_value=100) 47 | >>> type(result) 48 | 49 | >>> result in range(0,101) 50 | True 51 | 52 | """ 53 | return random.randint(min_value, max_value) 54 | 55 | 56 | def any_float(min_value=0, max_value=100, precision=2): 57 | """ 58 | Returns random float 59 | 60 | >>> result = any_float(min_value=0, max_value=100, precision=2) 61 | >>> type(result) 62 | 63 | >>> result >=0 and result <= 100 64 | True 65 | 66 | """ 67 | return round(random.uniform(min_value, max_value), precision) 68 | 69 | 70 | def any_letter(letters = ascii_letters, **kwargs): 71 | """ 72 | Return random letter 73 | 74 | >>> result = any_letter(letters = ascii_letters) 75 | >>> type(result) 76 | 77 | >>> len(result) 78 | 1 79 | >>> result in ascii_letters 80 | True 81 | 82 | """ 83 | return random.choice(letters) 84 | 85 | 86 | def any_string(letters = ascii_letters, min_length=3, max_length=100): 87 | """ 88 | Return string with random content 89 | 90 | >>> result = any_string(letters = ascii_letters, min_length=3, max_length=100) 91 | >>> type(result) 92 | 93 | >>> len(result) in range(3,101) 94 | True 95 | >>> any([c in ascii_letters for c in result]) 96 | True 97 | """ 98 | 99 | length = random.randint(min_length, max_length) 100 | letters = [any_letter(letters=letters) for _ in range(0, length)] 101 | return "".join(letters) 102 | 103 | 104 | def any_date(from_date=date(1990, 1, 1), to_date=date.today()): 105 | """ 106 | Return random date from the [from_date, to_date] interval 107 | 108 | >>> result = any_date(from_date=date(1990,1,1), to_date=date(1990,1,3)) 109 | >>> type(result) 110 | 111 | >>> result >= date(1990,1,1) and result <= date(1990,1,3) 112 | True 113 | """ 114 | days = any_int(min_value=0, max_value=(to_date - from_date).days) 115 | 116 | return from_date + timedelta(days=days) 117 | 118 | 119 | def any_datetime(from_date=datetime(1990, 1, 1), to_date=datetime.now()): 120 | """ 121 | Return random datetime from the [from_date, to_date] interval 122 | 123 | >>> result = any_datetime(from_date=datetime(1990,1,1), to_date=datetime(1990,1,3)) 124 | >>> type(result) 125 | 126 | >>> result >= datetime(1990,1,1) and result <= datetime(1990,1,3) 127 | True 128 | """ 129 | days = any_int(min_value=0, max_value=(to_date - from_date).days-1) 130 | time = timedelta(seconds=any_int(min_value=0, max_value=24*3600-1)) 131 | 132 | return from_date + timedelta(days=days) + time 133 | 134 | 135 | def any_decimal(min_value=Decimal(0), max_value=Decimal('99.99'), decimal_places=2): 136 | """ 137 | Return random decimal from the [min_value, max_value] interval 138 | 139 | >>> result = any_decimal(min_value=0.999, max_value=3, decimal_places=3) 140 | >>> type(result) 141 | 142 | >>> result >= Decimal('0.999') and result <= Decimal(3) 143 | True 144 | """ 145 | return Decimal(str(any_float(min_value=float(min_value), 146 | max_value=float(max_value), 147 | precision=decimal_places))) 148 | 149 | -------------------------------------------------------------------------------- /django_any/test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import time, random 3 | from unittest import _strclass 4 | from django import forms 5 | from django_any import any_form 6 | from django.test.client import Client as DjangoClient 7 | from django_any.contrib.auth import any_user 8 | from django.contrib.admin.helpers import AdminForm 9 | from django_any import xunit 10 | 11 | 12 | def _context_keys_iterator(context): 13 | for container_or_key in context: 14 | if isinstance(container_or_key, basestring): 15 | yield container_or_key 16 | else: 17 | for key in _context_keys_iterator(container_or_key): 18 | yield key 19 | 20 | 21 | def _request_context_forms(context): 22 | """ 23 | Lookup all stored in context froms instance 24 | """ 25 | for key in _context_keys_iterator(context): 26 | inst = context[key] 27 | if isinstance(inst, (forms.Form, forms.ModelForm)): 28 | yield inst 29 | elif isinstance(inst, forms.formsets.BaseFormSet): 30 | yield inst 31 | elif isinstance(inst, AdminForm): 32 | yield inst.form 33 | 34 | 35 | class Client(DjangoClient): 36 | def login_as(self, **kwargs): 37 | password = xunit.any_string() 38 | user = any_user(password=password, **kwargs) 39 | 40 | if self.login(username=user.username, password=password): 41 | return user 42 | raise AssertionError('Can''t login with autogenerated user') 43 | 44 | def post_any_data(self, url, extra=None, context_forms=_request_context_forms, **kwargs): 45 | response = self.get(url) 46 | 47 | post_data = {} 48 | 49 | # extract foms instances 50 | if callable(context_forms): 51 | forms_list = context_forms(response.context) 52 | elif isinstance(context_forms, (list, tuple)): 53 | forms_list = [response.context[form_name] for form_name in context_forms] 54 | else: 55 | raise TypeError('context_forms should be callable or list or tuple, not %s' % type(context_forms).__name__) 56 | 57 | # generate data 58 | for form in forms_list: 59 | if isinstance(form, forms.formsets.BaseFormSet): # TODO any_form ExtensionMethod 60 | #TODO support formset data 61 | form_data = form.management_form.initial 62 | form_data['MAX_NUM_FORMS'] = 0 63 | else: 64 | form_data, form_files = any_form(form.__class__, **kwargs) #TODO support form instance 65 | 66 | if form.prefix: 67 | form_data = dict([('%s-%s' % (form.prefix, key), value) for key, value in form_data.items()]) 68 | 69 | post_data.update(form_data) 70 | 71 | if extra: 72 | post_data.update(extra) 73 | 74 | return self.post(url, post_data) 75 | 76 | 77 | def without_random_seed(func): 78 | """ 79 | Marks that test method do not need to be started with random seed 80 | """ 81 | func.__django_any_without_random_seed = True 82 | return func 83 | 84 | 85 | def with_seed(seed): 86 | """ 87 | Marks that test method do not need to be started with specific seed 88 | """ 89 | def _wrapper(func): 90 | seeds = getattr(func, '__django_any_with_seed', []) 91 | seeds.append(seed) 92 | func.__django_any_with_seed = seeds 93 | return func 94 | return _wrapper 95 | 96 | 97 | def set_seed(func, seed=None): 98 | """ 99 | Set randon seed before executing function. If seed is 100 | not provided current timestamp used 101 | """ 102 | def _wrapper(self, seed=seed, *args, **kwargs): 103 | self.__django_any_seed = seed if seed else int(time.time()*1000) 104 | random.seed(self.__django_any_seed) 105 | return func(self, *args, **kwargs) 106 | return _wrapper 107 | 108 | 109 | class WithTestDataSeed(type): 110 | """ 111 | Metaclass for TestCases, manages random tests run 112 | """ 113 | def __new__(cls, cls_name, bases, attrs): 114 | attrs['__django_any_seed'] = 0 115 | 116 | def shortDescription(self): 117 | return "%s (%s) With seed %s" % (self._testMethodName, _strclass(self.__class__), getattr(self, '__django_any_seed')) 118 | 119 | for name, func in attrs.items(): 120 | if name.startswith('test') and hasattr(func, '__call__'): 121 | if getattr(func, '__django_any_without_random_seed', False): 122 | del attrs[name] 123 | else: 124 | attrs[name] = set_seed(func) 125 | 126 | for seed in getattr(func, '__django_any_with_seed', []): 127 | attrs['%s_%d' % (name, seed)] = set_seed(func, seed) 128 | 129 | testcase = super(WithTestDataSeed, cls).__new__(cls, cls_name, bases, attrs) 130 | testcase.shortDescription = shortDescription 131 | return testcase 132 | 133 | -------------------------------------------------------------------------------- /django_any/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=W0613, C0103 3 | """ 4 | Django forms data generators 5 | 6 | """ 7 | import random 8 | from datetime import date, datetime, time 9 | from django import forms 10 | from django.utils import formats 11 | from django_any import xunit 12 | from django_any.functions import valid_choices, split_model_kwargs, \ 13 | ExtensionMethod 14 | 15 | any_form = ExtensionMethod() 16 | any_form_field = ExtensionMethod() 17 | 18 | 19 | @any_form.register_default 20 | def any_form_default(form_cls, **kwargs): 21 | """ 22 | Returns tuple with form data and files 23 | """ 24 | form_data = {} 25 | form_files = {} 26 | 27 | form_fields, fields_args = split_model_kwargs(kwargs) 28 | 29 | for name, field in form_cls.base_fields.iteritems(): 30 | if name in form_fields: 31 | form_data[name] = kwargs[name] 32 | else: 33 | form_data[name] = any_form_field(field, **fields_args[name]) 34 | 35 | return form_data, form_files 36 | 37 | 38 | @any_form_field.decorator 39 | def field_required_attribute(function): 40 | """ 41 | Sometimes return None if field is not required 42 | 43 | >>> result = any_form_field(forms.BooleanField(required=False)) 44 | >>> result in ['', 'True', 'False'] 45 | True 46 | """ 47 | def _wrapper(field, **kwargs): 48 | if not field.required and random.random < 0.1: 49 | return None 50 | return function(field, **kwargs) 51 | return _wrapper 52 | 53 | 54 | @any_form_field.decorator 55 | def field_choices_attibute(function): 56 | """ 57 | Selection from field.choices 58 | """ 59 | def _wrapper(field, **kwargs): 60 | if hasattr(field.widget, 'choices'): 61 | return random.choice(list(valid_choices(field.widget.choices))) 62 | return function(field, **kwargs) 63 | 64 | return _wrapper 65 | 66 | 67 | @any_form_field.register(forms.BooleanField) 68 | def boolean_field_data(field, **kwargs): 69 | """ 70 | Return random value for BooleanField 71 | 72 | >>> result = any_form_field(forms.BooleanField()) 73 | >>> type(result) 74 | 75 | """ 76 | return str(xunit.any_boolean()) 77 | 78 | 79 | @any_form_field.register(forms.CharField) 80 | def char_field_data(field, **kwargs): 81 | """ 82 | Return random value for CharField 83 | >>> result = any_form_field(forms.CharField(min_length=3, max_length=10)) 84 | >>> type(result) 85 | 86 | """ 87 | min_length = kwargs.get('min_length', 1) 88 | max_length = kwargs.get('max_length', field.max_length or 255) 89 | return xunit.any_string(min_length=field.min_length or min_length, 90 | max_length=field.max_length or max_length) 91 | 92 | 93 | @any_form_field.register(forms.DecimalField) 94 | def decimal_field_data(field, **kwargs): 95 | """ 96 | Return random value for DecimalField 97 | 98 | >>> result = any_form_field(forms.DecimalField(max_value=100, min_value=11, max_digits=4, decimal_places = 2)) 99 | >>> type(result) 100 | 101 | >>> from decimal import Decimal 102 | >>> Decimal(result) >= 11, Decimal(result) <= Decimal('99.99') 103 | (True, True) 104 | """ 105 | min_value = 0 106 | max_value = 10 107 | from django.core.validators import MinValueValidator, MaxValueValidator 108 | for elem in field.validators: 109 | if isinstance(elem, MinValueValidator): 110 | min_value = elem.limit_value 111 | if isinstance(elem, MaxValueValidator): 112 | max_value = elem.limit_value 113 | if (field.max_digits and field.decimal_places): 114 | from decimal import Decimal 115 | max_value = min(max_value, 116 | Decimal('%s.%s' % ('9'*(field.max_digits-field.decimal_places), 117 | '9'*field.decimal_places))) 118 | 119 | min_value = kwargs.get('min_value') or min_value 120 | max_value = kwargs.get('max_value') or max_value 121 | 122 | return str(xunit.any_decimal(min_value=min_value, 123 | max_value=max_value, 124 | decimal_places = field.decimal_places or 2)) 125 | 126 | 127 | @any_form_field.register(forms.EmailField) 128 | def email_field_data(field, **kwargs): 129 | """ 130 | Return random value for EmailField 131 | 132 | >>> result = any_form_field(forms.EmailField(min_length=10, max_length=30)) 133 | >>> type(result) 134 | 135 | >>> len(result) <= 30, len(result) >= 10 136 | (True, True) 137 | """ 138 | max_length = 10 139 | if field.max_length: 140 | max_length = (field.max_length -5) / 2 141 | min_length = 10 142 | if field.min_length: 143 | min_length = (field.min_length-4) / 2 144 | return "%s@%s.%s" % ( 145 | xunit.any_string(min_length=min_length, max_length=max_length), 146 | xunit.any_string(min_length=min_length, max_length=max_length), 147 | xunit.any_string(min_length=2, max_length=3)) 148 | 149 | 150 | @any_form_field.register(forms.DateField) 151 | def date_field_data(field, **kwargs): 152 | """ 153 | Return random value for DateField 154 | 155 | >>> result = any_form_field(forms.DateField()) 156 | >>> type(result) 157 | 158 | """ 159 | from_date = kwargs.get('from_date', date(1990, 1, 1)) 160 | to_date = kwargs.get('to_date', date.today()) 161 | 162 | date_format = random.choice(field.input_formats or formats.get_format('DATE_INPUT_FORMATS')) 163 | 164 | return xunit.any_date(from_date=from_date, to_date=to_date).strftime(date_format) 165 | 166 | 167 | @any_form_field.register(forms.DateTimeField) 168 | def datetime_field_data(field, **kwargs): 169 | """ 170 | Return random value for DateTimeField 171 | 172 | >>> result = any_form_field(forms.DateTimeField()) 173 | >>> type(result) 174 | 175 | """ 176 | from_date = kwargs.get('from_date', datetime(1990, 1, 1)) 177 | to_date = kwargs.get('to_date', datetime.today()) 178 | date_format = random.choice(field.input_formats or formats.get_format('DATETIME_INPUT_FORMATS')) 179 | return xunit.any_datetime(from_date=from_date, to_date=to_date).strftime(date_format) 180 | 181 | 182 | @any_form_field.register(forms.FloatField) 183 | def float_field_data(field, **kwargs): 184 | """ 185 | Return random value for FloatField 186 | 187 | >>> result = any_form_field(forms.FloatField(max_value=200, min_value=100)) 188 | >>> type(result) 189 | 190 | >>> float(result) >=100, float(result) <=200 191 | (True, True) 192 | """ 193 | min_value = 0 194 | max_value = 100 195 | from django.core.validators import MinValueValidator, MaxValueValidator 196 | for elem in field.validators: 197 | if isinstance(elem, MinValueValidator): 198 | min_value = elem.limit_value 199 | if isinstance(elem, MaxValueValidator): 200 | max_value = elem.limit_value 201 | 202 | min_value = kwargs.get('min_value', min_value) 203 | max_value = kwargs.get('max_value', max_value) 204 | precision = kwargs.get('precision', 3) 205 | 206 | return str(xunit.any_float(min_value=min_value, max_value=max_value, precision=precision)) 207 | 208 | 209 | @any_form_field.register(forms.IntegerField) 210 | def integer_field_data(field, **kwargs): 211 | """ 212 | Return random value for IntegerField 213 | 214 | >>> result = any_form_field(forms.IntegerField(max_value=200, min_value=100)) 215 | >>> type(result) 216 | 217 | >>> int(result) >=100, int(result) <=200 218 | (True, True) 219 | """ 220 | min_value = 0 221 | max_value = 100 222 | from django.core.validators import MinValueValidator, MaxValueValidator 223 | for elem in field.validators: 224 | if isinstance(elem, MinValueValidator): 225 | min_value = elem.limit_value 226 | if isinstance(elem, MaxValueValidator): 227 | max_value = elem.limit_value 228 | 229 | min_value = kwargs.get('min_value', min_value) 230 | max_value = kwargs.get('max_value', max_value) 231 | 232 | return str(xunit.any_int(min_value=min_value, max_value=max_value)) 233 | 234 | 235 | @any_form_field.register(forms.IPAddressField) 236 | def ipaddress_field_data(field, **kwargs): 237 | """ 238 | Return random value for IPAddressField 239 | 240 | >>> result = any_form_field(forms.IPAddressField()) 241 | >>> type(result) 242 | 243 | >>> from django.core.validators import ipv4_re 244 | >>> import re 245 | >>> re.match(ipv4_re, result) is not None 246 | True 247 | """ 248 | choices = kwargs.get('choices') 249 | if choices: 250 | return random.choice(choices) 251 | else: 252 | nums = [str(xunit.any_int(min_value=0, max_value=255)) for _ in xrange(0, 4)] 253 | return ".".join(nums) 254 | 255 | 256 | @any_form_field.register(forms.NullBooleanField) 257 | def null_boolean_field_data(field, **kwargs): 258 | """ 259 | Return random value for NullBooleanField 260 | 261 | >>> result = any_form_field(forms.NullBooleanField()) 262 | >>> type(result) 263 | 264 | >>> result in [u'1', u'2', u'3'] 265 | True 266 | """ 267 | return random.choice(['None', 'True', 'False']) 268 | 269 | 270 | @any_form_field.register(forms.SlugField) 271 | def slug_field_data(field, **kwargs): 272 | """ 273 | Return random value for SlugField 274 | 275 | >>> result = any_form_field(forms.SlugField()) 276 | >>> type(result) 277 | 278 | >>> from django.core.validators import slug_re 279 | >>> import re 280 | >>> re.match(slug_re, result) is not None 281 | True 282 | """ 283 | min_length = kwargs.get('min_length', 1) 284 | max_length = kwargs.get('max_length', field.max_length or 20) 285 | 286 | from string import ascii_letters, digits 287 | letters = ascii_letters + digits + '_-' 288 | return xunit.any_string(letters = letters, min_length = min_length, max_length = max_length) 289 | 290 | 291 | @any_form_field.register(forms.URLField) 292 | def url_field_data(field, **kwargs): 293 | """ 294 | Return random value for URLField 295 | 296 | >>> result = any_form_field(forms.URLField()) 297 | >>> from django.core.validators import URLValidator 298 | >>> import re 299 | >>> re.match(URLValidator.regex, result) is not None 300 | True 301 | """ 302 | urls = kwargs.get('choices', 303 | ['http://news.yandex.ru/society.html', 304 | 'http://video.google.com/?hl=en&tab=wv', 305 | 'http://www.microsoft.com/en/us/default.aspx', 306 | 'http://habrahabr.ru/company/opera/', 307 | 'http://www.apple.com/support/hardware/', 308 | 'http://localhost/', 309 | 'http://72.14.221.99', 310 | 'http://fr.wikipedia.org/wiki/France']) 311 | 312 | return random.choice(urls) 313 | 314 | 315 | @any_form_field.register(forms.TimeField) 316 | def time_field_data(field, **kwargs): 317 | """ 318 | Return random value for TimeField 319 | 320 | >>> result = any_form_field(forms.TimeField()) 321 | >>> type(result) 322 | 323 | """ 324 | time_format = random.choice(field.input_formats or formats.get_format('TIME_INPUT_FORMATS')) 325 | 326 | return time(xunit.any_int(min_value=0, max_value=23), 327 | xunit.any_int(min_value=0, max_value=59), 328 | xunit.any_int(min_value=0, max_value=59)).strftime(time_format) 329 | 330 | 331 | @any_form_field.register(forms.TypedChoiceField) 332 | @any_form_field.register(forms.ChoiceField) 333 | def choice_field_data(field, **kwargs): 334 | """ 335 | Return random value for ChoiceField 336 | 337 | >>> CHOICES = [('YNG', 'Child'), ('OLD', 'Parent')] 338 | >>> result = any_form_field(forms.ChoiceField(choices=CHOICES)) 339 | >>> type(result) 340 | 341 | >>> result in ['YNG', 'OLD'] 342 | True 343 | >>> typed_result = any_form_field(forms.TypedChoiceField(choices=CHOICES)) 344 | >>> typed_result in ['YNG', 'OLD'] 345 | True 346 | """ 347 | if field.choices: 348 | return str(random.choice(list(valid_choices(field.choices)))) 349 | return 'None' 350 | 351 | 352 | @any_form_field.register(forms.MultipleChoiceField) 353 | def multiple_choice_field_data(field, **kwargs): 354 | """ 355 | Return random value for MultipleChoiceField 356 | 357 | >>> CHOICES = [('YNG', 'Child'), ('MIDDLE', 'Parent') ,('OLD', 'GrandParent')] 358 | >>> result = any_form_field(forms.MultipleChoiceField(choices=CHOICES)) 359 | >>> type(result) 360 | 361 | """ 362 | if field.choices: 363 | from django_any.functions import valid_choices 364 | l = list(valid_choices(field.choices)) 365 | random.shuffle(l) 366 | choices = [] 367 | count = xunit.any_int(min_value=1, max_value=len(field.choices)) 368 | for i in xrange(0, count): 369 | choices.append(l[i]) 370 | return ' '.join(choices) 371 | return 'None' 372 | 373 | 374 | @any_form_field.register(forms.models.ModelChoiceField) 375 | def model_choice_field_data(field, **kwargs): 376 | """ 377 | Return one of first ten items for field queryset 378 | """ 379 | data = list(field.queryset[:10]) 380 | if data: 381 | return random.choice(data) 382 | else: 383 | raise TypeError('No %s available in queryset' % field.queryset.model) 384 | 385 | -------------------------------------------------------------------------------- /django_any/models.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | # pylint: disable=E0102, W0613 3 | """ 4 | Values generators for common Django Fields 5 | """ 6 | import re, os, random 7 | from decimal import Decimal 8 | from datetime import date, datetime, time 9 | from string import ascii_letters, digits 10 | from random import choice 11 | 12 | from django.core.exceptions import ValidationError 13 | from django.core import validators 14 | from django.db import models, IntegrityError 15 | from django.db.models import Q 16 | from django.db.models.fields.files import FieldFile 17 | from django.contrib.webdesign.lorem_ipsum import paragraphs 18 | 19 | from django_any import xunit 20 | from django_any.functions import valid_choices, split_model_kwargs, \ 21 | ExtensionMethod 22 | 23 | any_field = ExtensionMethod() 24 | any_model = ExtensionMethod(by_instance=True) 25 | 26 | @any_field.decorator 27 | def any_field_blank(function): 28 | """ 29 | Sometimes return None if field could be blank 30 | """ 31 | def wrapper(field, **kwargs): 32 | if kwargs.get('isnull', False): 33 | return None 34 | 35 | if field.blank and random.random < 0.1: 36 | return None 37 | return function(field, **kwargs) 38 | return wrapper 39 | 40 | 41 | @any_field.decorator 42 | def any_field_choices(function): 43 | """ 44 | Selection from field.choices 45 | 46 | >>> CHOICES = [('YNG', 'Child'), ('OLD', 'Parent')] 47 | >>> result = any_field(models.CharField(max_length=3, choices=CHOICES)) 48 | >>> result in ['YNG', 'OLD'] 49 | True 50 | """ 51 | def wrapper(field, **kwargs): 52 | if field.choices: 53 | return random.choice(list(valid_choices(field.choices))) 54 | return function(field, **kwargs) 55 | 56 | return wrapper 57 | 58 | 59 | @any_field.register(models.BigIntegerField) 60 | def any_biginteger_field(field, **kwargs): 61 | """ 62 | Return random value for BigIntegerField 63 | 64 | >>> result = any_field(models.BigIntegerField()) 65 | >>> type(result) 66 | 67 | """ 68 | min_value = kwargs.get('min_value', 1) 69 | max_value = kwargs.get('max_value', 10**10) 70 | return long(xunit.any_int(min_value=min_value, max_value=max_value)) 71 | 72 | 73 | @any_field.register(models.BooleanField) 74 | def any_boolean_field(field, **kwargs): 75 | """ 76 | Return random value for BooleanField 77 | 78 | >>> result = any_field(models.BooleanField()) 79 | >>> type(result) 80 | 81 | """ 82 | return xunit.any_boolean() 83 | 84 | 85 | @any_field.register(models.PositiveIntegerField) 86 | def any_positiveinteger_field(field, **kwargs): 87 | """ 88 | An positive integer 89 | 90 | >>> result = any_field(models.PositiveIntegerField()) 91 | >>> type(result) 92 | 93 | >>> result > 0 94 | True 95 | """ 96 | min_value = kwargs.get('min_value', 1) 97 | max_value = kwargs.get('max_value', 9999) 98 | return xunit.any_int(min_value=min_value, max_value=max_value) 99 | 100 | 101 | @any_field.register(models.CharField) 102 | def any_char_field(field, **kwargs): 103 | """ 104 | Return random value for CharField 105 | 106 | >>> result = any_field(models.CharField(max_length=10)) 107 | >>> type(result) 108 | 109 | """ 110 | min_length = kwargs.get('min_length', 1) 111 | max_length = kwargs.get('max_length', field.max_length) 112 | return xunit.any_string(min_length=min_length, max_length=max_length) 113 | 114 | 115 | @any_field.register(models.CommaSeparatedIntegerField) 116 | def any_commaseparatedinteger_field(field, **kwargs): 117 | """ 118 | Return random value for CharField 119 | 120 | >>> result = any_field(models.CommaSeparatedIntegerField(max_length=10)) 121 | >>> type(result) 122 | 123 | >>> [int(num) for num in result.split(',')] and 'OK' 124 | 'OK' 125 | """ 126 | nums_count = field.max_length/2 127 | nums = [str(xunit.any_int(min_value=0, max_value=9)) for _ in xrange(0, nums_count)] 128 | return ",".join(nums) 129 | 130 | 131 | @any_field.register(models.DateField) 132 | def any_date_field(field, **kwargs): 133 | """ 134 | Return random value for DateField, 135 | skips auto_now and auto_now_add fields 136 | 137 | >>> result = any_field(models.DateField()) 138 | >>> type(result) 139 | 140 | """ 141 | if field.auto_now or field.auto_now_add: 142 | return None 143 | from_date = kwargs.get('from_date', date(1990, 1, 1)) 144 | to_date = kwargs.get('to_date', date.today()) 145 | return xunit.any_date(from_date=from_date, to_date=to_date) 146 | 147 | 148 | @any_field.register(models.DateTimeField) 149 | def any_datetime_field(field, **kwargs): 150 | """ 151 | Return random value for DateTimeField, 152 | skips auto_now and auto_now_add fields 153 | 154 | >>> result = any_field(models.DateTimeField()) 155 | >>> type(result) 156 | 157 | """ 158 | from_date = kwargs.get('from_date', datetime(1990, 1, 1)) 159 | to_date = kwargs.get('to_date', datetime.today()) 160 | return xunit.any_datetime(from_date=from_date, to_date=to_date) 161 | 162 | 163 | @any_field.register(models.DecimalField) 164 | def any_decimal_field(field, **kwargs): 165 | """ 166 | Return random value for DecimalField 167 | 168 | >>> result = any_field(models.DecimalField(max_digits=5, decimal_places=2)) 169 | >>> type(result) 170 | 171 | """ 172 | min_value = kwargs.get('min_value', 0) 173 | max_value = kwargs.get('max_value', 174 | Decimal('%s.%s' % ('9'*(field.max_digits-field.decimal_places), 175 | '9'*field.decimal_places))) 176 | decimal_places = kwargs.get('decimal_places', field.decimal_places) 177 | return xunit.any_decimal(min_value=min_value, max_value=max_value, 178 | decimal_places = decimal_places) 179 | 180 | 181 | @any_field.register(models.EmailField) 182 | def any_email_field(field, **kwargs): 183 | """ 184 | Return random value for EmailField 185 | 186 | >>> result = any_field(models.EmailField()) 187 | >>> type(result) 188 | 189 | >>> re.match(r"(?:^|\s)[-a-z0-9_.]+@(?:[-a-z0-9]+\.)+[a-z]{2,6}(?:\s|$)", result, re.IGNORECASE) is not None 190 | True 191 | """ 192 | return "%s@%s.%s" % (xunit.any_string(max_length=10), 193 | xunit.any_string(max_length=10), 194 | xunit.any_string(min_length=2, max_length=3)) 195 | 196 | 197 | @any_field.register(models.FloatField) 198 | def any_float_field(field, **kwargs): 199 | """ 200 | Return random value for FloatField 201 | 202 | >>> result = any_field(models.FloatField()) 203 | >>> type(result) 204 | 205 | """ 206 | min_value = kwargs.get('min_value', 1) 207 | max_value = kwargs.get('max_value', 100) 208 | precision = kwargs.get('precision', 3) 209 | return xunit.any_float(min_value=min_value, max_value=max_value, precision=precision) 210 | 211 | 212 | @any_field.register(models.FileField) 213 | def any_file_field(field, **kwargs): 214 | """ 215 | Lookup for nearest existing file 216 | 217 | """ 218 | def get_some_file(path): 219 | subdirs, files = field.storage.listdir(path) 220 | 221 | if files: 222 | result_file = random.choice(files) 223 | instance = field.storage.open("%s/%s" % (path, result_file)).file 224 | return FieldFile(instance, field, result_file) 225 | 226 | for subdir in subdirs: 227 | result = get_some_file("%s/%s" % (path, subdir)) 228 | if result: 229 | return result 230 | 231 | result = get_some_file(field.upload_to) 232 | 233 | if result is None and not field.null: 234 | raise TypeError("Can't found file in %s for non nullable FileField" % field.upload_to) 235 | return result 236 | 237 | 238 | @any_field.register(models.FilePathField) 239 | def any_filepath_field(field, **kwargs): 240 | """ 241 | Lookup for nearest existing file 242 | 243 | """ 244 | def get_some_file(path): 245 | subdirs, files = [], [] 246 | for entry in os.listdir(path): 247 | entry_path = os.path.join(path, entry) 248 | if os.path.isdir(entry_path): 249 | subdirs.append(entry_path) 250 | else: 251 | if not field.match or re.match(field.match,entry): 252 | files.append(entry_path) 253 | 254 | if files: 255 | return random.choice(files) 256 | 257 | if field.recursive: 258 | for subdir in subdirs: 259 | result = get_some_file(subdir) 260 | if result: 261 | return result 262 | 263 | result = get_some_file(field.path) 264 | if result is None and not field.null: 265 | raise TypeError("Can't found file in %s for non nullable FilePathField" % field.path) 266 | return result 267 | 268 | 269 | @any_field.register(models.IPAddressField) 270 | def any_ipaddress_field(field, **kwargs): 271 | """ 272 | Return random value for IPAddressField 273 | >>> result = any_field(models.IPAddressField()) 274 | >>> type(result) 275 | 276 | >>> from django.core.validators import ipv4_re 277 | >>> re.match(ipv4_re, result) is not None 278 | True 279 | """ 280 | nums = [str(xunit.any_int(min_value=0, max_value=255)) for _ in xrange(0, 4)] 281 | return ".".join(nums) 282 | 283 | 284 | @any_field.register(models.NullBooleanField) 285 | def any_nullboolean_field(field, **kwargs): 286 | """ 287 | Return random value for NullBooleanField 288 | >>> result = any_field(models.NullBooleanField()) 289 | >>> result in [None, True, False] 290 | True 291 | """ 292 | return random.choice([None, True, False]) 293 | 294 | 295 | @any_field.register(models.PositiveSmallIntegerField) 296 | def any_positivesmallinteger_field(field, **kwargs): 297 | """ 298 | Return random value for PositiveSmallIntegerField 299 | >>> result = any_field(models.PositiveSmallIntegerField()) 300 | >>> type(result) 301 | 302 | >>> result < 256, result > 0 303 | (True, True) 304 | """ 305 | min_value = kwargs.get('min_value', 1) 306 | max_value = kwargs.get('max_value', 255) 307 | return xunit.any_int(min_value=min_value, max_value=max_value) 308 | 309 | 310 | @any_field.register(models.SlugField) 311 | def any_slug_field(field, **kwargs): 312 | """ 313 | Return random value for SlugField 314 | >>> result = any_field(models.SlugField()) 315 | >>> type(result) 316 | 317 | >>> from django.core.validators import slug_re 318 | >>> re.match(slug_re, result) is not None 319 | True 320 | """ 321 | letters = ascii_letters + digits + '_-' 322 | return xunit.any_string(letters = letters, max_length = field.max_length) 323 | 324 | 325 | @any_field.register(models.SmallIntegerField) 326 | def any_smallinteger_field(field, **kwargs): 327 | """ 328 | Return random value for SmallIntegerValue 329 | >>> result = any_field(models.SmallIntegerField()) 330 | >>> type(result) 331 | 332 | >>> result > -256, result < 256 333 | (True, True) 334 | """ 335 | min_value = kwargs.get('min_value', -255) 336 | max_value = kwargs.get('max_value', 255) 337 | return xunit.any_int(min_value=min_value, max_value=max_value) 338 | 339 | 340 | @any_field.register(models.IntegerField) 341 | def any_integer_field(field, **kwargs): 342 | """ 343 | Return random value for IntegerField 344 | >>> result = any_field(models.IntegerField()) 345 | >>> type(result) 346 | 347 | """ 348 | min_value = kwargs.get('min_value', -10000) 349 | max_value = kwargs.get('max_value', 10000) 350 | return xunit.any_int(min_value=min_value, max_value=max_value) 351 | 352 | 353 | @any_field.register(models.TextField) 354 | def any_text_field(field, **kwargs): 355 | """ 356 | Return random 'lorem ipsum' Latin text 357 | >>> result = any_field(models.TextField()) 358 | >>> from django.contrib.webdesign.lorem_ipsum import COMMON_P 359 | >>> result[0] == COMMON_P 360 | True 361 | """ 362 | return paragraphs(10) 363 | 364 | 365 | @any_field.register(models.URLField) 366 | def any_url_field(field, **kwargs): 367 | """ 368 | Return random value for URLField 369 | >>> result = any_field(models.URLField()) 370 | >>> from django.core.validators import URLValidator 371 | >>> re.match(URLValidator.regex, result) is not None 372 | True 373 | """ 374 | url = kwargs.get('url') 375 | 376 | if not url: 377 | verified = [validator for validator in field.validators \ 378 | if isinstance(validator, validators.URLValidator) and \ 379 | validator.verify_exists == True] 380 | if verified: 381 | url = choice(['http://news.yandex.ru/society.html', 382 | 'http://video.google.com/?hl=en&tab=wv', 383 | 'http://www.microsoft.com/en/us/default.aspx', 384 | 'http://habrahabr.ru/company/opera/', 385 | 'http://www.apple.com/support/hardware/', 386 | 'http://ya.ru', 387 | 'http://google.com', 388 | 'http://fr.wikipedia.org/wiki/France']) 389 | else: 390 | url = "http://%s.%s/%s" % ( 391 | xunit.any_string(max_length=10), 392 | xunit.any_string(min_length=2, max_length=3), 393 | xunit.any_string(max_length=20)) 394 | 395 | return url 396 | 397 | 398 | @any_field.register(models.TimeField) 399 | def any_time_field(field, **kwargs): 400 | """ 401 | Return random value for TimeField 402 | >>> result = any_field(models.TimeField()) 403 | >>> type(result) 404 | 405 | """ 406 | return time( 407 | xunit.any_int(min_value=0, max_value=23), 408 | xunit.any_int(min_value=0, max_value=59), 409 | xunit.any_int(min_value=0, max_value=59)) 410 | 411 | 412 | @any_field.register(models.ForeignKey) 413 | def any_foreignkey_field(field, **kwargs): 414 | return any_model(field.rel.to, **kwargs) 415 | 416 | 417 | @any_field.register(models.OneToOneField) 418 | def any_onetoone_field(field, **kwargs): 419 | return any_model(field.rel.to, **kwargs) 420 | 421 | 422 | def _fill_model_fields(model, **kwargs): 423 | model_fields, fields_args = split_model_kwargs(kwargs) 424 | 425 | # fill local fields 426 | for field in model._meta.fields: 427 | if field.name in model_fields: 428 | if isinstance(kwargs[field.name], Q): 429 | """ 430 | Lookup ForeingKey field in db 431 | """ 432 | key_field = model._meta.get_field(field.name) 433 | value = key_field.rel.to.objects.get(kwargs[field.name]) 434 | setattr(model, field.name, value) 435 | else: 436 | # TODO support any_model call 437 | setattr(model, field.name, kwargs[field.name]) 438 | elif isinstance(field, models.OneToOneField) and field.rel.parent_link: 439 | """ 440 | skip link to parent instance 441 | """ 442 | elif isinstance(field, models.fields.AutoField): 443 | """ 444 | skip primary key field 445 | """ 446 | else: 447 | setattr(model, field.name, any_field(field, **fields_args[field.name])) 448 | 449 | # procceed reversed relations 450 | onetoone = [(relation.var_name, relation.field) \ 451 | for relation in model._meta.get_all_related_objects() \ 452 | if relation.field.unique] # TODO and not relation.field.rel.parent_link ?? 453 | for field_name, field in onetoone: 454 | if field_name in model_fields: 455 | # TODO support any_model call 456 | setattr(model, field_name, kwargs[field_name]) 457 | 458 | 459 | @any_model.register_default 460 | def any_model_default(model_cls, **kwargs): 461 | result = model_cls() 462 | 463 | attempts = 10 464 | while True: 465 | try: 466 | _fill_model_fields(result, **kwargs) 467 | result.full_clean() 468 | result.save() 469 | return result 470 | except (IntegrityError, ValidationError): 471 | attempts -= 1 472 | if not attempts: 473 | raise 474 | 475 | --------------------------------------------------------------------------------