diff --git a/.coveralls.yml b/.coveralls.yml
deleted file mode 100644
index b8c3786fbf3979144479b823cd7c14561d6404a8..0000000000000000000000000000000000000000
--- a/.coveralls.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-repo_token: ntJPGdu5gRJBoCna7HRZPkb71V2kTfeZ1
-service_name: travis-ci
-parallel: true # if the CI is running your build in parallel
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..38c5114423ae33cf917451198c162c703a93c8db
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+.DS_Store
+.idea/
+dist/
+build/
+cover/
+.settings
+.project
+.pydevproject
+.settings/org.eclipse.core.resources.prefs
+.settings/org.eclipse.core.runtime.prefs
+.settings/org.eclipse.ltk.core.refactoring.prefs
+.settings/org.eclipse.wst.validation.prefs
+*.bat
+*.sh
+*.coverage
+*.pyc
+*.pyo
+*.log
+.noseids
+*.sqlite
+virtualenv*
+
+django_dynamic_fixture.egg-info
+env/
+envpypy
+env26
+env27
+env32
+env33
+env34
+env2.6
+env2.7
+env3.2
+env3.3
+env3.4
+.tox
+test_:memory:
+.eggs/
+
+ddf_compatibility_report.*
+htmlcov/*
\ No newline at end of file
diff --git a/.pylintrc b/.pylintrc
index 73119be14e9bc1b3c06fe41d1cb116dc845d3d81..5348a0c7affc23b678aa9df9187642b06cd2c437 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -305,7 +305,7 @@ init-import=no
 
 # List of qualified module names which can have objects that can redefine
 # builtins.
-redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
+redefining-builtins-modules=past.builtins,future.builtins,builtins,io
 
 
 [FORMAT]
diff --git a/.pypirc b/.pypirc
deleted file mode 100644
index 0014db959eb730c0e9899b2cd3e90d13d097de9b..0000000000000000000000000000000000000000
--- a/.pypirc
+++ /dev/null
@@ -1,5 +0,0 @@
-# copy this to your ~ dir
-
-[server-login]
-username:paulocheque
-password:*******
diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000000000000000000000000000000000000..3b2c885c4014be4861874e0ea485f48b0c3ebb32
--- /dev/null
+++ b/.python-version
@@ -0,0 +1,5 @@
+3.7.17
+3.8.17
+3.9.17
+3.10.12
+3.11.4
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..01f839c60d881b87f5289b1fe71132361ce0dad1
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,10 @@
+# https://docs.readthedocs.io/en/stable/config-file/v2.html
+version: 2
+
+build:
+  os: ubuntu-22.04
+  tools:
+    python: "3.11"
+
+sphinx:
+  configuration: docs/source/conf.py
diff --git a/.travis.yml b/.travis.yml
index 84e1f21a5c24d90fd2a8a210462d245f0e3faea8..7087e31ec60b71695a03cdd0eff637e7aabc24a0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,61 +22,75 @@ addons:
 
 matrix:
   include:
-  # https://docs.djangoproject.com/en/1.11/ref/contrib/gis/install/
-  - python: 2.7
+  - python: 3.8
     env:
-      - DJANGO=1.11
+      - DJANGO=4.0
       - DATABASE=sqlite
-      - JSONFIELD=2.1.1
-      - POLYMORPHIC=1.3
-  - python: 2.7
+      - JSONFIELD=3.1.0
+      - POLYMORPHIC=3.0.0
+  - python: 3.8
+    env:
+      - DJANGO=4.1
+      - DATABASE=postgres
+      - JSONFIELD=3.1.0
+      - POLYMORPHIC=3.0.0
+  - python: 3.8
     env:
-      - DJANGO=1.11
+      - DJANGO=4.2
       - DATABASE=postgres
-      - JSONFIELD=2.1.1
-      - POLYMORPHIC=1.3
-  # https://docs.djangoproject.com/en/2.2/ref/contrib/gis/install/
-  - python: 3.6
+      - JSONFIELD=3.1.0
+      - POLYMORPHIC=3.0.0
+  - python: 3.9
     env:
-      - DJANGO=2.2
+      - DJANGO=4.0
       - DATABASE=sqlite
       - JSONFIELD=3.1.0
       - POLYMORPHIC=3.0.0
-  - python: 3.6
+  - python: 3.9
     env:
-      - DJANGO=2.2
+      - DJANGO=4.1
       - DATABASE=postgres
       - JSONFIELD=3.1.0
       - POLYMORPHIC=3.0.0
-  # https://docs.djangoproject.com/en/3.0/ref/contrib/gis/install/
-  # https://docs.djangoproject.com/en/3.0/releases/3.0/#id2
-  - python: 3.7
+  - python: 3.9
     env:
-      - DJANGO=3.0
-      - DATABASE=sqlite
+      - DJANGO=4.2
+      - DATABASE=postgres
       - JSONFIELD=3.1.0
       - POLYMORPHIC=3.0.0
-  - python: 3.8
+  - python: 3.10
     env:
-      - DJANGO=3.0
+      - DJANGO=4.0
       - DATABASE=sqlite
       - JSONFIELD=3.1.0
       - POLYMORPHIC=3.0.0
-  - python: 3.8
+  - python: 3.10
     env:
-      - DJANGO=3.0
+      - DJANGO=4.1
       - DATABASE=postgres
       - JSONFIELD=3.1.0
       - POLYMORPHIC=3.0.0
-  - python: 3.8
+  - python: 3.10
     env:
-      - DJANGO=3.1
+      - DJANGO=4.2
       - DATABASE=postgres
       - JSONFIELD=3.1.0
       - POLYMORPHIC=3.0.0
-  - python: 3.8
+  - python: 3.11
+    env:
+      - DJANGO=4.0
+      - DATABASE=sqlite
+      - JSONFIELD=3.1.0
+      - POLYMORPHIC=3.0.0
+  - python: 3.11
     env:
-      - DJANGO=3.2
+      - DJANGO=4.1
+      - DATABASE=postgres
+      - JSONFIELD=3.1.0
+      - POLYMORPHIC=3.0.0
+  - python: 3.11
+    env:
+      - DJANGO=4.2
       - DATABASE=postgres
       - JSONFIELD=3.1.0
       - POLYMORPHIC=3.0.0
@@ -100,14 +114,10 @@ before_script:
 
 install:
   - pip install -r requirements.txt
-  - pip install psycopg2 pytest pytest-django coveralls
+  - pip install psycopg2 pytest pytest-django
   - pip install Django~=${DJANGO}.0
   - pip install jsonfield~=${JSONFIELD}
   - pip install django-polymorphic~=${POLYMORPHIC}
 
-
 script:
   - pytest --create-db --reuse-db --no-migrations --ds=settings_${DATABASE}
-
-after_success:
-  - coveralls
diff --git a/Makefile b/Makefile
index af10629f5b25c59e3ce05c796afded90c5d00f80..7358ec1e14d6af1956a7f153bc363fad47bf37cf 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=3.1.2
+VERSION=4.0.0
 
 # Python env tasks
 
@@ -12,20 +12,24 @@ clean:
 	rm -rf ~*
 	rm -rf data/
 	rm -rf dist/
+	rm -rf build/
 	rm -rf .eggs/
-
-prepare:
-	clear ; python3 -m venv env
+	rm -rf .tox/
+	#rm -rf env/
 
 os_deps:
 	brew install gdal
-	env/bin/pip install --upgrade pip
+
+prepare:
+	clear ; python3.11 -m venv env
 
 deps:
 	clear
-	env/bin/pip install --upgrade pip
+	env/bin/python -m pip install --upgrade pip
+	env/bin/python -m pip install --upgrade setuptools wheel
 	env/bin/pip install -r requirements.txt
 	env/bin/pip install -r requirements-dev.txt
+	env/bin/pip list
 
 shell:
 	#clear ; env/bin/python -i -c "from ddf import *"
@@ -40,9 +44,7 @@ compile:
 test:
 	# Run specific test:
 	# TESTS=django_dynamic_fixture.tests.FILE::CLASS::METHOD make test
-	clear ; env/bin/pytest --create-db --reuse-db --no-migrations ${TESTS}
-	# clear ; time env/bin/tox --parallel all -e django111-py27
-	# clear ; time env/bin/tox --parallel all -e django20-py37
+	clear ; time env/bin/pytest --create-db --reuse-db --no-migrations ${TESTS}
 
 testfailed:
 	clear ; env/bin/pytest --create-db --reuse-db --no-migrations ${TESTS} --last-failed
@@ -70,67 +72,60 @@ test_mysql:
 
 cov:
 	clear ; env/bin/pytest --create-db --reuse-db --no-migrations -v --cov=django_dynamic_fixture --cov-report html
+	cp htmlcov/index.html docs/source/_static/coverage.html
 	open htmlcov/index.html
 
-coveralls:
-	clear ; env/bin/coveralls debug --verbose
-
-coveralls_publish:
-	clear ; env/bin/coveralls --verbose
-
-clear_github_img_cache:
-	curl -X PURGE https://camo.githubusercontent.com/95b7e3529338697ecffdf67add40931d066a35e1/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f7061756c6f6368657175652f646a616e676f2d64796e616d69632d666978747572652f62616467652e7376673f6272616e63683d6d6173746572
-
 code_style:
 	# Code Style
-	clear ; env/bin/pylint ddf django_dynamic_fixture ddf_setup queries
+	clear ; env/bin/pylint ddf django_dynamic_fixture queries
 
 code_checking:
 	# Code error checking
-	clear ; env/bin/python -m pyflakes ddf django_dynamic_fixture ddf_setup queries
+	clear ; env/bin/python -m pyflakes ddf django_dynamic_fixture queries
 
 code_feedbacks:
 	# PEP8, code style and circular complexity
-	clear ; env/bin/flake8 ddf django_dynamic_fixture ddf_setup queries
+	clear ; env/bin/flake8 ddf django_dynamic_fixture queries
 
-doc:
+code_ruff:
+	clear ; env/bin/ruff check .
+	#clear ; env/bin/ruff check . --fix
+
+check: code_style code_checking code_feedbacks code_ruff
+
+install_precommit_hooks: code_ruff
+	clear ; env/bin/ruff check .
+	env/bin/pre-commit install
+
+doc: cov
 	clear ; cd docs ; make clean html ; open build/html/index.html
 
 tox:
+	#brew update ; brew install pyenv
+	#pyenv install 3.8 3.9 3.10 3.11
+	#pyenv versions
+	#pyenv local 3.7 3.8 3.9 3.10 3.11
 	clear ; time env/bin/tox --parallel all
 
-build: clean prepare os_deps deps test
+build: clean os_deps prepare deps test cov
 
 # Python package tasks
 
-setup_clean:
-	clear ; env/bin/python setup.py clean --all
-
-setup_test:
-	clear ; time env/bin/python setup.py test
-
-lib: setup_clean setup_test
-	clear ; env/bin/python setup.py build
-
-register: setup_clean setup_test
-	clear ; env/bin/python setup.py sdist
-	clear ; env/bin/python setup.py register
+lib: clean test cov doc
+	# 	clear ; env/bin/python setup.py build
+	# 	clear ; env/bin/python setup.py sdist
+	clear ; env/bin/python -m build
+	clear ; env/bin/twine check dist/*
 
-publish: setup_clean setup_test
+publish: lib
 	# Fixing Python 3 Certificates
 	# /Applications/Python\ 3.7/Install\ Certificates.command
-	#
-	# http://guide.python-distribute.org/quickstart.html
-	# python setup.py sdist
-	# python setup.py register
-	# Create a .pypirc file in ~ dir (cp .pypirc ~)
-	# python setup.py sdist upload
 	# Manual upload to PypI
 	# http://pypi.python.org/pypi/THE-PROJECT
 	# Go to 'edit' link
 	# Update version and save
 	# Go to 'files' link and upload the file
-	clear ; env/bin/python setup.py clean sdist upload
+	clear ; env/bin/twine upload dist/* --username=UPDATE_ME --password=UPDATE_ME
 
 # Git tasks
 
diff --git a/README.mkd b/README.md
similarity index 94%
rename from README.mkd
rename to README.md
index d1e88e026177815e5519557dea68e39ff3aceee2..605c5c257755cbed2c6a76677634de69b8df5663 100644
--- a/README.mkd
+++ b/README.md
@@ -3,12 +3,11 @@ Django Dynamic Fixture
 
 [![Build Status](https://travis-ci.org/paulocheque/django-dynamic-fixture.svg?branch=master)](https://travis-ci.org/paulocheque/django-dynamic-fixture)
 [![Docs Status](https://readthedocs.org/projects/django-dynamic-fixture/badge/?version=latest)](http://django-dynamic-fixture.readthedocs.org/en/latest/index.html)
-[![Coverage Status](https://coveralls.io/repos/paulocheque/django-dynamic-fixture/badge.svg?branch=master)](https://coveralls.io/r/paulocheque/django-dynamic-fixture?branch=master)
 [![PyPI version](https://badge.fury.io/py/django-dynamic-fixture.svg)](https://badge.fury.io/py/django-dynamic-fixture)
 ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-dynamic-fixture)
 ![PyPI - Downloads](https://img.shields.io/pypi/dm/django-dynamic-fixture)
 
-**Latest version: 3.1.2 (Oct 2021)**
+**Latest version: 4.0.0 (Aug 2023)**
 
 Django Dynamic Fixture (DDF) is a complete and simple library to create dynamic model instances for testing purposes.
 
diff --git a/ddf_setup/__init__.py b/ddf_setup/__init__.py
deleted file mode 100644
index 5e87c5f68413c8d500fd2aea00c495d0105dd86f..0000000000000000000000000000000000000000
--- a/ddf_setup/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from ddf_setup.nose_plugin import DDFSetup
-
-__all__ = ['DDFSetup']
diff --git a/ddf_setup/nose_plugin.py b/ddf_setup/nose_plugin.py
deleted file mode 100644
index 54221f1a307699f5b349edc0442647ebd3d57cac..0000000000000000000000000000000000000000
--- a/ddf_setup/nose_plugin.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from nose.plugins import Plugin
-
-
-# http://readthedocs.org/docs/nose/en/latest/plugins/interface.html
-class DDFSetup(Plugin):
-    "python manage.py test --with-ddf-setup"
-    name = 'ddf-setup'
-    enabled = True
-
-    _error_message = None
-
-    def begin(self):
-        "Called before any tests are collected or run. Use this to perform any setup needed before testing begins."
-        try:
-            import ddf_setup
-        except ImportError as e:
-            self._error_message = str(e)
-
-    def report(self, stream):
-        """Called after all error output has been printed. Print your
-        plugin's report to the provided stream. Return None to allow
-        other plugins to print reports, any other value to stop them.
-
-        :param stream: stream object; send your output here
-        :type stream: file-like object
-        """
-        if self._error_message:
-            stream.write('\nDDF Setup error: %s\n' % self._error_message)
diff --git a/django_dynamic_fixture/__init__.py b/django_dynamic_fixture/__init__.py
index 5e153b95543439b2a98b5e09198f2c5fd5d56639..019937bbaadbd40a26e978803d5eae94e8c675f5 100644
--- a/django_dynamic_fixture/__init__.py
+++ b/django_dynamic_fixture/__init__.py
@@ -1,11 +1,9 @@
-# -*- coding: utf-8 -*-
 
 """
 This is the facade of all features of DDF.
 Module that contains wrappers and shortcuts (aliases).
 """
 import warnings
-import six
 
 from django.apps import apps
 
@@ -20,11 +18,11 @@ from django_dynamic_fixture.global_settings import DDF_DEFAULT_DATA_FIXTURE, DDF
 from django_dynamic_fixture.script_ddf_checkings import ddf_check_models
 
 
-__version__ = '3.1.2'
+__version__ = '4.0.0'
 
 
-if not django_greater_than('1.10'):
-    warnings.warn("DDF supports oficially only Django 1.11 or higher.", DeprecationWarning)
+if not django_greater_than(1, 10):
+    warnings.warn("DDF officially supports only Django 1.11 or higher.", DeprecationWarning)
 
 
 LOOKUP_SEP = '__'
@@ -189,18 +187,14 @@ DDFLibrary = DDFLibrary
 PRE_SAVE = set_pre_save_receiver
 POST_SAVE = set_post_save_receiver
 
-if six.PY3:
-    # Add type hints for Python >= 3.5
-    try:
-        import typing
+import typing
 
-        INSTANCE_TYPE = typing.TypeVar('INSTANCE')
+INSTANCE_TYPE = typing.TypeVar('INSTANCE')
 
-        hack_to_avoid_py2_syntax_errors = '''
-def new(model: typing.Type[INSTANCE_TYPE], n=1, ddf_lesson=None, persist_dependencies=True, **kwargs) -> INSTANCE_TYPE:
+def new(model: typing.Type[INSTANCE_TYPE], n: int = 1, ddf_lesson = None, persist_dependencies: bool = True, **kwargs) -> INSTANCE_TYPE:
     return _new(model, n=n, ddf_lesson=ddf_lesson, persist_dependencies=persist_dependencies, **kwargs)
 
-def get(model: typing.Type[INSTANCE_TYPE], n=1, ddf_lesson=None, **kwargs) -> INSTANCE_TYPE:
+def get(model: typing.Type[INSTANCE_TYPE], n: int=1, ddf_lesson=None, **kwargs) -> INSTANCE_TYPE:
     return _get(model, n=n, ddf_lesson=ddf_lesson, **kwargs)
 
 def teach(model: typing.Type[INSTANCE_TYPE], ddf_lesson=None, **kwargs):
@@ -209,8 +203,3 @@ def teach(model: typing.Type[INSTANCE_TYPE], ddf_lesson=None, **kwargs):
 N = new
 G = get
 T = teach
-        '''
-        exec(hack_to_avoid_py2_syntax_errors)
-    except (ImportError, SyntaxError) as e:
-        pass
-
diff --git a/django_dynamic_fixture/ddf.py b/django_dynamic_fixture/ddf.py
index 90a51301c99cc86c0a387f6652a95aeb763e2276..1e7f34583d5d465c3c3954a5e81852af145d52b7 100644
--- a/django_dynamic_fixture/ddf.py
+++ b/django_dynamic_fixture/ddf.py
@@ -1,10 +1,8 @@
-# -*- coding: utf-8 -*-
 
 import inspect
 import logging
 import re
 import sys
-import six
 
 from django.core.files import File
 from django.db.models import Field
@@ -24,7 +22,6 @@ from django_dynamic_fixture.django_helper import get_related_model, \
 
 
 LOGGER = logging.getLogger('DDFLog')
-_LOADED_DDF_SETUP_MODULES = [] # control to avoid a ddf_setup module be loaded more than one time.
 _PRE_SAVE = {} # receivers to be executed before saving a instance;
 _POST_SAVE = {} # receivers to be executed after saving a instance;
 
@@ -53,10 +50,6 @@ class InvalidModelError(Exception):
     "Invalid Model: The class is not a model or it is abstract."
 
 
-class InvalidDDFSetupError(Exception):
-    "ddf_setup.py has execution errors"
-
-
 class InvalidReceiverError(Exception):
     "Receiver is not a function that receive only the instance as parameter."
 
@@ -74,10 +67,7 @@ def _validate_function(model_class, callback_function):
     if not inspect.isfunction(callback_function) or not callback_function:
         raise InvalidReceiverError(model_class, 'Invalid function')
     if callback_function:
-        if six.PY3:
-            args = len(inspect.getfullargspec(callback_function).args)
-        else:
-            args = len(inspect.getargspec(callback_function).args)
+        args = len(inspect.getfullargspec(callback_function).args)
     if args != 1:
         raise InvalidReceiverError(model_class, 'Invalid number of function arguments')
 
@@ -102,7 +92,7 @@ def set_post_save_receiver(model_class, callback_function):
     _POST_SAVE[model_class] = callback_function
 
 
-class DataFixture(object):
+class DataFixture:
     '''
     Responsibility: return a valid data for a Django Field, according to its type, model class, constraints etc.
 
@@ -118,7 +108,7 @@ class DataFixture(object):
         self.plugins = {}
 
     def _field_fixture_template(self, field_class):
-        return '%s_config' % (field_class.__name__.lower(),)
+        return f'{field_class.__name__.lower()}_config'
 
     def _field_fixture_factory(self, field_class):
         try:
@@ -146,7 +136,7 @@ class DataFixture(object):
         is_supported_field = config != None
         if is_supported_field:
             key = get_unique_field_name(field)
-            data = eval('self.%s(field, "%s")' % (config, key,))
+            data = eval(f'self.{config}(field, "{key}")')
         else:
             if field.null:
                 data = None # a workaround for versatility
@@ -155,7 +145,7 @@ class DataFixture(object):
         return data
 
 
-class Copier(object):
+class Copier:
     '''
     Wrapper of an expression in the format 'field' or 'field.field' or 'field.field.field' etc
     This expression will be interpreted to copy the value of the specified field to the current field.
@@ -181,10 +171,10 @@ class Copier(object):
                 current_instance = getattr(current_instance, field)
             return current_instance
         except Exception as e:
-            six.reraise(InvalidCopierExpressionError, InvalidCopierExpressionError(self.expression, e), sys.exc_info()[2])
+            raise InvalidCopierExpressionError(self.expression, e)
 
 
-class Mask(object):
+class Mask:
     '''
     Wrapper for an expression mask that will be used to generate a random string with a custom format.
 
@@ -228,7 +218,7 @@ class Mask(object):
         return ''.join(chars)
 
 
-class DDFLibrary(object):
+class DDFLibrary:
     instance = None
     DEFAULT_KEY = 'ddf_default'
 
@@ -236,7 +226,7 @@ class DDFLibrary(object):
         self.configs = {} # {Model: {name: config}}"
 
     def __str__(self):
-        return '\n'.join(['%s = %s' % (key, value) for key, value in self.configs.items()])
+        return '\n'.join([f'{key} = {value}' for key, value in self.configs.items()])
 
     @classmethod
     def get_instance(cls):
@@ -252,7 +242,7 @@ class DDFLibrary(object):
         if model_class in self.configs and name in self.configs[model_class]:
             if not os.getenv('DDF_SHELL_MODE'):
                 msg = "Override a lesson is an anti-pattern and will turn your test suite very hard to understand."
-                msg = 'A lesson {} has already been saved for the model {}. {}'.format(name, model_class, msg)
+                msg = f'A lesson {name} has already been saved for the model {model_class}. {msg}'
                 warnings.warn(msg, RuntimeWarning)
         model_class_config = self.configs.setdefault(model_class, {})
         model_class_config[name] = kwargs
@@ -263,7 +253,7 @@ class DDFLibrary(object):
         # copy is important because this dict will be updated every time in the algorithm.
         config = self.configs.get(model_class, {})
         if name != self.DEFAULT_KEY and name not in config.keys():
-            raise InvalidConfigurationError('There is no lesson for model %s with the name "%s"' % (get_unique_model_name(model_class), name))
+            raise InvalidConfigurationError('There is no lesson for model {} with the name "{}"'.format(get_unique_model_name(model_class), name))
         return config.get(name, {}).copy() # default configuration never raises an error
 
     def clear(self):
@@ -276,7 +266,7 @@ class DDFLibrary(object):
             del self.configs[model_class]
 
 
-class DynamicFixture(object):
+class DynamicFixture:
     '''
     Responsibility: create a valid model instance according to the given configuration.
     '''
@@ -316,7 +306,7 @@ class DynamicFixture(object):
         self.fields_to_disable_auto_now_add = []
 
     def __str__(self):
-        return 'F(%s)' % (', '.join(six.text_type('%s=%s') % (key, value) for key, value in self.kwargs.items()))
+        return 'F(%s)' % (', '.join(f'{key}={value}' for key, value in self.kwargs.items()))
 
     def __eq__(self, that):
         return isinstance(that, self.__class__) and self.kwargs == that.kwargs
@@ -442,7 +432,7 @@ class DynamicFixture(object):
             except PendingField:
                 return # ignore this field for a while.
             except Exception as e:
-                six.reraise(InvalidConfigurationError, InvalidConfigurationError(get_unique_field_name(__field), e), sys.exc_info()[2])
+                raise InvalidConfigurationError(get_unique_field_name(__field), e)
         else:
             data = self._process_field_with_default_fixture(__field, model_class, persist_dependencies)
 
@@ -464,7 +454,7 @@ class DynamicFixture(object):
                 setattr(__instance, __field.name, data) # Model.field = data
         else:
             if self.debug_mode:
-                LOGGER.debug('%s.%s = %s' % (get_unique_model_name(model_class), __field.name, data))
+                LOGGER.debug(f'{get_unique_model_name(model_class)}.{__field.name} = {data}')
             try:
                 setattr(__instance, __field.name, data) # Model.field = data
             except (ValueError, AttributeError) as e:
@@ -474,7 +464,7 @@ class DynamicFixture(object):
                     field_value = data.id if data and isinstance(e, AttributeError) else data
                     setattr(__instance, "%s_id" % __field.name, field_value) # Model.field = data
                 else:
-                    six.reraise(*sys.exc_info())
+                    raise e
         self.fields_processed.append(__field.name)
 
     def _validate_kwargs(self, model_class, kwargs):
@@ -493,18 +483,6 @@ class DynamicFixture(object):
         '''
         self._validate_kwargs(model_class, kwargs)
 
-        # load ddf_setup.py of the model application
-        app_name = get_app_name_of_model(model_class)
-        if app_name not in _LOADED_DDF_SETUP_MODULES:
-            full_module_name = '%s.tests.ddf_setup' % app_name
-            try:
-                _LOADED_DDF_SETUP_MODULES.append(app_name)
-                import_module(full_module_name)
-            except ImportError:
-                pass # ignoring if module does not exist
-            except Exception as e:
-                six.reraise(InvalidDDFSetupError, InvalidDDFSetupError(e), sys.exc_info()[2])
-
         library = DDFLibrary.get_instance()
         configuration = {}
         # 1. Load the default/global lesson for the model.
@@ -654,17 +632,17 @@ class DynamicFixture(object):
                 try:
                     _PRE_SAVE[model_class](instance)
                 except Exception as e:
-                    six.reraise(InvalidReceiverError, InvalidReceiverError(e), sys.exc_info()[2])
+                    raise InvalidReceiverError(e)
             self._save_the_instance(instance)
             if model_class in _POST_SAVE:
                 try:
                     _POST_SAVE[model_class](instance)
                 except Exception as e:
-                    six.reraise(InvalidReceiverError, InvalidReceiverError(e), sys.exc_info()[2])
+                    raise InvalidReceiverError(e)
         except Exception as e:
             if self.print_errors:
                 print_field_values(instance)
-            six.reraise(BadDataError, BadDataError(get_unique_model_name(model_class), e), sys.exc_info()[2])
+            raise BadDataError(get_unique_model_name(model_class), e)
         self.fields_processed = [] # TODO: need more tests for M2M and Copier
         self.pending_fields = []
         for field in get_many_to_many_fields_from_model(model_class):
@@ -674,9 +652,9 @@ class DynamicFixture(object):
                 try:
                     self._process_many_to_many_field(field, manytomany_field, fixture, instance)
                 except InvalidManyToManyConfigurationError as e:
-                    six.reraise(InvalidManyToManyConfigurationError, e, sys.exc_info()[2])
+                    raise e
                 except Exception as e:
-                    six.reraise(InvalidManyToManyConfigurationError, InvalidManyToManyConfigurationError(get_unique_field_name(field), e), sys.exc_info()[2])
+                    raise InvalidManyToManyConfigurationError(get_unique_field_name(field), e)
         return instance
 
     def teach(self, model_class, ddf_lesson=None, **kwargs):
@@ -685,7 +663,10 @@ class DynamicFixture(object):
             if field_name in self._DDF_CONFIGS:
                 continue
             field = get_field_by_name_or_raise(model_class, field_name)
-            fixture = kwargs[field_name]
-            if field.unique and not (isinstance(fixture, (DynamicFixture, Copier, DataFixture)) or callable(fixture)):
+            if field.unique and not _is_dynamic_value(kwargs[field_name]):
                 raise InvalidConfigurationError('It is not possible to store static values for fields with unique=True (%s). Try using a lambda function instead.' % get_unique_field_name(field))
         library.add_configuration(model_class, kwargs, name=ddf_lesson)
+
+
+def _is_dynamic_value(fixture):
+    return isinstance(fixture, (DynamicFixture, Copier, DataFixture, Mask)) or callable(fixture)
diff --git a/django_dynamic_fixture/decorators.py b/django_dynamic_fixture/decorators.py
index cfc4853e88440ce610326e9cb68c76b3ae844361..11120e2104e9410bb7c65d8dc9b1bfab4934e8ab 100644
--- a/django_dynamic_fixture/decorators.py
+++ b/django_dynamic_fixture/decorators.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 
 from django.conf import settings
 
diff --git a/django_dynamic_fixture/django_helper.py b/django_dynamic_fixture/django_helper.py
index 869e1ef3b85e739bf7d0ebf946025355e4ecca66..81a8ab5b9a859faa435e4df359aaf92eb76da482 100644
--- a/django_dynamic_fixture/django_helper.py
+++ b/django_dynamic_fixture/django_helper.py
@@ -1,10 +1,6 @@
-# -*- coding: utf-8 -*-
 """
 Module to wrap dirty stuff of django core.
 """
-import re
-from distutils.version import StrictVersion
-
 import django
 from django.apps import apps
 from django.db import models  # noqa
@@ -18,11 +14,9 @@ except ImportError:
     from django.core.exceptions import FieldDoesNotExist
 
 
+def django_greater_than(major, minor=0):
+    return django.VERSION[:2] > (major, minor)
 
-def django_greater_than(version):
-    # Avoid StrictVersion errors with versions like 1.8c1
-    DJANGO_VERSION = re.match(r"\d\.\d\d?", django.get_version()).group(0)
-    return StrictVersion(DJANGO_VERSION) >= StrictVersion(version)
 
 # Apps
 def get_apps(application_labels=[], exclude_application_labels=[]):
@@ -49,8 +43,8 @@ def get_apps(application_labels=[], exclude_application_labels=[]):
                     applications.remove(app_label)
                 else:
                     raise ValueError(
-                        "Excluded application with label '{0}' "
-                        "is not installed.".format(app_label))
+                        f"Excluded application with label '{app_label}' "
+                        "is not installed.")
     return applications
 
 
diff --git a/django_dynamic_fixture/fdf.py b/django_dynamic_fixture/fdf.py
index 58aff29add74d10b0d38ed4a89504a9166784f4f..75909e9c23064d07b9c1f7c56836d24c3f732c20 100644
--- a/django_dynamic_fixture/fdf.py
+++ b/django_dynamic_fixture/fdf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 
 import os
 import tempfile
@@ -17,7 +16,7 @@ TEMP_PATH_DDF = os.path.join(TEMP_PATH, 'DDF_TEMP')
 
 class CustomFileSystemStorage(FileSystemStorage):
     def __init__(self, *args, **kwargs):
-        super(CustomFileSystemStorage, self).\
+        super().\
         __init__(location=TEMP_PATH_DDF, *args, **kwargs)
 
 
@@ -31,12 +30,12 @@ class FileSystemDjangoTestCase(TestCase):
         self.fdf_teardown()
 
     def _pre_setup(self):
-        super(FileSystemDjangoTestCase, self)._pre_setup()
+        super()._pre_setup()
         self.fdf_setup()
 
     def _post_teardown(self):
         "Try to remove all files and directories created by the test."
-        super(FileSystemDjangoTestCase, self)._post_teardown()
+        super()._post_teardown()
         self.fdf_teardown()
 
     def fdf_setup(self):
diff --git a/django_dynamic_fixture/fixture_algorithms/default_fixture.py b/django_dynamic_fixture/fixture_algorithms/default_fixture.py
index fa6a064d12b0c1f71cd776dc3f8b26aa0b7e2901..9916679d32dafec657a6fe84cb020fe3055da726 100644
--- a/django_dynamic_fixture/fixture_algorithms/default_fixture.py
+++ b/django_dynamic_fixture/fixture_algorithms/default_fixture.py
@@ -5,8 +5,6 @@ import random
 import string
 import uuid
 
-import six
-
 from django.core.exceptions import ImproperlyConfigured
 
 
@@ -29,7 +27,7 @@ from django_dynamic_fixture.ddf import DataFixture
 class BaseDataFixture(DataFixture):
     # Django >= 1.6
     def binaryfield_config(self, field, key):
-        return six.b('\x00\x46\xFE')
+        return b'\x00\x46\xFE'
 
     # Django >= 1.8
     def uuidfield_config(self, field, key):
@@ -45,7 +43,7 @@ class BaseDataFixture(DataFixture):
 
 
 # GIS/GeoDjango
-class GeoDjangoFixtureMixin(object):
+class GeoDjangoFixtureMixin:
     def create_point(self, x=None, y=None):
         # latitude: [-90,90], longitude: [-180,180]
         latitude = x or random.randint(-90, 90)
@@ -88,7 +86,7 @@ class GeoDjangoFixtureMixin(object):
 
 # Postgres fields
 # https://docs.djangoproject.com/en/1.8/ref/contrib/postgres/fields/
-class PostgresFixtureMixin(object):
+class PostgresFixtureMixin:
     def arrayfield_config(self, field, key, n=1):
         data = [self.generate_data(field.base_field) for i in range(n)]
         return data
diff --git a/django_dynamic_fixture/fixture_algorithms/random_fixture.py b/django_dynamic_fixture/fixture_algorithms/random_fixture.py
index af47dad39371c50fe99abff00818c23e7c2a86fc..07063cbadeb161a2959741a1f907f371eae76e65 100644
--- a/django_dynamic_fixture/fixture_algorithms/random_fixture.py
+++ b/django_dynamic_fixture/fixture_algorithms/random_fixture.py
@@ -1,11 +1,8 @@
-# -*- coding: utf-8 -*-
 from datetime import datetime, date, timedelta
 from decimal import Decimal
 import random
 import string
 
-import six
-
 from django.core.exceptions import ImproperlyConfigured
 
 try:
@@ -25,7 +22,7 @@ from django_dynamic_fixture.fixture_algorithms.default_fixture import BaseDataFi
 
 class RandomDataFixture(BaseDataFixture, GeoDjangoFixtureMixin, PostgresFixtureMixin):
     def random_string(self, n):
-        return six.text_type('').join(random.choice(string.ascii_uppercase + string.digits) for _ in range(n))
+        return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(n))
 
     # NUMBERS
     def integerfield_config(self, field, key, start=1, end=10 ** 6):
@@ -70,7 +67,7 @@ class RandomDataFixture(BaseDataFixture, GeoDjangoFixtureMixin, PostgresFixtureM
         return self.charfield_config(field, key)
 
     def commaseparatedintegerfield_config(self, field, key):
-        return six.text_type(random.randint(1, field.max_length)) #FIXME:
+        return str(random.randint(1, field.max_length)) #FIXME:
 
     # BOOLEAN
     def booleanfield_config(self, field, key):
@@ -92,10 +89,10 @@ class RandomDataFixture(BaseDataFixture, GeoDjangoFixtureMixin, PostgresFixtureM
 
     # FORMATTED STRINGS
     def emailfield_config(self, field, key):
-        return six.text_type('a%s@dynamicfixture.com') % self.random_string(10)
+        return f'a{self.random_string(10)}@dynamicfixture.com'
 
     def urlfield_config(self, field, key):
-        return six.text_type('http://dynamicfixture%s.com') % self.random_string(10)
+        return f'http://dynamicfixture{self.random_string(10)}.com'
 
     # Deprecated in Django >= 1.7
     def ipaddressfield_config(self, field, key):
@@ -103,10 +100,10 @@ class RandomDataFixture(BaseDataFixture, GeoDjangoFixtureMixin, PostgresFixtureM
         b = random.randint(1, 255)
         c = random.randint(1, 255)
         d = random.randint(1, 255)
-        return six.text_type('%s.%s.%s.%s') % (a, b, c, d)
+        return f'{a}.{b}.{c}.{d}'
 
     def xmlfield_config(self, field, key):
-        return six.text_type('<a>%s</a>') % self.random_string(5)
+        return f'<a>{self.random_string(5)}</a>'
 
     # FILES
     def filepathfield_config(self, field, key):
diff --git a/django_dynamic_fixture/fixture_algorithms/sequential_fixture.py b/django_dynamic_fixture/fixture_algorithms/sequential_fixture.py
index d9eb869c132ba5fa9987921826208f74f0e47b02..ad690db9784e5a4e600e37cec18f7fd2238a675d 100644
--- a/django_dynamic_fixture/fixture_algorithms/sequential_fixture.py
+++ b/django_dynamic_fixture/fixture_algorithms/sequential_fixture.py
@@ -1,10 +1,7 @@
-# -*- coding: utf-8 -*-
 from datetime import datetime, date, timedelta
 from decimal import Decimal
 import threading
 
-import six
-
 from django.core.exceptions import ImproperlyConfigured
 
 try:
@@ -23,7 +20,7 @@ from django_dynamic_fixture.fixture_algorithms.default_fixture import BaseDataFi
 from django_dynamic_fixture.django_helper import field_is_unique
 
 
-class AutoDataFiller(object):
+class AutoDataFiller:
     """
     Responsibility: generate a unique and sequential value for each key.
     """
@@ -52,7 +49,7 @@ class AutoDataFiller(object):
 class SequentialDataFixture(BaseDataFixture, GeoDjangoFixtureMixin, PostgresFixtureMixin):
 
     def __init__(self):
-        super(SequentialDataFixture, self).__init__()
+        super().__init__()
         self.filler = AutoDataFiller()
 
     def get_value(self, field, key):
@@ -89,10 +86,10 @@ class SequentialDataFixture(BaseDataFixture, GeoDjangoFixtureMixin, PostgresFixt
         data = self.get_value(field, key)
         if field.max_length:
             max_value = (10 ** field.max_length) - 1
-            data = six.text_type(data % max_value)
+            data = str(data % max_value)
             data = data[:field.max_length]
         else:
-            data = six.text_type(data)
+            data = str(data)
         return data
 
     def textfield_config(self, field, key):
@@ -126,10 +123,10 @@ class SequentialDataFixture(BaseDataFixture, GeoDjangoFixtureMixin, PostgresFixt
 
     # FORMATTED STRINGS
     def emailfield_config(self, field, key):
-        return six.text_type('a%s@dynamicfixture.com') % self.get_value(field, key)
+        return f'a{self.get_value(field, key)}@dynamicfixture.com'
 
     def urlfield_config(self, field, key):
-        return six.text_type('http://dynamicfixture%s.com') % self.get_value(field, key)
+        return f'http://dynamicfixture{self.get_value(field, key)}.com'
 
     # Deprecated in Django >= 1.7
     def ipaddressfield_config(self, field, key):
@@ -139,20 +136,20 @@ class SequentialDataFixture(BaseDataFixture, GeoDjangoFixtureMixin, PostgresFixt
         b = '1'
         c = '1'
         d = data % 256
-        return six.text_type('%s.%s.%s.%s') % (a, b, c, str(d))
+        return f'{a}.{b}.{c}.{d}'
 
     def xmlfield_config(self, field, key):
-        return six.text_type('<a>%s</a>') % self.get_value(field, key)
+        return f'<a>{self.get_value(field, key)}</a>'
 
     # FILES
     def filepathfield_config(self, field, key):
-        return six.text_type(self.get_value(field, key))
+        return str(self.get_value(field, key))
 
     def filefield_config(self, field, key):
-        return six.text_type(self.get_value(field, key))
+        return str(self.get_value(field, key))
 
     def imagefield_config(self, field, key):
-        return six.text_type(self.get_value(field, key))
+        return str(self.get_value(field, key))
 
 
 class GlobalSequentialDataFixture(SequentialDataFixture):
diff --git a/django_dynamic_fixture/fixture_algorithms/tests/abstract_test_generic_fixture.py b/django_dynamic_fixture/fixture_algorithms/tests/abstract_test_generic_fixture.py
index 417b8d05f6a8770f7ded1dd7d18cbfb08503353e..afb1dc22f8284797cf0302280f4130dde3fc6702 100644
--- a/django_dynamic_fixture/fixture_algorithms/tests/abstract_test_generic_fixture.py
+++ b/django_dynamic_fixture/fixture_algorithms/tests/abstract_test_generic_fixture.py
@@ -1,13 +1,11 @@
-# -*- coding: utf-8 -*-
 
 from django.db import models
 
 from datetime import datetime, date, time
 from decimal import Decimal
-import six
 
 
-class DataFixtureTestCase(object):
+class DataFixtureTestCase:
     def setUp(self):
         self.fixture = None
 
@@ -27,19 +25,19 @@ class DataFixtureTestCase(object):
             assert isinstance(self.fixture.generate_data(models.DecimalField(max_digits=2, decimal_places=1)), Decimal)
 
     def test_strings(self):
-        assert isinstance(self.fixture.generate_data(models.CharField(max_length=1)), six.text_type)
-        assert isinstance(self.fixture.generate_data(models.TextField()), six.text_type)
-        assert isinstance(self.fixture.generate_data(models.SlugField(max_length=1)), six.text_type)
-        assert isinstance(self.fixture.generate_data(models.CommaSeparatedIntegerField(max_length=1)), six.text_type)
+        assert isinstance(self.fixture.generate_data(models.CharField(max_length=1)), str)
+        assert isinstance(self.fixture.generate_data(models.TextField()), str)
+        assert isinstance(self.fixture.generate_data(models.SlugField(max_length=1)), str)
+        assert isinstance(self.fixture.generate_data(models.CommaSeparatedIntegerField(max_length=1)), str)
 
     def test_new_truncate_strings_to_max_length(self):
         for _ in range(12): # truncate start after the 10 object
-            assert isinstance(self.fixture.generate_data(models.CharField(max_length=1)), six.text_type)
+            assert isinstance(self.fixture.generate_data(models.CharField(max_length=1)), str)
 
     def test_boolean(self):
         assert isinstance(self.fixture.generate_data(models.BooleanField()), bool)
         value = self.fixture.generate_data(models.NullBooleanField())
-        assert isinstance(value, bool) or value == None
+        assert isinstance(value, bool) or value is None
 
     def test_date_time_related(self):
         assert isinstance(self.fixture.generate_data(models.DateField()), date)
@@ -47,18 +45,18 @@ class DataFixtureTestCase(object):
         assert isinstance(self.fixture.generate_data(models.DateTimeField()), datetime)
 
     def test_formatted_strings(self):
-        assert isinstance(self.fixture.generate_data(models.EmailField(max_length=100)), six.text_type)
-        assert isinstance(self.fixture.generate_data(models.URLField(max_length=100)), six.text_type)
-        assert isinstance(self.fixture.generate_data(models.IPAddressField(max_length=100)), six.text_type)
-        assert isinstance(self.fixture.generate_data(models.GenericIPAddressField(max_length=100)), six.text_type)
+        assert isinstance(self.fixture.generate_data(models.EmailField(max_length=100)), str)
+        assert isinstance(self.fixture.generate_data(models.URLField(max_length=100)), str)
+        assert isinstance(self.fixture.generate_data(models.IPAddressField(max_length=100)), str)
+        assert isinstance(self.fixture.generate_data(models.GenericIPAddressField(max_length=100)), str)
 
     def test_files(self):
-        assert isinstance(self.fixture.generate_data(models.FilePathField(max_length=100)), six.text_type)
-        assert isinstance(self.fixture.generate_data(models.FileField()), six.text_type)
+        assert isinstance(self.fixture.generate_data(models.FilePathField(max_length=100)), str)
+        assert isinstance(self.fixture.generate_data(models.FileField()), str)
         try:
             import pil
             # just test it if the PIL package is installed
-            assert isinstance(self.fixture.generate_data(models.ImageField(max_length=100)), six.text_type)
+            assert isinstance(self.fixture.generate_data(models.ImageField(max_length=100)), str)
         except ImportError:
             pass
 
diff --git a/django_dynamic_fixture/fixture_algorithms/tests/test_default_fixture.py b/django_dynamic_fixture/fixture_algorithms/tests/test_default_fixture.py
index a636b8a58a1a04db89c745ceb543c0ecf09a5109..ce93a5b95b7401349006ff9c680292f3f930100f 100644
--- a/django_dynamic_fixture/fixture_algorithms/tests/test_default_fixture.py
+++ b/django_dynamic_fixture/fixture_algorithms/tests/test_default_fixture.py
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from datetime import datetime
-import six
 import uuid
 
 from django.db import models
@@ -8,8 +5,8 @@ from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
 
 
-from django.contrib.gis.geos import *
 try:
+    from django.contrib.gis.geos import *
     from django.contrib.gis.db import models as geomodels
 except ImproperlyConfigured:
     pass  # environment without geo libs
diff --git a/django_dynamic_fixture/fixture_algorithms/tests/test_default_fixture_postgres.py b/django_dynamic_fixture/fixture_algorithms/tests/test_default_fixture_postgres.py
index f10a8c0bdaacc6bda11244ee5caef4112c6f2e6f..d2a50e57f6ce8824f3a3ccabdcfa0ecd445ccd84 100644
--- a/django_dynamic_fixture/fixture_algorithms/tests/test_default_fixture_postgres.py
+++ b/django_dynamic_fixture/fixture_algorithms/tests/test_default_fixture_postgres.py
@@ -1,6 +1,4 @@
-# -*- coding: utf-8 -*-
 from datetime import datetime
-import six
 
 from django.db import models
 from django.test import TestCase
@@ -14,7 +12,7 @@ try:
     import psycopg2
     from django.contrib.postgres.fields import ArrayField
 
-    class PostgresDataFixtureTestMixin(object):
+    class PostgresDataFixtureTestMixin:
         def test_arrayfield_integer_config(self):
             data = self.fixture.generate_data(ArrayField(models.IntegerField()))
             assert isinstance(data, list)
@@ -23,7 +21,7 @@ try:
         def test_arrayfield_char_config(self):
             data = self.fixture.generate_data(ArrayField(models.CharField()))
             assert isinstance(data, list)
-            assert isinstance(data[0], six.text_type)
+            assert isinstance(data[0], str)
 
         def test_arrayfield_datetime_config(self):
             data = self.fixture.generate_data(ArrayField(models.DateTimeField()))
@@ -33,7 +31,7 @@ try:
         def test_arrayfield_email_config(self):
             data = self.fixture.generate_data(ArrayField(models.EmailField(max_length=100)))
             assert isinstance(data, list)
-            assert isinstance(data[0], six.text_type)
+            assert isinstance(data[0], str)
 
 
     class PostgresSequentialDataFixtureTestCase(TestCase, PostgresDataFixtureTestMixin):
diff --git a/django_dynamic_fixture/fixture_algorithms/tests/test_random_fixture.py b/django_dynamic_fixture/fixture_algorithms/tests/test_random_fixture.py
index a7ee674478dac8add279e231ad1368f3771762b4..d57dd8434265a2cea792e82ddd4f3b2d6a638fd8 100644
--- a/django_dynamic_fixture/fixture_algorithms/tests/test_random_fixture.py
+++ b/django_dynamic_fixture/fixture_algorithms/tests/test_random_fixture.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 
 from django.test import TestCase
 
diff --git a/django_dynamic_fixture/fixture_algorithms/tests/test_sequential_fixture.py b/django_dynamic_fixture/fixture_algorithms/tests/test_sequential_fixture.py
index 3d3fdb45e3cb96074ad01b97d80b15961df9f1f5..f8b382ac89153e2f16bad700814cff0eab58a667 100644
--- a/django_dynamic_fixture/fixture_algorithms/tests/test_sequential_fixture.py
+++ b/django_dynamic_fixture/fixture_algorithms/tests/test_sequential_fixture.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from django.db import models
 
 from django.test import TestCase
diff --git a/django_dynamic_fixture/fixture_algorithms/tests/test_unique_random_fixture.py b/django_dynamic_fixture/fixture_algorithms/tests/test_unique_random_fixture.py
index 2e743486bc6805f23c049a9248b9efac6f99c776..e298981f63d58b9d23c334afb0c9b68ffe96b2e9 100644
--- a/django_dynamic_fixture/fixture_algorithms/tests/test_unique_random_fixture.py
+++ b/django_dynamic_fixture/fixture_algorithms/tests/test_unique_random_fixture.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from warnings import catch_warnings
 
 from django.db import models
diff --git a/django_dynamic_fixture/fixture_algorithms/unique_random_fixture.py b/django_dynamic_fixture/fixture_algorithms/unique_random_fixture.py
index a2deb0c2c00b6beba8f1ce55c6fd6ee654694920..8142aefb74d2cc5fff8b373bd6ce059a253f7923 100644
--- a/django_dynamic_fixture/fixture_algorithms/unique_random_fixture.py
+++ b/django_dynamic_fixture/fixture_algorithms/unique_random_fixture.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from datetime import datetime, date, timedelta
 from decimal import Decimal
 from itertools import chain
@@ -8,8 +7,6 @@ import string
 import struct
 from warnings import warn
 
-import six
-
 from django.core.exceptions import ImproperlyConfigured
 
 try:
@@ -37,7 +34,7 @@ class UniqueRandomDataFixture(BaseDataFixture, GeoDjangoFixtureMixin, PostgresFi
     )
 
     def __init__(self):
-        super(UniqueRandomDataFixture, self).__init__()
+        super().__init__()
         self.filler = AutoDataFiller()
 
     def get_counter(self, field, key):
@@ -47,10 +44,10 @@ class UniqueRandomDataFixture(BaseDataFixture, GeoDjangoFixtureMixin, PostgresFi
         return result
 
     def random_string(self, field, key, n=None):
-        counter = six.text_type(self.get_counter(field, key))
+        counter = str(self.get_counter(field, key))
         length = n or self.DEFAULT_LENGTH
         result = counter
-        result += six.text_type('').join(
+        result += ''.join(
             random.choice(string.ascii_letters)
             for _ in range(length - len(counter))
         )
@@ -149,10 +146,10 @@ class UniqueRandomDataFixture(BaseDataFixture, GeoDjangoFixtureMixin, PostgresFi
 
     # FORMATTED STRINGS
     def emailfield_config(self, field, key):
-        return six.text_type('a%s@dynamicfixture.com') % self.random_string(field, key)
+        return f'a{self.random_string(field, key)}@dynamicfixture.com'
 
     def urlfield_config(self, field, key):
-        return six.text_type('http://dynamicfixture%s.com') % self.random_string(field, key)
+        return f'http://dynamicfixture{self.random_string(field, key)}.com'
 
     # Deprecated in Django >= 1.7
     def ipaddressfield_config(self, field, key):
@@ -160,10 +157,10 @@ class UniqueRandomDataFixture(BaseDataFixture, GeoDjangoFixtureMixin, PostgresFi
 
         integer = self.random_integer(field, key, signed=False)
         integer %= MAX_IP
-        return six.text_type(socket.inet_ntoa(struct.pack('!L', integer)))
+        return str(socket.inet_ntoa(struct.pack('!L', integer)))
 
     def xmlfield_config(self, field, key):
-        return six.text_type('<a>%s</a>') % self.random_string(field, key)
+        return f'<a>{self.random_string(field, key)}</a>'
 
     # FILES
     def filepathfield_config(self, field, key):
diff --git a/django_dynamic_fixture/global_settings.py b/django_dynamic_fixture/global_settings.py
index 436efb575ba8d28e0000ad224091f0e99806d7ea..29bc83cf02f7aa63c37416089a7e511c52a1527e 100644
--- a/django_dynamic_fixture/global_settings.py
+++ b/django_dynamic_fixture/global_settings.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 
 """
 Module that contains wrappers and shortcuts.
@@ -19,7 +18,6 @@ try:
     from importlib import import_module
 except ImportError:
     from django.utils.importlib import import_module
-import six
 
 from django_dynamic_fixture.fixture_algorithms.sequential_fixture import SequentialDataFixture, StaticSequentialDataFixture, GlobalSequentialDataFixture
 from django_dynamic_fixture.fixture_algorithms.random_fixture import RandomDataFixture
@@ -40,11 +38,7 @@ def get_ddf_config(name, default, cast=None, options=None, msg=''):
             raise DDFImproperlyConfigured()
         return value
     except Exception as e:
-        six.reraise(
-            DDFImproperlyConfigured,
-            DDFImproperlyConfigured('{}="{}": {} ({})'.format(name, value, msg, e),
-            sys.exc_info()[2])
-        )
+        raise DDFImproperlyConfigured(f'{name}="{value}": {msg} ({e})')
 
 
 def get_boolean_config(config_name, default=False):
@@ -70,7 +64,10 @@ def get_data_fixture(default='sequential'):
         else:
             return INTERNAL_DATA_FIXTURES[default]
     except:
-        six.reraise(DDFImproperlyConfigured, DDFImproperlyConfigured("DDF_DEFAULT_DATA_FIXTURE (%s) must be 'sequential', 'static_sequential', 'global_sequential', 'random' or 'path.to.CustomDataFixtureClass'." % settings.DDF_DEFAULT_DATA_FIXTURE), sys.exc_info()[2])
+        raise DDFImproperlyConfigured(
+            f"DDF_DEFAULT_DATA_FIXTURE ({settings.DDF_DEFAULT_DATA_FIXTURE}) must be "
+            "'sequential', 'static_sequential', 'global_sequential', 'random' or 'path.to.CustomDataFixtureClass'."
+        )
 
 
 DDF_DEFAULT_DATA_FIXTURE = get_data_fixture(default='sequential')
diff --git a/django_dynamic_fixture/models_test.py b/django_dynamic_fixture/models_test.py
index d17fc54be396c57d387c73ebd06679ade4930342..17d1dde7b90bc76ef31e9a622ca52d2b5eda92a2 100644
--- a/django_dynamic_fixture/models_test.py
+++ b/django_dynamic_fixture/models_test.py
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
 
 # https://docs.djangoproject.com/en/3.0/ref/models/fields
 import django
@@ -250,7 +249,7 @@ class CustomDjangoField2(models.IntegerField):
     pass
 
 
-class CustomDjangoFieldMixin(object):
+class CustomDjangoFieldMixin:
     pass
 
 
@@ -353,11 +352,11 @@ class ModelForLibrary(models.Model):
         app_label = 'django_dynamic_fixture'
 
 
-class ModelForDDFSetup(models.Model):
-    integer = models.IntegerField(null=True)
+class ModelWithUniqueCharField(models.Model):
+    text_unique = models.CharField(max_length=20, unique=True)
 
     class Meta:
-        verbose_name = 'DDF setup'
+        verbose_name = 'Unique char field'
         app_label = 'django_dynamic_fixture'
 
 
diff --git a/django_dynamic_fixture/tests/conftest.py b/django_dynamic_fixture/tests/conftest.py
index d9292b6f51385dff0ffc1e381d94d7f823926aa0..5fe07b834879edb59716746cf275d55f8cc58838 100644
--- a/django_dynamic_fixture/tests/conftest.py
+++ b/django_dynamic_fixture/tests/conftest.py
@@ -4,9 +4,6 @@
 import django
 django.setup()
 
-# Django-Nose must import test_models to avoid 'no such table' problem
-from django_dynamic_fixture import models_test
-
 # Give DB access to PyTests
 import pytest
 pytest.mark.django_db
diff --git a/django_dynamic_fixture/tests/ddf_setup.py b/django_dynamic_fixture/tests/ddf_setup.py
deleted file mode 100644
index b4d47cb2a5ac6cbb354a125d927d54d817e20dc0..0000000000000000000000000000000000000000
--- a/django_dynamic_fixture/tests/ddf_setup.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import platform
-import django
-
-
-print('\n\n')
-print(':: DDF-Nose plugin. ddf_setup.py loaded.')
-print(platform.python_version())
-print('Django {}'.format(django.VERSION))
-print('\n\n')
diff --git a/django_dynamic_fixture/tests/test_ddf.py b/django_dynamic_fixture/tests/test_ddf.py
index b6e85f423ac51dd205e0c7f954b019929c719b49..5adadac2105a8c4300cebc2a9c40760b2ceb25e4 100644
--- a/django_dynamic_fixture/tests/test_ddf.py
+++ b/django_dynamic_fixture/tests/test_ddf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from datetime import datetime, date
 from decimal import Decimal
 import uuid
@@ -55,14 +54,14 @@ class NewFullFillAttributesWithAutoDataTest(DDFTestCase):
 
     def test_new_fill_string_fields_with_text_type_strings(self):
         instance = self.ddf.new(ModelWithStrings)
-        assert isinstance(instance.string, six.text_type)
-        assert isinstance(instance.text, six.text_type)
-        assert isinstance(instance.slug, six.text_type)
-        assert isinstance(instance.commaseparated, six.text_type)
+        assert isinstance(instance.string, str)
+        assert isinstance(instance.text, str)
+        assert isinstance(instance.slug, str)
+        assert isinstance(instance.commaseparated, str)
 
     def test_new_fill_boolean_fields_with_False_and_None(self):
         instance = self.ddf.new(ModelWithBooleans)
-        assert instance.boolean == False
+        assert instance.boolean is False
         assert instance.nullboolean is None
 
     def test_new_fill_time_related_fields_with_current_values(self):
@@ -73,15 +72,15 @@ class NewFullFillAttributesWithAutoDataTest(DDFTestCase):
 
     def test_new_fill_formatted_strings_fields_with_basic_values(self):
         instance = self.ddf.new(ModelWithFieldsWithCustomValidation)
-        assert isinstance(instance.email, six.text_type)
-        assert isinstance(instance.url, six.text_type)
-        assert isinstance(instance.ip, six.text_type)
-        assert isinstance(instance.ipv6, six.text_type)
+        assert isinstance(instance.email, str)
+        assert isinstance(instance.url, str)
+        assert isinstance(instance.ip, str)
+        assert isinstance(instance.ipv6, str)
 
     def test_new_fill_file_fields_with_basic_strings(self):
         instance = self.ddf.new(ModelWithFileFields)
-        assert isinstance(instance.filepath, six.text_type)
-        assert isinstance(instance.file.path, six.text_type)
+        assert isinstance(instance.filepath, str)
+        assert isinstance(instance.file.path, str)
         try:
             import pil
             # just test it if the PIL package is installed
@@ -95,10 +94,7 @@ class NewFullFillAttributesWithAutoDataTest(DDFTestCase):
         assert bytes(instance.binary) == bytes(value)
 
         instance = self.ddf.get(ModelWithBinary)
-        if six.PY3:
-            assert isinstance(instance.binary, six.binary_type), type(instance.binary)
-        else:
-            assert isinstance(instance.binary, (six.binary_type, str, unicode)), type(instance.binary)
+        assert isinstance(instance.binary, bytes), type(instance.binary)
 
 
 class NewFullFillAttributesWithDefaultDataTest(DDFTestCase):
@@ -472,7 +468,7 @@ class ExceptionsLayoutMessagesTest(DDFTestCase):
             assert """django_dynamic_fixture.models_test.ModelAbstract""" == str(e)
 
     def test_InvalidModelError_for_common_object(self):
-        class MyClass(object): pass
+        class MyClass: pass
         try:
             self.ddf.new(MyClass)
             self.fail()
@@ -490,13 +486,13 @@ class AvoidNameCollisionTest(DDFTestCase):
     def test_avoid_common_name_instance(self):
         self.ddf = DynamicFixture(data_fixture, fill_nullable_fields=False)
         instance = self.ddf.new(ModelWithCommonNames)
-        assert instance.instance != None
+        assert instance.instance is not None
 
         instance = self.ddf.new(ModelWithCommonNames, instance=3)
         assert instance.instance == 3
 
         instance = self.ddf.get(ModelWithCommonNames)
-        assert instance.instance != None
+        assert instance.instance is not None
 
         instance = self.ddf.get(ModelWithCommonNames, instance=4)
         assert instance.instance == 4
@@ -504,13 +500,13 @@ class AvoidNameCollisionTest(DDFTestCase):
     def test_avoid_common_name_field(self):
         self.ddf = DynamicFixture(data_fixture, fill_nullable_fields=False)
         instance = self.ddf.new(ModelWithCommonNames)
-        assert instance.field != None
+        assert instance.field is not None
 
         instance = self.ddf.new(ModelWithCommonNames, field=5)
         assert instance.field == 5
 
         instance = self.ddf.get(ModelWithCommonNames)
-        assert instance.field != None
+        assert instance.field is not None
 
         instance = self.ddf.get(ModelWithCommonNames, field=6)
         assert instance.field == 6
diff --git a/django_dynamic_fixture/tests/test_ddf_checkings.py b/django_dynamic_fixture/tests/test_ddf_checkings.py
index d05b285ce45dfe95834d91585b92e01500afaf44..207e0219062608c9da5e065090f5cbe8027cbcb3 100644
--- a/django_dynamic_fixture/tests/test_ddf_checkings.py
+++ b/django_dynamic_fixture/tests/test_ddf_checkings.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 
 from django.test import TestCase
 
diff --git a/django_dynamic_fixture/tests/test_ddf_copier.py b/django_dynamic_fixture/tests/test_ddf_copier.py
index 980dc380fd472d1b22cb4236c0aca30cb7aff78b..6022e428ca547f22516513ecb7921378529ef005 100644
--- a/django_dynamic_fixture/tests/test_ddf_copier.py
+++ b/django_dynamic_fixture/tests/test_ddf_copier.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from django.test import TestCase
 import pytest
 
diff --git a/django_dynamic_fixture/tests/test_ddf_custom_fields.py b/django_dynamic_fixture/tests/test_ddf_custom_fields.py
index 6679f415b90657eeb1fc2aa6e01d8eb8033683ee..3c8a49a09e819676b87b2da7cf665ecf11dfb717 100644
--- a/django_dynamic_fixture/tests/test_ddf_custom_fields.py
+++ b/django_dynamic_fixture/tests/test_ddf_custom_fields.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from django.conf import settings
 
 from django.test import TestCase
diff --git a/django_dynamic_fixture/tests/test_ddf_custom_models.py b/django_dynamic_fixture/tests/test_ddf_custom_models.py
index 99bed8ce66c506983d43c5d406908746e0048624..dfa2d6fb7349c31c6dc09ce1cd66af54c766c47d 100644
--- a/django_dynamic_fixture/tests/test_ddf_custom_models.py
+++ b/django_dynamic_fixture/tests/test_ddf_custom_models.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from django.test import TestCase
 import pytest
 
diff --git a/django_dynamic_fixture/tests/test_ddf_geo.py b/django_dynamic_fixture/tests/test_ddf_geo.py
index 1c96de88c0d97841e9e888eabbb4c5b5128fad2f..84ea2fc921084fb122d973d7a88f20296b7352fb 100644
--- a/django_dynamic_fixture/tests/test_ddf_geo.py
+++ b/django_dynamic_fixture/tests/test_ddf_geo.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
 
diff --git a/django_dynamic_fixture/tests/test_ddf_setup.py b/django_dynamic_fixture/tests/test_ddf_setup.py
deleted file mode 100644
index 0f495884b5e6432840f97bbb22fbb2716804e032..0000000000000000000000000000000000000000
--- a/django_dynamic_fixture/tests/test_ddf_setup.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.test import TestCase
-
-from django_dynamic_fixture import G, N, teach, DDFLibrary
-from django_dynamic_fixture.models_test import ModelForDDFSetup
-
-
-class ModuleDDFSetUpTest(TestCase):
-    def setUp(self):
-        DDFLibrary.instance = DDFLibrary() # singleton object is cleared by another tests
-        teach(ModelForDDFSetup, integer=9999) # using EXCLUSIVE_DDF_LIBRARY
-
-    def tearDown(self):
-        DDFLibrary.instance = DDFLibrary()
-
-    def test_setup_module_load_before_any_test_of_this_module(self):
-        instance = G(ModelForDDFSetup)
-        assert instance.integer == 9999
-
-
-class ApplicationDDFSetupTest(TestCase):
-    def setUp(self):
-        DDFLibrary.instance = DDFLibrary()
-        teach(ModelForDDFSetup, integer=1000, ddf_lesson='test1')
-        teach(ModelForDDFSetup, integer=1001)
-        teach(ModelForDDFSetup, integer=1002, ddf_lesson='test2')
-
-    def tearDown(self):
-        DDFLibrary.instance = DDFLibrary()
-
-    def test_ddf_setup_will_load_initial_lessons(self):
-        instance = G(ModelForDDFSetup)
-        assert instance.integer == 1001
-        instance = G(ModelForDDFSetup, ddf_lesson='test1')
-        assert instance.integer == 1000
-        instance = G(ModelForDDFSetup, ddf_lesson='test2')
-        assert instance.integer == 1002
diff --git a/django_dynamic_fixture/tests/test_ddf_signals.py b/django_dynamic_fixture/tests/test_ddf_signals.py
index 5d84f7921c77a10cfc9d12d4617df5483ab0ce16..83633658d44627c4903461645a2cb6bf69003e64 100644
--- a/django_dynamic_fixture/tests/test_ddf_signals.py
+++ b/django_dynamic_fixture/tests/test_ddf_signals.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from django.test import TestCase
 import pytest
 
diff --git a/django_dynamic_fixture/tests/test_ddf_teaching_and_lessons.py b/django_dynamic_fixture/tests/test_ddf_teaching_and_lessons.py
index 4d91c6b812b0c5bed268f45e2cff34c23f0ed635..d15639bdb00de04171288dc6983956b0e524fc81 100644
--- a/django_dynamic_fixture/tests/test_ddf_teaching_and_lessons.py
+++ b/django_dynamic_fixture/tests/test_ddf_teaching_and_lessons.py
@@ -1,4 +1,5 @@
-# -*- coding: utf-8 -*-
+import re
+
 from django.test import TestCase
 import pytest
 
@@ -37,6 +38,16 @@ class TeachAndLessonsTest(DDFTestCase):
         with pytest.raises(InvalidConfigurationError):
             self.ddf.teach(ModelForLibrary, integer_unique=1000)
 
+    def test_it_allows_to_use_masks_as_lessons_for_unique_integer_fields(self):
+        self.ddf.teach(ModelForLibrary, integer_unique=Mask('1###'))
+        instance = self.ddf.get(ModelForLibrary)
+        assert 1000 <= int(instance.integer_unique) <= 1999
+
+    def test_it_allows_to_use_masks_as_lessons_for_unique_char_fields(self):
+        self.ddf.teach(ModelWithUniqueCharField, text_unique=Mask('---- ### __'))
+        instance = self.ddf.get(ModelWithUniqueCharField)
+        assert re.match(r'[A-Z]{4} [0-9]{3} [a-z]{2}', instance.text_unique)
+
     def test_it_must_accept_dynamic_values_for_fields_with_unicity(self):
         self.ddf.teach(ModelForLibrary, integer_unique=lambda field: 1000)
 
diff --git a/django_dynamic_fixture/tests/test_decorators.py b/django_dynamic_fixture/tests/test_decorators.py
index 5c6a4a8eb60f8b7544495424a292777c07dac202..1086708e436bbf581b927433f6e25a47a4034b07 100644
--- a/django_dynamic_fixture/tests/test_decorators.py
+++ b/django_dynamic_fixture/tests/test_decorators.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from unittest import TestCase
 
 from django.conf import settings
@@ -21,7 +20,7 @@ class SkipForDatabaseTest(TestCase):
     def test_annotated_method_only_for_postgres(self):
         decorators.DATABASE_ENGINE = decorators.SQLITE3
         self.method_postgres()
-        assert self.it_was_executed == False
+        assert self.it_was_executed is False
 
         decorators.DATABASE_ENGINE = decorators.POSTGRES
         self.method_postgres()
@@ -43,7 +42,7 @@ class OnlyForDatabaseTest(TestCase):
     def test_annotated_method_skip_for_sqlite3(self):
         decorators.DATABASE_ENGINE = decorators.SQLITE3
         self.method_sqlite3()
-        assert self.it_was_executed == False
+        assert self.it_was_executed is False
 
         decorators.DATABASE_ENGINE = decorators.POSTGRES
         self.method_sqlite3()
diff --git a/django_dynamic_fixture/tests/test_django_helper.py b/django_dynamic_fixture/tests/test_django_helper.py
index 672959135d326929654f7601bea70fd90128de3e..316ef22a25e03c2d2006a5855b6d4a9f2b2efed3 100644
--- a/django_dynamic_fixture/tests/test_django_helper.py
+++ b/django_dynamic_fixture/tests/test_django_helper.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 
 from django.test import TestCase
 from django.db import models
diff --git a/django_dynamic_fixture/tests/test_fdf.py b/django_dynamic_fixture/tests/test_fdf.py
index 1dd853a42f67162809868495bdd8767bc2f08e2d..c01bd1ea1c8ffce3edca4b90155bb68ef4afba51 100644
--- a/django_dynamic_fixture/tests/test_fdf.py
+++ b/django_dynamic_fixture/tests/test_fdf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from django_dynamic_fixture.fdf import *
 from django.core.files import File
 
@@ -183,7 +182,7 @@ class FileSystemDjangoTestCaseTearDownTest(FileSystemDjangoTestCase):
 class FileSystemDjangoTestCaseTearDownFrameworkConfigurationTest(FileSystemDjangoTestCase):
 
     def tearDown(self):
-        super(FileSystemDjangoTestCaseTearDownFrameworkConfigurationTest, self).tearDown()
+        super().tearDown()
 
         self.assertFileDoesNotExists(self.filepath1)
         self.assertFileDoesNotExists(self.filepath2)
diff --git a/django_dynamic_fixture/tests/test_global_settings.py b/django_dynamic_fixture/tests/test_global_settings.py
index 45fc75cd6a51c4d217192357b10faf643e9d3fae..82c49097a757bbc089e0d78b69aa12af25bf5052 100644
--- a/django_dynamic_fixture/tests/test_global_settings.py
+++ b/django_dynamic_fixture/tests/test_global_settings.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+import importlib
 
 from django import conf
 
@@ -9,7 +9,7 @@ from django_dynamic_fixture import global_settings
 from django_dynamic_fixture.fixture_algorithms.sequential_fixture import SequentialDataFixture, \
     StaticSequentialDataFixture
 from django_dynamic_fixture.fixture_algorithms.random_fixture import RandomDataFixture
-from six.moves import reload_module
+
 
 
 class AbstractGlobalSettingsTestCase(TestCase):
@@ -19,99 +19,99 @@ class AbstractGlobalSettingsTestCase(TestCase):
         if hasattr(conf.settings, 'DDF_IGNORE_FIELDS'): del conf.settings.DDF_IGNORE_FIELDS
         if hasattr(conf.settings, 'DDF_NUMBER_OF_LAPS'): del conf.settings.DDF_NUMBER_OF_LAPS
         if hasattr(conf.settings, 'DDF_VALIDATE_MODELS'): del conf.settings.DDF_VALIDATE_MODELS
-        reload_module(conf)
+        importlib.reload(conf)
 
 
-class CustomDataFixture(object): pass
+class CustomDataFixture: pass
 
 class DDF_DEFAULT_DATA_FIXTURE_TestCase(AbstractGlobalSettingsTestCase):
     def test_not_configured_must_load_default_value(self):
-        reload_module(global_settings)
+        importlib.reload(global_settings)
         assert SequentialDataFixture == type(global_settings.DDF_DEFAULT_DATA_FIXTURE)
 
     def test_may_be_an_internal_data_fixture_nick_name(self):
         conf.settings.DDF_DEFAULT_DATA_FIXTURE = 'sequential'
-        reload_module(global_settings)
+        importlib.reload(global_settings)
         assert SequentialDataFixture == type(global_settings.DDF_DEFAULT_DATA_FIXTURE)
 
         conf.settings.DDF_DEFAULT_DATA_FIXTURE = 'random'
-        reload_module(global_settings)
+        importlib.reload(global_settings)
         assert RandomDataFixture == type(global_settings.DDF_DEFAULT_DATA_FIXTURE)
 
         conf.settings.DDF_DEFAULT_DATA_FIXTURE = 'static_sequential'
-        reload_module(global_settings)
+        importlib.reload(global_settings)
         assert StaticSequentialDataFixture == type(global_settings.DDF_DEFAULT_DATA_FIXTURE)
 
     def test_may_be_a_path_to_a_custom_data_fixture(self):
         conf.settings.DDF_DEFAULT_DATA_FIXTURE = 'django_dynamic_fixture.tests.test_global_settings.CustomDataFixture'
-        reload_module(global_settings)
+        importlib.reload(global_settings)
         assert CustomDataFixture == type(global_settings.DDF_DEFAULT_DATA_FIXTURE)
 
     def test_if_path_can_not_be_found_it_will_raise_an_exception(self):
         conf.settings.DDF_DEFAULT_DATA_FIXTURE = 'unknown_path.CustomDataFixture'
         with pytest.raises(Exception):
-            reload_module(global_settings)
+            importlib.reload(global_settings)
 
 
 class DDF_FILL_NULLABLE_FIELDS_TestCase(AbstractGlobalSettingsTestCase):
     def test_not_configured_must_load_default_value(self):
-        reload_module(global_settings)
-        assert global_settings.DDF_FILL_NULLABLE_FIELDS == False
+        importlib.reload(global_settings)
+        assert global_settings.DDF_FILL_NULLABLE_FIELDS is False
 
     def test_must_be_a_boolean(self):
         conf.settings.DDF_FILL_NULLABLE_FIELDS = True
-        reload_module(global_settings)
+        importlib.reload(global_settings)
         assert global_settings.DDF_FILL_NULLABLE_FIELDS
 
     def test_must_raise_an_exception_if_it_is_not_a_boolean(self):
         conf.settings.DDF_FILL_NULLABLE_FIELDS = 'x'
         with pytest.raises(Exception):
-            reload_module(global_settings)
+            importlib.reload(global_settings)
 
 
 class DDF_IGNORE_FIELDS_TestCase(AbstractGlobalSettingsTestCase):
     def test_not_configured_must_load_default_value(self):
-        reload_module(global_settings)
+        importlib.reload(global_settings)
         assert global_settings.DDF_IGNORE_FIELDS == []
 
     def test_must_be_a_list_of_strings(self):
         conf.settings.DDF_IGNORE_FIELDS = ['x']
-        reload_module(global_settings)
+        importlib.reload(global_settings)
         assert global_settings.DDF_IGNORE_FIELDS == ['x']
 
     def test_must_raise_an_exception_if_it_is_not_an_list_of_strings(self):
         conf.settings.DDF_IGNORE_FIELDS = None
         with pytest.raises(Exception):
-            reload_module(global_settings)
+            importlib.reload(global_settings)
 
 
 class DDF_FK_MIN_DEPTH_TestCase(AbstractGlobalSettingsTestCase):
     def test_not_configured_must_load_default_value(self):
-        reload_module(global_settings)
+        importlib.reload(global_settings)
         assert global_settings.DDF_FK_MIN_DEPTH == 0
 
     def test_must_be_an_integer(self):
         conf.settings.DDF_FK_MIN_DEPTH = 2
-        reload_module(global_settings)
+        importlib.reload(global_settings)
         assert global_settings.DDF_FK_MIN_DEPTH == 2
 
     def test_must_raise_an_exception_if_it_is_not_an_integer(self):
         conf.settings.DDF_FK_MIN_DEPTH = None
         with pytest.raises(Exception):
-            reload_module(global_settings)
+            importlib.reload(global_settings)
 
 
 class DDF_VALIDATE_MODELS_TestCase(AbstractGlobalSettingsTestCase):
     def test_not_configured_must_load_default_value(self):
-        reload_module(global_settings)
-        assert global_settings.DDF_VALIDATE_MODELS == False
+        importlib.reload(global_settings)
+        assert global_settings.DDF_VALIDATE_MODELS is False
 
     def test_must_be_a_boolean(self):
         conf.settings.DDF_VALIDATE_MODELS = False
-        reload_module(global_settings)
-        assert global_settings.DDF_VALIDATE_MODELS == False
+        importlib.reload(global_settings)
+        assert global_settings.DDF_VALIDATE_MODELS is False
 
     def test_must_raise_an_exception_if_it_is_not_a_boolean(self):
         conf.settings.DDF_VALIDATE_MODELS = 'x'
         with pytest.raises(Exception):
-            reload_module(global_settings)
+            importlib.reload(global_settings)
diff --git a/django_dynamic_fixture/tests/test_mask.py b/django_dynamic_fixture/tests/test_mask.py
index 924f58b23ad9930ff0073e91cae10108f189886d..c5262d679ddf98eea0b8c09bc259852e2805da63 100644
--- a/django_dynamic_fixture/tests/test_mask.py
+++ b/django_dynamic_fixture/tests/test_mask.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import re
 
 from django.test import TestCase
diff --git a/django_dynamic_fixture/tests/test_module_ddf_shortcut.py b/django_dynamic_fixture/tests/test_module_ddf_shortcut.py
index e5f32062a6e583ce28fb0a9caa9c165b121f79ca..135f44f3aa1fa96bd3a9fc1fab5e24ef78347e8c 100644
--- a/django_dynamic_fixture/tests/test_module_ddf_shortcut.py
+++ b/django_dynamic_fixture/tests/test_module_ddf_shortcut.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 
 try:
     from ddf import N, G, F, C, P, PRE_SAVE, POST_SAVE
diff --git a/django_dynamic_fixture/tests/test_sample_app.py b/django_dynamic_fixture/tests/test_sample_app.py
index 659be10bbe24c2f3448168a42ee203aac81ef446..9fef95be42c068280dd14cd86bbd491ab3320b62 100644
--- a/django_dynamic_fixture/tests/test_sample_app.py
+++ b/django_dynamic_fixture/tests/test_sample_app.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import pytest
 
 from django.test import TestCase
diff --git a/django_dynamic_fixture/tests/test_wrappers.py b/django_dynamic_fixture/tests/test_wrappers.py
index c3c66899ed0d26ad10fdcfd4543831c84433515c..b0a7c68aad5fa21edff3b6db51090b152845d845 100644
--- a/django_dynamic_fixture/tests/test_wrappers.py
+++ b/django_dynamic_fixture/tests/test_wrappers.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 
 from django.test import TransactionTestCase as TestCase
 
diff --git a/docs/source/_static/coverage.html b/docs/source/_static/coverage.html
new file mode 100644
index 0000000000000000000000000000000000000000..883dfc7cf574ae42416576e3f33e1a4a46987a55
--- /dev/null
+++ b/docs/source/_static/coverage.html
@@ -0,0 +1,389 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>Coverage report</title>
+    <link rel="icon" sizes="32x32" href="favicon_32.png">
+    <link rel="stylesheet" href="style.css" type="text/css">
+    <script type="text/javascript" src="coverage_html.js" defer></script>
+</head>
+<body class="indexfile">
+<header>
+    <div class="content">
+        <h1>Coverage report:
+            <span class="pc_cov">92%</span>
+        </h1>
+        <aside id="help_panel_wrapper">
+            <input id="help_panel_state" type="checkbox">
+            <label for="help_panel_state">
+                <img id="keyboard_icon" src="keybd_closed.png" alt="Show/hide keyboard shortcuts" />
+            </label>
+            <div id="help_panel">
+                <p class="legend">Shortcuts on this page</p>
+                <div class="keyhelp">
+                    <p>
+                        <kbd>n</kbd>
+                        <kbd>s</kbd>
+                        <kbd>m</kbd>
+                        <kbd>x</kbd>
+                        <kbd>c</kbd>
+                        &nbsp; change column sorting
+                    </p>
+                    <p>
+                        <kbd>[</kbd>
+                        <kbd>]</kbd>
+                        &nbsp; prev/next file
+                    </p>
+                    <p>
+                        <kbd>?</kbd> &nbsp; show/hide this help
+                    </p>
+                </div>
+            </div>
+        </aside>
+        <form id="filter_container">
+            <input id="filter" type="text" value="" placeholder="filter..." />
+        </form>
+        <p class="text">
+            <a class="nav" href="https://coverage.readthedocs.io/en/7.3.0">coverage.py v7.3.0</a>,
+            created at 2023-08-26 20:11 -0500
+        </p>
+    </div>
+</header>
+<main id="index">
+    <table class="index" data-sortable>
+        <thead>
+            <tr class="tablehead" title="Click to sort">
+                <th class="name left" aria-sort="none" data-shortcut="n">Module</th>
+                <th aria-sort="none" data-default-sort-order="descending" data-shortcut="s">statements</th>
+                <th aria-sort="none" data-default-sort-order="descending" data-shortcut="m">missing</th>
+                <th aria-sort="none" data-default-sort-order="descending" data-shortcut="x">excluded</th>
+                <th class="right" aria-sort="none" data-shortcut="c">coverage</th>
+            </tr>
+        </thead>
+        <tbody>
+            <tr class="file">
+                <td class="name left"><a href="d_194004471fb5367f___init___py.html">django_dynamic_fixture/__init__.py</a></td>
+                <td>91</td>
+                <td>2</td>
+                <td>0</td>
+                <td class="right" data-ratio="89 91">98%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_194004471fb5367f_ddf_py.html">django_dynamic_fixture/ddf.py</a></td>
+                <td>399</td>
+                <td>36</td>
+                <td>9</td>
+                <td class="right" data-ratio="363 399">91%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_194004471fb5367f_decorators_py.html">django_dynamic_fixture/decorators.py</a></td>
+                <td>21</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="21 21">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_194004471fb5367f_django_helper_py.html">django_dynamic_fixture/django_helper.py</a></td>
+                <td>133</td>
+                <td>16</td>
+                <td>0</td>
+                <td class="right" data-ratio="117 133">88%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_194004471fb5367f_fdf_py.html">django_dynamic_fixture/fdf.py</a></td>
+                <td>128</td>
+                <td>13</td>
+                <td>2</td>
+                <td class="right" data-ratio="115 128">90%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_194004471fb5367f_fields_py.html">django_dynamic_fixture/fields.py</a></td>
+                <td>34</td>
+                <td>13</td>
+                <td>5</td>
+                <td class="right" data-ratio="21 34">62%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_745a1b59d8af1d7f___init___py.html">django_dynamic_fixture/fixture_algorithms/__init__.py</a></td>
+                <td>12</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="12 12">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_745a1b59d8af1d7f_default_fixture_py.html">django_dynamic_fixture/fixture_algorithms/default_fixture.py</a></td>
+                <td>59</td>
+                <td>22</td>
+                <td>0</td>
+                <td class="right" data-ratio="37 59">63%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_745a1b59d8af1d7f_random_fixture_py.html">django_dynamic_fixture/fixture_algorithms/random_fixture.py</a></td>
+                <td>77</td>
+                <td>6</td>
+                <td>0</td>
+                <td class="right" data-ratio="71 77">92%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_745a1b59d8af1d7f_sequential_fixture_py.html">django_dynamic_fixture/fixture_algorithms/sequential_fixture.py</a></td>
+                <td>105</td>
+                <td>7</td>
+                <td>6</td>
+                <td class="right" data-ratio="98 105">93%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_cae8023b4e3b30dc___init___py.html">django_dynamic_fixture/fixture_algorithms/tests/__init__.py</a></td>
+                <td>0</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="0 0">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_cae8023b4e3b30dc_abstract_test_generic_fixture_py.html">django_dynamic_fixture/fixture_algorithms/tests/abstract_test_generic_fixture.py</a></td>
+                <td>47</td>
+                <td>2</td>
+                <td>0</td>
+                <td class="right" data-ratio="45 47">96%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_cae8023b4e3b30dc_test_default_fixture_py.html">django_dynamic_fixture/fixture_algorithms/tests/test_default_fixture.py</a></td>
+                <td>39</td>
+                <td>23</td>
+                <td>0</td>
+                <td class="right" data-ratio="16 39">41%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_cae8023b4e3b30dc_test_default_fixture_postgres_py.html">django_dynamic_fixture/fixture_algorithms/tests/test_default_fixture_postgres.py</a></td>
+                <td>49</td>
+                <td>2</td>
+                <td>0</td>
+                <td class="right" data-ratio="47 49">96%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_cae8023b4e3b30dc_test_random_fixture_py.html">django_dynamic_fixture/fixture_algorithms/tests/test_random_fixture.py</a></td>
+                <td>6</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="6 6">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_cae8023b4e3b30dc_test_sequential_fixture_py.html">django_dynamic_fixture/fixture_algorithms/tests/test_sequential_fixture.py</a></td>
+                <td>32</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="32 32">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_cae8023b4e3b30dc_test_unique_random_fixture_py.html">django_dynamic_fixture/fixture_algorithms/tests/test_unique_random_fixture.py</a></td>
+                <td>39</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="39 39">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_745a1b59d8af1d7f_unique_random_fixture_py.html">django_dynamic_fixture/fixture_algorithms/unique_random_fixture.py</a></td>
+                <td>116</td>
+                <td>13</td>
+                <td>3</td>
+                <td class="right" data-ratio="103 116">89%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_194004471fb5367f_global_settings_py.html">django_dynamic_fixture/global_settings.py</a></td>
+                <td>52</td>
+                <td>5</td>
+                <td>0</td>
+                <td class="right" data-ratio="47 52">90%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_194004471fb5367f_models_py.html">django_dynamic_fixture/models.py</a></td>
+                <td>4</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="4 4">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_194004471fb5367f_models_sample_app_py.html">django_dynamic_fixture/models_sample_app.py</a></td>
+                <td>25</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="25 25">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_194004471fb5367f_models_test_py.html">django_dynamic_fixture/models_test.py</a></td>
+                <td>286</td>
+                <td>13</td>
+                <td>0</td>
+                <td class="right" data-ratio="273 286">95%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_194004471fb5367f_models_third_party_py.html">django_dynamic_fixture/models_third_party.py</a></td>
+                <td>43</td>
+                <td>14</td>
+                <td>0</td>
+                <td class="right" data-ratio="29 43">67%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_194004471fb5367f_script_ddf_checkings_py.html">django_dynamic_fixture/script_ddf_checkings.py</a></td>
+                <td>60</td>
+                <td>20</td>
+                <td>0</td>
+                <td class="right" data-ratio="40 60">67%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803___init___py.html">django_dynamic_fixture/tests/__init__.py</a></td>
+                <td>0</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="0 0">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_conftest_py.html">django_dynamic_fixture/tests/conftest.py</a></td>
+                <td>13</td>
+                <td>2</td>
+                <td>0</td>
+                <td class="right" data-ratio="11 13">85%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_ddf_py.html">django_dynamic_fixture/tests/test_ddf.py</a></td>
+                <td>393</td>
+                <td>8</td>
+                <td>0</td>
+                <td class="right" data-ratio="385 393">98%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_ddf_checkings_py.html">django_dynamic_fixture/tests/test_ddf_checkings.py</a></td>
+                <td>22</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="22 22">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_ddf_copier_py.html">django_dynamic_fixture/tests/test_ddf_copier.py</a></td>
+                <td>52</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="52 52">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_ddf_custom_fields_py.html">django_dynamic_fixture/tests/test_ddf_custom_fields.py</a></td>
+                <td>95</td>
+                <td>25</td>
+                <td>0</td>
+                <td class="right" data-ratio="70 95">74%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_ddf_custom_models_py.html">django_dynamic_fixture/tests/test_ddf_custom_models.py</a></td>
+                <td>19</td>
+                <td>2</td>
+                <td>0</td>
+                <td class="right" data-ratio="17 19">89%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_ddf_geo_py.html">django_dynamic_fixture/tests/test_ddf_geo.py</a></td>
+                <td>31</td>
+                <td>12</td>
+                <td>0</td>
+                <td class="right" data-ratio="19 31">61%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_ddf_signals_py.html">django_dynamic_fixture/tests/test_ddf_signals.py</a></td>
+                <td>76</td>
+                <td>4</td>
+                <td>0</td>
+                <td class="right" data-ratio="72 76">95%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_ddf_teaching_and_lessons_py.html">django_dynamic_fixture/tests/test_ddf_teaching_and_lessons.py</a></td>
+                <td>143</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="143 143">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_decorators_py.html">django_dynamic_fixture/tests/test_decorators.py</a></td>
+                <td>33</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="33 33">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_django_helper_py.html">django_dynamic_fixture/tests/test_django_helper.py</a></td>
+                <td>159</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="159 159">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_fdf_py.html">django_dynamic_fixture/tests/test_fdf.py</a></td>
+                <td>134</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="134 134">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_global_settings_py.html">django_dynamic_fixture/tests/test_global_settings.py</a></td>
+                <td>86</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="86 86">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_mask_py.html">django_dynamic_fixture/tests/test_mask.py</a></td>
+                <td>31</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="31 31">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_module_ddf_shortcut_py.html">django_dynamic_fixture/tests/test_module_ddf_shortcut.py</a></td>
+                <td>7</td>
+                <td>2</td>
+                <td>0</td>
+                <td class="right" data-ratio="5 7">71%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_sample_app_py.html">django_dynamic_fixture/tests/test_sample_app.py</a></td>
+                <td>46</td>
+                <td>0</td>
+                <td>0</td>
+                <td class="right" data-ratio="46 46">100%</td>
+            </tr>
+            <tr class="file">
+                <td class="name left"><a href="d_2810ef72909b6803_test_wrappers_py.html">django_dynamic_fixture/tests/test_wrappers.py</a></td>
+                <td>203</td>
+                <td>4</td>
+                <td>0</td>
+                <td class="right" data-ratio="199 203">98%</td>
+            </tr>
+        </tbody>
+        <tfoot>
+            <tr class="total">
+                <td class="name left">Total</td>
+                <td>3400</td>
+                <td>266</td>
+                <td>25</td>
+                <td class="right" data-ratio="3134 3400">92%</td>
+            </tr>
+        </tfoot>
+    </table>
+    <p id="no_rows">
+        No items found using the specified filter.
+    </p>
+</main>
+<footer>
+    <div class="content">
+        <p>
+            <a class="nav" href="https://coverage.readthedocs.io/en/7.3.0">coverage.py v7.3.0</a>,
+            created at 2023-08-26 20:11 -0500
+        </p>
+    </div>
+    <aside class="hidden">
+        <a id="prevFileLink" class="nav" href="d_2810ef72909b6803_test_wrappers_py.html"/>
+        <a id="nextFileLink" class="nav" href="d_194004471fb5367f___init___py.html"/>
+        <button type="button" class="button_prev_file" data-shortcut="["/>
+        <button type="button" class="button_next_file" data-shortcut="]"/>
+        <button type="button" class="button_show_hide_help" data-shortcut="?"/>
+    </aside>
+</footer>
+</body>
+</html>
diff --git a/docs/source/about.rst b/docs/source/about.rst
index b62cce19f63a6afd3beafeba82e0474b6cac025f..a04ab5f2a75de4706c47697e4b9abc62c29fcaa6 100644
--- a/docs/source/about.rst
+++ b/docs/source/about.rst
@@ -45,7 +45,7 @@ TODO list
 Tests and Bugfixes
 -------------------------------------------------------------------------------
 * with_queries bugfixes (always print 0 queries)
-* Deal with relatioships with dynamic related_name
+* Deal with relationships with dynamic related_name
 * bugfix in fdf or ddf: some files/directories are not deleted
 * tests with files in ddf
 * tests with proxy models
diff --git a/docs/source/change_log.rst b/docs/source/change_log.rst
index ce20afe0f41c81ede6f32ff089c27f18b3ad564b..ab9a617f61f9921e76767e3006ba35eafeaf0c65 100644
--- a/docs/source/change_log.rst
+++ b/docs/source/change_log.rst
@@ -8,6 +8,21 @@ Change Log
 
 Date format: yyyy/mm/dd
 
+Version 4.0.0 - 2023/08/26
+-------------------------------------------------------------------------------
+  * <http://pypi.python.org/pypi/django-dynamic-fixture/4.0.0>
+  * Removed compatibilty of Python 2
+  * Removed compatibilty of Django 3 or lower
+  * Removed Nose plugins
+  * Removed Coveralls integration
+
+Version 3.1.3 - 2023/08/18
+-------------------------------------------------------------------------------
+  * <http://pypi.python.org/pypi/django-dynamic-fixture/3.1.3>
+  * Removed deprecation warnings: PR #150
+  * Fixed doc typos: PR #151
+  * Allow Mask objects as lessons for unique fields: PR #153
+
 Version 3.1.2 - 2021/10/01
 -------------------------------------------------------------------------------
   * <http://pypi.python.org/pypi/django-dynamic-fixture/3.1.2>
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 7b21fd4262cf6cad56dd555651749820e0ba6380..c6bb3f905b602da8b960dff197f1adadf7d4eea3 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 #
 # DDF documentation build configuration file, created by
 # sphinx-quickstart on Mon Sep  1 18:20:45 2014.
@@ -45,17 +44,17 @@ source_suffix = '.rst'
 master_doc = 'index'
 
 # General information about the project.
-project = u'DDF'
-copyright = u'2014, Paulo Cheque'
+project = 'DDF'
+copyright = '2014, Paulo Cheque'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
 # The short X.Y version.
-version = '3.1.2'
+version = '4.0.0'
 # The full version, including alpha/beta/rc tags.
-release = '3.1.2'
+release = '4.0.0'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -205,8 +204,8 @@ latex_elements = {
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
-  ('index', 'DDF.tex', u'DDF Documentation',
-   u'Paulo Cheque', 'manual'),
+  ('index', 'DDF.tex', 'DDF Documentation',
+   'Paulo Cheque', 'manual'),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
@@ -235,8 +234,8 @@ latex_documents = [
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
 man_pages = [
-    ('index', 'ddf', u'DDF Documentation',
-     [u'Paulo Cheque'], 1)
+    ('index', 'ddf', 'DDF Documentation',
+     ['Paulo Cheque'], 1)
 ]
 
 # If true, show URL addresses after external links.
@@ -249,8 +248,8 @@ man_pages = [
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-  ('index', 'DDF', u'DDF Documentation',
-   u'Paulo Cheque', 'DDF', 'One line description of project.',
+  ('index', 'DDF', 'DDF Documentation',
+   'Paulo Cheque', 'DDF', 'One line description of project.',
    'Miscellaneous'),
 ]
 
diff --git a/docs/source/ddf.rst b/docs/source/ddf.rst
index bdbaa357ff7264728c86c3b695838740b263a524..ca44408215a75724bec1827d47eb63c98f6b9d06 100644
--- a/docs/source/ddf.rst
+++ b/docs/source/ddf.rst
@@ -21,7 +21,7 @@ It receives a model class and it will return a **valid and persisted instance**
     assert len(author.name) > 0
 
 
-This facilitates writing tests and it hides all dummy data that polutes the source code. But all **important data of the test may be explicitily defined**. This is even a good practice, because it let the test very clear and cohesive::
+This facilitates writing tests and it hides all dummy data that pollutes the source code. But all **important data of the test may be explicitly defined**. This is even a good practice, because it let the test very clear and cohesive::
 
 
     book = G(Book, name='The Lord of the Rings', publish_date=date(1954, 07, 29))
@@ -171,7 +171,7 @@ Teaching DDF with Lessons (shelve in 2.1.0) (New in 3.0.0)
 
 Sometimes DDF can not generate a valid and persisted instance because it contains custom fields or custom validations (field or model validation). In these cases, it is possible to **teach DDF how to build a valid instance**. It is necessary to create a valid configuration and save it in an internal and global DDF library of configurations. All future instances of that model will use the saved lesson as base.
 
-In the **PyTest** **conftest.py** file, the **DDF-Nose plugin** **your_app.tests.ddf_setup.py** file or another global module that will be loaded before the test suite::
+In the **PyTest** **conftest.py** file or another global module that will be loaded before the test suite::
 
     from ddf import teach
     teach(Author, name='Eistein')
diff --git a/docs/source/index.rst b/docs/source/index.rst
index ae95c87eda5735bc667316a2ced577e57283fc5b..af93208088bcad7834165a7cd8ec8d913506ec7f 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -8,9 +8,9 @@ Welcome to DDF's documentation!
 
 Django Dynamic Fixture (DDF) is a complete and simple library to create **dynamic model instances** for **testing purposes**.
 
-It lets you **focus on your tests**, instead of focusing on generating some dummy data which is boring and polutes the test source code.
+It lets you **focus on your tests**, instead of focusing on generating some dummy data which is boring and pollutes the test source code.
 
-It exists to solve the **anti-pattern** of Static Fixtures and Factory objects. Are you tired to mantain dozens of yml/json files and factory objects?
+It exists to solve the **anti-pattern** of Static Fixtures and Factory objects. Are you tired to maintain dozens of yml/json files and factory objects?
 
 
 .. toctree::
@@ -23,7 +23,6 @@ It exists to solve the **anti-pattern** of Static Fixtures and Factory objects.
    data_fixtures
    more
    patterns
-   nose_plugins
    fdf
    about
    change_log
diff --git a/docs/source/more.rst b/docs/source/more.rst
index af55e55899865f21f8bdbabe21638571ffb27bdc..426bac754e396a1fdd1b59665a0814a65cce749f 100644
--- a/docs/source/more.rst
+++ b/docs/source/more.rst
@@ -82,7 +82,6 @@ List of Exceptions
 * *BadDataError*: The data passed to a field has some problem (not unique or invalid) or a required attribute is in ignore list.
 * *InvalidCopierExpressionError*: The specified expression used in a Copier is invalid.
 * *InvalidModelError*: Invalid Model: The class is not a model or it is abstract.
-* *InvalidDDFSetupError*: ddf_setup.py has execution errors
 
 
 Decorators (New in 1.4.0)
@@ -123,7 +122,7 @@ In the test file::
 Signals PRE_SAVE and POST_SAVE:
 ===============================================================================
 
-In very special cases a signal may facilitate implementing tests with DDF, but Django signals may not be satisfatory for testing pourposes because the developer does not have control of the execution order of the receivers. For this reason, DDF provides its own signals. It is possible to have only one receiver for each model, to avoid anti-patterns::
+In very special cases a signal may facilitate implementing tests with DDF, but Django signals may not be satisfactory for testing purposes because the developer does not have control of the execution order of the receivers. For this reason, DDF provides its own signals. It is possible to have only one receiver for each model, to avoid anti-patterns::
 
     from django_dynamic_fixture import PRE_SAVE, POST_SAVE
     def callback_function(instance):
diff --git a/docs/source/nose_plugins.rst b/docs/source/nose_plugins.rst
deleted file mode 100644
index 3428c478bfa8ad596e725d4d72b63bbfcd243b3f..0000000000000000000000000000000000000000
--- a/docs/source/nose_plugins.rst
+++ /dev/null
@@ -1,28 +0,0 @@
-.. _nose_plugins:
-
-Nose plugins
-*******************************************************************************
-
-.. contents::
-   :local:
-
-DDF Setup Nose Plugin (New in 1.6.0)
-===============================================================================
-
-This plugin create a "setup suite". It will load the file *ddf_setup.py* in the root of your Django project before to execute the first test.
-The file *ddf_setup.py* may contain default and custom lessons configurations or pythonic initial data. Django just load initial data from YAML, JSON, XML etc::
-
-    python manage.py test --with-ddf-setup
-
-
-Queries Nose Plugin (New in 1.4.0)
-===============================================================================
-
-This plugin print how many queries are executed by a test. It may be useful to determine performance issues of system features::
-
-    python manage.py test --with-queries
-
-The following command print how many queries are executed when a model is saved. It may be useful to determine performance issues in overriding *save* methods and in extensive use of listeners *pre_save* and *post_save*::
-
-    python manage.py count_queries_on_save
-
diff --git a/docs/source/overview.rst b/docs/source/overview.rst
index 44257dc458350f276b46e04d001354f502669e98..c6de629ecef83c1c2d27a6c3160dc8de7016d5d1 100644
--- a/docs/source/overview.rst
+++ b/docs/source/overview.rst
@@ -100,22 +100,32 @@ Support and Compatibility
 +---------------------------------------------------------+
 | DDF current support                                     |
 +================+=================+======================+
-| Python 3.8     | Django 3.x.x    | DDF 3.*.* - Jan 2020 |
+| Python 3.11    | Django 4.x.x    | DDF 4.*.* - Aug 2023 |
 +----------------+-----------------+----------------------+
-| Python 3.7     | Django 3.x.x    | DDF 3.*.* - Jan 2020 |
+| Python 3.10    | Django 4.x.x    | DDF 4.*.* - Aug 2023 |
 +----------------+-----------------+----------------------+
-| Python 3.6     | Django 3.x.x    | DDF 3.*.* - Jan 2020 |
+| Python 3.9     | Django 4.x.x    | DDF 4.*.* - Aug 2023 |
 +----------------+-----------------+----------------------+
-| Python > 3.5   | Django 2.x.x    | DDF 3.*.* - Jan 2020 |
-+----------------+-----------------+----------------------+
-| Python 2.7     | Django 1.11.x   | DDF 3.*.* - Jan 2020 |
+| Python 3.8     | Django 4.x.x    | DDF 4.*.* - Aug 2023 |
 +----------------+-----------------+----------------------+
 
+For old versions of Python or Django, use old versions of DDF too:
+
 +---------------------------------------------------------+
 | DDF old support                                         |
 +================+=================+======================+
+| Python 3.8     | Django 3.x.x    | DDF 3.*.* - Jan 2020 |
++----------------+-----------------+----------------------+
+| Python 3.7     | Django 3.x.x    | DDF 3.*.* - Jan 2020 |
++----------------+-----------------+----------------------+
+| Python 3.6     | Django 3.x.x    | DDF 3.*.* - Jan 2020 |
++----------------+-----------------+----------------------+
+| Python > 3.5   | Django 2.x.x    | DDF 3.*.* - Jan 2020 |
++----------------+-----------------+----------------------+
 | Python 3.3     | Django < 1.11.x | DDF 2.0.* - Dec 2017 |
 +----------------+-----------------+----------------------+
+| Python 2.7     | Django 1.11.x   | DDF 3.*.* - Jan 2020 |
++----------------+-----------------+----------------------+
 | Python <= 2.7  | Django < 1.11.x | DDF 2.0.* - Dec 2017 |
 +----------------+-----------------+----------------------+
 
@@ -136,7 +146,7 @@ Motivation
 ===============================================================================
 
   * It is a terrible practice to use **static data** in tests (yml/json/sql files).
-  * It is very hard to mantain lots of **Factory objects**.
+  * It is very hard to maintain lots of **Factory objects**.
   * Creating fixtures 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.
 
@@ -150,7 +160,7 @@ Another thing, the DDF was planned to have a **lean and clean syntax**. We belie
 Also, DDF is flexible, since it is possible to customize the entire data generation or by field.
 
   * Either they are incomplete, or bugged or it produces erratic tests, because they use random and uncontrolled data.
-  * The syntax of others tools is too verbose, which polutes the tests.
+  * The syntax of others tools is too verbose, which pollutes the tests.
   * Complete, lean and practice documentation.
   * It is hard to debug tests with another tools.
   * List of other tools: <https://www.djangopackages.com/grids/g/testing/> or <http://djangopackages.com/grids/g/fixtures>
@@ -159,7 +169,5 @@ Also, DDF is flexible, since it is possible to customize the entire data generat
 Plus:
 
   * **PyTest** compatible
-  * **Nose plugin** that enables a setup for the entire suite (unittest2 includes only setups for class and module)
-  * **Nose plugin** to count how many queries are executed by test
   * **Command** to count how many queries are executed to save any kind of model instance
   * **FileSystemDjangoTestCase** that facilitates to create tests for features that use filesystem.
diff --git a/docs/source/settings.rst b/docs/source/settings.rst
index 1228cb5ce6e7081eaa1feac8cd29e5efb22fb6c1..fe376aea49bd7dbfcd8e6256943e9d1b711551f0 100644
--- a/docs/source/settings.rst
+++ b/docs/source/settings.rst
@@ -38,7 +38,7 @@ You can configure DDF in ``settings.py`` file. You can also override the global
     DDF_FIELD_FIXTURES = {'path.to.your.Field': lambda: random.randint(0, 10) }
 
 
-* **DDF_FK_MIN_DEPTH** (Default = 0):  For models with non required foreign keys (FKs with `null=True`), like FKs to itself (``ForeignKey('self')``), cyclic dependencies or even optional FKs, DDF will avoid infinite loops because it stops creating objects indefinetely, because it will stop after the min depth was achieved.::
+* **DDF_FK_MIN_DEPTH** (Default = 0):  For models with non required foreign keys (FKs with `null=True`), like FKs to itself (``ForeignKey('self')``), cyclic dependencies or even optional FKs, DDF will avoid infinite loops because it stops creating objects indefinitely, because it will stop after the min depth was achieved.::
 
     # You can override the global config for one case:
     G(Model, fk_min_depth=5)
@@ -53,4 +53,4 @@ You can configure DDF in ``settings.py`` file. You can also override the global
     G(Model, debug_mode=False)
 
 
-* **DDF_SHELL_MODE** (Default = False): To disable some DDF warnings so DDF can be used better in Python shell: to populate the DB, for exampel.
+* **DDF_SHELL_MODE** (Default = False): To disable some DDF warnings so DDF can be used better in Python shell: to populate the DB, for example.
diff --git a/queries/__init__.py b/queries/__init__.py
index ed40ac8cf2fe5425bfd604989b062e404fd0809d..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/queries/__init__.py
+++ b/queries/__init__.py
@@ -1,3 +0,0 @@
-from queries.nose_plugin import Queries
-
-__all__ = ['Queries']
diff --git a/queries/count_queries_on_save.py b/queries/count_queries_on_save.py
index f036cbcf3c367bb25760f53f6d1c0186b95b9875..091acbe6fc74fe9e69109aebb483dcdf9c06b924 100644
--- a/queries/count_queries_on_save.py
+++ b/queries/count_queries_on_save.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 
 from django_dynamic_fixture import new
 
@@ -8,7 +7,7 @@ from django import db
 from django_dynamic_fixture.django_helper import get_models_of_an_app, is_model_managed, get_unique_model_name, get_apps
 
 
-class Report(object):
+class Report:
     def __init__(self):
         self.data = []
         self.errors = []
@@ -30,7 +29,7 @@ class Report(object):
             print(err)
 
 
-class CountQueriesOnSave(object):
+class CountQueriesOnSave:
     def __init__(self):
         self.report = Report()
 
diff --git a/queries/management/commands/count_queries_on_save.py b/queries/management/commands/count_queries_on_save.py
index 9249b6720ee634c0e718d1f71705cf39514187b8..b3bd7251d4e75ed13c809660937589ab414a93b7 100644
--- a/queries/management/commands/count_queries_on_save.py
+++ b/queries/management/commands/count_queries_on_save.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 
 from optparse import make_option
 
diff --git a/queries/nose_plugin.py b/queries/nose_plugin.py
deleted file mode 100644
index 8584d734031b266f0764e2b9b3cedec6516b97c1..0000000000000000000000000000000000000000
--- a/queries/nose_plugin.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from nose.plugins import Plugin
-
-
-# http://readthedocs.org/docs/nose/en/latest/plugins/interface.html
-class Queries(Plugin):
-    "python manage.py test --with-queries"
-    name = 'queries'
-    _queries_by_test_methods = []
-
-    def configure(self, options, conf):
-        """
-        Called after the command line has been parsed, with the parsed options and the config container. 
-        Here, implement any config storage or changes to state or operation that are set by command line options.
-        DO NOT return a value from this method unless you want to stop all other plugins from being configured.
-        """
-        super(Queries, self).configure(options, conf)
-        if self.enabled:
-            from django.db import connection
-            connection.use_debug_cursor = True
-
-    def beforeTest(self, test):
-        "Called before the test is run (before startTest)."
-        from django.db import connection
-        self.initial_amount_of_queries = len(connection.queries)
-
-    def afterTest(self, test):
-        "Called after the test has been run and the result recorded (after stopTest)."
-        from django.db import connection
-        self.final_amount_of_queries = len(connection.queries)
-        self._queries_by_test_methods.append((test, self.final_amount_of_queries - self.initial_amount_of_queries))
-
-    def report(self, stream):
-        """Called after all error output has been printed. Print your
-        plugin's report to the provided stream. Return None to allow
-        other plugins to print reports, any other value to stop them.
-
-        :param stream: stream object; send your output here
-        :type stream: file-like object
-        """
-        stream.write('\nREPORT OF AMOUNT OF QUERIES BY TEST:\n')
-        for x in self._queries_by_test_methods:
-            testcase = x[0]
-            queries = x[1]
-            stream.write('\n%s: %s' % (testcase, queries))
-        stream.write('\n')
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 46480abbb87d39d980d0d193bc9b7c7009d8ea3d..66c05b19b7f843ccb69f4bad8ae79e6afb62b7e7 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,3 +1,8 @@
+# Build/Package/Publish
+build
+twine
+
+# Tests and Coverage
 pytest
 pytest-django
 pytest-xdist # Run tests in parallel
@@ -8,12 +13,13 @@ psycopg2-binary
 #psycopg2==2.8.4 # For tests with Postgres fields
 #or psycopg2cffi==2.7.5 # For pypy
 
-coveralls
-python-coveralls
-
+# Code
 flake8
 pyflakes
 pylint
+ruff
+pre-commit
 
+# Docs
 sphinx
 sphinx_rtd_theme
diff --git a/requirements.txt b/requirements.txt
index 853b6286e60afda0fddca0b7fdb7e410bea34000..3e38d0d829043ad2323933096831540fe39f69a2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,11 +1,3 @@
-# Python2/3 compatibility
-six
-
-# For the Nose plugin
-nose>=1.3
-django-nose>=1.4.5
-nose-progressive>=1.5
-
 # Database Drivers
 #mysql-python
 #psycopg2 # not pypy compatible
diff --git a/settings_ddf.py b/settings_ddf.py
index b948a1fc37ffe2c98f4456e77b3640326601c875..4c94a4f0123471173b8b98d65e01f76ab30c0a4a 100644
--- a/settings_ddf.py
+++ b/settings_ddf.py
@@ -15,7 +15,6 @@ INSTALLED_APPS = ()
 
 INSTALLED_APPS += (
     'queries',
-    'django_nose',
     'django_dynamic_fixture',
     'django.contrib.contenttypes',
 )
@@ -26,20 +25,6 @@ except ImportError:
     pass
 
 
-
-TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
-NOSE_PLUGINS = ['queries.Queries', 'ddf_setup.DDFSetup']
-
-# Tell nose to measure coverage on the 'foo' and 'bar' apps
-NOSE_ARGS = [
-    '--with-coverage',
-    '--cover-html',
-    '--cover-package=django_dynamic_fixture',
-    '--cover-tests',
-    '--cover-erase',
-    '--verbosity=1'
-    ]
-
 # EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
 # EMAIL_FILE_PATH = '/tmp/invest-messages'  # change this to a proper location
 EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
@@ -59,4 +44,4 @@ DDF_FIELD_FIXTURES = {
     # https://github.com/bradjasper/django-jsonfield
     'jsonfield.fields.JSONCharField': {'ddf_fixture': lambda: json.dumps({'some random value': 'c'})},
     'jsonfield.fields.JSONField': {'ddf_fixture': lambda: json.dumps([1, 2, 3])},
-}
\ No newline at end of file
+}
diff --git a/setup.py b/setup.py
index 8ab82f4fbb4fbacb045c7ddfb208e1718946d9f7..0e85f6b8625b9783ead24045b250e52bfe98ce28 100644
--- a/setup.py
+++ b/setup.py
@@ -1,27 +1,14 @@
-#from distutils.core import setup
+from pathlib import Path
 from setuptools import setup, find_packages
 
-# http://guide.python-distribute.org/quickstart.html
-# python setup.py sdist
-# Create a .pypirc file in ~ dir (cp .pypirc ~)
-# python setup.py register
-# python setup.py sdist upload
-# pip install django-dynamic-fixture
-# pip install django-dynamic-fixture --upgrade --no-deps
-# Manual upload to PypI
-# http://pypi.python.org/pypi/django-dynamic-fixture
-# Go to 'edit' link
-# Update version and save
-# Go to 'files' link and upload the file
+VERSION = '4.0.0'
 
-VERSION = '3.1.2'
+tests_require = []
 
-tests_require = [
-]
+install_requires = []
 
-install_requires = [
-    'six'
-]
+this_directory = Path(__file__).parent
+long_description = (this_directory / 'README.md').read_text()
 
 setup(name='django-dynamic-fixture',
       url='https://github.com/paulocheque/django-dynamic-fixture',
@@ -29,16 +16,17 @@ setup(name='django-dynamic-fixture',
       author_email='paulocheque@gmail.com',
       keywords='python django testing fixture',
       description='A full library to create dynamic model instances for testing purposes.',
+      long_description_content_type='text/markdown',
+      long_description=long_description,
       license='MIT',
       classifiers=[
           'Framework :: Django',
           'Operating System :: OS Independent',
           'Topic :: Software Development',
-          'Programming Language :: Python :: 2.7',
-          'Programming Language :: Python :: 3.5',
-          'Programming Language :: Python :: 3.6',
-          'Programming Language :: Python :: 3.7',
           'Programming Language :: Python :: 3.8',
+          'Programming Language :: Python :: 3.9',
+          'Programming Language :: Python :: 3.10',
+          'Programming Language :: Python :: 3.11',
           'Programming Language :: Python :: Implementation :: PyPy',
       ],
 
@@ -48,7 +36,6 @@ setup(name='django-dynamic-fixture',
       test_suite='pytest',
       extras_require={'test': tests_require},
 
-      entry_points={ 'nose.plugins': ['queries = queries:Queries', 'ddf_setup = ddf_setup:DDFSetup'] },
       packages=find_packages(),
 )
 
diff --git a/tox.ini b/tox.ini
index e4eb0972fc3c438087670880e7642b1113e70d4b..2a07cf242b83ca7e8528dfcc2b02e5dc495d1250 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,31 +1,27 @@
 [tox]
 envlist =
-    django{111}-py{27},
-    django{20}-py{37},
-    django{30}-py{37},
-    django{30}-py{38},
-    django{31}-py{38},
-    django{32}-py{38},
+    py38-django40
+    py38-django41
+    py38-django42
+    py39-django40
+    py39-django41
+    py39-django42
+    py310-django40
+    py310-django41
+    py310-django42
+    py311-django40
+    py311-django41
+    py311-django42
 
 [testenv]
-passenv = PYTEST_ADDOPTS TERM
-
-setenv =
-    VIRTUAL_ENV={envdir}
-
-basepython=
-    py27: python2.7
-    py36: python3.6
-    py37: python3.7
-    py38: python3.8
-    pypy: pypy
+whitelist_externals = pytest
+allowlist_externals = pytest
 
 deps =
     -r{toxinidir}/requirements.txt
-    django111: django>=1.11,<2.0
-    django20: django>=2.0,<2.1
-    django30: django>=3.0,<3.1
-    django31: django>=3.1,<3.2
-    django32: django>=3.2,<3.3
+    -r{toxinidir}/requirements-dev.txt
+    django40: Django>=4.0,<4.1
+    django41: Django>=4.1,<4.2
+    django42: Django>=4.2,<4.3
 
-commands = env/bin/pytest -n 3 --create-db --reuse-db --no-migrations
+commands = pytest -n 3 --create-db --reuse-db --no-migrations