From ea4f647bb36cebb1c275af401acd66df130dd99d Mon Sep 17 00:00:00 2001
From: Michael Fladischer <FladischerMichael@fladi.at>
Date: Wed, 6 Sep 2023 08:08:08 +0000
Subject: [PATCH] New upstream version 4.0.0

---
 .coveralls.yml                                |   3 -
 .gitignore                                    |  41 ++
 .pylintrc                                     |   2 +-
 .pypirc                                       |   5 -
 .python-version                               |   5 +
 .readthedocs.yaml                             |  10 +
 .travis.yml                                   |  74 ++--
 Makefile                                      |  81 ++--
 README.mkd => README.md                       |   3 +-
 ddf_setup/__init__.py                         |   3 -
 ddf_setup/nose_plugin.py                      |  30 --
 django_dynamic_fixture/__init__.py            |  25 +-
 django_dynamic_fixture/ddf.py                 |  71 ++--
 django_dynamic_fixture/decorators.py          |   1 -
 django_dynamic_fixture/django_helper.py       |  14 +-
 django_dynamic_fixture/fdf.py                 |   7 +-
 .../fixture_algorithms/default_fixture.py     |   8 +-
 .../fixture_algorithms/random_fixture.py      |  15 +-
 .../fixture_algorithms/sequential_fixture.py  |  25 +-
 .../tests/abstract_test_generic_fixture.py    |  30 +-
 .../tests/test_default_fixture.py             |   5 +-
 .../tests/test_default_fixture_postgres.py    |   8 +-
 .../tests/test_random_fixture.py              |   1 -
 .../tests/test_sequential_fixture.py          |   1 -
 .../tests/test_unique_random_fixture.py       |   1 -
 .../unique_random_fixture.py                  |  17 +-
 django_dynamic_fixture/global_settings.py     |  13 +-
 django_dynamic_fixture/models_test.py         |   9 +-
 django_dynamic_fixture/tests/conftest.py      |   3 -
 django_dynamic_fixture/tests/ddf_setup.py     |   9 -
 django_dynamic_fixture/tests/test_ddf.py      |  38 +-
 .../tests/test_ddf_checkings.py               |   1 -
 .../tests/test_ddf_copier.py                  |   1 -
 .../tests/test_ddf_custom_fields.py           |   1 -
 .../tests/test_ddf_custom_models.py           |   1 -
 django_dynamic_fixture/tests/test_ddf_geo.py  |   1 -
 .../tests/test_ddf_setup.py                   |  37 --
 .../tests/test_ddf_signals.py                 |   1 -
 .../tests/test_ddf_teaching_and_lessons.py    |  13 +-
 .../tests/test_decorators.py                  |   5 +-
 .../tests/test_django_helper.py               |   1 -
 django_dynamic_fixture/tests/test_fdf.py      |   3 +-
 .../tests/test_global_settings.py             |  50 +--
 django_dynamic_fixture/tests/test_mask.py     |   1 -
 .../tests/test_module_ddf_shortcut.py         |   1 -
 .../tests/test_sample_app.py                  |   1 -
 django_dynamic_fixture/tests/test_wrappers.py |   1 -
 docs/source/_static/coverage.html             | 389 ++++++++++++++++++
 docs/source/about.rst                         |   2 +-
 docs/source/change_log.rst                    |  15 +
 docs/source/conf.py                           |  21 +-
 docs/source/ddf.rst                           |   4 +-
 docs/source/index.rst                         |   5 +-
 docs/source/more.rst                          |   3 +-
 docs/source/nose_plugins.rst                  |  28 --
 docs/source/overview.rst                      |  28 +-
 docs/source/settings.rst                      |   4 +-
 queries/__init__.py                           |   3 -
 queries/count_queries_on_save.py              |   5 +-
 .../commands/count_queries_on_save.py         |   1 -
 queries/nose_plugin.py                        |  47 ---
 requirements-dev.txt                          |  12 +-
 requirements.txt                              |   8 -
 settings_ddf.py                               |  17 +-
 setup.py                                      |  35 +-
 tox.ini                                       |  42 +-
 66 files changed, 771 insertions(+), 575 deletions(-)
 delete mode 100644 .coveralls.yml
 create mode 100644 .gitignore
 delete mode 100644 .pypirc
 create mode 100644 .python-version
 create mode 100644 .readthedocs.yaml
 rename README.mkd => README.md (94%)
 delete mode 100644 ddf_setup/__init__.py
 delete mode 100644 ddf_setup/nose_plugin.py
 delete mode 100644 django_dynamic_fixture/tests/ddf_setup.py
 delete mode 100644 django_dynamic_fixture/tests/test_ddf_setup.py
 create mode 100644 docs/source/_static/coverage.html
 delete mode 100644 docs/source/nose_plugins.rst
 delete mode 100644 queries/nose_plugin.py

diff --git a/.coveralls.yml b/.coveralls.yml
deleted file mode 100644
index b8c3786..0000000
--- 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 0000000..38c5114
--- /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 73119be..5348a0c 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 0014db9..0000000
--- 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 0000000..3b2c885
--- /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 0000000..01f839c
--- /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 84e1f21..7087e31 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 af10629..7358ec1 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 d1e88e0..605c5c2 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 5e87c5f..0000000
--- 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 54221f1..0000000
--- 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 5e153b9..019937b 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 90a5130..1e7f345 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 cfc4853..11120e2 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 869e1ef..81a8ab5 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 58aff29..75909e9 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 fa6a064..9916679 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 af47dad..07063cb 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 d9eb869..ad690db 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 417b8d0..afb1dc2 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 a636b8a..ce93a5b 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 f10a8c0..d2a50e5 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 a7ee674..d57dd84 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 3d3fdb4..f8b382a 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 2e74348..e298981 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 a2deb0c..8142aef 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 436efb5..29bc83c 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 d17fc54..17d1dde 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 d9292b6..5fe07b8 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 b4d47cb..0000000
--- 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 b6e85f4..5adadac 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 d05b285..207e021 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 980dc38..6022e42 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 6679f41..3c8a49a 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 99bed8c..dfa2d6f 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 1c96de8..84ea2fc 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 0f49588..0000000
--- 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 5d84f79..8363365 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 4d91c6b..d15639b 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 5c6a4a8..1086708 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 6729591..316ef22 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 1dd853a..c01bd1e 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 45fc75c..82c4909 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 924f58b..c5262d6 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 e5f3206..135f44f 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 659be10..9fef95b 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 c3c6689..b0a7c68 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 0000000..883dfc7
--- /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 b62cce1..a04ab5f 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 ce20afe..ab9a617 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 7b21fd4..c6bb3f9 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 bdbaa35..ca44408 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 ae95c87..af93208 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 af55e55..426bac7 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 3428c47..0000000
--- 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 44257dc..c6de629 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 1228cb5..fe376ae 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 ed40ac8..e69de29 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 f036cbc..091acbe 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 9249b67..b3bd725 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 8584d73..0000000
--- 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 46480ab..66c05b1 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 853b628..3e38d0d 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 b948a1f..4c94a4f 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 8ab82f4..0e85f6b 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 e4eb097..2a07cf2 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
-- 
GitLab