Skip to main content

A full library to create dynamic model instances for testing purposes.

Project description

=Description=
A full library to create dynamic model instances for testing purposes.

=Installation=
pip install django-dynamic-fixture

or

# Download zip file
# Extract it
# Execute in the extracted directory: python setup.py install

=Upgrade=
pip install django-dynamic-fixture --upgrade --no-deps

=Motivation=
* It is a TERRIBLE practice to use STATIC data in tests.
* Create dynamic fixture for each model is boring and it produces a lot of replicated code.
* It is a bad idea to use uncontrolled data in tests, like bizarre random data.

=Comparison with another fixture tools=

* We tried to use another fixture tools in a big Django project but the experience was not satisfactory.
* Either they are incomplete, or bugged or it produces erratic tests, because they use random and uncontrolled data.
* Also, the syntax of others tools is too verbose, which polutes the tests.
* Complete, lean and practice documentation.
* It is hard to debug tests with another tools.
* List of other tools: http://djangopackages.com/grids/g/fixtures

=Features=
* Highly customizable: you can customize fields recursively
* Deal with unique=True
* Deal with cyclic dependencies (including self references)
* Deal with many to many relationship (common M2M or M2M with additional data, i.e. through='table')
* Deal with custom fields (specially if the custom field inherit of a django field)
* It is supported for parallel tests
* Deal with auto calculated attributes
* It is easy to debug errors

=Example of Usage=

{{{
from django_dynamic_fixture import N, G, F, P
#or use old default names:
#from django_dynamic_fixture import new, get, DynamicFixture as F, print_field_values

# Models:

from django.db import models

class ModelA(models.Model): pass

class ModelY(models.Model):
other_text = models.CharField()
other_list = models.ManyToManyField('ModelA')

class ModelX(models.Model):
some_text = models.CharField(null=True)
parent_left = models.ForeingKey('self')
y_reference = models.ForeingKey('ModelY')
list_a = models.ManyToManyField('ModelA')

# Summary:

# DynamicFixture (F): receive arguments and create model instances.
# new: it is just a wrapper: it creates a F to create a not saved model instance.
# get: basically, call the new method and save the instance. You can set ManyToMany fields only after the instance is saved.

# Examples:

instance_of_modelx = N(ModelX)
assert instance_of_modelx.some_text != None
assert instance_of_modelx.parent_left != None
assert instance_of_modelx.parent_left.parent_left == None
assert instance_of_modelx.id == None # new do not save the instance
assert instance_of_modelx.y_reference.id != None # save dependencies by default
assert len(instance_of_modelx.list_a.all()) == 0 # do not create many2many fields by default

instance_of_modelx = N(ModelX, fill_nullable_fields=False) # default = True
assert instance_of_modelx.some_text == None

# you can ignore fields, but do not ignore required fields (with null=False).
instance_of_modelx = G(ModelX, ignore_fields=['some_text'])
assert instance_of_modelx.some_text == None

# Very nice feature to work with trees
instance_of_modelx = N(ModelX, number_of_laps=2) # default = 1
assert instance_of_modelx.parent_left != None
assert instance_of_modelx.parent_left.parent_left != None
assert instance_of_modelx.parent_left.parent_left.parent_left == None

# This feature is specially useful to test search methods
instance_of_modelx = N(ModelX, some_text='some fixed data') # attribute accepts static data
assert instance_of_modelx.some_text == 'some fixed data'

# Use this with attention. First, check for design mistakes
instance_of_modelx = N(ModelX, id=99999) # you can define the id too
assert instance_of_modelx.id == 99999

# You can create your own function to create data..
instance_of_modelx = N(ModelX, some_text=lambda field: field.name) # attribute accepts callables
assert instance_of_modelx.some_text == 'some_text'

# Use this with attention, you can get an error if you try to save an instance with not saved dependencies
instance_of_modelx = N(ModelX, persist_dependencies=False)
assert instance_of_modelx.y_reference.id == None

instance_of_modelx = G(ModelX) # get save the model instance
assert instance_of_modelx.id != None

instance_of_modelx = G(ModelX, list_a=2) # Many2Many can receive a number of instances to be created
assert len(instance_of_modelx.list_a.all()) == 2

instance_of_modelx = G(ModelX, list_a=[F(), F(), F()]) # Many2Many can receive a list of DynamicFixtures
assert len(instance_of_modelx.list_a.all()) == 3

a = G(ModelA)
instance_of_modelx = G(ModelX, list_a=[F(), a, F()]) # Many2Many can receive a list of instances
assert len(instance_of_modelx.list_a.all()) == 3

# You can pass arguments to F (DynamicFixture) recursively. This works for ForeignKey and ManyToMany Fields! Easy and customizable!
instance_of_modelx = G(ModelX, parent_left=F(other_text='wow', other_list=2))
assert len(instance_of_modelx.y_reference.other_list.all()) == 2

# Highly recursivable example:
# X has a ForeignKey to A
# A has a ForeignKey to B
# B has a ForeignKey to C
# this will create instances of C, B, A and X (in this order). Attribute d of C will be 'some value'
G(X, a=F(b=F(c=F(d='some value'))))


# for debug:
P(instance_of_modelx)

# Custom FileField:
class ModelX(models.Model):
my_file = models.FileField(upload_to='/')

from tempfile import mkstemp
pdf_a = File(open(mkstemp()[1], 'w'), name='a.pdf')
G(ModelX, my_file=pdf_a)
}}}

== Decorators ==

{{{
from django_dynamic_fixture.decorators import skip_for_database, only_for_database, SQLITE3

@only_for_database(SQLITE3)
def test_something1(self): pass

@skip_for_database(SQLITE3)
def test_something2(self): pass

@only_for_database("some value used in settings.DATABASES['default']['ENGINE']")
def test_something3(self): pass

@skip_for_database("some value used in settings.DATABASES['default']['ENGINE']")
def test_something4(self): pass
}}}

== Queries Module ==
{{{
python manage.py test --with-queries
python manage.py count_queries_on_save
}}}

=Information about the logic of the library=

==List of exceptions==
* UnsupportedFieldError: DynamicFixture does not support this field.
* InvalidConfigurationError: The specified configuration for the field can not be applied or it is bugged.
* InvalidManyToManyConfigurationError: M2M attribute configuration must be a number or a list of DynamicFixture or model instances.
* BadDataError: The data passed to a field has some problem (not unique or invalid) or a required attribute is in ignore list.

==DynamicFixture assume==

* if a field has a default value, it does not have unique=True.
* if a field has choices, it does not have unique=True.
* if there is a cyclic dependency, in some part the relationship must be nullable.
* if any of these requirements is a problem, you have to use customized values or override the behavior.

==DynamicFixture rules==

===General===

* The id (AutoField) is auto filled, unless you set a value to it.
* if a field has default value, it will be used by default.
* if a field has choices, it select each the first option by default.
* it fill nullable fields with data unless fill_nullable_fields is False.
* boolean fields will always receive False, unless it has default value.
* null boolean fields will always receive None, unless it has default value.
* Strings (CharField, Text, Url, Email...) and numbers (IntegerField, Float, Decimal...) are filled with a sequential and unique number for each test (1, '1', 2, '2'...). Each attribute has its own counter.
* You can override the behavior of the string and number fillers, if you want to.

===Custom Attributes===

* if it receive a customized data, it do not care about attributes null, unique, default or choices.
* if the specified fixture was bugged, it will raise an InvalidConfigurationError.

===Custom Fields===

* if it does not recognize the Field class, it will raise an UnsupportedFieldError.
* if a field is not default in Django, but it inherits from a Django field, it will be filled using its config.
* if a field is not default in Django and not related with a Django field, it will raise an UnsupportedFieldError.
* Customized data is also valid for unsupported fields.

===Related Objects===

* ForeignKey will be filled by default, considering unicity of data.
* It deal with cyclic dependencies, including self references (this avoid infinite recursion).
* By default, the fixture create just one cycle, but it is possible to specify more maps with number_of_laps parameter.


===Many to Many relationship===

* To add models in a m2m relation, the model must be persisted, so use the 'get' method.
* it expect to receive a parameter with the amount of instances that need to be created (using default configuration).
* Also, it can receive a list with DynamicFixture (F) or model instances (for custom items). The size of the list is the number of items that will be created.
* It works for default Many2Many and Many2Many with through.


===Ignoring fields===

* Useful when some fields need some calculation. For example, Django-MPTT models.
* Ignored fields are propagated ONLY to self references.
* Do not ignore required fields with 'get', only with 'new'. In other words, do not save an instance without a required field, unless you are expecting for an exception.


===Patterns===

* Use 'new' method for unit tests (not integration tests) for the main model.
* Use 'ignore_fields' option to deal with fields filled by listeners.
* Use custom values for unsupported fields.
* Use 'number_of_laps' option to test trees.


===Anti-Patterns===

* Use a auto generated data in an assertion method.


=Links of Comments=
* http://www.reddit.com/r/django/comments/fv1re/django_dynamic_fixture
* http://djangopackages.com/grids/g/fixtures/
* http://news.ycombinator.com/item?id=2275406
* http://pythonsmalltalk.blogspot.com/2011/02/django-dynamic-fixture.html
* http://pythonsmalltalk.blogspot.com/2011/03/django-dynamic-fixture-121.html
* http://pypi.python.org/pypi/django-dynamic-fixture/1.2
* http://code.activestate.com/pypm/django-dynamic-fixture
* http://groups.google.com/group/django-mptt-dev/browse_thread/thread/d9eb9e22ad4aa656
* http://groups.google.com/group/django-users/browse_thread/thread/1346a60008c21a7b
* http://groups.google.com/group/django-brasil/browse_thread/thread/757df09d3c3be81d
* http://linux.softpedia.com/get/Internet/HTTP-WWW-/django-dynamic-fixture-68711.shtml


=Change Log=

==Version 1.5.0==
* 2012/03/24 (yyyy/mm/dd)
* http://pypi.python.org/pypi/django-dynamic-fixture/1.5.0
* New features in DDF:
* global settings: DDF_DEFAULT_DATA_FIXTURE, DDF_FILL_NULLABLE_FIELDS, DDF_IGNORE_FIELDS, DDF_NUMBER_OF_LAPS, DDF_VALIDATE_MODELS
* new data fixture that generates random data
* new data fixture that use sequential numbers only for fields that have unique=True
* P function now accept a list of model instances
* Option to call model_instance.full_clean() validation method before saving the object (DDF_VALIDATE_MODELS).
* Validate field names. If a invalid field name is passed as argument, it will raise an InvalidConfigurationError exception.
* DateField options 'auto_add_now' and 'auto_add' are disabled if a custom value is used.

==Version 1.4.3==
* 2012/02/23 (yyyy/mm/dd)
* http://pypi.python.org/pypi/django-dynamic-fixture/1.4.3
* Bugfix in ForeignKeys with default values

==Version 1.4.2==
* 2011/11/07 (yyyy/mm/dd)
* http://pypi.python.org/pypi/django-dynamic-fixture/1.4.2
* Bugfix in FileSystemDjangoTestCase

==Version 1.4.1==
* 2011/11/07 (yyyy/mm/dd)
* http://pypi.python.org/pypi/django-dynamic-fixture/1.4.1
* New features in DDF:
* Now you can set a custom File to a FileField and the file will be saved in the file storage system.

* New features in FileSystemDjangoTestCase:
* create_django_file_using_file create a django.File using the content of your file
* create_django_file_with_temp_file now accepts a content attribute that will be saved in the generated file

* Bugfix in FileSystemDjangoTestCase:
* now create_django_file_with_temp_file close the generated file

==Version 1.4.0==
* 2011/10/29 (yyyy/mm/dd)
* http://pypi.python.org/pypi/django-dynamic-fixture/1.4.0
* New features:
* Nose plugin to count queries on each test
* Command line to count queries on the save (insert and update) of each model

* Bugfixes in DDF:
* Field with choice and default must use the default value, not the first choice value
* Validation if the class is a models.Model instance
* Showing all stack trace, when an exception occurs

* Bugfixes in decorators: default values of database engines were not used correctly
* Bugfixes in FileSystemDjangoTestCase tests

==Version 1.3.1==
* 2011/10/03 (yyyy/mm/dd)
* http://pypi.python.org/pypi/django-dynamic-fixture/1.3.1
* Bugfixes in FileSystemDjangoTestCase

==Version 1.3.0==
* 2011/10/03 (yyyy/mm/dd)
* http://pypi.python.org/pypi/django-dynamic-fixture/1.3.0
* New Feature: File System Django Test Case
* New Feature: Decorators skip_for_database and only_for_database
* Bugfix: Inheritance problems, before this version the DDF filled fields with the attribute parent_link

==Version 1.2.3==

* 2011/06/27 (yyyy/mm/dd)
* http://pypi.python.org/pypi/django-dynamic-fixture/1.2.3
* Bugfix in string truncation to max_length

==Version 1.2.2==

* 2011/05/05 (yyyy/mm/dd)
* http://pypi.python.org/pypi/django-dynamic-fixture/1.2.2
* Improvements in exception messages

==Version 1.2.1==

* 2011/03/11 (yyyy/mm/dd)
* http://pypi.python.org/pypi/django-dynamic-fixture/1.2.1
* Propagate ignored fields to self references
* Refactoring + Bug fixes

==Version 1.2==
* 2011/03/04 (yyyy/mm/dd)
* http://pypi.python.org/pypi/django-dynamic-fixture/1.2
* New Feature: ignore_fields
* New Feature: now it is possible to set the ID

==Version 1.1==
* Bug fixes
* http://pypi.python.org/pypi/django-dynamic-fixture/1.0 (1.0 has the 1.1 package)

==Version 1.0==
* Initial version
* Ready to use in big projects


= Testing =
* python manage.py test
* python manage.py test --with-coverage --cover-inclusive --cover-html --cover-package=django_dynamic_fixture.* --with-queries


= TODO List =
* auto config of denormalizated fields
* with_queries documentation and bugfixes (always print 0 queries)
* related_name documentation or workaround
* today, yesterday, tomorrow on fdf
* bugfix in fdf or ddf: some files/directories are not deleted
* tests with files in ddf
* tests with proxy models
* doc factory: example to generate models with validators in fields or in clean methods
* tests with GenericRelations, GenericForeignKey etc
* more tests with OneToOneField(parent_link=True)

Project details


Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page