├── examples ├── __init__.py ├── simple │ ├── __init__.py │ ├── assets │ │ ├── __init__.py │ │ ├── static │ │ │ ├── css │ │ │ │ └── app.css │ │ │ └── js │ │ │ │ └── app.js │ │ └── README.rst │ ├── books │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ ├── __init__.py │ │ │ │ └── books_create_test_data.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0003_book_pages.py │ │ │ ├── 0002_auto_20170223_1938.py │ │ │ ├── 0007_book_description.py │ │ │ ├── 0015_auto_20170713_0418.py │ │ │ ├── 0013_auto_20170630_1919.py │ │ │ ├── 0010_auto_20170629_1930.py │ │ │ ├── 0005_auto_20170518_1553.py │ │ │ ├── 0008_book_state.py │ │ │ ├── 0011_auto_20170630_1655.py │ │ │ ├── 0009_auto_20170626_1331.py │ │ │ ├── 0016_auto_20170714_1020.py │ │ │ ├── 0004_auto_20170322_2001.py │ │ │ ├── 0006_auto_20170619_0428.py │ │ │ ├── 0012_auto_20170630_1900.py │ │ │ ├── 0014_authorproxy_profile.py │ │ │ └── 0001_initial.py │ │ ├── __init__.py │ │ ├── templates │ │ │ └── books │ │ │ │ ├── index.html │ │ │ │ ├── create_authors.html │ │ │ │ ├── author_list_values.html │ │ │ │ ├── update_books.html │ │ │ │ ├── publisher_list.html │ │ │ │ ├── publisher_ids.html │ │ │ │ ├── author_list.html │ │ │ │ ├── book_list_values.html │ │ │ │ ├── book_list.html │ │ │ │ ├── add_authors_to_book.html │ │ │ │ ├── book_list_with_counts.html │ │ │ │ ├── author_list_with_counts.html │ │ │ │ └── author_list_values_with_counts.html │ │ ├── serializers │ │ │ ├── __init__.py │ │ │ ├── publisher.py │ │ │ ├── book.py │ │ │ ├── profile.py │ │ │ └── author.py │ │ ├── apps.py │ │ ├── models │ │ │ ├── __init__.py │ │ │ ├── order_line.py │ │ │ ├── tag.py │ │ │ ├── order.py │ │ │ ├── publisher.py │ │ │ ├── book.py │ │ │ ├── author.py │ │ │ └── profile.py │ │ ├── constants.py │ │ ├── mixins.py │ │ ├── README.rst │ │ ├── admin.py │ │ ├── viewsets.py │ │ └── urls.py │ ├── settings │ │ ├── __init__.py │ │ ├── __init__.example │ │ ├── README.rst │ │ ├── dev.py │ │ ├── core.py │ │ ├── local_settings.example │ │ └── testing.py │ ├── templates │ │ ├── README.rst │ │ ├── home.html │ │ ├── snippets │ │ │ ├── article.html │ │ │ ├── menu_items_exercises.html │ │ │ └── menu_items.html │ │ └── base.html │ ├── manage.py │ ├── factories │ │ ├── __init__.py │ │ ├── factory_faker.py │ │ ├── books_orderline.py │ │ ├── README.rst │ │ ├── books_order.py │ │ ├── books_tag.py │ │ ├── books_publisher.py │ │ ├── books_author.py │ │ ├── books_profile.py │ │ ├── auth_user.py │ │ ├── files.py │ │ └── books_book.py │ ├── wsgi.py │ └── urls.py ├── requirements │ ├── debug.in │ ├── deployment.in │ ├── documentation.in │ ├── style_checkers.in │ ├── common.in │ ├── testing.in │ ├── docs.in │ ├── dev.in │ ├── test.in │ ├── django_3_0.in │ ├── django_2_2.in │ ├── django_3_1.in │ ├── django_3_2.in │ ├── django_4_0.in │ ├── django_4_1.in │ ├── common.txt │ ├── README.rst │ ├── style_checkers.txt │ ├── debug.txt │ ├── test.txt │ ├── docs.txt │ ├── deployment.txt │ ├── django_4_0.txt │ ├── django_3_2.txt │ ├── django_4_1.txt │ ├── django_2_2.txt │ ├── django_3_0.txt │ ├── django_3_1.txt │ ├── testing.txt │ └── documentation.txt ├── README.rst └── django_rest_framework_tricks_demo_installer.sh ├── scripts ├── coverage.sh ├── tox.sh ├── isort.sh ├── runtests.sh ├── dist.sh ├── pylint_example.sh ├── reinstall.sh ├── collectstatic.sh ├── pylint.sh ├── show_urls.sh ├── shell.sh ├── make_pypi_long_description.sh ├── create_test_data.sh ├── migrate.sh ├── makemigrations.sh ├── runcustomserver.sh ├── runserver.sh ├── pycodestyle.sh ├── create_dirs.sh ├── pycodestyle_example.sh ├── make_release.sh ├── build_docs.sh ├── rebuild_docs.sh ├── uninstall.sh ├── install.sh ├── prepare_project.sh ├── make_messages.sh ├── clean_up.sh ├── test.sh ├── compile_requirements.sh └── README.rst ├── setup.cfg ├── src └── rest_framework_tricks │ ├── tests │ ├── __init__.py │ ├── test_utils.py │ ├── base.py │ └── test_fields.py │ ├── models │ ├── __init__.py │ └── fields │ │ └── __init__.py │ ├── fields │ ├── __init__.py │ └── file.py │ ├── filters │ ├── __init__.py │ └── ordering.py │ ├── __init__.py │ ├── apps.py │ ├── serializers │ └── __init__.py │ ├── utils.py │ └── helpers.py ├── .coveralls.yml ├── .env ├── docs ├── changelog.rst ├── advanced_usage_examples.rst ├── index.rst ├── package.rst ├── documentation.rst ├── rest_framework_tricks.models.rst ├── rest_framework_tricks.filters.rst ├── rest_framework_tricks.serializers.rst ├── rest_framework_tricks.models.fields.rst ├── rest_framework_tricks.rst └── rest_framework_tricks.tests.rst ├── ROADMAP.rst ├── .pylintrc ├── .coveragerc ├── .isort.cfg ├── MANIFEST.in ├── CREDITS.rst ├── TODOS.rst ├── pytest.ini ├── .hgignore ├── .gitignore ├── pyproject.toml ├── threadedtests.py ├── tox.ini ├── .github └── workflows │ └── test.yml ├── setup.py └── CHANGELOG.rst /examples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/simple/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/simple/assets/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | coverage html 2 | -------------------------------------------------------------------------------- /examples/simple/assets/static/css/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 -------------------------------------------------------------------------------- /src/rest_framework_tricks/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: github-actions 2 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | GITHUB_TOKEN 2 | COVERALLS_REPO_TOKEN 3 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGELOG.rst -------------------------------------------------------------------------------- /examples/simple/books/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/tox.sh: -------------------------------------------------------------------------------- 1 | xvfb-run python toxtests.py 2 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/requirements/debug.in: -------------------------------------------------------------------------------- 1 | ipython 2 | ipdb 3 | -------------------------------------------------------------------------------- /examples/simple/books/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/isort.sh: -------------------------------------------------------------------------------- 1 | reset 2 | isort -rc src/ --diff 3 | -------------------------------------------------------------------------------- /scripts/runtests.sh: -------------------------------------------------------------------------------- 1 | xvfb-run python runtests.py 2 | -------------------------------------------------------------------------------- /examples/requirements/deployment.in: -------------------------------------------------------------------------------- 1 | twine 2 | pip-tools 3 | -------------------------------------------------------------------------------- /examples/simple/settings/__init__.py: -------------------------------------------------------------------------------- 1 | from .dev import * 2 | -------------------------------------------------------------------------------- /ROADMAP.rst: -------------------------------------------------------------------------------- 1 | Roadmap 2 | ======= 3 | Road-map/upcoming releases. 4 | -------------------------------------------------------------------------------- /examples/simple/assets/static/js/app.js: -------------------------------------------------------------------------------- 1 | $(document).foundation() 2 | -------------------------------------------------------------------------------- /examples/simple/books/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Books app. 3 | """ 4 | -------------------------------------------------------------------------------- /examples/simple/settings/__init__.example: -------------------------------------------------------------------------------- 1 | from .dev import * 2 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/fields/__init__.py: -------------------------------------------------------------------------------- 1 | from .file import * 2 | -------------------------------------------------------------------------------- /docs/advanced_usage_examples.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../ADVANCED_USAGE_EXAMPLES.rst -------------------------------------------------------------------------------- /examples/requirements/documentation.in: -------------------------------------------------------------------------------- 1 | -r django_3_2.in 2 | -r docs.in 3 | -------------------------------------------------------------------------------- /examples/requirements/style_checkers.in: -------------------------------------------------------------------------------- 1 | pylint 2 | pycodestyle 3 | black 4 | -------------------------------------------------------------------------------- /scripts/dist.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | python setup.py sdist bdist_wheel 3 | -------------------------------------------------------------------------------- /scripts/pylint_example.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | pylint examples/simple/ 3 | -------------------------------------------------------------------------------- /scripts/reinstall.sh: -------------------------------------------------------------------------------- 1 | reset 2 | ./scripts/uninstall.sh 3 | ./scripts/install.sh -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | .. include:: documentation.rst 3 | -------------------------------------------------------------------------------- /examples/simple/settings/README.rst: -------------------------------------------------------------------------------- 1 | settings 2 | ======== 3 | Setting modules. 4 | -------------------------------------------------------------------------------- /scripts/collectstatic.sh: -------------------------------------------------------------------------------- 1 | cd examples/simple/ 2 | ./manage.py collectstatic --noinput 3 | -------------------------------------------------------------------------------- /examples/requirements/common.in: -------------------------------------------------------------------------------- 1 | python-memcached==1.58 2 | pytz 3 | six>=1.9 4 | Pillow 5 | -------------------------------------------------------------------------------- /examples/requirements/testing.in: -------------------------------------------------------------------------------- 1 | -r django_3_2.in 2 | -r test.in 3 | -r style_checkers.in 4 | -------------------------------------------------------------------------------- /scripts/pylint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | reset 3 | pylint src/rest_framework_tricks/ 4 | -------------------------------------------------------------------------------- /examples/simple/templates/README.rst: -------------------------------------------------------------------------------- 1 | templates 2 | ========= 3 | Base templates and snippets. 4 | -------------------------------------------------------------------------------- /scripts/show_urls.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd examples/simple/ 3 | ./manage.py show_urls 4 | -------------------------------------------------------------------------------- /scripts/shell.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd examples/simple/ 3 | ./manage.py shell --traceback -v 3 4 | -------------------------------------------------------------------------------- /scripts/make_pypi_long_description.sh: -------------------------------------------------------------------------------- 1 | python setup.py --long-description | rst2html.py > builddocs/pypi.html -------------------------------------------------------------------------------- /examples/requirements/docs.in: -------------------------------------------------------------------------------- 1 | rst2pdf 2 | sphinx 3 | Jinja2 4 | MarkupSafe 5 | Sphinx 6 | docutils 7 | rstcheck==3.3.1 8 | -------------------------------------------------------------------------------- /scripts/create_test_data.sh: -------------------------------------------------------------------------------- 1 | cd examples/simple/ 2 | ./manage.py books_create_test_data --number=100 --traceback -v 3 3 | -------------------------------------------------------------------------------- /scripts/migrate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd examples/simple/ 3 | ./manage.py migrate --settings=settings.dev "$@" 4 | -------------------------------------------------------------------------------- /examples/requirements/dev.in: -------------------------------------------------------------------------------- 1 | -r django_3_2.in 2 | -r deployment.in 3 | -r debug.in 4 | -r docs.in 5 | -r style_checkers.in 6 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/index.html: -------------------------------------------------------------------------------- 1 | {% extends "home.html" %} 2 | {% block meta-title %}Index{% endblock %} 3 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | ignore= 3 | #disable=R,C 4 | init-hook='import sys; import os; sys.path.append(os.path.abspath("src"))' 5 | -------------------------------------------------------------------------------- /examples/README.rst: -------------------------------------------------------------------------------- 1 | Example project for `django-rest-framework-tricks` 2 | ================================================== 3 | -------------------------------------------------------------------------------- /scripts/makemigrations.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd examples/simple/ 3 | ./manage.py makemigrations --settings=settings.dev "$@" 4 | -------------------------------------------------------------------------------- /scripts/runcustomserver.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd examples/simple/ 3 | ./manage.py runserver --traceback -v 3 "0.0.0.0:$@" 4 | -------------------------------------------------------------------------------- /scripts/runserver.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd examples/simple/ 3 | ./manage.py runserver 0.0.0.0:8000 --traceback -v 3 "$@" 4 | -------------------------------------------------------------------------------- /scripts/pycodestyle.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | reset 3 | pycodestyle src/rest_framework_tricks/ --exclude migrations,south_migrations 4 | -------------------------------------------------------------------------------- /examples/simple/books/serializers/__init__.py: -------------------------------------------------------------------------------- 1 | from .author import * 2 | from .book import * 3 | from .profile import * 4 | from .publisher import * 5 | -------------------------------------------------------------------------------- /scripts/create_dirs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir -p examples/logs/ 4 | mkdir -p examples/db/ 5 | mkdir -p examples/tmp/ 6 | mkdir -p var/ 7 | -------------------------------------------------------------------------------- /examples/simple/assets/README.rst: -------------------------------------------------------------------------------- 1 | assets 2 | ====== 3 | Assets for the site. Do not put here any other code that generic CSS, images 4 | and JavaScript files. 5 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | relative_files = True 3 | omit = 4 | src/rest_framework_tricks/tests/* 5 | example/simple/settings/* 6 | example/simple/wsgi.py 7 | -------------------------------------------------------------------------------- /scripts/pycodestyle_example.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | reset 3 | pycodestyle examples/simple/ --exclude migrations,south_migrations,examples/simple/books/tests/, 4 | -------------------------------------------------------------------------------- /scripts/make_release.sh: -------------------------------------------------------------------------------- 1 | ./scripts/uninstall.sh 2 | ./scripts/clean_up.sh 3 | python setup.py register 4 | python setup.py sdist bdist_wheel 5 | twine upload dist/* --verbose 6 | -------------------------------------------------------------------------------- /docs/package.rst: -------------------------------------------------------------------------------- 1 | 2 | Package 3 | ======= 4 | Contents: 5 | 6 | .. contents:: Table of Contents 7 | 8 | .. toctree:: 9 | :maxdepth: 20 10 | 11 | rest_framework_tricks 12 | -------------------------------------------------------------------------------- /examples/requirements/test.in: -------------------------------------------------------------------------------- 1 | Faker>9.0 2 | coverage>=4.5.4 3 | factory_boy>=3.2 4 | py 5 | pytest-cov==2.12.0 6 | pytest-django==4.3.0 7 | pytest-ordering==0.6 8 | pytest-pythonpath 9 | pytest==6.2.4 10 | tox 11 | -------------------------------------------------------------------------------- /examples/simple/books/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | __all__ = ("Config",) 4 | 5 | 6 | class Config(AppConfig): 7 | """Config.""" 8 | 9 | name = "books" 10 | label = "books" 11 | -------------------------------------------------------------------------------- /examples/simple/books/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .author import * 2 | from .book import * 3 | from .order import * 4 | from .order_line import * 5 | from .profile import * 6 | from .publisher import * 7 | from .tag import * 8 | -------------------------------------------------------------------------------- /scripts/build_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ./scripts/prepare_docs.sh 3 | 4 | sphinx-build -n -a -b html docs builddocs 5 | sphinx-build -n -a -b pdf docs builddocs 6 | cd builddocs && zip -r ../builddocs.zip . -x ".*" && cd .. 7 | -------------------------------------------------------------------------------- /scripts/rebuild_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rm -rf builddocs/ 3 | sphinx-apidoc src/rest_framework_tricks --full -o docs -H 'django-rest-framework-tricks' -A 'Artur Barseghyan ' -V '0.1' -f -d 20 4 | cp docs/conf.distrib docs/conf.py 5 | -------------------------------------------------------------------------------- /scripts/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | pip uninstall django-rest-framework-tricks -y 3 | rm build -rf 4 | rm dist -rf 5 | rm -rf src/rest_framework_tricks.egg-info 6 | rm -rf src/django-rest-framework-tricks.egg-info 7 | rm builddocs.zip 8 | rm builddocs/ -rf 9 | -------------------------------------------------------------------------------- /examples/requirements/django_3_0.in: -------------------------------------------------------------------------------- 1 | -r common.in 2 | -r test.in 3 | 4 | Django>=3.0,<3.1 5 | django-debug-toolbar>=2.1 6 | django-debug-toolbar-force>=0.1.7 7 | django-ormex>=0.2.1 8 | djangorestframework>=3.11.0,<3.12 9 | drf-spectacular 10 | drf-spectacular-sidecar 11 | -------------------------------------------------------------------------------- /examples/requirements/django_2_2.in: -------------------------------------------------------------------------------- 1 | -r common.in 2 | -r test.in 3 | 4 | Django>=2.2,<2.3 5 | django-debug-toolbar>=1.6 6 | django-debug-toolbar-force>=0.1.1 7 | django-ormex>=0.2 8 | djangorestframework>=3.10,<3.11 9 | drf-spectacular[sidecar] 10 | drf-spectacular-sidecar 11 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | pip install -r examples/requirements/dev.txt 2 | python setup.py develop 3 | mkdir -p examples/logs examples/db examples/media examples/media/static 4 | python examples/simple/manage.py collectstatic --noinput 5 | python examples/simple/manage.py migrate --noinput 6 | -------------------------------------------------------------------------------- /examples/requirements/django_3_1.in: -------------------------------------------------------------------------------- 1 | -r common.in 2 | -r test.in 3 | 4 | Django>=3.1,<3.2 5 | django-debug-toolbar>=2.1 6 | django-debug-toolbar-force>=0.1.7 7 | django-ormex>=0.2.1 8 | djangorestframework>=3.12.0,<3.13 9 | drf-spectacular[sidecar] 10 | drf-spectacular-sidecar 11 | -------------------------------------------------------------------------------- /examples/requirements/django_3_2.in: -------------------------------------------------------------------------------- 1 | -r common.in 2 | -r test.in 3 | 4 | Django>=3.2,<3.3 5 | django-debug-toolbar>=2.1 6 | django-debug-toolbar-force>=0.1.7 7 | django-ormex>=0.2.1 8 | djangorestframework>=3.12.0,<3.13 9 | drf-spectacular[sidecar] 10 | drf-spectacular-sidecar 11 | -------------------------------------------------------------------------------- /examples/requirements/django_4_0.in: -------------------------------------------------------------------------------- 1 | -r common.in 2 | -r test.in 3 | 4 | Django>=4.0,<4.1 5 | django-debug-toolbar>=2.1 6 | django-debug-toolbar-force>=0.1.7 7 | django-ormex>=0.2.1 8 | djangorestframework>=3.12.0,<3.13 9 | drf-spectacular[sidecar] 10 | drf-spectacular-sidecar 11 | -------------------------------------------------------------------------------- /examples/requirements/django_4_1.in: -------------------------------------------------------------------------------- 1 | -r common.in 2 | -r test.in 3 | 4 | Django>=4.1,<4.2 5 | django-debug-toolbar>=2.1 6 | django-debug-toolbar-force>=0.1.7 7 | django-ormex>=0.2.1 8 | djangorestframework>=3.13.0,<3.14 9 | drf-spectacular[sidecar] 10 | drf-spectacular-sidecar 11 | -------------------------------------------------------------------------------- /examples/simple/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/filters/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Filters. 3 | """ 4 | 5 | from .ordering import * 6 | 7 | __author__ = "Artur Barseghyan " 8 | __copyright__ = "2017-2022 Artur Barseghyan" 9 | __license__ = "GPL-2.0-only OR LGPL-2.1-or-later" 10 | __all__ = ("OrderingFilter",) 11 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/models/fields/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fields. 3 | """ 4 | from .nested_proxy import * 5 | 6 | __author__ = "Artur Barseghyan " 7 | __copyright__ = "2017-2022 Artur Barseghyan" 8 | __license__ = "GPL-2.0-only OR LGPL-2.1-or-later" 9 | __all__ = ("NestedProxyField",) 10 | -------------------------------------------------------------------------------- /examples/simple/settings/dev.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | if LOGGING: 4 | LOGGING["loggers"].update( 5 | { 6 | "django.db": { 7 | "handlers": ["console"], 8 | "level": "DEBUG", 9 | "propagate": False, 10 | } 11 | } 12 | ) 13 | -------------------------------------------------------------------------------- /examples/simple/factories/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Factories. 3 | """ 4 | 5 | from .auth_user import * 6 | from .books_author import * 7 | from .books_book import * 8 | from .books_order import * 9 | from .books_orderline import * 10 | from .books_profile import * 11 | from .books_publisher import * 12 | from .books_tag import * 13 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | line_length=80 3 | force_to_top= 4 | skip= 5 | known_future_library= 6 | known_standard_library= 7 | known_third_party= 8 | known_first_party=rest_framework_tricks 9 | indent=' ' 10 | multi_line_output=3 11 | length_sort=1 12 | forced_separate=django.contrib,django.utils 13 | default_section=rest_framework_tricks 14 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/create_authors.html: -------------------------------------------------------------------------------- 1 | {% extends "books/author_list.html" %} 2 | {% block meta-title %}Create authors{% endblock %} 3 | 4 | {% block nav-menu-items %} 5 | {% with True as books_create_authors_is_active %} 6 | {% include "snippets/menu_items.html" %} 7 | {% endwith %} 8 | {% endblock nav-menu-items %} 9 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/author_list_values.html: -------------------------------------------------------------------------------- 1 | {% extends "books/author_list.html" %} 2 | {% block meta-title %}Authors (values){% endblock %} 3 | 4 | {% block nav-menu-items %} 5 | {% with True as books_authors_values_is_active %} 6 | {% include "snippets/menu_items.html" %} 7 | {% endwith %} 8 | {% endblock nav-menu-items %} 9 | -------------------------------------------------------------------------------- /examples/simple/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block meta-title %}Home{% endblock %} 4 | {% block main-content %} 5 |
6 | {#

Home

#} 7 | {#

Landing page content.

#} 8 |
9 | {% endblock main-content %} 10 | 11 | {% block sidebar-content %}{% endblock sidebar-content %} 12 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Collection of various tricks for Django REST framework. 3 | """ 4 | 5 | __title__ = "django-rest-framework-tricks" 6 | __version__ = "0.2.14" 7 | __author__ = "Artur Barseghyan " 8 | __copyright__ = "2017-2022 Artur Barseghyan" 9 | __license__ = "GPL-2.0-only OR LGPL-2.1-or-later" 10 | -------------------------------------------------------------------------------- /examples/simple/factories/factory_faker.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Factory faker tweaked. 4 | """ 5 | 6 | from __future__ import unicode_literals 7 | 8 | from factory import Faker as OriginalFaker 9 | 10 | __all__ = ("Faker",) 11 | 12 | 13 | class Faker(OriginalFaker): 14 | """Override to change the default locale.""" 15 | 16 | _DEFAULT_LOCALE = "nl_NL" 17 | -------------------------------------------------------------------------------- /scripts/prepare_project.sh: -------------------------------------------------------------------------------- 1 | cp examples/simple/settings/__init__.example examples/simple/settings/__init__.py 2 | cp examples/simple/settings/local_settings.example examples/simple/settings/local_settings.py 3 | 4 | mkdir -p examples/db 5 | mkdir -p examples/logs 6 | 7 | cd examples/simple 8 | 9 | ./manage.py migrate 10 | 11 | ./manage.py books_create_test_data --number=10 12 | -------------------------------------------------------------------------------- /docs/documentation.rst: -------------------------------------------------------------------------------- 1 | 2 | Project documentation 3 | ===================== 4 | Contents: 5 | 6 | .. contents:: Table of Contents 7 | 8 | .. toctree:: 9 | :maxdepth: 20 10 | 11 | index 12 | advanced_usage_examples 13 | changelog 14 | package 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | 23 | -------------------------------------------------------------------------------- /examples/requirements/common.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile common.in 6 | # 7 | pillow==9.3.0 8 | # via -r common.in 9 | python-memcached==1.58 10 | # via -r common.in 11 | pytz==2022.6 12 | # via -r common.in 13 | six==1.16.0 14 | # via 15 | # -r common.in 16 | # python-memcached 17 | -------------------------------------------------------------------------------- /docs/rest_framework_tricks.models.rst: -------------------------------------------------------------------------------- 1 | rest\_framework\_tricks\.models package 2 | ======================================= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | rest_framework_tricks.models.fields 10 | 11 | Module contents 12 | --------------- 13 | 14 | .. automodule:: rest_framework_tricks.models 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE_GPL2.0.txt 3 | include LICENSE_LGPL_2.1.txt 4 | include CHANGELOG.rst 5 | 6 | recursive-include src/rest_framework_tricks * 7 | 8 | include runtests.py 9 | include .coveragerc 10 | include pytest.ini 11 | include shell.py 12 | include tox.ini 13 | 14 | graft docs/ 15 | 16 | global-exclude *.pyc 17 | global-exclude *.py,cover 18 | global-exclude __pycache__ 19 | -------------------------------------------------------------------------------- /scripts/make_messages.sh: -------------------------------------------------------------------------------- 1 | echo 'Making messages for django-rest-framework-tricks...' 2 | cd src/rest_framework_tricks/ 3 | django-admin.py makemessages -l de 4 | django-admin.py makemessages -l nl 5 | django-admin.py makemessages -l ru 6 | 7 | echo 'Making messages for example projects...' 8 | cd ../../examples/simple/ 9 | django-admin.py makemessages -l de 10 | django-admin.py makemessages -l nl 11 | django-admin.py makemessages -l ru 12 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/apps.py: -------------------------------------------------------------------------------- 1 | """ 2 | Apps. 3 | """ 4 | 5 | from django.apps import AppConfig 6 | 7 | __author__ = "Artur Barseghyan " 8 | __copyright__ = "2017-2022 Artur Barseghyan" 9 | __license__ = "GPL-2.0-only OR LGPL-2.1-or-later" 10 | __all__ = ("Config",) 11 | 12 | 13 | class Config(AppConfig): 14 | """Config.""" 15 | 16 | name = "rest_framework_tricks" 17 | label = "rest_framework_tricks" 18 | -------------------------------------------------------------------------------- /scripts/clean_up.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | find . -name "*.pyc" -exec rm -rf {} \; 3 | find . -name "__pycache__" -exec rm -rf {} \; 4 | find . -name "*.orig" -exec rm -rf {} \; 5 | find . -name "*.py,cover" -exec rm -rf {} \; 6 | rm -rf build/ 7 | rm -rf dist/ 8 | rm -rf .cache/ 9 | rm -rf htmlcov/ 10 | rm -rf examples/django-rest-framework-tricks-env/ 11 | rm -rf examples/rest_framework_tricks_demo_installer/ 12 | rm examples/rest_framework_tricks_demo_installer.tar.gz 13 | -------------------------------------------------------------------------------- /examples/simple/settings/core.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | __all__ = ( 4 | "gettext", 5 | "project_dir", 6 | "PROJECT_DIR", 7 | ) 8 | 9 | 10 | def project_dir(base): 11 | """Absolute path to a file from current directory.""" 12 | return os.path.abspath( 13 | os.path.join(os.path.dirname(__file__), base).replace("\\", "/") 14 | ) 15 | 16 | 17 | def gettext(val): 18 | """Dummy gettext.""" 19 | return val 20 | 21 | 22 | PROJECT_DIR = project_dir 23 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/serializers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Serializers. 3 | """ 4 | 5 | from .nested_proxy import * 6 | 7 | __author__ = "Artur Barseghyan " 8 | __copyright__ = "2017-2022 Artur Barseghyan" 9 | __license__ = "GPL-2.0-only OR LGPL-2.1-or-later" 10 | __all__ = ( 11 | "extract_nested_serializers", 12 | "HyperlinkedModelSerializer", 13 | "is_nested_proxy_field", 14 | "ModelSerializer", 15 | "NestedProxyFieldIdentifier", 16 | "set_instance_values", 17 | ) 18 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0003_book_pages.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("books", "0002_auto_20170223_1938"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="book", 16 | name="pages", 17 | field=models.IntegerField(default=200), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /examples/simple/books/models/order_line.py: -------------------------------------------------------------------------------- 1 | """ 2 | Order line models. 3 | """ 4 | 5 | from django.db import models 6 | 7 | __all__ = ("OrderLine",) 8 | 9 | 10 | class OrderLine(models.Model): 11 | """Order line.""" 12 | 13 | book = models.ForeignKey( 14 | "books.Book", related_name="order_lines", on_delete=models.CASCADE 15 | ) 16 | 17 | class Meta: 18 | """Meta options.""" 19 | 20 | ordering = ["order__created"] 21 | 22 | def __str__(self): 23 | return str(self.book.isbn) 24 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0002_auto_20170223_1938.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("books", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="book", 16 | name="isbn", 17 | field=models.CharField(unique=True, max_length=100), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /examples/simple/books/models/tag.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tag models. 3 | """ 4 | 5 | from django.db import models 6 | from django.utils.translation import gettext_lazy as _ 7 | 8 | __all__ = ("Tag",) 9 | 10 | 11 | class Tag(models.Model): 12 | """Simple tag model.""" 13 | 14 | title = models.CharField(max_length=255, unique=True) 15 | 16 | class Meta: 17 | """Meta options.""" 18 | 19 | verbose_name = _("Tag") 20 | verbose_name_plural = _("Tags") 21 | 22 | def __str__(self): 23 | return self.title 24 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/update_books.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block meta-title %}Update books{% endblock %} 3 | 4 | {% block nav-menu-items %} 5 | {% with True as books_update_books_is_active %} 6 | {% include "snippets/menu_items.html" %} 7 | {% endwith %} 8 | {% endblock nav-menu-items %} 9 | 10 | {% block main-content %} 11 |

Update books

12 |

Number of books updated: {{ number_of_books_updated }}

13 | {% endblock %} 14 | {% block sidebar-content %}{% endblock sidebar-content %} 15 | -------------------------------------------------------------------------------- /docs/rest_framework_tricks.filters.rst: -------------------------------------------------------------------------------- 1 | rest\_framework\_tricks\.filters package 2 | ======================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | rest\_framework\_tricks\.filters\.ordering module 8 | ------------------------------------------------- 9 | 10 | .. automodule:: rest_framework_tricks.filters.ordering 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: rest_framework_tricks.filters 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0007_book_description.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.5 on 2017-06-19 10:05 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("books", "0006_auto_20170619_0428"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="book", 17 | name="description", 18 | field=models.TextField(blank=True, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0015_auto_20170713_0418.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.2 on 2017-07-13 09:18 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("books", "0014_authorproxy_profile"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="profile", 17 | name="salutation", 18 | field=models.CharField(max_length=50), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0013_auto_20170630_1919.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-07-01 00:19 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("books", "0012_auto_20170630_1900"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="author", 17 | name="company_website", 18 | field=models.URLField(blank=True, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | reset 2 | #./scripts/uninstall.sh 3 | #./scripts/install.sh 4 | python examples/simple/manage.py test rest_framework_tricks --traceback -v 3 --settings=settings.testing 5 | #python examples/simple/manage.py test rest_framework_tricks.tests.test_nested_proxy_field.TestNestedProxyFieldCreateAction.test_nested_proxy_field_model_serializer_depth --traceback -v 3 --settings=settings.testing 6 | #python examples/simple/manage.py test rest_framework_tricks.tests.test_nested_proxy_field.TestNestedProxyFieldUpdateAction.test_nested_proxy_field_model_serializer_depth --traceback -v 3 --settings=settings.testing 7 | -------------------------------------------------------------------------------- /CREDITS.rst: -------------------------------------------------------------------------------- 1 | Credits 2 | ======= 3 | Authors 4 | ------- 5 | - `Artur Barseghyan `_. 6 | 7 | Original idea 8 | ------------- 9 | The package has been started as a single trick for making the nested 10 | serializers easier in Django REST framework. Idea of setting dummy field 11 | on a model level and making a dynamic property of it came out as a result of 12 | collaborative efforts of Artur Barseghyan (author of this package) and 13 | `Peter Uittenbroek `_. 14 | 15 | Contributors 16 | ------------ 17 | Thanks to the following people for their contributions: 18 | -------------------------------------------------------------------------------- /docs/rest_framework_tricks.serializers.rst: -------------------------------------------------------------------------------- 1 | rest\_framework\_tricks\.serializers package 2 | ============================================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | rest\_framework\_tricks\.serializers\.nested\_proxy module 8 | ---------------------------------------------------------- 9 | 10 | .. automodule:: rest_framework_tricks.serializers.nested_proxy 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: rest_framework_tricks.serializers 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /docs/rest_framework_tricks.models.fields.rst: -------------------------------------------------------------------------------- 1 | rest\_framework\_tricks\.models\.fields package 2 | =============================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | rest\_framework\_tricks\.models\.fields\.nested\_proxy module 8 | ------------------------------------------------------------- 9 | 10 | .. automodule:: rest_framework_tricks.models.fields.nested_proxy 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: rest_framework_tricks.models.fields 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /examples/simple/factories/books_orderline.py: -------------------------------------------------------------------------------- 1 | """ 2 | Books OrderLine model factory. 3 | """ 4 | 5 | from factory import SubFactory 6 | from factory.django import DjangoModelFactory 7 | 8 | from books.models import OrderLine 9 | 10 | # from .factory_faker import Faker 11 | 12 | __all__ = ("OrderLineFactory",) 13 | 14 | 15 | class OrderLineFactory(DjangoModelFactory): 16 | """Order line factory.""" 17 | 18 | # owner = SubFactory('factories.books_order.OrderFactory') 19 | book = SubFactory("factories.books_book.BookFactory") 20 | # created = Faker('date') 21 | # updated = Faker('date') 22 | 23 | class Meta: 24 | """Meta class.""" 25 | 26 | model = OrderLine 27 | -------------------------------------------------------------------------------- /TODOS.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | TODOs 3 | ===== 4 | Based on the MoSCoW principle. Must haves and should haves are planned to be 5 | worked on. 6 | 7 | * Features/issues marked with plus (+) are implemented/solved. 8 | * Features/issues marked with minus (-) are yet to be implemented. 9 | 10 | Must haves 11 | ========== 12 | .. code-block:: text 13 | 14 | - Test with various versions of Django REST framework. 15 | + Try to reproduce the "missing fields in the validated data" problem. 16 | + Handle unlimited nesting depth. 17 | 18 | Should haves 19 | ============ 20 | .. code-block:: text 21 | 22 | 23 | Could haves 24 | =========== 25 | .. code-block:: text 26 | 27 | 28 | Would haves 29 | =========== 30 | .. code-block:: text 31 | -------------------------------------------------------------------------------- /examples/simple/books/constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | Constants. 3 | """ 4 | 5 | BOOK_PUBLISHING_STATUS_PUBLISHED = "published" 6 | BOOK_PUBLISHING_STATUS_NOT_PUBLISHED = "not_published" 7 | BOOK_PUBLISHING_STATUS_IN_PROGRESS = "in_progress" 8 | BOOK_PUBLISHING_STATUS_CANCELLED = "cancelled" 9 | BOOK_PUBLISHING_STATUS_REJECTED = "rejected" 10 | BOOK_PUBLISHING_STATUS_CHOICES = ( 11 | (BOOK_PUBLISHING_STATUS_PUBLISHED, "Published"), 12 | (BOOK_PUBLISHING_STATUS_NOT_PUBLISHED, "Not published"), 13 | (BOOK_PUBLISHING_STATUS_IN_PROGRESS, "In progress"), 14 | (BOOK_PUBLISHING_STATUS_CANCELLED, "Cancelled"), 15 | (BOOK_PUBLISHING_STATUS_REJECTED, "Rejected"), 16 | ) 17 | BOOK_PUBLISHING_STATUS_DEFAULT = BOOK_PUBLISHING_STATUS_PUBLISHED 18 | -------------------------------------------------------------------------------- /examples/simple/books/models/order.py: -------------------------------------------------------------------------------- 1 | """ 2 | Order models. 3 | """ 4 | 5 | from django.conf import settings 6 | from django.db import models 7 | from django.utils.translation import gettext_lazy as _ 8 | 9 | __all__ = ("Order",) 10 | 11 | 12 | class Order(models.Model): 13 | """Order.""" 14 | 15 | owner = models.ForeignKey( 16 | settings.AUTH_USER_MODEL, on_delete=models.CASCADE 17 | ) 18 | lines = models.ManyToManyField("books.OrderLine", blank=True) 19 | created = models.DateField(auto_now_add=True) 20 | updated = models.DateField(auto_now=True) 21 | 22 | class Meta: 23 | """Meta options.""" 24 | 25 | ordering = ["-created"] 26 | 27 | def __str__(self): 28 | return _("Order") 29 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | norecursedirs= 3 | *.egg 4 | .hg 5 | .git 6 | .tox 7 | .env 8 | _sass 9 | build 10 | dist 11 | migrations 12 | releases 13 | rest_framework_tricks_demo_installer 14 | django-rest-framework-tricks-env 15 | python_files = 16 | test_*.py 17 | tests.py 18 | python_paths = 19 | src 20 | examples/simple 21 | DJANGO_SETTINGS_MODULE=settings.testing 22 | addopts= 23 | --cov=rest_framework_tricks 24 | --ignore=.tox 25 | --ignore=requirements 26 | --ignore=var 27 | --ignore=rest_framework_tricks_demo_installer 28 | --ignore=django-rest-framework-tricks-env 29 | --ignore=releases 30 | --cov-report=html 31 | --cov-report=term 32 | --cov-report=annotate 33 | --cov-append 34 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/publisher_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block meta-title %}Publishers{% endblock %} 3 | 4 | {% block nav-menu-items %} 5 | {% with True as books_publishers_is_active %} 6 | {% include "snippets/menu_items.html" %} 7 | {% endwith %} 8 | {% endblock nav-menu-items %} 9 | 10 | {% block main-content %} 11 |

Publishers

12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for publisher in object_list %} 19 | 20 | 21 | 22 | {% endfor %} 23 |
Name
{{ publisher }}
24 | {% endblock %} 25 | {% block sidebar-content %}{% endblock sidebar-content %} 26 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/publisher_ids.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block meta-title %}Publisher IDs{% endblock %} 3 | 4 | {% block nav-menu-items %} 5 | {% with True as books_publisher_ids_is_active %} 6 | {% include "snippets/menu_items.html" %} 7 | {% endwith %} 8 | {% endblock nav-menu-items %} 9 | 10 | {% block main-content %} 11 |

Publisher IDs

12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for publisher_id in object_list %} 19 | 20 | 21 | 22 | {% endfor %} 23 |
Publisher ID
{{ publisher_id }}
24 | {% endblock %} 25 | {% block sidebar-content %}{% endblock sidebar-content %} 26 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0010_auto_20170629_1930.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-06-30 00:30 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ("books", "0009_auto_20170626_1331"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name="book", 18 | name="publisher", 19 | field=models.ForeignKey( 20 | blank=True, 21 | null=True, 22 | on_delete=django.db.models.deletion.CASCADE, 23 | related_name="books", 24 | to="books.Publisher", 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: regexp 2 | \.pyc$ 3 | \.hgignore~ 4 | \.gitignore~ 5 | \.git/ 6 | \.tox/ 7 | \.travis\.yml~ 8 | \.cache/ 9 | \.coverage$ 10 | ^htmlcov/ 11 | \.py,cover 12 | \.idea/ 13 | ^vagrant/\.vagrant 14 | \.pytest_cache/ 15 | 16 | ^MANIFEST\.in~ 17 | ^tmp/ 18 | \.zip 19 | ^release/ 20 | ^var/ 21 | ^releases/ 22 | ^demo/ 23 | ^deploy/ 24 | ^examples/db/ 25 | ^examples/tmp/ 26 | ^examples/logs/ 27 | ^examples/media/ 28 | ^examples/media/cache/ 29 | ^examples/static/ 30 | ^examples/simple/settings/local_settings\.py 31 | #^examples/simple/settings/__init__\.py 32 | ^builddocs/ 33 | ^builddocs\.zip 34 | ^build/ 35 | ^dist/ 36 | ^src/django_rest_framework_tricks\.egg-info 37 | \.DS_Store 38 | ^examples/rest_framework_tricks_demo_installer/ 39 | ^examples/django-rest-framework-tricks-env/ 40 | ^examples/rest_framework_tricks_demo_installer\.tar\.gz 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | .hgignore~ 3 | .gitignore~ 4 | .hg/ 5 | .hgtags 6 | .tox/ 7 | .travis.yml~ 8 | .cache/ 9 | .coverage 10 | /htmlcov/ 11 | *.py,cover 12 | .idea/ 13 | /vagrant/.vagrant/ 14 | .pytest_cache/ 15 | 16 | MANIFEST.in~ 17 | MIND_BUCKET.rst 18 | codebin/ 19 | /tmp/ 20 | .zip 21 | /demo/ 22 | /demos/ 23 | /release/ 24 | /releases/ 25 | /deploy/ 26 | /examples/db/ 27 | /var/ 28 | /examples/tmp/ 29 | /examples/logs/ 30 | /examples/media/ 31 | /examples/media/cache/ 32 | /examples/static/ 33 | /builddocs/ 34 | /builddocs.zip 35 | /build/ 36 | /dist/ 37 | /src/django_rest_framework_tricks.egg-info 38 | #/examples/simple/settings/__init__.py 39 | /examples/simple/settings/local_settings.py 40 | /examples/rest_framework_tricks_demo_installer/ 41 | /examples/django-rest-framework-tricks-env/ 42 | /examples/rest_framework_tricks_demo_installer.tar.gz 43 | -------------------------------------------------------------------------------- /examples/simple/factories/README.rst: -------------------------------------------------------------------------------- 1 | factories 2 | ========= 3 | Project dummy data (dynamic fixtures). Extremely useful if you want to test 4 | your application on a large data set (which you don't have or difficult to 5 | recreate each time for each developer). 6 | 7 | Usage examples 8 | -------------- 9 | **Create a single ``Book`` instance** 10 | 11 | .. code-block:: python 12 | 13 | import factories 14 | 15 | book = factories.BookFactory() 16 | 17 | **Create many (100) ``Book`` instances** 18 | 19 | .. code-block:: python 20 | 21 | import factories 22 | 23 | book = factories.BookFactory.create_batch(100) 24 | 25 | **Create a single ``Book`` instance with pre-defined attributes** 26 | 27 | .. code-block:: python 28 | 29 | book = factories.BookFactory( 30 | title="My book title", 31 | publisher__country="AU", 32 | ) 33 | -------------------------------------------------------------------------------- /examples/requirements/README.rst: -------------------------------------------------------------------------------- 1 | requirements 2 | ============ 3 | Project requirements. Consists of loose files, combined with each other. 4 | 5 | code_style.txt 6 | -------------- 7 | Code style checkers. 8 | 9 | common.txt 10 | ---------- 11 | Common (non Django specific) packages. 12 | 13 | debug.txt 14 | --------- 15 | Common (non Django specific) packages. 16 | 17 | dev.txt 18 | ------- 19 | Everything needed to development environment, including debugging tools, 20 | documentation generators, Django packages, testing packages, code style 21 | packages. 22 | 23 | django_1_8.txt 24 | -------------- 25 | What's necessary to run Django. 26 | 27 | docs.txt 28 | -------- 29 | Documentation related (generators). 30 | 31 | test.txt 32 | -------- 33 | What's necessary to run the tests. 34 | 35 | testing.txt 36 | ----------- 37 | A combination of Django and test requirements. 38 | -------------------------------------------------------------------------------- /examples/simple/books/mixins.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mixins. 3 | """ 4 | 5 | from django.http import JsonResponse 6 | 7 | __all__ = ("JSONResponseMixin",) 8 | 9 | 10 | class JSONResponseMixin: 11 | """A mixin that can be used to render a JSON response.""" 12 | 13 | def render_to_json_response(self, context, **response_kwargs): 14 | """Return a JSON response, transforming 'context' for the payload.""" 15 | return JsonResponse(self.get_data(context), **response_kwargs) 16 | 17 | def get_data(self, context): 18 | """Return an object to be serialized as JSON by json.dumps().""" 19 | # Note: This is *EXTREMELY* naive; in reality, you'll need 20 | # to do much more complex handling to ensure that arbitrary 21 | # objects -- such as Django model instances or querysets 22 | # -- can be serialized as JSON. 23 | return context 24 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Example configuration for Black. 2 | 3 | # NOTE: you have to use single-quoted strings in TOML for regular expressions. 4 | # It's the equivalent of r-strings in Python. Multiline strings are treated as 5 | # verbose regular expressions by Black. Use [ ] to denote a significant space 6 | # character. 7 | 8 | [tool.black] 9 | line-length = 79 10 | target-version = ['py36', 'py37', 'py38', 'py39'] 11 | include = '\.pyi?$' 12 | extend-exclude = ''' 13 | /( 14 | # The following are specific to Black, you probably don't want those. 15 | | blib2to3 16 | | tests/data 17 | | profiling 18 | | migrations 19 | )/ 20 | ''' 21 | 22 | 23 | # Build system information below. 24 | # NOTE: You don't need this in your own Black configuration. 25 | 26 | [build-system] 27 | requires = ["setuptools>=41.0", "setuptools-scm", "wheel"] 28 | build-backend = "setuptools.build_meta" 29 | -------------------------------------------------------------------------------- /examples/simple/books/README.rst: -------------------------------------------------------------------------------- 1 | books 2 | ===== 3 | Sample application. 4 | 5 | Most of the code taken from `official Django documentation 6 | `_. 7 | 8 | models 9 | ------ 10 | - Author 11 | - Book 12 | - Order 13 | - OrderLine 14 | - Publisher 15 | 16 | management commands 17 | ------------------- 18 | books_create_test_data 19 | ~~~~~~~~~~~~~~~~~~~~~~ 20 | Create project dummy data (dynamic fixtures). By default creates a 100 Book 21 | records. Accepts an optional --number parameter for customising the number 22 | of Book records created. 23 | 24 | **Create Book records** 25 | 26 | .. code-block:: sh 27 | 28 | ./manage.py books_create_test_data 29 | 30 | **Create 1000 Book records** 31 | 32 | .. code-block:: sh 33 | 34 | ./manage.py books_create_test_data --number=1000 35 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0005_auto_20170518_1553.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-18 20:53 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("books", "0004_auto_20170322_2001"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name="author", 17 | options={"ordering": ["id"]}, 18 | ), 19 | migrations.AlterModelOptions( 20 | name="publisher", 21 | options={"ordering": ["id"]}, 22 | ), 23 | migrations.AlterField( 24 | model_name="author", 25 | name="headshot", 26 | field=models.ImageField(blank=True, null=True, upload_to="authors"), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /examples/requirements/style_checkers.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile style_checkers.in 6 | # 7 | astroid==2.12.13 8 | # via pylint 9 | black==22.10.0 10 | # via -r style_checkers.in 11 | click==8.1.3 12 | # via black 13 | dill==0.3.6 14 | # via pylint 15 | isort==5.10.1 16 | # via pylint 17 | lazy-object-proxy==1.8.0 18 | # via astroid 19 | mccabe==0.7.0 20 | # via pylint 21 | mypy-extensions==0.4.3 22 | # via black 23 | pathspec==0.10.2 24 | # via black 25 | platformdirs==2.5.4 26 | # via 27 | # black 28 | # pylint 29 | pycodestyle==2.9.1 30 | # via -r style_checkers.in 31 | pylint==2.15.6 32 | # via -r style_checkers.in 33 | tomli==2.0.1 34 | # via 35 | # black 36 | # pylint 37 | tomlkit==0.11.6 38 | # via pylint 39 | wrapt==1.14.1 40 | # via astroid 41 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/author_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block meta-title %}Authors{% endblock %} 3 | 4 | {% block nav-menu-items %} 5 | {% with True as books_authors_is_active %} 6 | {% include "snippets/menu_items.html" %} 7 | {% endwith %} 8 | {% endblock nav-menu-items %} 9 | 10 | {% block main-content %} 11 |

Authors

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for author in object_list %} 21 | 22 | 23 | 24 | 25 | 26 | {% endfor %} 27 |
SalutationNameE-mail
{{ author.salutation }}{{ author.name }}{{ author.email }}
28 | {% endblock %} 29 | {% block sidebar-content %}{% endblock sidebar-content %} 30 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test utils. 3 | """ 4 | import pytest 5 | 6 | from ..utils import DictProxy 7 | 8 | from .base import BaseTestCase 9 | 10 | __author__ = "Artur Barseghyan " 11 | __copyright__ = "2017-2022 Artur Barseghyan" 12 | __license__ = "GPL-2.0-only OR LGPL-2.1-or-later" 13 | __all__ = ("TestUtils",) 14 | 15 | 16 | @pytest.mark.django_db 17 | class TestUtils(BaseTestCase): 18 | """Test utils.""" 19 | 20 | pytestmark = pytest.mark.django_db 21 | 22 | def test_dict_proxy(self): 23 | """Test DictProxy.""" 24 | 25 | __dict = { 26 | "name": self.faker.name(), 27 | "date": self.faker.date(), 28 | } 29 | 30 | __dict_proxy = DictProxy(__dict) 31 | 32 | for __key in __dict.keys(): 33 | self.assertEqual(getattr(__dict_proxy, __key), __dict[__key]) 34 | 35 | print(__dict_proxy) 36 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utils. 3 | """ 4 | 5 | import json 6 | 7 | __author__ = "Artur Barseghyan " 8 | __copyright__ = "2017-2022 Artur Barseghyan" 9 | __license__ = "GPL-2.0-only OR LGPL-2.1-or-later" 10 | __all__ = ("DictProxy",) 11 | 12 | 13 | class DictProxy: 14 | """Dictionary proxy. 15 | 16 | Example: 17 | 18 | >>> from rest_framework_tricks.utils import DictProxy 19 | >>> 20 | >>> 21 | >>> __dict = { 22 | >>> 'name': self.faker.name(), 23 | >>> 'date': self.faker.date(), 24 | >>> } 25 | >>> 26 | >>> __dict_proxy = DictProxy(__dict) 27 | """ 28 | 29 | def __init__(self, mapping): 30 | self.__mapping = mapping 31 | 32 | def __getattr__(self, item): 33 | return self.__mapping.get(item, None) 34 | 35 | def __str__(self): 36 | return json.dumps(self.__mapping) 37 | 38 | __repr__ = __str__ 39 | -------------------------------------------------------------------------------- /docs/rest_framework_tricks.rst: -------------------------------------------------------------------------------- 1 | rest\_framework\_tricks package 2 | =============================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | rest_framework_tricks.filters 10 | rest_framework_tricks.models 11 | rest_framework_tricks.serializers 12 | rest_framework_tricks.tests 13 | 14 | Submodules 15 | ---------- 16 | 17 | rest\_framework\_tricks\.apps module 18 | ------------------------------------ 19 | 20 | .. automodule:: rest_framework_tricks.apps 21 | :members: 22 | :undoc-members: 23 | :show-inheritance: 24 | 25 | rest\_framework\_tricks\.utils module 26 | ------------------------------------- 27 | 28 | .. automodule:: rest_framework_tricks.utils 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | 33 | 34 | Module contents 35 | --------------- 36 | 37 | .. automodule:: rest_framework_tricks 38 | :members: 39 | :undoc-members: 40 | :show-inheritance: 41 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0008_book_state.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.5 on 2017-06-19 12:02 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("books", "0007_book_description"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="book", 17 | name="state", 18 | field=models.CharField( 19 | choices=[ 20 | ("pulished", "Published"), 21 | ("not_published", "Not published"), 22 | ("in_progress", "In progress"), 23 | ("canelled", "Cancelled"), 24 | ("rejected", "Rejected"), 25 | ], 26 | default="pulished", 27 | max_length=100, 28 | ), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /examples/simple/factories/books_order.py: -------------------------------------------------------------------------------- 1 | """ 2 | Books Book model factory. 3 | """ 4 | 5 | from factory import SubFactory # , post_generation 6 | from factory.django import DjangoModelFactory 7 | 8 | from books.models import Order 9 | 10 | # from .factory_faker import Faker 11 | # from .books_orderline import OrderLineFactory 12 | 13 | __all__ = ("OrderFactory",) 14 | 15 | 16 | class OrderFactory(DjangoModelFactory): 17 | """Order factory.""" 18 | 19 | owner = SubFactory("factories.auth_user.UserFactory") 20 | # created = Faker('date') 21 | # updated = Faker('date') 22 | 23 | class Meta: 24 | """Meta class.""" 25 | 26 | model = Order 27 | 28 | # @post_generation 29 | # def order_lines(obj, created, extracted, **kwargs): 30 | # """Create `OrderLine` objects for the created `Order` instance.""" 31 | # if created: 32 | # # Create 4 `OrderLine` objects. 33 | # order_lines = OrderLineFactory.create_batch(4, **kwargs) 34 | # obj.order_lines.add(*order_lines) 35 | -------------------------------------------------------------------------------- /threadedtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | from multiprocessing import Process 4 | import os 5 | import sys 6 | 7 | import pytest 8 | 9 | 10 | def main(): 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.testing") 12 | sys.path.insert(0, "src") 13 | sys.path.insert(0, "examples/simple") 14 | 15 | parser = argparse.ArgumentParser(description="""\nTesting.\n""") 16 | parser.add_argument( 17 | "-t", 18 | "--threads", 19 | dest="threads", 20 | type=int, 21 | help="Number of threads", 22 | metavar="THREADS", 23 | ) 24 | __args = parser.parse_args() 25 | __threads = 5 26 | if __args.threads: 27 | sys.argv = sys.argv[1:] 28 | __threads = __args.threads 29 | 30 | __processes = [Process(target=pytest.main) for __i in range(__threads)] 31 | for __process in __processes: 32 | __process.start() 33 | return [__process.join() for __process in __processes] 34 | 35 | 36 | if __name__ == "__main__": 37 | sys.exit(main()) 38 | -------------------------------------------------------------------------------- /examples/simple/books/models/publisher.py: -------------------------------------------------------------------------------- 1 | """ 2 | Publisher models. 3 | """ 4 | 5 | from django.db import models 6 | 7 | from rest_framework_tricks.models.fields import NestedProxyField 8 | 9 | __all__ = ("Publisher",) 10 | 11 | 12 | class Publisher(models.Model): 13 | """Publisher.""" 14 | 15 | name = models.CharField(max_length=30) 16 | info = models.TextField(null=True, blank=True) 17 | address = models.CharField(max_length=255, null=True, blank=True) 18 | city = models.CharField(max_length=255, null=True, blank=True) 19 | state_province = models.CharField(max_length=255, null=True, blank=True) 20 | country = models.CharField(max_length=255, null=True, blank=True) 21 | website = models.URLField() 22 | 23 | # This does not cause a model change 24 | address_information = NestedProxyField( 25 | "address", "city", "state_province", "country", as_object=True 26 | ) 27 | 28 | class Meta: 29 | """Meta options.""" 30 | 31 | ordering = ["id"] 32 | 33 | def __str__(self): 34 | return self.name 35 | -------------------------------------------------------------------------------- /examples/django_rest_framework_tricks_demo_installer.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | wget -O rest_framework_tricks_demo_installer.tar.gz https://github.com/barseghyanartur/django-rest-framework-tricks/archive/stable.tar.gz 3 | virtualenv django-rest-framework-tricks-env 4 | source django-rest-framework-tricks-env/bin/activate 5 | mkdir rest_framework_tricks_demo_installer/ 6 | tar -xvf rest_framework_tricks_demo_installer.tar.gz -C rest_framework_tricks_demo_installer 7 | cd rest_framework_tricks_demo_installer/django-rest-framework-tricks-stable/examples/simple/ 8 | pip install -r ../../requirements.txt 9 | pip install https://github.com/barseghyanartur/django-rest-framework-tricks/archive/stable.tar.gz 10 | mkdir ../media/ 11 | mkdir ../media/static/ 12 | mkdir ../static/ 13 | mkdir ../db/ 14 | mkdir ../logs/ 15 | mkdir ../tmp/ 16 | cp settings/local_settings.example settings/local_settings.py 17 | ./manage.py migrate --noinput --traceback -v 3 18 | ./manage.py collectstatic --noinput --traceback -v 3 19 | ./manage.py books_create_test_data --number=20 --traceback -v 3 20 | ./manage.py runserver 0.0.0.0:8001 --traceback -v 3 21 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/book_list_values.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block meta-title %}Books (values){% endblock %} 3 | 4 | {% block nav-menu-items %} 5 | {% with True as books_books_values_is_active %} 6 | {% include "snippets/menu_items.html" %} 7 | {% endwith %} 8 | {% endblock nav-menu-items %} 9 | 10 | {% block main-content %} 11 |

Books

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for book in object_list %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 |
BookAuthorsPublisherNumber of pagesPrice
{{ book.title }}{{ book.authors__name}}{{ book.publisher__name }}{{ book.pages }}{{ book.price }}
32 | {% endblock %} 33 | {% block sidebar-content %}{% endblock sidebar-content %} 34 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0011_auto_20170630_1655.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-06-30 21:55 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("books", "0010_auto_20170629_1930"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="publisher", 17 | name="address", 18 | field=models.CharField(max_length=255), 19 | ), 20 | migrations.AlterField( 21 | model_name="publisher", 22 | name="city", 23 | field=models.CharField(max_length=255), 24 | ), 25 | migrations.AlterField( 26 | model_name="publisher", 27 | name="country", 28 | field=models.CharField(max_length=255), 29 | ), 30 | migrations.AlterField( 31 | model_name="publisher", 32 | name="state_province", 33 | field=models.CharField(max_length=255), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0009_auto_20170626_1331.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.2 on 2017-06-26 18:31 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("books", "0008_book_state"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="publisher", 17 | name="info", 18 | field=models.TextField(blank=True, null=True), 19 | ), 20 | migrations.AlterField( 21 | model_name="book", 22 | name="state", 23 | field=models.CharField( 24 | choices=[ 25 | ("published", "Published"), 26 | ("not_published", "Not published"), 27 | ("in_progress", "In progress"), 28 | ("cancelled", "Cancelled"), 29 | ("rejected", "Rejected"), 30 | ], 31 | default="published", 32 | max_length=100, 33 | ), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /examples/simple/factories/books_tag.py: -------------------------------------------------------------------------------- 1 | """ 2 | Books Tag model factory. 3 | """ 4 | 5 | import random 6 | 7 | from factory import LazyAttribute 8 | from factory.django import DjangoModelFactory 9 | 10 | from books.models import Tag 11 | 12 | from .factory_faker import Faker 13 | 14 | __all__ = ( 15 | "TagFactory", 16 | "LimitedTagFactory", 17 | ) 18 | 19 | 20 | class BaseTagFactory(DjangoModelFactory): 21 | """Base tag factory.""" 22 | 23 | # Although the ``max_length`` of the of the ``title`` field of the 24 | # ``Tag`` model is set to 255, for usability we set this one to 20. 25 | title = Faker("catch_phrase") 26 | 27 | class Meta: 28 | """Meta class.""" 29 | 30 | model = Tag 31 | abstract = True 32 | django_get_or_create = ("title",) 33 | 34 | 35 | class TagFactory(BaseTagFactory): 36 | """Tag factory.""" 37 | 38 | 39 | class LimitedTagFactory(BaseTagFactory): 40 | """Tag factory, but limited to 20 tags.""" 41 | 42 | id = LazyAttribute(lambda __x: random.randint(1, 20)) 43 | 44 | class Meta: 45 | """Meta class.""" 46 | 47 | django_get_or_create = ("id",) 48 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/book_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block meta-title %}Books{% endblock %} 3 | 4 | {% block nav-menu-items %} 5 | {% with True as books_books_is_active %} 6 | {% include "snippets/menu_items.html" %} 7 | {% endwith %} 8 | {% endblock nav-menu-items %} 9 | 10 | {% block main-content %} 11 |

Books

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for book in object_list %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 |
BookAuthorsPublisherNumber of pagesPrice
{{ book.title }}{% for author in book.authors.all %}{{ author.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{{ book.publisher.name }}{{ book.pages }}{{ book.price }}
32 | {% endblock %} 33 | {% block sidebar-content %}{% endblock sidebar-content %} 34 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/add_authors_to_book.html: -------------------------------------------------------------------------------- 1 | {% extends "books/book_list.html" %} 2 | {% block meta-title %}Add authors to a book{% endblock %} 3 | 4 | {% block nav-menu-items %} 5 | {% with True as books_add_authors_to_book_is_active %} 6 | {% include "snippets/menu_items.html" %} 7 | {% endwith %} 8 | {% endblock nav-menu-items %} 9 | 10 | {% block main-content %} 11 |

Books

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for book in object_list %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 |
BookAuthorsPublisherNumber of pagesPrice
{{ book.title }}{% for author in book_authors %}{{ author.name }},{% endfor %}{{ book.publisher.name }}{{ book.pages }}{{ book.price }}
32 | {% endblock %} 33 | {% block sidebar-content %}{% endblock sidebar-content %} 34 | -------------------------------------------------------------------------------- /scripts/compile_requirements.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd examples/requirements/ 3 | 4 | echo "pip-compile common.in" 5 | pip-compile common.in "$@" 6 | 7 | echo "pip-compile debug.in" 8 | pip-compile debug.in "$@" 9 | 10 | echo "pip-compile deployment.in" 11 | pip-compile deployment.in "$@" 12 | 13 | echo "pip-compile deployment.in" 14 | pip-compile dev.in "$@" 15 | 16 | echo "pip-compile django_2_2.in" 17 | pip-compile django_2_2.in "$@" 18 | 19 | echo "pip-compile django_3_0.in" 20 | pip-compile django_3_0.in "$@" 21 | 22 | echo "pip-compile django_3_1.in" 23 | pip-compile django_3_1.in "$@" 24 | 25 | echo "pip-compile django_3_2.in" 26 | pip-compile django_3_2.in "$@" 27 | 28 | echo "pip-compile django_4_0.in" 29 | pip-compile django_4_0.in "$@" 30 | 31 | echo "pip-compile django_4_1.in" 32 | pip-compile django_4_1.in "$@" 33 | 34 | echo "pip-compile docs.in" 35 | pip-compile docs.in "$@" 36 | 37 | echo "pip-compile documentation.in" 38 | pip-compile documentation.in "$@" 39 | 40 | echo "pip-compile style_checkers.in" 41 | pip-compile style_checkers.in "$@" 42 | 43 | echo "pip-compile test.in" 44 | pip-compile test.in "$@" 45 | 46 | echo "pip-compile testing.in" 47 | pip-compile testing.in "$@" 48 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0016_auto_20170714_1020.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.2 on 2017-07-14 15:20 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("books", "0015_auto_20170713_0418"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="publisher", 17 | name="address", 18 | field=models.CharField(blank=True, max_length=255, null=True), 19 | ), 20 | migrations.AlterField( 21 | model_name="publisher", 22 | name="city", 23 | field=models.CharField(blank=True, max_length=255, null=True), 24 | ), 25 | migrations.AlterField( 26 | model_name="publisher", 27 | name="country", 28 | field=models.CharField(blank=True, max_length=255, null=True), 29 | ), 30 | migrations.AlterField( 31 | model_name="publisher", 32 | name="state_province", 33 | field=models.CharField(blank=True, max_length=255, null=True), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /examples/simple/settings/local_settings.example: -------------------------------------------------------------------------------- 1 | import os 2 | from .core import PROJECT_DIR 3 | 4 | 5 | DEBUG = True 6 | DEBUG_TOOLBAR = True 7 | DEBUG_TEMPLATE = True 8 | DEV = True 9 | 10 | DATABASES = { 11 | 'default': { 12 | # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 13 | 'ENGINE': 'django.db.backends.sqlite3', 14 | # Or path to database file if using sqlite3. 15 | 'NAME': PROJECT_DIR('../../db/example.db'), 16 | # The following settings are not used with sqlite3: 17 | 'USER': 'root', 18 | 'PASSWORD': 'test', 19 | # Empty for localhost through domain sockets or '127.0.0.1' for 20 | # localhost through TCP. 21 | 'HOST': '', 22 | # Set to empty string for default. 23 | 'PORT': '', 24 | } 25 | } 26 | 27 | INTERNAL_IPS = ('127.0.0.1',) 28 | 29 | EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' 30 | EMAIL_FILE_PATH = PROJECT_DIR('../../tmp') 31 | 32 | DEFAULT_FROM_EMAIL = '' 33 | 34 | os.environ.setdefault( 35 | 'DJANGO_REST_FRAMEWORK_TRICKS_SOURCE_PATH', 36 | '/home/user/repositories/django-rest-framework-tricks/src' 37 | ) 38 | 39 | # LOGGING = {} # Disable logging 40 | -------------------------------------------------------------------------------- /examples/requirements/debug.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile debug.in 6 | # 7 | asttokens==2.1.0 8 | # via stack-data 9 | backcall==0.2.0 10 | # via ipython 11 | decorator==5.1.1 12 | # via 13 | # ipdb 14 | # ipython 15 | executing==1.2.0 16 | # via stack-data 17 | ipdb==0.13.9 18 | # via -r debug.in 19 | ipython==8.6.0 20 | # via 21 | # -r debug.in 22 | # ipdb 23 | jedi==0.18.2 24 | # via ipython 25 | matplotlib-inline==0.1.6 26 | # via ipython 27 | parso==0.8.3 28 | # via jedi 29 | pexpect==4.8.0 30 | # via ipython 31 | pickleshare==0.7.5 32 | # via ipython 33 | prompt-toolkit==3.0.33 34 | # via ipython 35 | ptyprocess==0.7.0 36 | # via pexpect 37 | pure-eval==0.2.2 38 | # via stack-data 39 | pygments==2.13.0 40 | # via ipython 41 | six==1.16.0 42 | # via asttokens 43 | stack-data==0.6.1 44 | # via ipython 45 | toml==0.10.2 46 | # via ipdb 47 | traitlets==5.5.0 48 | # via 49 | # ipython 50 | # matplotlib-inline 51 | wcwidth==0.2.5 52 | # via prompt-toolkit 53 | 54 | # The following packages are considered to be unsafe in a requirements file: 55 | # setuptools 56 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/book_list_with_counts.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block meta-title %}Books (with counts){% endblock %} 3 | 4 | {% block nav-menu-items %} 5 | {% with True as books_books_with_counts_is_active %} 6 | {% include "snippets/menu_items.html" %} 7 | {% endwith %} 8 | {% endblock nav-menu-items %} 9 | 10 | {% block main-content %} 11 |

Books

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for book in object_list %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {% endfor %} 33 |
BookAuthorsPublisherNumber of pagesPricePrice per page
{{ book.title }}{% for author in book.authors.all %}{{ author.name }},{% endfor %}{{ book.publisher.name }}{{ book.pages }}{{ book.price }}{{ book.price_per_page }}
34 | {% endblock %} 35 | {% block sidebar-content %}{% endblock sidebar-content %} 36 | -------------------------------------------------------------------------------- /docs/rest_framework_tricks.tests.rst: -------------------------------------------------------------------------------- 1 | rest\_framework\_tricks\.tests package 2 | ====================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | rest\_framework\_tricks\.tests\.base module 8 | ------------------------------------------- 9 | 10 | .. automodule:: rest_framework_tricks.tests.base 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | rest\_framework\_tricks\.tests\.test\_nested\_proxy\_field module 16 | ----------------------------------------------------------------- 17 | 18 | .. automodule:: rest_framework_tricks.tests.test_nested_proxy_field 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | rest\_framework\_tricks\.tests\.test\_ordering\_filter module 24 | ------------------------------------------------------------- 25 | 26 | .. automodule:: rest_framework_tricks.tests.test_ordering_filter 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | rest\_framework\_tricks\.tests\.test\_utils module 32 | -------------------------------------------------- 33 | 34 | .. automodule:: rest_framework_tricks.tests.test_utils 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: rest_framework_tricks.tests 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /examples/simple/templates/snippets/article.html: -------------------------------------------------------------------------------- 1 |
2 |

Blog Post Title

3 |
Written by John Smith on August 12, 2012.
4 |
5 |
6 |

Bacon ipsum dolor sit amet nulla ham qui sint exercitation eiusmod commodo, chuck duis velit. Aute in reprehenderit, dolore aliqua non est magna in labore pig pork biltong. Eiusmod swine spare ribs reprehenderit culpa.

7 |

Boudin aliqua adipisicing rump corned beef. Nulla corned beef sunt ball tip, qui bresaola enim jowl. Capicola short ribs minim salami nulla nostrud pastrami.

8 |
9 |
10 | 11 |
12 |
13 |

Pork drumstick turkey fugiat. Tri-tip elit turducken pork chop in. Swine short ribs meatball irure bacon nulla pork belly cupidatat meatloaf cow. Nulla corned beef sunt ball tip, qui bresaola enim jowl. Capicola short ribs minim salami nulla nostrud pastrami. Nulla corned beef sunt ball tip, qui bresaola enim jowl. Capicola short ribs minim salami nulla nostrud pastrami.

14 |

Pork drumstick turkey fugiat. Tri-tip elit turducken pork chop in. Swine short ribs meatball irure bacon nulla pork belly cupidatat meatloaf cow. Nulla corned beef sunt ball tip, qui bresaola enim jowl. Capicola short ribs minim salami nulla nostrud pastrami.

15 |
-------------------------------------------------------------------------------- /examples/simple/books/migrations/0004_auto_20170322_2001.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("books", "0003_book_pages"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="book", 16 | name="stock_count", 17 | field=models.PositiveIntegerField(default=30), 18 | ), 19 | migrations.AlterField( 20 | model_name="book", 21 | name="authors", 22 | field=models.ManyToManyField(related_name="books", to="books.Author"), 23 | ), 24 | migrations.AlterField( 25 | model_name="book", 26 | name="pages", 27 | field=models.PositiveIntegerField(default=200), 28 | ), 29 | migrations.AlterField( 30 | model_name="book", 31 | name="publisher", 32 | field=models.ForeignKey( 33 | related_name="books", to="books.Publisher", on_delete=models.CASCADE 34 | ), 35 | ), 36 | migrations.AlterField( 37 | model_name="orderline", 38 | name="book", 39 | field=models.ForeignKey( 40 | related_name="order_lines", to="books.Book", on_delete=models.CASCADE 41 | ), 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /examples/simple/books/management/commands/books_create_test_data.py: -------------------------------------------------------------------------------- 1 | """ 2 | Create test book data. 3 | """ 4 | 5 | from __future__ import unicode_literals 6 | 7 | from django.core.management.base import BaseCommand, CommandError 8 | 9 | import factories 10 | 11 | 12 | DEFAULT_NUMBER_OF_ITEMS_TO_CREATE = 100 13 | 14 | 15 | class Command(BaseCommand): 16 | """Create test data.""" 17 | 18 | help = "Create test books data." 19 | 20 | requires_system_checks = False 21 | 22 | def add_arguments(self, parser): 23 | parser.add_argument( 24 | "--number", 25 | action="store", 26 | dest="number", 27 | type=int, 28 | default=DEFAULT_NUMBER_OF_ITEMS_TO_CREATE, 29 | help="Number of items to create.", 30 | ) 31 | 32 | def handle(self, *args, **options): 33 | if options.get("number"): 34 | number = options["number"] 35 | else: 36 | number = DEFAULT_NUMBER_OF_ITEMS_TO_CREATE 37 | 38 | try: 39 | books = factories.BookFactory.create_batch(number) 40 | print("{} book objects created.".format(number)) 41 | except Exception as err: 42 | raise CommandError(str(err)) 43 | 44 | try: 45 | book = factories.SingleBookFactory() 46 | print("A single book object is created.") 47 | except Exception as err: 48 | raise CommandError(str(err)) 49 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0006_auto_20170619_0428.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.5 on 2017-06-19 09:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("books", "0005_auto_20170518_1553"), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="Tag", 17 | fields=[ 18 | ( 19 | "id", 20 | models.AutoField( 21 | auto_created=True, 22 | primary_key=True, 23 | serialize=False, 24 | verbose_name="ID", 25 | ), 26 | ), 27 | ("title", models.CharField(max_length=255, unique=True)), 28 | ], 29 | options={ 30 | "verbose_name_plural": "Tags", 31 | "verbose_name": "Tag", 32 | }, 33 | ), 34 | migrations.AddField( 35 | model_name="book", 36 | name="summary", 37 | field=models.TextField(blank=True, null=True), 38 | ), 39 | migrations.AddField( 40 | model_name="book", 41 | name="tags", 42 | field=models.ManyToManyField( 43 | blank=True, related_name="books", to="books.Tag" 44 | ), 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py{37,38,39,310}-{django22}-djangorestframework{310} 4 | py{37,38,39,310}-{django30}-djangorestframework{311} 5 | py{37,38,39,310}-{django31}-djangorestframework{312} 6 | py{37,38,39,310,311}-{django32}-djangorestframework{312} 7 | py{38,39,310,311}-{django40}-djangorestframework{312} 8 | py{38,39,310,311}-{django41}-djangorestframework{313} 9 | ; py{38,39,310,311}-{django41}-djangorestframework{314} 10 | 11 | [testenv] 12 | ;envlogdir= 13 | ; examples/logs/ 14 | ; examples/db/ 15 | ; examples/tmp/ 16 | passenv = * 17 | deps = 18 | django22: -r{toxinidir}/examples/requirements/django_2_2.txt 19 | django30: -r{toxinidir}/examples/requirements/django_3_0.txt 20 | django31: -r{toxinidir}/examples/requirements/django_3_1.txt 21 | django32: -r{toxinidir}/examples/requirements/django_3_2.txt 22 | django40: -r{toxinidir}/examples/requirements/django_4_0.txt 23 | django41: -r{toxinidir}/examples/requirements/django_4_1.txt 24 | djangorestframework310: djangorestframework>=3.10,<3.11 25 | djangorestframework311: djangorestframework>=3.11.0,<3.12 26 | djangorestframework312: djangorestframework>=3.12.0,<3.13 27 | djangorestframework313: djangorestframework>=3.13.0,<3.14 28 | ; djangorestframework314: djangorestframework>=3.14.0,<3.15 29 | commands = 30 | {envpython} -m pytest -vvv 31 | 32 | [gh-actions] 33 | python = 34 | 3.7: py37 35 | 3.8: py38 36 | 3.9: py39 37 | 3.10: py310 38 | 3.11: py311 39 | -------------------------------------------------------------------------------- /examples/simple/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | from django.core.wsgi import get_wsgi_application 19 | 20 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 21 | # if running multiple sites in the same mod_wsgi process. To fix this, use 22 | # mod_wsgi daemon mode with each site in its own daemon process, or use 23 | # os.environ["DJANGO_SETTINGS_MODULE"] = "example.settings" 24 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 25 | 26 | # This application object is used by any WSGI server configured to use this 27 | # file. This includes Django's development server, if the WSGI_APPLICATION 28 | # setting points here. 29 | application = get_wsgi_application() 30 | 31 | # Apply WSGI middleware here. 32 | # from helloworld.wsgi import HelloWorldApplication 33 | # application = HelloWorldApplication(application) 34 | -------------------------------------------------------------------------------- /examples/requirements/test.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile test.in 6 | # 7 | attrs==22.1.0 8 | # via pytest 9 | coverage[toml]==6.5.0 10 | # via 11 | # -r test.in 12 | # pytest-cov 13 | distlib==0.3.6 14 | # via virtualenv 15 | factory-boy==3.2.1 16 | # via -r test.in 17 | faker==15.3.3 18 | # via 19 | # -r test.in 20 | # factory-boy 21 | filelock==3.8.0 22 | # via 23 | # tox 24 | # virtualenv 25 | iniconfig==1.1.1 26 | # via pytest 27 | packaging==21.3 28 | # via 29 | # pytest 30 | # tox 31 | platformdirs==2.5.4 32 | # via virtualenv 33 | pluggy==0.13.1 34 | # via 35 | # pytest 36 | # tox 37 | py==1.11.0 38 | # via 39 | # -r test.in 40 | # pytest 41 | # tox 42 | pyparsing==3.0.9 43 | # via packaging 44 | pytest==6.2.4 45 | # via 46 | # -r test.in 47 | # pytest-cov 48 | # pytest-django 49 | # pytest-ordering 50 | # pytest-pythonpath 51 | pytest-cov==2.12.0 52 | # via -r test.in 53 | pytest-django==4.3.0 54 | # via -r test.in 55 | pytest-ordering==0.6 56 | # via -r test.in 57 | pytest-pythonpath==0.7.4 58 | # via -r test.in 59 | python-dateutil==2.8.2 60 | # via faker 61 | six==1.16.0 62 | # via 63 | # python-dateutil 64 | # tox 65 | toml==0.10.2 66 | # via pytest 67 | tomli==2.0.1 68 | # via 69 | # coverage 70 | # tox 71 | tox==3.27.1 72 | # via -r test.in 73 | virtualenv==20.16.7 74 | # via tox 75 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: test ${{ matrix.python-version }} - ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }}-latest 9 | strategy: 10 | fail-fast: true 11 | max-parallel: 4 12 | matrix: 13 | os: 14 | - Ubuntu 15 | # - Windows 16 | # - MacOs 17 | python-version: 18 | - "3.11" 19 | - "3.10" 20 | - "3.9" 21 | - "3.8" 22 | - "3.7" 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | - name: Install Dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | - name: Install tox 34 | run: python -m pip install tox-gh-actions 35 | - name: Run test suite 36 | run: tox -r 37 | env: 38 | PYTEST_ADDOPTS: "-vv --durations=10" 39 | - name: Coveralls 40 | uses: AndreMiras/coveralls-python-action@develop 41 | with: 42 | parallel: true 43 | flag-name: Run Tests 44 | 45 | coveralls_finish: 46 | needs: test 47 | runs-on: ubuntu-latest 48 | steps: 49 | - name: Coveralls Finished 50 | env: 51 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 52 | GITHUB_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 53 | uses: AndreMiras/coveralls-python-action@develop 54 | with: 55 | parallel-finished: true 56 | debug: True 57 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/helpers.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | from typing import List, Dict, Any 3 | 4 | __author__ = "Artur Barseghyan " 5 | __copyright__ = "2017-2022 Artur Barseghyan" 6 | __license__ = "GPL-2.0-only OR LGPL-2.1-or-later" 7 | __all__ = ( 8 | "get_nested_data_field_names", 9 | "flatten_nested_data", 10 | ) 11 | 12 | 13 | def get_nested_data_field_names(serializer) -> List[str]: 14 | """Get nested data field names.""" 15 | nested_field_names = [] 16 | for field_name, field in serializer.fields.items(): 17 | if getattr(field, "Meta", None) and getattr( 18 | field.Meta, "nested_proxy_field", False 19 | ): 20 | nested_field_names.append(field_name) 21 | return nested_field_names 22 | 23 | 24 | def flatten_nested_data( 25 | validated_data: Dict[str, Any], nested_field_names: List[str] 26 | ) -> Dict[str, Any]: 27 | """Flatten nested data. 28 | 29 | Usage example: 30 | 31 | class MySerializer(serializers.ModelSerializer): 32 | 33 | def create(self, validated_data): 34 | # Do something else 35 | nested_field_names = get_nested_field_names(self) 36 | validated_data = flatten_nested_data( 37 | validated_data, nested_field_names 38 | ) 39 | return super().create(validated_data) 40 | """ 41 | validated_data = deepcopy(validated_data) 42 | for field_name in nested_field_names: 43 | data = validated_data.pop(field_name, None) 44 | if data: 45 | validated_data.update(data) 46 | return validated_data 47 | -------------------------------------------------------------------------------- /examples/simple/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | Urls. 3 | """ 4 | 5 | from django.urls import include, re_path as url, path 6 | from django.conf import settings 7 | from django.contrib import admin 8 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 9 | from django.conf.urls.static import static 10 | from django.views.generic import TemplateView 11 | from drf_spectacular.views import ( 12 | SpectacularAPIView, 13 | SpectacularRedocView, 14 | SpectacularSwaggerView, 15 | ) 16 | 17 | from books import urls as books_urls 18 | 19 | __all__ = ("urlpatterns",) 20 | 21 | admin.autodiscover() 22 | 23 | urlpatterns = [ 24 | # Admin URLs 25 | url(r"^admin/", admin.site.urls), 26 | 27 | # Books URLs 28 | url(r"^books/", include(books_urls)), 29 | # Home page 30 | url(r"^$", TemplateView.as_view(template_name="home.html")), 31 | 32 | # Schema 33 | path("schema/", SpectacularAPIView.as_view(), name="schema"), 34 | path( 35 | "schema/swagger/", 36 | SpectacularSwaggerView.as_view(url_name="schema"), 37 | name="swagger-ui", 38 | ), 39 | path( 40 | "schema/redoc/", 41 | SpectacularRedocView.as_view(url_name="schema"), 42 | name="redoc", 43 | ), 44 | ] 45 | 46 | # Serving media and static in debug/developer mode. 47 | if settings.DEBUG: 48 | urlpatterns += staticfiles_urlpatterns() 49 | urlpatterns += static( 50 | settings.MEDIA_URL, document_root=settings.MEDIA_ROOT 51 | ) 52 | 53 | if settings.DEBUG_TOOLBAR is True: 54 | import debug_toolbar 55 | 56 | urlpatterns = [ 57 | url(r"^__debug__/", include(debug_toolbar.urls)), 58 | ] + urlpatterns 59 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/tests/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base tests. 3 | """ 4 | import logging 5 | 6 | from django.test import TestCase 7 | from rest_framework.test import APITestCase 8 | from faker import Faker 9 | 10 | import pytest 11 | 12 | import factories 13 | 14 | __author__ = "Artur Barseghyan " 15 | __copyright__ = "2017-2022 Artur Barseghyan" 16 | __license__ = "GPL-2.0-only OR LGPL-2.1-or-later" 17 | __all__ = ( 18 | "BaseRestFrameworkTestCase", 19 | "BaseTestCase", 20 | ) 21 | 22 | LOGGER = logging.getLogger(__name__) 23 | 24 | 25 | @pytest.mark.django_db 26 | class BaseRestFrameworkTestCase(APITestCase): 27 | """Base REST framework test case.""" 28 | 29 | pytestmark = pytest.mark.django_db 30 | 31 | @classmethod 32 | def setUpTestData(cls): 33 | """Set up class.""" 34 | 35 | # Create user 36 | cls.user = factories.TestUsernameSuperAdminUserFactory() 37 | 38 | # Fake data 39 | cls.faker = Faker() 40 | 41 | def authenticate(self): 42 | """Helper for logging the user in. 43 | 44 | :return: 45 | """ 46 | self.client.login( 47 | username=factories.auth_user.TEST_USERNAME, 48 | password=factories.auth_user.TEST_PASSWORD, 49 | ) 50 | 51 | 52 | @pytest.mark.django_db 53 | class BaseTestCase(TestCase): 54 | """Base test case.""" 55 | 56 | pytestmark = pytest.mark.django_db 57 | 58 | @classmethod 59 | def setUpTestData(cls): 60 | """Set up class.""" 61 | 62 | # Create user 63 | cls.user = factories.TestUsernameSuperAdminUserFactory() 64 | 65 | # Fake data 66 | cls.faker = Faker() 67 | -------------------------------------------------------------------------------- /examples/simple/factories/books_publisher.py: -------------------------------------------------------------------------------- 1 | """ 2 | Books Publisher model factory. 3 | """ 4 | 5 | import random 6 | 7 | from factory import LazyAttribute 8 | from factory.django import DjangoModelFactory 9 | 10 | from books.models import Publisher 11 | 12 | from .factory_faker import Faker 13 | 14 | __all__ = ( 15 | "PublisherFactory", 16 | "LimitedPublisherFactory", 17 | "SinglePublisherFactory", 18 | ) 19 | 20 | 21 | class BasePublisherFactory(DjangoModelFactory): 22 | """Base publisher factory.""" 23 | 24 | name = Faker("company") 25 | address = Faker("address") 26 | city = Faker("city") 27 | state_province = Faker("province") 28 | country = Faker("country") 29 | website = Faker("url") 30 | 31 | class Meta: 32 | """Meta class.""" 33 | 34 | model = Publisher 35 | abstract = True 36 | 37 | 38 | class PublisherFactory(BasePublisherFactory): 39 | """Publisher factory.""" 40 | 41 | 42 | class LimitedPublisherFactory(BasePublisherFactory): 43 | """Publisher factory, but limited to 20 publishers.""" 44 | 45 | id = LazyAttribute(lambda __x: random.randint(1, 20)) 46 | 47 | class Meta: 48 | """Meta class.""" 49 | 50 | django_get_or_create = ("id",) 51 | 52 | 53 | class SinglePublisherFactory(BasePublisherFactory): 54 | """Publisher factory, but limited to a single publisher.""" 55 | 56 | id = 999999 57 | name = "GWW" 58 | address = "Schuitendiep 3" 59 | city = "Groningen" 60 | state_province = "Groningen" 61 | country = "NL" 62 | website = "https://gw20e.com" 63 | 64 | class Meta: 65 | """Meta class.""" 66 | 67 | django_get_or_create = ("id",) 68 | -------------------------------------------------------------------------------- /examples/simple/settings/testing.py: -------------------------------------------------------------------------------- 1 | from .base import * # NOQA 2 | 3 | DEV = False 4 | DEBUG = False 5 | DEBUG_TOOLBAR = False 6 | DEBUG_TOOLBAR_PANELS = {} 7 | 8 | DATABASES = { 9 | "default": { 10 | "ENGINE": "django.db.backends.sqlite3", 11 | "NAME": PROJECT_DIR(os.path.join("..", "..", "db", "example.db")), 12 | } 13 | } 14 | 15 | # A sample logging configuration. The only tangible logging 16 | # performed by this configuration is to send an email to 17 | # the site admins on every HTTP 500 error when DEBUG=False. 18 | # See http://docs.djangoproject.com/en/dev/topics/logging for 19 | # more details on how to customize your logging configuration. 20 | LOGGING = { 21 | "version": 1, 22 | "disable_existing_loggers": False, 23 | "filters": { 24 | "require_debug_false": {"()": "django.utils.log.RequireDebugFalse"} 25 | }, 26 | "root": { 27 | "level": "DEBUG", 28 | "handlers": ["console"], 29 | }, 30 | "formatters": { 31 | "verbose": { 32 | "format": "\n%(levelname)s %(asctime)s [%(pathname)s:%(lineno)s] " 33 | "%(message)s" 34 | }, 35 | }, 36 | "handlers": { 37 | "mail_admins": { 38 | "level": "ERROR", 39 | "filters": ["require_debug_false"], 40 | "class": "django.utils.log.AdminEmailHandler", 41 | }, 42 | "console": { 43 | "level": "ERROR", 44 | "class": "logging.StreamHandler", 45 | "formatter": "verbose", 46 | }, 47 | }, 48 | "loggers": { 49 | "": { 50 | "handlers": ["console"], 51 | "level": "DEBUG", 52 | "propagate": False, 53 | }, 54 | }, 55 | } 56 | -------------------------------------------------------------------------------- /examples/simple/books/serializers/publisher.py: -------------------------------------------------------------------------------- 1 | """ 2 | Publisher serializers. 3 | """ 4 | 5 | from rest_framework import serializers 6 | from rest_framework_tricks.serializers import ( 7 | HyperlinkedModelSerializer, 8 | ModelSerializer, 9 | ) 10 | 11 | from ..models import Publisher 12 | 13 | __all__ = ("PublisherSerializer",) 14 | 15 | # **************************************************************************** 16 | # ****************************** Publisher *********************************** 17 | # **************************************************************************** 18 | 19 | 20 | class AddressInformationSerializer(serializers.ModelSerializer): 21 | """Address information serializer.""" 22 | 23 | address = serializers.CharField(required=False, allow_null=True) 24 | city = serializers.CharField(required=False, allow_null=True) 25 | state_province = serializers.CharField(required=False, allow_null=True) 26 | country = serializers.CharField(required=False, allow_null=True) 27 | 28 | class Meta: 29 | """Meta options.""" 30 | 31 | model = Publisher 32 | fields = ( 33 | "address", 34 | "city", 35 | "state_province", 36 | "country", 37 | ) 38 | nested_proxy_field = True 39 | 40 | 41 | class PublisherSerializer(ModelSerializer): 42 | """Publisher serializer.""" 43 | 44 | address_information = AddressInformationSerializer(required=False) 45 | 46 | class Meta: 47 | """Meta options.""" 48 | 49 | model = Publisher 50 | fields = ( 51 | "id", 52 | "name", 53 | "info", 54 | "website", 55 | "address_information", 56 | ) 57 | -------------------------------------------------------------------------------- /examples/simple/factories/books_author.py: -------------------------------------------------------------------------------- 1 | """ 2 | Books Author model factory. 3 | """ 4 | 5 | import random 6 | 7 | from factory import LazyAttribute 8 | from factory.django import DjangoModelFactory 9 | 10 | from books.models import Author 11 | 12 | from .factory_faker import Faker 13 | 14 | __all__ = ( 15 | "AuthorFactory", 16 | "LimitedAuthorFactory", 17 | "SingleAuthorFactory", 18 | ) 19 | 20 | 21 | class BaseAuthorFactory(DjangoModelFactory): 22 | """Base author factory.""" 23 | 24 | salutation = Faker("text", max_nb_chars=10) 25 | name = Faker("name") 26 | email = Faker("email") 27 | birth_date = Faker("date") 28 | biography = Faker("text") 29 | phone_number = Faker("phone_number") 30 | website = Faker("url") 31 | company = Faker("company") 32 | company_phone_number = Faker("phone_number") 33 | company_email = Faker("email") 34 | company_website = Faker("url") 35 | 36 | class Meta: 37 | """Meta class.""" 38 | 39 | model = Author 40 | abstract = True 41 | 42 | 43 | class AuthorFactory(BaseAuthorFactory): 44 | """Author factory.""" 45 | 46 | 47 | class LimitedAuthorFactory(BaseAuthorFactory): 48 | """Author factory, but limited to 20 authors.""" 49 | 50 | id = LazyAttribute(lambda __x: random.randint(1, 20)) 51 | 52 | class Meta: 53 | """Meta class.""" 54 | 55 | django_get_or_create = ("id",) 56 | 57 | 58 | class SingleAuthorFactory(BaseAuthorFactory): 59 | """Author factory, limited to a single author.""" 60 | 61 | id = 999999 62 | name = "Artur Barseghyan" 63 | email = "barseghyan@gw20e.com" 64 | 65 | class Meta: 66 | """Meta class.""" 67 | 68 | django_get_or_create = ("id",) 69 | -------------------------------------------------------------------------------- /examples/simple/books/admin.py: -------------------------------------------------------------------------------- 1 | """ 2 | Books module admin. 3 | """ 4 | 5 | from django.contrib import admin 6 | 7 | from .models import * 8 | 9 | __all__ = ( 10 | "AuthorAdmin", 11 | "BookAdmin", 12 | "OrderAdmin", 13 | "OrderLineAdmin", 14 | "PublisherAdmin", 15 | "TagAdmin", 16 | ) 17 | 18 | 19 | @admin.register(OrderLine) 20 | class OrderLineAdmin(admin.ModelAdmin): 21 | """Order line admin.""" 22 | 23 | list_display = ( 24 | "id", 25 | "book", 26 | ) 27 | search_fields = ("book__title",) 28 | 29 | 30 | @admin.register(Order) 31 | class OrderAdmin(admin.ModelAdmin): 32 | """Order admin.""" 33 | 34 | list_display = ("id", "created", "owner") 35 | filter_horizontal = ("lines",) 36 | # readonly_fields = ('lines',) 37 | fields = ( 38 | "owner", 39 | "lines", 40 | ) 41 | 42 | 43 | @admin.register(Book) 44 | class BookAdmin(admin.ModelAdmin): 45 | """Book admin.""" 46 | 47 | list_display = ("title", "isbn", "price", "publication_date") 48 | search_fields = ("title",) 49 | filter_horizontal = ( 50 | "authors", 51 | "tags", 52 | ) 53 | 54 | 55 | @admin.register(Author) 56 | class AuthorAdmin(admin.ModelAdmin): 57 | """Author admin.""" 58 | 59 | list_display = ( 60 | "name", 61 | "email", 62 | ) 63 | search_fields = ("name",) 64 | 65 | 66 | @admin.register(Publisher) 67 | class PublisherAdmin(admin.ModelAdmin): 68 | """Publisher admin.""" 69 | 70 | list_display = ("name",) 71 | search_fields = ("name",) 72 | 73 | 74 | @admin.register(Tag) 75 | class TagAdmin(admin.ModelAdmin): 76 | """Tag admin.""" 77 | 78 | list_display = ("title",) 79 | search_fields = ("title",) 80 | -------------------------------------------------------------------------------- /examples/requirements/docs.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile docs.in 6 | # 7 | alabaster==0.7.12 8 | # via sphinx 9 | babel==2.11.0 10 | # via sphinx 11 | certifi==2022.9.24 12 | # via requests 13 | charset-normalizer==2.1.1 14 | # via requests 15 | docutils==0.19 16 | # via 17 | # -r docs.in 18 | # rst2pdf 19 | # rstcheck 20 | # sphinx 21 | idna==3.4 22 | # via requests 23 | imagesize==1.4.1 24 | # via sphinx 25 | importlib-metadata==5.0.0 26 | # via rst2pdf 27 | jinja2==3.1.2 28 | # via 29 | # -r docs.in 30 | # rst2pdf 31 | # sphinx 32 | markupsafe==2.1.1 33 | # via 34 | # -r docs.in 35 | # jinja2 36 | packaging==21.3 37 | # via 38 | # rst2pdf 39 | # sphinx 40 | pillow==9.3.0 41 | # via reportlab 42 | pygments==2.13.0 43 | # via 44 | # rst2pdf 45 | # sphinx 46 | pyparsing==3.0.9 47 | # via packaging 48 | pytz==2022.6 49 | # via babel 50 | pyyaml==6.0 51 | # via rst2pdf 52 | reportlab==3.6.12 53 | # via rst2pdf 54 | requests==2.28.1 55 | # via sphinx 56 | rst2pdf==0.99 57 | # via -r docs.in 58 | rstcheck==3.3.1 59 | # via -r docs.in 60 | smartypants==2.0.1 61 | # via rst2pdf 62 | snowballstemmer==2.2.0 63 | # via sphinx 64 | sphinx==5.3.0 65 | # via -r docs.in 66 | sphinxcontrib-applehelp==1.0.2 67 | # via sphinx 68 | sphinxcontrib-devhelp==1.0.2 69 | # via sphinx 70 | sphinxcontrib-htmlhelp==2.0.0 71 | # via sphinx 72 | sphinxcontrib-jsmath==1.0.1 73 | # via sphinx 74 | sphinxcontrib-qthelp==1.0.3 75 | # via sphinx 76 | sphinxcontrib-serializinghtml==1.1.5 77 | # via sphinx 78 | urllib3==1.26.12 79 | # via requests 80 | zipp==3.10.0 81 | # via importlib-metadata 82 | -------------------------------------------------------------------------------- /scripts/README.rst: -------------------------------------------------------------------------------- 1 | scripts 2 | ======= 3 | Various helper (shell) scripts. Prior running any of those, please install 4 | the project requirements. 5 | 6 | .. code-block:: sh 7 | 8 | pip install -r requirements.txt 9 | 10 | To set up the example project, type: 11 | 12 | .. code-block:: sh 13 | 14 | ./scripts/prepare_project.sh 15 | 16 | build_docs.sh 17 | ------------- 18 | Build project documentation. 19 | 20 | clean_up.sh 21 | ----------- 22 | Removes temporary files, compiled python files, log files. 23 | 24 | collectstatic.sh 25 | ---------------- 26 | Collect the project static. Shortcut for collecting the statics from current 27 | directory without switching to the ``examples/simple`` directory. 28 | 29 | create_test_data.sh 30 | ------------------- 31 | Create test project data (100 book records, authors, publishers, orders at 32 | once). 33 | 34 | prepare_docs.sh 35 | --------------- 36 | Prepares necessary changes for building the docs (combines multiple files 37 | together) but does not yet build documentation. 38 | 39 | prepare_project.sh 40 | ------------------ 41 | Makes necessary changes to the settings, runs migrations, collects statics, 42 | creates necessary directories, creates project data. 43 | 44 | pycodestyle_example.sh 45 | ---------------------- 46 | Checks the project code using ``pycodestyle`` package. 47 | 48 | pylint_example.sh 49 | ----------------- 50 | Checks the project code using ``pylint`` package. 51 | 52 | rebuild_docs.sh 53 | --------------- 54 | Run this each time you have changed a lot of things in the package (added lots 55 | of modules). 56 | 57 | runserver.sh 58 | ------------ 59 | Shortcut for running the Django dev server from current directory without 60 | switching to the ``examples/simple`` directory. 61 | 62 | shell.sh 63 | -------- 64 | Shortcut for running the Django dev shell from current directory without 65 | switching to the ``examples/simple`` directory. 66 | -------------------------------------------------------------------------------- /examples/simple/factories/books_profile.py: -------------------------------------------------------------------------------- 1 | """ 2 | Books Profile model factory. 3 | """ 4 | 5 | import random 6 | 7 | from factory import LazyAttribute 8 | from factory.django import DjangoModelFactory 9 | 10 | from books.models import Profile 11 | 12 | from .factory_faker import Faker 13 | 14 | __all__ = ( 15 | "ProfileFactory", 16 | "LimitedProfileFactory", 17 | "SingleProfileFactory", 18 | ) 19 | 20 | 21 | class BaseProfileFactory(DjangoModelFactory): 22 | """Base author factory.""" 23 | 24 | salutation = Faker("text", max_nb_chars=10) 25 | first_name = Faker("first_name") 26 | last_name = Faker("last_name") 27 | email = Faker("email") 28 | birth_date = Faker("date") 29 | biography = Faker("text") 30 | phone_number = Faker("phone_number") 31 | website = Faker("url") 32 | company = Faker("company") 33 | company_phone_number = Faker("phone_number") 34 | company_email = Faker("email") 35 | company_website = Faker("url") 36 | bank_name = Faker("company") 37 | bank_account_name = Faker("name") 38 | bank_account_number = Faker("pystr") 39 | 40 | class Meta: 41 | """Meta class.""" 42 | 43 | model = Profile 44 | abstract = True 45 | 46 | 47 | class ProfileFactory(BaseProfileFactory): 48 | """Profile factory.""" 49 | 50 | 51 | class LimitedProfileFactory(BaseProfileFactory): 52 | """Profile factory, but limited to 20 profiles.""" 53 | 54 | id = LazyAttribute(lambda __x: random.randint(1, 20)) 55 | 56 | class Meta: 57 | """Meta class.""" 58 | 59 | django_get_or_create = ("id",) 60 | 61 | 62 | class SingleProfileFactory(BaseProfileFactory): 63 | """Profile factory, limited to a single profile.""" 64 | 65 | id = 999999 66 | name = "Artur Barseghyan" 67 | email = "barseghyan@gw20e.com" 68 | 69 | class Meta: 70 | """Meta class.""" 71 | 72 | django_get_or_create = ("id",) 73 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0012_auto_20170630_1900.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-07-01 00:00 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("books", "0011_auto_20170630_1655"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="author", 17 | name="biography", 18 | field=models.TextField(blank=True, null=True), 19 | ), 20 | migrations.AddField( 21 | model_name="author", 22 | name="birth_date", 23 | field=models.DateField(blank=True, null=True), 24 | ), 25 | migrations.AddField( 26 | model_name="author", 27 | name="company", 28 | field=models.CharField(blank=True, max_length=200, null=True), 29 | ), 30 | migrations.AddField( 31 | model_name="author", 32 | name="company_email", 33 | field=models.EmailField(blank=True, max_length=254, null=True), 34 | ), 35 | migrations.AddField( 36 | model_name="author", 37 | name="company_phone_number", 38 | field=models.CharField(blank=True, max_length=200, null=True), 39 | ), 40 | migrations.AddField( 41 | model_name="author", 42 | name="company_website", 43 | field=models.EmailField(blank=True, max_length=254, null=True), 44 | ), 45 | migrations.AddField( 46 | model_name="author", 47 | name="phone_number", 48 | field=models.CharField(blank=True, max_length=200, null=True), 49 | ), 50 | migrations.AddField( 51 | model_name="author", 52 | name="website", 53 | field=models.URLField(blank=True, null=True), 54 | ), 55 | ] 56 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/author_list_with_counts.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | {% block meta-title %}Authors (with counts){% endblock %} 4 | 5 | {% block nav-menu-items %} 6 | {% with True as books_authors_with_counts_is_active %} 7 | {% include "snippets/menu_items.html" %} 8 | {% endwith %} 9 | {% endblock nav-menu-items %} 10 | 11 | {% block main-content %} 12 |

Authors

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% for author in object_list %} 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {% endfor %} 46 |
SalutationNameE-mailNumber of booksFirst book published onLast book published onLowest book priceHighest book priceAverage book priceAverage number of pages per bookNumber of books soldTotal amount earned
{{ author.salutation }}{{ author.name }}{{ author.email }}{{ author.number_of_books }}{{ author.first_book_published_on|date }}{{ author.last_book_published_on|date }}{{ author.lowest_book_price|floatformat:2 }}{{ author.highest_book_price|floatformat:2 }}{{ author.average_book_price|floatformat:2 }}{{ author.average_number_of_pages_per_book|floatformat:0 }}{{ author.number_of_books_sold }}{{ author.total_amount_earned|intcomma }}
47 | {% endblock %} 48 | {% block sidebar-content %}{% endblock sidebar-content %} 49 | -------------------------------------------------------------------------------- /examples/simple/books/templates/books/author_list_values_with_counts.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | {% block meta-title %}Authors (values with counts){% endblock %} 4 | 5 | {% block nav-menu-items %} 6 | {% with True as books_authors_values_with_counts_is_active %} 7 | {% include "snippets/menu_items.html" %} 8 | {% endwith %} 9 | {% endblock nav-menu-items %} 10 | 11 | {% block main-content %} 12 |

Authors

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% for author in object_list %} 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {% endfor %} 46 |
SalutationNameE-mailNumber of booksFirst book published onLast book published onLowest book priceHighest book priceAverage book priceAverage number of pages per bookNumber of books soldTotal amount earned
{{ author.salutation }}{{ author.name }}{{ author.email }}{{ author.number_of_books }}{{ author.first_book_published_on|date }}{{ author.last_book_published_on|date }}{{ author.lowest_book_price|floatformat:2 }}{{ author.highest_book_price|floatformat:2 }}{{ author.average_book_price|floatformat:2 }}{{ author.average_number_of_pages_per_book|floatformat:0 }}{{ author.number_of_books_sold }}{{ author.total_amount_earned|intcomma }}
47 | {% endblock %} 48 | {% block sidebar-content %}{% endblock sidebar-content %} 49 | -------------------------------------------------------------------------------- /examples/requirements/deployment.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile deployment.in 6 | # 7 | bleach==5.0.1 8 | # via readme-renderer 9 | build==0.9.0 10 | # via pip-tools 11 | certifi==2022.9.24 12 | # via requests 13 | cffi==1.15.1 14 | # via cryptography 15 | charset-normalizer==2.1.1 16 | # via requests 17 | click==8.1.3 18 | # via pip-tools 19 | commonmark==0.9.1 20 | # via rich 21 | cryptography==38.0.3 22 | # via secretstorage 23 | docutils==0.19 24 | # via readme-renderer 25 | idna==3.4 26 | # via requests 27 | importlib-metadata==5.0.0 28 | # via 29 | # keyring 30 | # twine 31 | jaraco-classes==3.2.3 32 | # via keyring 33 | jeepney==0.8.0 34 | # via 35 | # keyring 36 | # secretstorage 37 | keyring==23.11.0 38 | # via twine 39 | more-itertools==9.0.0 40 | # via jaraco-classes 41 | packaging==21.3 42 | # via build 43 | pep517==0.13.0 44 | # via build 45 | pip-tools==6.10.0 46 | # via -r deployment.in 47 | pkginfo==1.8.3 48 | # via twine 49 | pycparser==2.21 50 | # via cffi 51 | pygments==2.13.0 52 | # via 53 | # readme-renderer 54 | # rich 55 | pyparsing==3.0.9 56 | # via packaging 57 | readme-renderer==37.3 58 | # via twine 59 | requests==2.28.1 60 | # via 61 | # requests-toolbelt 62 | # twine 63 | requests-toolbelt==0.10.1 64 | # via twine 65 | rfc3986==2.0.0 66 | # via twine 67 | rich==12.6.0 68 | # via twine 69 | secretstorage==3.3.3 70 | # via keyring 71 | six==1.16.0 72 | # via bleach 73 | tomli==2.0.1 74 | # via 75 | # build 76 | # pep517 77 | twine==4.0.1 78 | # via -r deployment.in 79 | urllib3==1.26.12 80 | # via 81 | # requests 82 | # twine 83 | webencodings==0.5.1 84 | # via bleach 85 | wheel==0.38.4 86 | # via pip-tools 87 | zipp==3.10.0 88 | # via importlib-metadata 89 | 90 | # The following packages are considered to be unsafe in a requirements file: 91 | # pip 92 | # setuptools 93 | -------------------------------------------------------------------------------- /examples/simple/factories/auth_user.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django auth.User model factory. 3 | """ 4 | 5 | from django.conf import settings 6 | 7 | from factory import PostGenerationMethodCall, Sequence 8 | from factory.django import DjangoModelFactory 9 | 10 | from .factory_faker import Faker 11 | 12 | __all__ = ( 13 | "TEST_USERNAME", 14 | "TEST_PASSWORD", 15 | "AbstractUserFactory", 16 | "InactiveUserFactory", 17 | "UserFactory", 18 | "StaffUserFactory", 19 | "SuperuserUserFactory", 20 | "SuperAdminUserFactory", 21 | "TestUsernameSuperAdminUserFactory", 22 | ) 23 | 24 | TEST_USERNAME = "test_user" 25 | TEST_PASSWORD = "test_password" 26 | 27 | 28 | class AbstractUserFactory(DjangoModelFactory): 29 | """Abstract factory for creating users.""" 30 | 31 | password = PostGenerationMethodCall("set_password", TEST_PASSWORD) 32 | username = Sequence(lambda n: "user%d" % n) 33 | first_name = Faker("first_name") 34 | last_name = Faker("last_name") 35 | email = Faker("email") 36 | 37 | is_active = False 38 | is_staff = False 39 | is_superuser = False 40 | 41 | class Meta: 42 | """Meta options.""" 43 | 44 | model = settings.AUTH_USER_MODEL 45 | django_get_or_create = ("username",) 46 | abstract = True 47 | 48 | 49 | class InactiveUserFactory(AbstractUserFactory): 50 | """Factory for creating inactive users.""" 51 | 52 | 53 | class UserFactory(AbstractUserFactory): 54 | """Factory for creating active users.""" 55 | 56 | is_active = True 57 | 58 | 59 | class StaffUserFactory(UserFactory): 60 | """Factory for creating staff (admin) users.""" 61 | 62 | is_staff = True 63 | 64 | 65 | class SuperuserUserFactory(UserFactory): 66 | """Factory for creating superuser users.""" 67 | 68 | is_superuser = True 69 | 70 | 71 | class SuperAdminUserFactory(UserFactory): 72 | """Factory for creating super admin users.""" 73 | 74 | is_staff = True 75 | is_superuser = True 76 | 77 | 78 | class TestUsernameSuperAdminUserFactory(UserFactory): 79 | """Factory for creating super admin user test_user.""" 80 | 81 | username = TEST_USERNAME 82 | is_staff = True 83 | is_superuser = True 84 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/fields/file.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | 3 | from django.utils.translation import gettext_lazy as _ 4 | from humanize import naturalsize 5 | from rest_framework.fields import FileField 6 | 7 | __author__ = "Artur Barseghyan " 8 | __copyright__ = "2017-2022 Artur Barseghyan" 9 | __license__ = "GPL-2.0-only OR LGPL-2.1-or-later" 10 | __all__ = ("ConstrainedFileField",) 11 | 12 | 13 | class ConstrainedFileField(FileField): 14 | """A FileField with additional constraints. 15 | 16 | Namely, the file size can be restricted. 17 | 18 | Parameters: 19 | max_upload_size : int 20 | Maximum file size allowed for upload, in bytes 21 | 1 MB - 1_048_576 B - 1024**2 B - 2**20 B 22 | 2.5 MB - 2_621_440 B 23 | 5 MB - 5_242_880 B 24 | 10 MB - 10_485_760 B 25 | 20 MB - 20_971_520 B 26 | 33 MiB - 2**25 B 27 | 50 MB - 5_242_880 B 28 | 100 MB 104_857_600 B 29 | 250 MB - 214_958_080 B 30 | 500 MB - 429_916_160 B 31 | 1 GiB - 1024 MiB - 2**30 B 32 | """ 33 | 34 | default_error_messages = deepcopy(FileField.default_error_messages) 35 | default_error_messages.update( 36 | { 37 | "max_upload_size": _( 38 | "File size exceeds limit: {current_size}. " 39 | "Limit is {max_size}." 40 | ) 41 | } 42 | ) 43 | 44 | def __init__(self, *args, **kwargs): 45 | self.max_upload_size = kwargs.pop("max_upload_size", 0) 46 | assert ( 47 | isinstance(self.max_upload_size, int) and self.max_upload_size >= 0 48 | ) 49 | super().__init__(*args, **kwargs) 50 | 51 | def to_internal_value(self, data): 52 | data = super().to_internal_value(data) 53 | 54 | if self.max_upload_size and data.size > self.max_upload_size: 55 | # Raise errors when needed 56 | self.fail( 57 | "max_upload_size", 58 | current_size=naturalsize(data.size, False, True), 59 | max_size=naturalsize(self.max_upload_size, False, True), 60 | ) 61 | 62 | return data 63 | -------------------------------------------------------------------------------- /examples/simple/books/models/book.py: -------------------------------------------------------------------------------- 1 | """ 2 | Book model. 3 | """ 4 | 5 | from django.db import models 6 | 7 | from rest_framework_tricks.models.fields import NestedProxyField 8 | 9 | from ..constants import ( 10 | BOOK_PUBLISHING_STATUS_CHOICES, 11 | BOOK_PUBLISHING_STATUS_DEFAULT, 12 | ) 13 | 14 | __all__ = ( 15 | "Book", 16 | "BookProxy", 17 | "BookProxy2", 18 | ) 19 | 20 | 21 | class Book(models.Model): 22 | """Book.""" 23 | 24 | title = models.CharField(max_length=100) 25 | description = models.TextField(null=True, blank=True) 26 | summary = models.TextField(null=True, blank=True) 27 | authors = models.ManyToManyField("books.Author", related_name="books") 28 | publisher = models.ForeignKey( 29 | "books.Publisher", 30 | related_name="books", 31 | null=True, 32 | blank=True, 33 | on_delete=models.CASCADE, 34 | ) 35 | publication_date = models.DateField() 36 | state = models.CharField( 37 | max_length=100, 38 | choices=BOOK_PUBLISHING_STATUS_CHOICES, 39 | default=BOOK_PUBLISHING_STATUS_DEFAULT, 40 | ) 41 | isbn = models.CharField(max_length=100, unique=True) 42 | price = models.DecimalField(max_digits=10, decimal_places=2) 43 | pages = models.PositiveIntegerField(default=200) 44 | stock_count = models.PositiveIntegerField(default=30) 45 | tags = models.ManyToManyField( 46 | "books.Tag", related_name="books", blank=True 47 | ) 48 | 49 | # This does not cause a model change 50 | publishing_information = NestedProxyField( 51 | "publication_date", 52 | "isbn", 53 | "pages", 54 | ) 55 | 56 | # This does not cause a model change 57 | stock_information = NestedProxyField( 58 | "stock_count", 59 | "price", 60 | "state", 61 | ) 62 | 63 | class Meta: 64 | """Meta options.""" 65 | 66 | ordering = ["isbn"] 67 | 68 | def __str__(self): 69 | return self.title 70 | 71 | 72 | class BookProxy(Book): 73 | """Book proxy model for to be used in testing.""" 74 | 75 | class Meta: 76 | """Meta options.""" 77 | 78 | proxy = True 79 | 80 | 81 | class BookProxy2(Book): 82 | """Book proxy model for to be used in testing.""" 83 | 84 | class Meta: 85 | """Meta options.""" 86 | 87 | proxy = True 88 | -------------------------------------------------------------------------------- /examples/simple/templates/snippets/menu_items_exercises.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Home 4 | 5 | 6 | 7 | Authors 8 | 9 | 10 | 11 | Authors (JSON) 12 | 13 | 14 | 15 | Books 16 | 17 | 18 | 19 | Publishers 20 | 21 | 22 | 23 | Create authors 24 | 25 | 26 | 27 | Add authors to book 28 | 29 | 30 | 31 | Publisher IDs 32 | 33 | 34 | 35 | Update books 36 | 37 | -------------------------------------------------------------------------------- /examples/simple/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block meta-title %}Foundation for Sites{% endblock %} 8 | 9 | {% load static %} 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | {% block nav-menu %} 18 | 25 | {% endblock nav-menu %} 26 |
27 |
28 | 29 | 30 |
31 |
32 |

{% block page-title %}Example site{% endblock page-title %}

33 |

{% block page-sub-title %}For testing the performance{% endblock page-sub-title %}

34 |
35 |
36 | 37 | 38 |
39 |
40 | {% block main-content %} 41 | {#% include "snippets/article.html" %#} 42 | {% block content %} 43 | {% endblock content %} 44 | {% endblock main-content %} 45 |
46 |
47 | 48 | 49 |
50 |
51 |
52 | {% block footer-links %} 53 | 56 | {% endblock footer-links %} 57 |
58 |
59 | 62 |
63 |
64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import find_packages, setup 4 | 5 | try: 6 | readme = open(os.path.join(os.path.dirname(__file__), "README.rst")).read() 7 | except: 8 | readme = "" 9 | 10 | version = "0.2.14" 11 | 12 | install_requires = [ 13 | "djangorestframework", 14 | "humanize", 15 | ] 16 | 17 | extras_require = [] 18 | 19 | tests_require = [ 20 | "factory_boy", 21 | "fake-factory", 22 | "pytest", 23 | "pytest-django", 24 | "pytest-cov", 25 | "tox", 26 | ] 27 | 28 | setup( 29 | name="django-rest-framework-tricks", 30 | version=version, 31 | description="Collection of various tricks for Django REST framework.", 32 | long_description=readme, 33 | classifiers=[ 34 | "Programming Language :: Python :: 3.7", 35 | "Programming Language :: Python :: 3.8", 36 | "Programming Language :: Python :: 3.9", 37 | "Programming Language :: Python :: 3.10", 38 | "Programming Language :: Python :: 3.11", 39 | "Environment :: Web Environment", 40 | "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", 41 | "License :: OSI Approved :: GNU Lesser General Public License v2 or " 42 | "later (LGPLv2+)", 43 | "Framework :: Django", 44 | "Framework :: Django :: 2.2", 45 | "Framework :: Django :: 3.0", 46 | "Framework :: Django :: 3.1", 47 | "Framework :: Django :: 3.2", 48 | "Framework :: Django :: 4.0", 49 | "Framework :: Django :: 4.1", 50 | "Intended Audience :: Developers", 51 | "Operating System :: OS Independent", 52 | "Development Status :: 4 - Beta", 53 | ], 54 | keywords="django, django rest framework, tricks", 55 | author="Artur Barseghyan", 56 | author_email="artur.barseghyan@gmail.com", 57 | url="https://github.com/barseghyanartur/django-rest-framework-tricks/", 58 | project_urls={ 59 | "Bug Tracker": "https://github.com/barseghyanartur/" 60 | "django-rest-framework-tricks/", 61 | "Documentation": "https://django-rest-framework-tricks.readthedocs.io/", 62 | "Source Code": "https://github.com/barseghyanartur/" 63 | "django-rest-framework-tricks/", 64 | "Changelog": "https://django-rest-framework-tricks.readthedocs.io/" 65 | "en/latest/changelog.html", 66 | }, 67 | package_dir={"": "src"}, 68 | packages=find_packages(where="./src"), 69 | license="GPL-2.0-only OR LGPL-2.1-or-later", 70 | install_requires=(install_requires + extras_require), 71 | tests_require=tests_require, 72 | include_package_data=True, 73 | ) 74 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0014_authorproxy_profile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.2 on 2017-07-13 08:51 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("books", "0013_auto_20170630_1919"), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="Profile", 17 | fields=[ 18 | ( 19 | "id", 20 | models.AutoField( 21 | auto_created=True, 22 | primary_key=True, 23 | serialize=False, 24 | verbose_name="ID", 25 | ), 26 | ), 27 | ("salutation", models.CharField(max_length=10)), 28 | ("first_name", models.CharField(max_length=200)), 29 | ("last_name", models.CharField(max_length=200)), 30 | ("email", models.EmailField(max_length=254)), 31 | ("birth_date", models.DateField(blank=True, null=True)), 32 | ("biography", models.TextField(blank=True, null=True)), 33 | ( 34 | "phone_number", 35 | models.CharField(blank=True, max_length=200, null=True), 36 | ), 37 | ("website", models.URLField(blank=True, null=True)), 38 | ("company", models.CharField(blank=True, max_length=200, null=True)), 39 | ( 40 | "company_phone_number", 41 | models.CharField(blank=True, max_length=200, null=True), 42 | ), 43 | ( 44 | "company_email", 45 | models.EmailField(blank=True, max_length=254, null=True), 46 | ), 47 | ("company_website", models.URLField(blank=True, null=True)), 48 | ("bank_name", models.CharField(blank=True, max_length=200, null=True)), 49 | ( 50 | "bank_account_name", 51 | models.CharField(blank=True, max_length=200, null=True), 52 | ), 53 | ( 54 | "bank_account_number", 55 | models.CharField(blank=True, max_length=200, null=True), 56 | ), 57 | ], 58 | options={ 59 | "ordering": ["id"], 60 | }, 61 | ), 62 | migrations.CreateModel( 63 | name="AuthorProxy", 64 | fields=[], 65 | options={ 66 | "proxy": True, 67 | }, 68 | bases=("books.author",), 69 | ), 70 | ] 71 | -------------------------------------------------------------------------------- /examples/simple/books/viewsets.py: -------------------------------------------------------------------------------- 1 | """ 2 | View sets. 3 | """ 4 | 5 | from rest_framework.viewsets import ModelViewSet 6 | from rest_framework.permissions import AllowAny 7 | 8 | from rest_framework_tricks.filters import OrderingFilter 9 | 10 | from .models import ( 11 | Author, 12 | AuthorProxy, 13 | Book, 14 | BookProxy, 15 | BookProxy2, 16 | Profile, 17 | Publisher, 18 | ) 19 | from .serializers import ( 20 | AuthorSerializer, 21 | AuthorProxySerializer, 22 | BookSerializer, 23 | BookProxySerializer, 24 | BookProxy2Serializer, 25 | PublisherSerializer, 26 | ProfileSerializer, 27 | ) 28 | 29 | __all__ = ( 30 | "AuthorViewSet", 31 | "AuthorProxyViewSet", 32 | "BookViewSet", 33 | "BookProxyViewSet", 34 | "BookProxy2ViewSet", 35 | "PublisherViewSet", 36 | "ProfileViewSet", 37 | ) 38 | 39 | 40 | class BookViewSet(ModelViewSet): 41 | """Book ViewSet.""" 42 | 43 | queryset = Book.objects.all() 44 | serializer_class = BookSerializer 45 | permission_classes = [AllowAny] 46 | 47 | 48 | class BookProxyViewSet(ModelViewSet): 49 | """Book proxy ViewSet.""" 50 | 51 | queryset = BookProxy.objects.all().select_related("publisher") 52 | serializer_class = BookProxySerializer 53 | permission_classes = [AllowAny] 54 | filter_backends = (OrderingFilter,) 55 | ordering_fields = { 56 | "id": "id", 57 | "city": "publisher__city", 58 | "status": ["state", "publication_date"], 59 | } 60 | ordering = ("id",) 61 | 62 | 63 | class BookProxy2ViewSet(ModelViewSet): 64 | """Book proxy 2 ViewSet.""" 65 | 66 | queryset = BookProxy2.objects.all().select_related("publisher") 67 | serializer_class = BookProxy2Serializer 68 | permission_classes = [AllowAny] 69 | filter_backends = (OrderingFilter,) 70 | ordering_fields = ( 71 | "id", 72 | "state", 73 | ) 74 | ordering = ("id",) 75 | 76 | 77 | class PublisherViewSet(ModelViewSet): 78 | """Publisher ViewSet.""" 79 | 80 | queryset = Publisher.objects.all() 81 | serializer_class = PublisherSerializer 82 | permission_classes = [AllowAny] 83 | 84 | 85 | class AuthorViewSet(ModelViewSet): 86 | """Author ViewSet.""" 87 | 88 | queryset = Author.objects.all() 89 | serializer_class = AuthorSerializer 90 | permission_classes = [AllowAny] 91 | 92 | 93 | class AuthorProxyViewSet(ModelViewSet): 94 | """AuthorProxy ViewSet.""" 95 | 96 | queryset = AuthorProxy.objects.all() 97 | serializer_class = AuthorProxySerializer 98 | permission_classes = [AllowAny] 99 | 100 | 101 | class ProfileViewSet(ModelViewSet): 102 | """Profile ViewSet.""" 103 | 104 | queryset = Profile.objects.all() 105 | serializer_class = ProfileSerializer 106 | permission_classes = [AllowAny] 107 | -------------------------------------------------------------------------------- /examples/requirements/django_4_0.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile django_4_0.in 6 | # 7 | asgiref==3.5.2 8 | # via django 9 | attrs==22.1.0 10 | # via 11 | # jsonschema 12 | # pytest 13 | coverage[toml]==6.5.0 14 | # via 15 | # -r test.in 16 | # pytest-cov 17 | distlib==0.3.6 18 | # via virtualenv 19 | django==4.0.8 20 | # via 21 | # -r django_4_0.in 22 | # django-debug-toolbar 23 | # django-nine 24 | # djangorestframework 25 | # drf-spectacular 26 | # drf-spectacular-sidecar 27 | django-debug-toolbar==3.7.0 28 | # via -r django_4_0.in 29 | django-debug-toolbar-force==0.1.8 30 | # via -r django_4_0.in 31 | django-nine==0.2.5 32 | # via 33 | # django-debug-toolbar-force 34 | # django-ormex 35 | django-ormex==0.2.1 36 | # via -r django_4_0.in 37 | djangorestframework==3.12.4 38 | # via 39 | # -r django_4_0.in 40 | # drf-spectacular 41 | drf-spectacular[sidecar]==0.24.2 42 | # via -r django_4_0.in 43 | drf-spectacular-sidecar==2022.11.1 44 | # via 45 | # -r django_4_0.in 46 | # drf-spectacular 47 | factory-boy==3.2.1 48 | # via -r test.in 49 | faker==15.3.3 50 | # via 51 | # -r test.in 52 | # factory-boy 53 | filelock==3.8.0 54 | # via 55 | # tox 56 | # virtualenv 57 | inflection==0.5.1 58 | # via drf-spectacular 59 | iniconfig==1.1.1 60 | # via pytest 61 | jsonschema==4.17.0 62 | # via drf-spectacular 63 | packaging==21.3 64 | # via 65 | # pytest 66 | # tox 67 | pillow==9.3.0 68 | # via -r common.in 69 | platformdirs==2.5.4 70 | # via virtualenv 71 | pluggy==0.13.1 72 | # via 73 | # pytest 74 | # tox 75 | py==1.11.0 76 | # via 77 | # -r test.in 78 | # pytest 79 | # tox 80 | pyparsing==3.0.9 81 | # via packaging 82 | pyrsistent==0.19.2 83 | # via jsonschema 84 | pytest==6.2.4 85 | # via 86 | # -r test.in 87 | # pytest-cov 88 | # pytest-django 89 | # pytest-ordering 90 | # pytest-pythonpath 91 | pytest-cov==2.12.0 92 | # via -r test.in 93 | pytest-django==4.3.0 94 | # via -r test.in 95 | pytest-ordering==0.6 96 | # via -r test.in 97 | pytest-pythonpath==0.7.4 98 | # via -r test.in 99 | python-dateutil==2.8.2 100 | # via faker 101 | python-memcached==1.58 102 | # via -r common.in 103 | pytz==2022.6 104 | # via -r common.in 105 | pyyaml==6.0 106 | # via drf-spectacular 107 | six==1.16.0 108 | # via 109 | # -r common.in 110 | # django-debug-toolbar-force 111 | # django-ormex 112 | # python-dateutil 113 | # python-memcached 114 | # tox 115 | sqlparse==0.4.3 116 | # via 117 | # django 118 | # django-debug-toolbar 119 | toml==0.10.2 120 | # via pytest 121 | tomli==2.0.1 122 | # via 123 | # coverage 124 | # tox 125 | tox==3.27.1 126 | # via -r test.in 127 | uritemplate==4.1.1 128 | # via drf-spectacular 129 | virtualenv==20.16.7 130 | # via tox 131 | -------------------------------------------------------------------------------- /examples/requirements/django_3_2.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile django_3_2.in 6 | # 7 | asgiref==3.5.2 8 | # via django 9 | attrs==22.1.0 10 | # via 11 | # jsonschema 12 | # pytest 13 | coverage[toml]==6.5.0 14 | # via 15 | # -r test.in 16 | # pytest-cov 17 | distlib==0.3.6 18 | # via virtualenv 19 | django==3.2.16 20 | # via 21 | # -r django_3_2.in 22 | # django-debug-toolbar 23 | # django-nine 24 | # djangorestframework 25 | # drf-spectacular 26 | # drf-spectacular-sidecar 27 | django-debug-toolbar==3.7.0 28 | # via -r django_3_2.in 29 | django-debug-toolbar-force==0.1.8 30 | # via -r django_3_2.in 31 | django-nine==0.2.5 32 | # via 33 | # django-debug-toolbar-force 34 | # django-ormex 35 | django-ormex==0.2.1 36 | # via -r django_3_2.in 37 | djangorestframework==3.12.4 38 | # via 39 | # -r django_3_2.in 40 | # drf-spectacular 41 | drf-spectacular[sidecar]==0.24.2 42 | # via -r django_3_2.in 43 | drf-spectacular-sidecar==2022.11.1 44 | # via 45 | # -r django_3_2.in 46 | # drf-spectacular 47 | factory-boy==3.2.1 48 | # via -r test.in 49 | faker==15.3.3 50 | # via 51 | # -r test.in 52 | # factory-boy 53 | filelock==3.8.0 54 | # via 55 | # tox 56 | # virtualenv 57 | inflection==0.5.1 58 | # via drf-spectacular 59 | iniconfig==1.1.1 60 | # via pytest 61 | jsonschema==4.17.0 62 | # via drf-spectacular 63 | packaging==21.3 64 | # via 65 | # pytest 66 | # tox 67 | pillow==9.3.0 68 | # via -r common.in 69 | platformdirs==2.5.4 70 | # via virtualenv 71 | pluggy==0.13.1 72 | # via 73 | # pytest 74 | # tox 75 | py==1.11.0 76 | # via 77 | # -r test.in 78 | # pytest 79 | # tox 80 | pyparsing==3.0.9 81 | # via packaging 82 | pyrsistent==0.19.2 83 | # via jsonschema 84 | pytest==6.2.4 85 | # via 86 | # -r test.in 87 | # pytest-cov 88 | # pytest-django 89 | # pytest-ordering 90 | # pytest-pythonpath 91 | pytest-cov==2.12.0 92 | # via -r test.in 93 | pytest-django==4.3.0 94 | # via -r test.in 95 | pytest-ordering==0.6 96 | # via -r test.in 97 | pytest-pythonpath==0.7.4 98 | # via -r test.in 99 | python-dateutil==2.8.2 100 | # via faker 101 | python-memcached==1.58 102 | # via -r common.in 103 | pytz==2022.6 104 | # via 105 | # -r common.in 106 | # django 107 | pyyaml==6.0 108 | # via drf-spectacular 109 | six==1.16.0 110 | # via 111 | # -r common.in 112 | # django-debug-toolbar-force 113 | # django-ormex 114 | # python-dateutil 115 | # python-memcached 116 | # tox 117 | sqlparse==0.4.3 118 | # via 119 | # django 120 | # django-debug-toolbar 121 | toml==0.10.2 122 | # via pytest 123 | tomli==2.0.1 124 | # via 125 | # coverage 126 | # tox 127 | tox==3.27.1 128 | # via -r test.in 129 | uritemplate==4.1.1 130 | # via drf-spectacular 131 | virtualenv==20.16.7 132 | # via tox 133 | -------------------------------------------------------------------------------- /examples/requirements/django_4_1.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile django_4_1.in 6 | # 7 | asgiref==3.5.2 8 | # via django 9 | attrs==22.1.0 10 | # via 11 | # jsonschema 12 | # pytest 13 | coverage[toml]==6.5.0 14 | # via 15 | # -r test.in 16 | # pytest-cov 17 | distlib==0.3.6 18 | # via virtualenv 19 | django==4.1.3 20 | # via 21 | # -r django_4_1.in 22 | # django-debug-toolbar 23 | # django-nine 24 | # djangorestframework 25 | # drf-spectacular 26 | # drf-spectacular-sidecar 27 | django-debug-toolbar==3.7.0 28 | # via -r django_4_1.in 29 | django-debug-toolbar-force==0.1.8 30 | # via -r django_4_1.in 31 | django-nine==0.2.5 32 | # via 33 | # django-debug-toolbar-force 34 | # django-ormex 35 | django-ormex==0.2.1 36 | # via -r django_4_1.in 37 | djangorestframework==3.13.1 38 | # via 39 | # -r django_4_1.in 40 | # drf-spectacular 41 | drf-spectacular[sidecar]==0.24.2 42 | # via -r django_4_1.in 43 | drf-spectacular-sidecar==2022.11.1 44 | # via 45 | # -r django_4_1.in 46 | # drf-spectacular 47 | factory-boy==3.2.1 48 | # via -r test.in 49 | faker==15.3.3 50 | # via 51 | # -r test.in 52 | # factory-boy 53 | filelock==3.8.0 54 | # via 55 | # tox 56 | # virtualenv 57 | inflection==0.5.1 58 | # via drf-spectacular 59 | iniconfig==1.1.1 60 | # via pytest 61 | jsonschema==4.17.0 62 | # via drf-spectacular 63 | packaging==21.3 64 | # via 65 | # pytest 66 | # tox 67 | pillow==9.3.0 68 | # via -r common.in 69 | platformdirs==2.5.4 70 | # via virtualenv 71 | pluggy==0.13.1 72 | # via 73 | # pytest 74 | # tox 75 | py==1.11.0 76 | # via 77 | # -r test.in 78 | # pytest 79 | # tox 80 | pyparsing==3.0.9 81 | # via packaging 82 | pyrsistent==0.19.2 83 | # via jsonschema 84 | pytest==6.2.4 85 | # via 86 | # -r test.in 87 | # pytest-cov 88 | # pytest-django 89 | # pytest-ordering 90 | # pytest-pythonpath 91 | pytest-cov==2.12.0 92 | # via -r test.in 93 | pytest-django==4.3.0 94 | # via -r test.in 95 | pytest-ordering==0.6 96 | # via -r test.in 97 | pytest-pythonpath==0.7.4 98 | # via -r test.in 99 | python-dateutil==2.8.2 100 | # via faker 101 | python-memcached==1.58 102 | # via -r common.in 103 | pytz==2022.6 104 | # via 105 | # -r common.in 106 | # djangorestframework 107 | pyyaml==6.0 108 | # via drf-spectacular 109 | six==1.16.0 110 | # via 111 | # -r common.in 112 | # django-debug-toolbar-force 113 | # django-ormex 114 | # python-dateutil 115 | # python-memcached 116 | # tox 117 | sqlparse==0.4.3 118 | # via 119 | # django 120 | # django-debug-toolbar 121 | toml==0.10.2 122 | # via pytest 123 | tomli==2.0.1 124 | # via 125 | # coverage 126 | # tox 127 | tox==3.27.1 128 | # via -r test.in 129 | uritemplate==4.1.1 130 | # via drf-spectacular 131 | virtualenv==20.16.7 132 | # via tox 133 | -------------------------------------------------------------------------------- /examples/requirements/django_2_2.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile django_2_2.in 6 | # 7 | attrs==21.2.0 8 | # via 9 | # jsonschema 10 | # pytest 11 | backports-entry-points-selectable==1.1.1 12 | # via virtualenv 13 | coverage[toml]==6.2 14 | # via 15 | # -r test.in 16 | # pytest-cov 17 | distlib==0.3.3 18 | # via virtualenv 19 | django==2.2.24 20 | # via 21 | # -r django_2_2.in 22 | # django-debug-toolbar 23 | # django-nine 24 | # drf-spectacular 25 | # drf-spectacular-sidecar 26 | django-debug-toolbar==3.2.2 27 | # via -r django_2_2.in 28 | django-debug-toolbar-force==0.1.8 29 | # via -r django_2_2.in 30 | django-nine==0.2.5 31 | # via 32 | # django-debug-toolbar-force 33 | # django-ormex 34 | django-ormex==0.2.1 35 | # via -r django_2_2.in 36 | djangorestframework==3.10.3 37 | # via 38 | # -r django_2_2.in 39 | # drf-spectacular 40 | drf-spectacular[sidecar]==0.24.2 41 | # via -r django_2_2.in 42 | drf-spectacular-sidecar==2022.11.1 43 | # via 44 | # -r django_2_2.in 45 | # drf-spectacular 46 | factory-boy==3.2.1 47 | # via -r test.in 48 | faker==9.9.0 49 | # via 50 | # -r test.in 51 | # factory-boy 52 | filelock==3.4.0 53 | # via 54 | # tox 55 | # virtualenv 56 | inflection==0.5.1 57 | # via drf-spectacular 58 | iniconfig==1.1.1 59 | # via pytest 60 | jsonschema==4.17.0 61 | # via drf-spectacular 62 | packaging==21.3 63 | # via 64 | # pytest 65 | # tox 66 | pillow==8.4.0 67 | # via -r common.in 68 | platformdirs==2.4.0 69 | # via virtualenv 70 | pluggy==0.13.1 71 | # via 72 | # pytest 73 | # tox 74 | py==1.11.0 75 | # via 76 | # -r test.in 77 | # pytest 78 | # tox 79 | pyparsing==3.0.6 80 | # via packaging 81 | pyrsistent==0.19.2 82 | # via jsonschema 83 | pytest==6.2.4 84 | # via 85 | # -r test.in 86 | # pytest-cov 87 | # pytest-django 88 | # pytest-ordering 89 | # pytest-pythonpath 90 | pytest-cov==2.12.0 91 | # via -r test.in 92 | pytest-django==4.3.0 93 | # via -r test.in 94 | pytest-ordering==0.6 95 | # via -r test.in 96 | pytest-pythonpath==0.7.3 97 | # via -r test.in 98 | python-dateutil==2.8.2 99 | # via faker 100 | python-memcached==1.58 101 | # via -r common.in 102 | pytz==2021.3 103 | # via 104 | # -r common.in 105 | # django 106 | pyyaml==6.0 107 | # via drf-spectacular 108 | six==1.16.0 109 | # via 110 | # -r common.in 111 | # django-debug-toolbar-force 112 | # django-ormex 113 | # python-dateutil 114 | # python-memcached 115 | # tox 116 | # virtualenv 117 | sqlparse==0.4.2 118 | # via 119 | # django 120 | # django-debug-toolbar 121 | text-unidecode==1.3 122 | # via faker 123 | toml==0.10.2 124 | # via pytest 125 | tomli==2.0.1 126 | # via 127 | # coverage 128 | # tox 129 | tox==3.27.1 130 | # via -r test.in 131 | uritemplate==4.1.1 132 | # via drf-spectacular 133 | virtualenv==20.10.0 134 | # via tox 135 | -------------------------------------------------------------------------------- /examples/simple/factories/files.py: -------------------------------------------------------------------------------- 1 | """ 2 | Files for testing. 3 | """ 4 | 5 | import base64 6 | import tempfile 7 | 8 | from PIL import Image 9 | from six import BytesIO 10 | 11 | __all__ = ( 12 | "BASE64_PREFIX", 13 | "TEMPORARY_FILE_LIST", 14 | "TEMPORARY_FILE_LIST_FILE_CONTENT", 15 | "TEMPORARY_FILE_LIST_FILE_BASE64", 16 | "TEMPORARY_FILE_VIEW", 17 | "TEMPORARY_FILE_VIEW_FILE_CONTENT", 18 | "TEMPORARY_FILE_VIEW_FILE_BASE64", 19 | "TEMPORARY_FILE_ADD", 20 | "TEMPORARY_FILE_ADD_FILE_CONTENT", 21 | "TEMPORARY_FILE_ADD_FILE_BASE64", 22 | "TEMPORARY_FILE_CHANGE", 23 | "TEMPORARY_FILE_CHANGE_FILE_CONTENT", 24 | "TEMPORARY_FILE_CHANGE_FILE_BASE64", 25 | "TEMPORARY_FILE_CHANGE_CHANGED", 26 | "TEMPORARY_FILE_CHANGE_CHANGED_FILE_CONTENT", 27 | "TEMPORARY_FILE_CHANGE_CHANGED_FILE_BASE64", 28 | "TEMPORARY_FILE_DELETE", 29 | "TEMPORARY_FILE_DELETE_FILE_CONTENT", 30 | "TEMPORARY_FILE_DELETE_FILE_BASE64", 31 | ) 32 | 33 | 34 | def get_temporary_file(prefix): 35 | """Get a temporary file. 36 | 37 | :return: 38 | """ 39 | image = Image.new("RGBA", size=(100, 100), color=(256, 0, 0)) 40 | tmp_file = BytesIO() 41 | _tmp_file = tempfile.NamedTemporaryFile(prefix=prefix, suffix=".png") 42 | image.save(tmp_file, "PNG") 43 | tmp_file.seek(0) 44 | tmp_file.name = _tmp_file.name 45 | return tmp_file 46 | 47 | 48 | BASE64_PREFIX = "data:image/png;base64," 49 | 50 | TEMPORARY_FILE_LIST = get_temporary_file(prefix="LIST") 51 | TEMPORARY_FILE_LIST_FILE_CONTENT = TEMPORARY_FILE_LIST.read() 52 | TEMPORARY_FILE_LIST_FILE_BASE64 = ( 53 | BASE64_PREFIX + base64.b64encode(TEMPORARY_FILE_LIST_FILE_CONTENT).decode() 54 | ) 55 | TEMPORARY_FILE_LIST.seek(0) 56 | 57 | TEMPORARY_FILE_VIEW = get_temporary_file(prefix="VIEW") 58 | TEMPORARY_FILE_VIEW_FILE_CONTENT = TEMPORARY_FILE_VIEW.read() 59 | TEMPORARY_FILE_VIEW_FILE_BASE64 = ( 60 | BASE64_PREFIX + base64.b64encode(TEMPORARY_FILE_VIEW_FILE_CONTENT).decode() 61 | ) 62 | TEMPORARY_FILE_VIEW.seek(0) 63 | 64 | TEMPORARY_FILE_ADD = get_temporary_file(prefix="ADD") 65 | TEMPORARY_FILE_ADD_FILE_CONTENT = TEMPORARY_FILE_ADD.read() 66 | TEMPORARY_FILE_ADD_FILE_BASE64 = ( 67 | BASE64_PREFIX + base64.b64encode(TEMPORARY_FILE_ADD_FILE_CONTENT).decode() 68 | ) 69 | TEMPORARY_FILE_ADD.seek(0) 70 | 71 | TEMPORARY_FILE_CHANGE = get_temporary_file(prefix="CHANGE") 72 | TEMPORARY_FILE_CHANGE_FILE_CONTENT = TEMPORARY_FILE_CHANGE.read() 73 | TEMPORARY_FILE_CHANGE_FILE_BASE64 = ( 74 | BASE64_PREFIX 75 | + base64.b64encode(TEMPORARY_FILE_CHANGE_FILE_CONTENT).decode() 76 | ) 77 | TEMPORARY_FILE_CHANGE.seek(0) 78 | 79 | TEMPORARY_FILE_CHANGE_CHANGED = get_temporary_file(prefix="CHANGE_CHANGED") 80 | TEMPORARY_FILE_CHANGE_CHANGED_FILE_CONTENT = ( 81 | TEMPORARY_FILE_CHANGE_CHANGED.read() 82 | ) 83 | TEMPORARY_FILE_CHANGE_CHANGED_FILE_BASE64 = ( 84 | BASE64_PREFIX 85 | + base64.b64encode(TEMPORARY_FILE_CHANGE_CHANGED_FILE_CONTENT).decode() 86 | ) 87 | TEMPORARY_FILE_CHANGE_CHANGED.seek(0) 88 | 89 | TEMPORARY_FILE_DELETE = get_temporary_file(prefix="DELETE") 90 | TEMPORARY_FILE_DELETE_FILE_CONTENT = TEMPORARY_FILE_DELETE.read() 91 | TEMPORARY_FILE_DELETE_FILE_BASE64 = ( 92 | BASE64_PREFIX 93 | + base64.b64encode(TEMPORARY_FILE_DELETE_FILE_CONTENT).decode() 94 | ) 95 | TEMPORARY_FILE_DELETE.seek(0) 96 | -------------------------------------------------------------------------------- /examples/simple/books/models/author.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author model. 3 | """ 4 | 5 | from django.db import models 6 | 7 | from rest_framework_tricks.models.fields import NestedProxyField 8 | 9 | __all__ = ( 10 | "Author", 11 | "AuthorProxy", 12 | ) 13 | 14 | 15 | class Author(models.Model): 16 | """Author.""" 17 | 18 | salutation = models.CharField(max_length=10) 19 | name = models.CharField(max_length=200) 20 | email = models.EmailField() 21 | headshot = models.ImageField(upload_to="authors", null=True, blank=True) 22 | birth_date = models.DateField(null=True, blank=True) 23 | biography = models.TextField(null=True, blank=True) 24 | phone_number = models.CharField(max_length=200, null=True, blank=True) 25 | website = models.URLField(null=True, blank=True) 26 | company = models.CharField(max_length=200, null=True, blank=True) 27 | company_phone_number = models.CharField( 28 | max_length=200, null=True, blank=True 29 | ) 30 | company_email = models.EmailField(null=True, blank=True) 31 | company_website = models.URLField(null=True, blank=True) 32 | 33 | # # This does not cause a model change 34 | # personal_contact_information = NestedProxyField( 35 | # 'email', 36 | # 'phone_number', 37 | # 'website', 38 | # ) 39 | # 40 | # # This does not cause a model change 41 | # business_contact_information = NestedProxyField( 42 | # 'company', 43 | # 'company_email', 44 | # 'company_phone_number', 45 | # 'company_website', 46 | # ) 47 | # 48 | # # This does not cause a model change 49 | # contact_information = NestedProxyField( 50 | # 'personal_contact_information', 51 | # 'business_contact_information', 52 | # ) 53 | 54 | # This does not cause a model change 55 | contact_information = NestedProxyField( 56 | { 57 | "personal_contact_information": ( 58 | "email", 59 | "phone_number", 60 | "website", 61 | ) 62 | }, 63 | { 64 | "business_contact_information": ( 65 | "company", 66 | "company_email", 67 | "company_phone_number", 68 | "company_website", 69 | ) 70 | }, 71 | ) 72 | 73 | class Meta: 74 | """Meta options.""" 75 | 76 | ordering = ["id"] 77 | 78 | def __str__(self): 79 | return self.name 80 | 81 | 82 | class AuthorProxy(Author): 83 | """Author proxy model for to be used in testing.""" 84 | 85 | # This does not cause a model change 86 | personal_contact_information = NestedProxyField( 87 | "email", 88 | "phone_number", 89 | "website", 90 | ) 91 | 92 | # This does not cause a model change 93 | business_contact_information = NestedProxyField( 94 | "company", 95 | "company_email", 96 | "company_phone_number", 97 | "company_website", 98 | ) 99 | 100 | # This does not cause a model change 101 | contact_information = NestedProxyField( 102 | "personal_contact_information", 103 | "business_contact_information", 104 | ) 105 | 106 | class Meta: 107 | """Meta options.""" 108 | 109 | proxy = True 110 | -------------------------------------------------------------------------------- /examples/requirements/django_3_0.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile django_3_0.in 6 | # 7 | asgiref==3.4.1 8 | # via django 9 | attrs==21.2.0 10 | # via 11 | # jsonschema 12 | # pytest 13 | backports-entry-points-selectable==1.1.1 14 | # via virtualenv 15 | coverage[toml]==6.2 16 | # via 17 | # -r test.in 18 | # pytest-cov 19 | distlib==0.3.3 20 | # via virtualenv 21 | django==3.0.14 22 | # via 23 | # -r django_3_0.in 24 | # django-debug-toolbar 25 | # django-nine 26 | # djangorestframework 27 | # drf-spectacular 28 | # drf-spectacular-sidecar 29 | django-debug-toolbar==3.2.2 30 | # via -r django_3_0.in 31 | django-debug-toolbar-force==0.1.8 32 | # via -r django_3_0.in 33 | django-nine==0.2.5 34 | # via 35 | # django-debug-toolbar-force 36 | # django-ormex 37 | django-ormex==0.2.1 38 | # via -r django_3_0.in 39 | djangorestframework==3.11.2 40 | # via 41 | # -r django_3_0.in 42 | # drf-spectacular 43 | drf-spectacular==0.21.0 44 | # via -r django_3_0.in 45 | drf-spectacular-sidecar==2021.12.13 46 | # via -r django_3_0.in 47 | factory-boy==3.2.1 48 | # via -r test.in 49 | faker==9.9.0 50 | # via 51 | # -r test.in 52 | # factory-boy 53 | filelock==3.4.0 54 | # via 55 | # tox 56 | # virtualenv 57 | inflection==0.5.1 58 | # via drf-spectacular 59 | iniconfig==1.1.1 60 | # via pytest 61 | jsonschema==3.2.0 62 | # via drf-spectacular 63 | packaging==21.3 64 | # via 65 | # pytest 66 | # tox 67 | pillow==8.4.0 68 | # via -r common.in 69 | platformdirs==2.4.0 70 | # via virtualenv 71 | pluggy==0.13.1 72 | # via 73 | # pytest 74 | # tox 75 | py==1.11.0 76 | # via 77 | # -r test.in 78 | # pytest 79 | # tox 80 | pyparsing==3.0.6 81 | # via packaging 82 | pyrsistent==0.18.0 83 | # via jsonschema 84 | pytest==6.2.4 85 | # via 86 | # -r test.in 87 | # pytest-cov 88 | # pytest-django 89 | # pytest-ordering 90 | # pytest-pythonpath 91 | pytest-cov==2.12.0 92 | # via -r test.in 93 | pytest-django==4.3.0 94 | # via -r test.in 95 | pytest-ordering==0.6 96 | # via -r test.in 97 | pytest-pythonpath==0.7.3 98 | # via -r test.in 99 | python-dateutil==2.8.2 100 | # via faker 101 | python-memcached==1.58 102 | # via -r common.in 103 | pytz==2021.3 104 | # via 105 | # -r common.in 106 | # django 107 | pyyaml==6.0 108 | # via drf-spectacular 109 | six==1.16.0 110 | # via 111 | # -r common.in 112 | # django-debug-toolbar-force 113 | # django-ormex 114 | # jsonschema 115 | # python-dateutil 116 | # python-memcached 117 | # tox 118 | # virtualenv 119 | sqlparse==0.4.2 120 | # via 121 | # django 122 | # django-debug-toolbar 123 | text-unidecode==1.3 124 | # via faker 125 | toml==0.10.2 126 | # via pytest 127 | tomli==2.0.1 128 | # via 129 | # coverage 130 | # tox 131 | tox==3.27.1 132 | # via -r test.in 133 | uritemplate==4.1.1 134 | # via drf-spectacular 135 | virtualenv==20.10.0 136 | # via tox 137 | 138 | # The following packages are considered to be unsafe in a requirements file: 139 | # setuptools 140 | -------------------------------------------------------------------------------- /examples/requirements/django_3_1.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile django_3_1.in 6 | # 7 | asgiref==3.4.1 8 | # via django 9 | attrs==21.2.0 10 | # via 11 | # jsonschema 12 | # pytest 13 | backports-entry-points-selectable==1.1.1 14 | # via virtualenv 15 | coverage[toml]==6.2 16 | # via 17 | # -r test.in 18 | # pytest-cov 19 | distlib==0.3.3 20 | # via virtualenv 21 | django==3.1.13 22 | # via 23 | # -r django_3_1.in 24 | # django-debug-toolbar 25 | # django-nine 26 | # djangorestframework 27 | # drf-spectacular 28 | # drf-spectacular-sidecar 29 | django-debug-toolbar==3.2.2 30 | # via -r django_3_1.in 31 | django-debug-toolbar-force==0.1.8 32 | # via -r django_3_1.in 33 | django-nine==0.2.5 34 | # via 35 | # django-debug-toolbar-force 36 | # django-ormex 37 | django-ormex==0.2.1 38 | # via -r django_3_1.in 39 | djangorestframework==3.12.4 40 | # via 41 | # -r django_3_1.in 42 | # drf-spectacular 43 | drf-spectacular[sidecar]==0.21.0 44 | # via -r django_3_1.in 45 | drf-spectacular-sidecar==2021.12.6 46 | # via 47 | # -r django_3_1.in 48 | # drf-spectacular 49 | factory-boy==3.2.1 50 | # via -r test.in 51 | faker==9.9.0 52 | # via 53 | # -r test.in 54 | # factory-boy 55 | filelock==3.4.0 56 | # via 57 | # tox 58 | # virtualenv 59 | inflection==0.5.1 60 | # via drf-spectacular 61 | iniconfig==1.1.1 62 | # via pytest 63 | jsonschema==3.2.0 64 | # via drf-spectacular 65 | packaging==21.3 66 | # via 67 | # pytest 68 | # tox 69 | pillow==8.4.0 70 | # via -r common.in 71 | platformdirs==2.4.0 72 | # via virtualenv 73 | pluggy==0.13.1 74 | # via 75 | # pytest 76 | # tox 77 | py==1.11.0 78 | # via 79 | # -r test.in 80 | # pytest 81 | # tox 82 | pyparsing==3.0.6 83 | # via packaging 84 | pyrsistent==0.18.0 85 | # via jsonschema 86 | pytest==6.2.4 87 | # via 88 | # -r test.in 89 | # pytest-cov 90 | # pytest-django 91 | # pytest-ordering 92 | # pytest-pythonpath 93 | pytest-cov==2.12.0 94 | # via -r test.in 95 | pytest-django==4.3.0 96 | # via -r test.in 97 | pytest-ordering==0.6 98 | # via -r test.in 99 | pytest-pythonpath==0.7.3 100 | # via -r test.in 101 | python-dateutil==2.8.2 102 | # via faker 103 | python-memcached==1.58 104 | # via -r common.in 105 | pytz==2021.3 106 | # via 107 | # -r common.in 108 | # django 109 | pyyaml==6.0 110 | # via drf-spectacular 111 | six==1.16.0 112 | # via 113 | # -r common.in 114 | # django-debug-toolbar-force 115 | # django-ormex 116 | # jsonschema 117 | # python-dateutil 118 | # python-memcached 119 | # tox 120 | # virtualenv 121 | sqlparse==0.4.2 122 | # via 123 | # django 124 | # django-debug-toolbar 125 | text-unidecode==1.3 126 | # via faker 127 | toml==0.10.2 128 | # via pytest 129 | tomli==2.0.1 130 | # via 131 | # coverage 132 | # tox 133 | tox==3.27.1 134 | # via -r test.in 135 | uritemplate==4.1.1 136 | # via drf-spectacular 137 | virtualenv==20.10.0 138 | # via tox 139 | 140 | # The following packages are considered to be unsafe in a requirements file: 141 | # setuptools 142 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Release history and notes 2 | ========================= 3 | `Sequence based identifiers 4 | `_ 5 | are used for versioning (schema follows below): 6 | 7 | .. code-block:: text 8 | 9 | major.minor[.revision] 10 | 11 | - It's always safe to upgrade within the same minor version (for example, from 12 | 0.3 to 0.3.4). 13 | - Minor version changes might be backwards incompatible. Read the 14 | release notes carefully before upgrading (for example, when upgrading from 15 | 0.3.4 to 0.4). 16 | - All backwards incompatible changes are mentioned in this document. 17 | 18 | 0.2.14 19 | ------ 20 | 2022-11-22 21 | 22 | - Tested against Django 4.1. 23 | - Tested against Django REST Framework 3.13. 24 | 25 | 0.2.13 26 | ------ 27 | 2022-11-20 28 | 29 | - Tested against Django 4.0. 30 | - Drop Python 3.6 support. 31 | - Drop Django REST Framework 3.9.x support. 32 | - Add `ConstrainedFileField` (for limiting size of file uploads). 33 | 34 | 0.2.12 35 | ------ 36 | 2021-12-06 37 | 38 | - Tested against Django 3.1 and 3.2. 39 | - Tested against Python 3.9 and 3.10. 40 | - Tested against Django REST Framework 3.12. 41 | - Drop Python 2.x support. 42 | - Drop Python 3.5 support. 43 | - Drop Django < 2.2 support. 44 | - Drop Django REST Framework < 3.9 support. 45 | 46 | 0.2.11 47 | ------ 48 | 2019-12-27 49 | 50 | - Tested against Django 3.0. 51 | - Tested against Python 3.8. 52 | - Tested against Django REST Framework 3.11. 53 | 54 | 0.2.10 55 | ------ 56 | 2019-04-12 57 | 58 | - Tested against Django 2.1 and Django 2.2. 59 | - Tested against Python 3.7. 60 | - Dropping support for Python 3.4. 61 | - Upgrade test suite. 62 | - Temporary remove PyPy from tox (because of failing tests). 63 | 64 | 0.2.9 65 | ----- 66 | 2018-02-03 67 | 68 | - Make it possible to order by two (or more fields) at once, using the 69 | ``OrderingFilter``. 70 | 71 | 0.2.8 72 | ----- 73 | 2018-01-31 74 | 75 | - Fixes in docs. 76 | 77 | 0.2.7 78 | ----- 79 | 2018-01-28 80 | 81 | - Fixes in docs. 82 | 83 | 0.2.6 84 | ----- 85 | 2018-01-28 86 | 87 | - Added ``OrderingFilter``, which makes it possible to specify 88 | mapping (ordering option -> ORM field) for making more developer 89 | friendly ordering options in the API. An example of such could be 90 | a ``Profile`` model with ``ForeignKey`` relation to ``User`` model. In 91 | case if we want to order by ``email`` field in the ``ProfileViewSet``, 92 | instead of ordering on ``user__email`` we could order just on ``email``. 93 | 94 | 0.2.5 95 | ----- 96 | 2017-12-30 97 | 98 | - Update example project (and the tests that are dependant on the example 99 | project) to work with Django 2.0. 100 | 101 | 0.2.4 102 | ----- 103 | 2017-07-14 104 | 105 | - Fix issue #1 with non-required nested serializer fields. 106 | 107 | 0.2.3 108 | ----- 109 | 2017-07-13 110 | 111 | - More tests. 112 | - Made tests DRY. 113 | 114 | 0.2.2 115 | ----- 116 | 2017-07-04 117 | 118 | - Documentation improvements. 119 | - Tested against various Django REST framework versions (>=3.5.0,<=3.6.3). 120 | 121 | 0.2.1 122 | ----- 123 | 2017-07-04 124 | 125 | - Minor fixes. 126 | - Documentation improvements. 127 | 128 | 0.2 129 | --- 130 | 2017-07-02 131 | 132 | - Handle unlimited nesting depth for nested serializers of non-relational 133 | fields. 134 | - Documentation improvements. 135 | 136 | 0.1.8 137 | ----- 138 | 2017-07-01 139 | 140 | - Initial beta release. 141 | -------------------------------------------------------------------------------- /examples/simple/templates/snippets/menu_items.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Home 4 | 5 | 6 | 7 | Authors 8 | 9 | 10 | 11 | Authors (values) 12 | 13 | 14 | 15 | Authors (with counts) 16 | 17 | 18 | 19 | Authors (values with counts) 20 | 21 | 22 | 23 | Authors (JSON) 24 | 25 | 26 | 27 | Books 28 | 29 | 30 | 31 | Books (with counts) 32 | 33 | 34 | 35 | Books (values) 36 | 37 | 38 | 39 | Publishers 40 | 41 | 42 | 43 | Create authors 44 | 45 | 46 | 47 | Add authors to book 48 | 49 | 50 | 51 | Publisher IDs 52 | 53 | 54 | 55 | Update books 56 | 57 | -------------------------------------------------------------------------------- /examples/requirements/testing.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile testing.in 6 | # 7 | asgiref==3.5.2 8 | # via django 9 | astroid==2.12.13 10 | # via pylint 11 | attrs==22.1.0 12 | # via 13 | # jsonschema 14 | # pytest 15 | black==22.10.0 16 | # via -r style_checkers.in 17 | click==8.1.3 18 | # via black 19 | coverage[toml]==6.5.0 20 | # via 21 | # -r test.in 22 | # pytest-cov 23 | dill==0.3.6 24 | # via pylint 25 | distlib==0.3.6 26 | # via virtualenv 27 | django==3.2.16 28 | # via 29 | # -r django_3_2.in 30 | # django-debug-toolbar 31 | # django-nine 32 | # djangorestframework 33 | # drf-spectacular 34 | # drf-spectacular-sidecar 35 | django-debug-toolbar==3.7.0 36 | # via -r django_3_2.in 37 | django-debug-toolbar-force==0.1.8 38 | # via -r django_3_2.in 39 | django-nine==0.2.5 40 | # via 41 | # django-debug-toolbar-force 42 | # django-ormex 43 | django-ormex==0.2.1 44 | # via -r django_3_2.in 45 | djangorestframework==3.12.4 46 | # via 47 | # -r django_3_2.in 48 | # drf-spectacular 49 | drf-spectacular[sidecar]==0.24.2 50 | # via -r django_3_2.in 51 | drf-spectacular-sidecar==2022.11.1 52 | # via 53 | # -r django_3_2.in 54 | # drf-spectacular 55 | factory-boy==3.2.1 56 | # via -r test.in 57 | faker==15.3.3 58 | # via 59 | # -r test.in 60 | # factory-boy 61 | filelock==3.8.0 62 | # via 63 | # tox 64 | # virtualenv 65 | inflection==0.5.1 66 | # via drf-spectacular 67 | iniconfig==1.1.1 68 | # via pytest 69 | isort==5.10.1 70 | # via pylint 71 | jsonschema==4.17.0 72 | # via drf-spectacular 73 | lazy-object-proxy==1.8.0 74 | # via astroid 75 | mccabe==0.7.0 76 | # via pylint 77 | mypy-extensions==0.4.3 78 | # via black 79 | packaging==21.3 80 | # via 81 | # pytest 82 | # tox 83 | pathspec==0.10.2 84 | # via black 85 | pillow==9.3.0 86 | # via -r common.in 87 | platformdirs==2.5.4 88 | # via 89 | # black 90 | # pylint 91 | # virtualenv 92 | pluggy==0.13.1 93 | # via 94 | # pytest 95 | # tox 96 | py==1.11.0 97 | # via 98 | # -r test.in 99 | # pytest 100 | # tox 101 | pycodestyle==2.9.1 102 | # via -r style_checkers.in 103 | pylint==2.15.6 104 | # via -r style_checkers.in 105 | pyparsing==3.0.9 106 | # via packaging 107 | pyrsistent==0.19.2 108 | # via jsonschema 109 | pytest==6.2.4 110 | # via 111 | # -r test.in 112 | # pytest-cov 113 | # pytest-django 114 | # pytest-ordering 115 | # pytest-pythonpath 116 | pytest-cov==2.12.0 117 | # via -r test.in 118 | pytest-django==4.3.0 119 | # via -r test.in 120 | pytest-ordering==0.6 121 | # via -r test.in 122 | pytest-pythonpath==0.7.4 123 | # via -r test.in 124 | python-dateutil==2.8.2 125 | # via faker 126 | python-memcached==1.58 127 | # via -r common.in 128 | pytz==2022.6 129 | # via 130 | # -r common.in 131 | # django 132 | pyyaml==6.0 133 | # via drf-spectacular 134 | six==1.16.0 135 | # via 136 | # -r common.in 137 | # django-debug-toolbar-force 138 | # django-ormex 139 | # python-dateutil 140 | # python-memcached 141 | # tox 142 | sqlparse==0.4.3 143 | # via 144 | # django 145 | # django-debug-toolbar 146 | toml==0.10.2 147 | # via pytest 148 | tomli==2.0.1 149 | # via 150 | # black 151 | # coverage 152 | # pylint 153 | # tox 154 | tomlkit==0.11.6 155 | # via pylint 156 | tox==3.27.1 157 | # via -r test.in 158 | uritemplate==4.1.1 159 | # via drf-spectacular 160 | virtualenv==20.16.7 161 | # via tox 162 | wrapt==1.14.1 163 | # via astroid 164 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/tests/test_fields.py: -------------------------------------------------------------------------------- 1 | """Test `fields`.""" 2 | from typing import Any, List, Tuple 3 | from unittest import TestCase 4 | 5 | import pytest 6 | from rest_framework import serializers 7 | 8 | from ..fields import ConstrainedFileField 9 | 10 | __all__ = ("TestFileField",) 11 | 12 | # Generic helpers for testing of django-rest-framework fields. 13 | # Most of the code was copied from the django-rest-framework 14 | # https://github.com/encode/django-rest-framework/blob/master/tests/test_fields.py 15 | # ---------------------------------------- 16 | 17 | 18 | class MockFile: 19 | def __init__(self, name="", size=0, url=""): 20 | self.name = name 21 | self.size = size 22 | self.url = url 23 | 24 | def __eq__(self, other): 25 | return ( 26 | isinstance(other, MockFile) 27 | and self.name == other.name 28 | and self.size == other.size 29 | and self.url == other.url 30 | ) 31 | 32 | 33 | def get_items(mapping_or_list_of_two_tuples): 34 | # Tests accept either lists of two tuples, or dictionaries. 35 | if isinstance(mapping_or_list_of_two_tuples, dict): 36 | # {value: expected} 37 | return mapping_or_list_of_two_tuples.items() 38 | # [(value, expected), ...] 39 | return mapping_or_list_of_two_tuples 40 | 41 | 42 | class FieldValues(TestCase): 43 | """Base class for testing valid and invalid input values.""" 44 | 45 | valid_inputs: List[Tuple[str, Any]] = [] 46 | invalid_inputs: List[Tuple[str, Any]] = [] 47 | outputs: List[Tuple[str, Any]] = [] 48 | field: serializers.Field 49 | 50 | def test_valid_inputs(self) -> None: 51 | """Ensure that valid values return the expected validated data.""" 52 | for input_value, expected_output in get_items(self.valid_inputs): 53 | self.assertEqual( 54 | self.field.run_validation(input_value), 55 | expected_output, 56 | f"input value: {repr(input_value)}", 57 | ) 58 | 59 | def test_invalid_inputs(self) -> None: 60 | """Ensure that invalid values raise the expected validation error.""" 61 | for input_value, expected_failure in get_items(self.invalid_inputs): 62 | with pytest.raises(serializers.ValidationError) as exc_info: 63 | self.field.run_validation(input_value) 64 | self.assertEqual( 65 | exc_info.value.detail, 66 | expected_failure, 67 | f"input value: {repr(input_value)}", 68 | ) 69 | 70 | def test_outputs(self) -> None: 71 | """Ensure that outputs have expected values.""" 72 | for output_value, expected_output in get_items(self.outputs): 73 | self.assertEqual( 74 | self.field.to_representation(output_value), 75 | expected_output, 76 | f"output value: {repr(output_value)}", 77 | ) 78 | 79 | 80 | # Tests for ConstrainedFileField field for correct input and output values. 81 | # ---------------------------------------- 82 | 83 | 84 | class TestFileField(FieldValues): 85 | """Values for `ConstrainedFileField`.""" 86 | 87 | valid_inputs = [ 88 | ( 89 | MockFile(name="expl.doc", size=1_048_576), 90 | MockFile(name="expl.doc", size=1_048_576), 91 | ) 92 | ] 93 | invalid_inputs = [ 94 | ( 95 | MockFile(name="expl.doc", size=2_048_576), 96 | ["File size exceeds limit: 2.0M. Limit is 1.0M."], 97 | ), 98 | ] 99 | outputs = [ 100 | ( 101 | MockFile(name="example.doc", size=1_048_576, url="/example.doc"), 102 | "/example.doc", 103 | ), 104 | ("", None), 105 | ] 106 | field = ConstrainedFileField(max_length=10, max_upload_size=1_048_576) 107 | -------------------------------------------------------------------------------- /examples/simple/books/models/profile.py: -------------------------------------------------------------------------------- 1 | """ 2 | Profile models. 3 | """ 4 | 5 | from django.db import models 6 | 7 | from rest_framework_tricks.models.fields import NestedProxyField 8 | 9 | __all__ = ("Profile",) 10 | 11 | 12 | class Profile(models.Model): 13 | """Profile.""" 14 | 15 | salutation = models.CharField(max_length=50) 16 | first_name = models.CharField(max_length=200) 17 | last_name = models.CharField(max_length=200) 18 | email = models.EmailField() 19 | birth_date = models.DateField(null=True, blank=True) 20 | biography = models.TextField(null=True, blank=True) 21 | phone_number = models.CharField(max_length=200, null=True, blank=True) 22 | website = models.URLField(null=True, blank=True) 23 | company = models.CharField(max_length=200, null=True, blank=True) 24 | company_phone_number = models.CharField( 25 | max_length=200, null=True, blank=True 26 | ) 27 | company_email = models.EmailField(null=True, blank=True) 28 | company_website = models.URLField(null=True, blank=True) 29 | 30 | bank_name = models.CharField(max_length=200, null=True, blank=True) 31 | bank_account_name = models.CharField(max_length=200, null=True, blank=True) 32 | bank_account_number = models.CharField( 33 | max_length=200, null=True, blank=True 34 | ) 35 | 36 | # This does not cause a model change 37 | personal_information = NestedProxyField( 38 | "salutation", "first_name", "last_name", "birth_date", "biography" 39 | ) 40 | 41 | # This does not cause a model change 42 | personal_contact_information = NestedProxyField( 43 | "email", 44 | "phone_number", 45 | "website", 46 | ) 47 | 48 | # This does not cause a model change 49 | business_contact_information = NestedProxyField( 50 | "company", 51 | "company_email", 52 | "company_phone_number", 53 | "company_website", 54 | ) 55 | 56 | # This does not cause a model change 57 | contact_information = NestedProxyField( 58 | "personal_contact_information", 59 | "business_contact_information", 60 | ) 61 | 62 | # This does not cause a model change 63 | bank_information = NestedProxyField( 64 | "bank_name", 65 | "bank_account_name", 66 | "bank_account_number", 67 | ) 68 | 69 | # This does not cause a model change 70 | data = NestedProxyField( 71 | "personal_information", 72 | "contact_information", 73 | "bank_information", 74 | ) 75 | 76 | # This does not cause a model change 77 | information = NestedProxyField( 78 | "data", 79 | ) 80 | 81 | # This is the structure we want to achieve 82 | # { 83 | # 'information': { 84 | # 'data': { 85 | # 'personal_information': ( 86 | # 'salutation', 87 | # 'first_name', 88 | # 'last_name', 89 | # 'birth_date', 90 | # 'biography', 91 | # ), 92 | # 'contact_information': { 93 | # 'personal_contact_information': ( 94 | # 'email', 95 | # 'phone_number', 96 | # 'website', 97 | # ), 98 | # 'business_contact_information': ( 99 | # 'company', 100 | # 'company_email', 101 | # 'company_phone_number', 102 | # 'company_website', 103 | # ) 104 | # }, 105 | # 'bank_information': ( 106 | # 'bank_name', 107 | # 'bank_account_name', 108 | # 'bank_account_number', 109 | # ), 110 | # } 111 | # } 112 | # } 113 | 114 | class Meta: 115 | """Meta options.""" 116 | 117 | ordering = ["id"] 118 | 119 | def __str__(self): 120 | return self.name 121 | -------------------------------------------------------------------------------- /examples/simple/books/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | Urls. 3 | """ 4 | 5 | from django.urls import re_path as url, include 6 | import rest_framework 7 | from rest_framework.routers import DefaultRouter 8 | 9 | from .views import ( 10 | AddAuthorsToBookView, 11 | AuthorListView, 12 | AuthorListJSONView, 13 | AuthorListValuesView, 14 | AuthorListValuesWithCountsView, 15 | AuthorListWithCountsView, 16 | CreateAuthorsView, 17 | BookListView, 18 | BookListValuesView, 19 | BookListWithCountsView, 20 | PublisherIDsView, 21 | PublisherListView, 22 | UpdateBooksView, 23 | IndexView, 24 | ) 25 | from .viewsets import ( 26 | AuthorViewSet, 27 | AuthorProxyViewSet, 28 | BookViewSet, 29 | BookProxyViewSet, 30 | BookProxy2ViewSet, 31 | ProfileViewSet, 32 | PublisherViewSet, 33 | ) 34 | 35 | __all__ = ("urlpatterns",) 36 | 37 | BASENAME = "basename" 38 | 39 | try: 40 | drf_version = rest_framework.__version__.split(".")[:2] 41 | drf_version = tuple(int(_i) for _i in drf_version) 42 | if drf_version < (3, 9): 43 | BASENAME = "base_name" 44 | except: 45 | pass 46 | 47 | router = DefaultRouter() 48 | 49 | router.register(r"books", BookViewSet, **{BASENAME: "book"}) 50 | 51 | router.register(r"proxy-books", BookProxyViewSet, **{BASENAME: "bookproxy"}) 52 | 53 | router.register(r"proxy2-books", BookProxy2ViewSet, **{BASENAME: "bookproxy2"}) 54 | 55 | router.register(r"publishers", PublisherViewSet, **{BASENAME: "publisher"}) 56 | 57 | router.register(r"profiles", ProfileViewSet, **{BASENAME: "profile"}) 58 | 59 | router.register(r"authors", AuthorViewSet, **{BASENAME: "author"}) 60 | 61 | router.register( 62 | r"proxy-authors", AuthorProxyViewSet, **{BASENAME: "authorproxy"} 63 | ) 64 | 65 | urlpatterns = [ 66 | url(r"^api/", include(router.urls)), 67 | # Authors list 68 | url(r"^authors/$", AuthorListView.as_view(), name="books.authors"), 69 | # Authors list (more efficient) 70 | url( 71 | r"^authors-values/$", 72 | AuthorListValuesView.as_view(), 73 | name="books.authors_values", 74 | ), 75 | # Authors list (with counts) 76 | url( 77 | r"^authors-with-counts/$", 78 | AuthorListWithCountsView.as_view(), 79 | name="books.authors_with_counts", 80 | ), 81 | # Authors list (values with counts) 82 | url( 83 | r"^authors-values-with-counts/$", 84 | AuthorListValuesWithCountsView.as_view(), 85 | name="books.authors_values_with_counts", 86 | ), 87 | # Authors list in JSON format 88 | url( 89 | r"^authors-json/$", 90 | AuthorListJSONView.as_view(), 91 | name="books.authors_json", 92 | ), 93 | # Books list 94 | url(r"^books/$", BookListView.as_view(), name="books.books"), 95 | # Books list (with counts) 96 | url( 97 | r"^books-with-counts/$", 98 | BookListWithCountsView.as_view(), 99 | name="books.books_with_counts", 100 | ), 101 | # Books list (more efficient) 102 | url( 103 | r"^books-values/$", 104 | view=BookListValuesView.as_view(), 105 | name="books.books_values", 106 | ), 107 | # Publishers list 108 | url( 109 | r"^publishers/$", PublisherListView.as_view(), name="books.publishers" 110 | ), 111 | # Publisher IDs 112 | url( 113 | r"^publisher-ids/$", 114 | PublisherIDsView.as_view(), 115 | name="books.publisher_ids", 116 | ), 117 | # Create authors view 118 | url( 119 | r"^create-authors/$", 120 | CreateAuthorsView.as_view(), 121 | name="books.create_authors", 122 | ), 123 | # Add authors to a book view 124 | url( 125 | r"^add-authors-to-book/$", 126 | AddAuthorsToBookView.as_view(), 127 | name="books.add_authors_to_book", 128 | ), 129 | # Update books view 130 | url( 131 | r"^update-books/$", 132 | UpdateBooksView.as_view(), 133 | name="books.update_books", 134 | ), 135 | # Index view 136 | url(r"^$", IndexView.as_view(), name="books.index"), 137 | ] 138 | -------------------------------------------------------------------------------- /examples/simple/books/serializers/book.py: -------------------------------------------------------------------------------- 1 | """ 2 | Book serializers. 3 | """ 4 | 5 | from rest_framework import serializers 6 | from rest_framework_tricks.serializers import ( 7 | HyperlinkedModelSerializer, 8 | ModelSerializer, 9 | ) 10 | 11 | from ..models import Book, BookProxy, BookProxy2 12 | 13 | __all__ = ( 14 | "BookSerializer", 15 | "BookProxySerializer", 16 | "BookProxy2Serializer", 17 | ) 18 | 19 | # **************************************************************************** 20 | # ******************************** Book ************************************** 21 | # **************************************************************************** 22 | 23 | 24 | class PublishingInformationSerializer(serializers.ModelSerializer): 25 | """Publishing information serializer.""" 26 | 27 | publication_date = serializers.DateField(required=False) 28 | isbn = serializers.CharField(required=False) 29 | pages = serializers.IntegerField(required=False) 30 | 31 | class Meta: 32 | """Meta options.""" 33 | 34 | model = Book 35 | fields = ( 36 | "publication_date", 37 | "isbn", 38 | "pages", 39 | ) 40 | nested_proxy_field = True 41 | 42 | 43 | class StockInformationSerializer(serializers.ModelSerializer): 44 | """Stock information serializer.""" 45 | 46 | class Meta: 47 | """Meta options.""" 48 | 49 | model = Book 50 | fields = ( 51 | "stock_count", 52 | "price", 53 | "state", 54 | ) 55 | nested_proxy_field = True 56 | 57 | 58 | class BookSerializer(HyperlinkedModelSerializer): 59 | """Book serializer.""" 60 | 61 | publishing_information = PublishingInformationSerializer(required=False) 62 | stock_information = StockInformationSerializer(required=False) 63 | 64 | class Meta: 65 | """Meta options.""" 66 | 67 | model = Book 68 | fields = ( 69 | "url", 70 | "id", 71 | "title", 72 | "description", 73 | "summary", 74 | "publishing_information", 75 | "stock_information", 76 | ) 77 | 78 | 79 | # **************************************************************************** 80 | # ***************************** BookProxy ************************************ 81 | # **************************************************************************** 82 | 83 | 84 | class BookProxySerializer(HyperlinkedModelSerializer): 85 | """Book proxy serializer.""" 86 | 87 | publishing_information = PublishingInformationSerializer(required=False) 88 | stock_information = StockInformationSerializer(required=False) 89 | city = serializers.CharField(source="publisher.city", read_only=True) 90 | 91 | class Meta: 92 | """Meta options.""" 93 | 94 | model = BookProxy 95 | fields = ( 96 | "url", 97 | "id", 98 | "title", 99 | "description", 100 | "summary", 101 | "publishing_information", 102 | "stock_information", 103 | "city", 104 | "state", 105 | ) 106 | 107 | 108 | # **************************************************************************** 109 | # ***************************** BookProxy ************************************ 110 | # **************************************************************************** 111 | 112 | 113 | class BookProxy2Serializer(HyperlinkedModelSerializer): 114 | """Book proxy serializer.""" 115 | 116 | publishing_information = PublishingInformationSerializer(required=False) 117 | stock_information = StockInformationSerializer(required=False) 118 | city = serializers.CharField(source="publisher.city", read_only=True) 119 | 120 | class Meta: 121 | """Meta options.""" 122 | 123 | model = BookProxy2 124 | fields = ( 125 | "url", 126 | "id", 127 | "title", 128 | "description", 129 | "summary", 130 | "publishing_information", 131 | "stock_information", 132 | "city", 133 | ) 134 | -------------------------------------------------------------------------------- /src/rest_framework_tricks/filters/ordering.py: -------------------------------------------------------------------------------- 1 | """ 2 | Ordering filter. 3 | """ 4 | from rest_framework.filters import OrderingFilter as DjangoOrderingFilter 5 | 6 | __author__ = "Artur Barseghyan " 7 | __copyright__ = "2017-2022 Artur Barseghyan" 8 | __license__ = "GPL-2.0-only OR LGPL-2.1-or-later" 9 | __all__ = ("OrderingFilter",) 10 | 11 | 12 | class OrderingFilter(DjangoOrderingFilter): 13 | """Ordering filter improved. 14 | 15 | Example: 16 | 17 | >>> from rest_framework_tricks.filters import OrderingFilter 18 | >>> 19 | >>> class BooksViewSet(mixins.RetrieveModelMixin, 20 | >>> mixins.ListModelMixin, 21 | >>> viewsets.GenericViewSet): 22 | >>> 23 | >>> serializer_class = BookSerializer 24 | >>> filter_backends = ( 25 | >>> OrderingFilter, 26 | >>> ) 27 | >>> ordering_fields = { 28 | >>> 'email': 'user__email', 29 | >>> 'full_name': 'user__first_name', 30 | >>> 'last_login': 'user__last_login', 31 | >>> 'is_active': 'user__is_active', 32 | >>> } 33 | 34 | Then it can be used in a view set as follows: 35 | 36 | GET /books/api/proxy-books/?ordering=email 37 | """ 38 | 39 | def get_valid_fields(self, queryset, view, context=None): 40 | """Done. 41 | 42 | :param queryset: 43 | :param view: 44 | :param context: 45 | :return: 46 | """ 47 | valid_fields = getattr(view, "ordering_fields", self.ordering_fields) 48 | if context is None: 49 | context = {} 50 | 51 | if isinstance(valid_fields, dict): 52 | return valid_fields.items() 53 | else: 54 | return super(OrderingFilter, self).get_valid_fields( 55 | queryset, view, context 56 | ) 57 | 58 | def get_ordering(self, request, queryset, view): 59 | """Get ordering. 60 | 61 | Important: list returned in this method is used directly 62 | in the filter_queryset method like: 63 | 64 | >>> queryset.order_by(*ordering) 65 | 66 | Ordering is set by a comma delimited ?ordering=... query parameter. 67 | 68 | The `ordering` query parameter can be overridden by setting 69 | the `ordering_param` value on the OrderingFilter or by 70 | specifying an `ORDERING_PARAM` value in the API settings. 71 | """ 72 | valid_fields = getattr(view, "ordering_fields", self.ordering_fields) 73 | 74 | # If valid_fields is a dictionary, treat it differently 75 | if isinstance(valid_fields, dict): 76 | params = request.query_params.get(self.ordering_param) 77 | 78 | if params: 79 | fields = [param.strip() for param in params.split(",")] 80 | _ordering = self.remove_invalid_fields( 81 | queryset, fields, view, request 82 | ) 83 | 84 | ordering = [] 85 | for item in _ordering: 86 | if "-" in item: 87 | value = valid_fields.get(item[1:]) 88 | if isinstance(value, (tuple, list)): 89 | value = ["-{}".format(__v) for __v in value] 90 | ordering += value 91 | else: 92 | ordering.append("-{}".format(value)) 93 | else: 94 | value = valid_fields.get(item) 95 | if isinstance(value, (tuple, list)): 96 | ordering += value 97 | else: 98 | ordering.append(value) 99 | if ordering: 100 | return ordering 101 | 102 | # No ordering was included, or all the ordering fields were invalid 103 | return self.get_default_ordering(view) 104 | 105 | # In all other cases, use default behaviour 106 | else: 107 | return super(OrderingFilter, self).get_ordering( 108 | request, queryset, view 109 | ) 110 | -------------------------------------------------------------------------------- /examples/simple/books/serializers/profile.py: -------------------------------------------------------------------------------- 1 | """ 2 | Profile serializers. 3 | """ 4 | 5 | from rest_framework import serializers 6 | from rest_framework_tricks.serializers import ( 7 | HyperlinkedModelSerializer, 8 | ModelSerializer, 9 | ) 10 | 11 | from ..models import Profile 12 | 13 | __all__ = ("ProfileSerializer",) 14 | 15 | # **************************************************************************** 16 | # ******************************* Profile ************************************ 17 | # **************************************************************************** 18 | 19 | 20 | class PersonalInformationSerializer(serializers.ModelSerializer): 21 | """Personal information serializer.""" 22 | 23 | class Meta: 24 | """Meta options.""" 25 | 26 | model = Profile 27 | fields = ( 28 | "salutation", 29 | "first_name", 30 | "last_name", 31 | "birth_date", 32 | "biography", 33 | ) 34 | nested_proxy_field = True 35 | 36 | 37 | class PersonalContactInformationSerializer(serializers.ModelSerializer): 38 | """Personal contact information serializer.""" 39 | 40 | class Meta: 41 | """Meta options.""" 42 | 43 | model = Profile 44 | fields = ( 45 | "email", 46 | "phone_number", 47 | "website", 48 | ) 49 | nested_proxy_field = True 50 | 51 | 52 | class BusinessContactInformationSerializer(serializers.ModelSerializer): 53 | """Business contact information serializer.""" 54 | 55 | class Meta: 56 | """Meta options.""" 57 | 58 | model = Profile 59 | fields = ( 60 | "company", 61 | "company_email", 62 | "company_phone_number", 63 | "company_website", 64 | ) 65 | nested_proxy_field = True 66 | 67 | 68 | class ContactInformationSerializer(serializers.ModelSerializer): 69 | """Contact information serializer.""" 70 | 71 | personal_contact_information = PersonalContactInformationSerializer( 72 | required=False 73 | ) 74 | business_contact_information = BusinessContactInformationSerializer( 75 | required=False 76 | ) 77 | 78 | class Meta: 79 | """Meta options.""" 80 | 81 | model = Profile 82 | fields = ( 83 | "personal_contact_information", 84 | "business_contact_information", 85 | ) 86 | nested_proxy_field = True 87 | 88 | 89 | class BankInformationSerializer(serializers.ModelSerializer): 90 | """Bank information serializer.""" 91 | 92 | class Meta: 93 | """Meta options.""" 94 | 95 | model = Profile 96 | fields = ( 97 | "bank_name", 98 | "bank_account_name", 99 | "bank_account_number", 100 | ) 101 | nested_proxy_field = True 102 | 103 | 104 | class DataSerializer(serializers.ModelSerializer): 105 | """Data serializer.""" 106 | 107 | personal_information = PersonalInformationSerializer(required=False) 108 | contact_information = ContactInformationSerializer(required=False) 109 | bank_information = BankInformationSerializer(required=False) 110 | 111 | class Meta: 112 | """Meta options.""" 113 | 114 | model = Profile 115 | fields = ( 116 | "personal_information", 117 | "contact_information", 118 | "bank_information", 119 | ) 120 | nested_proxy_field = True 121 | 122 | 123 | class InformationSerializer(serializers.ModelSerializer): 124 | """information serializer.""" 125 | 126 | data = DataSerializer(required=False) 127 | 128 | class Meta: 129 | """Meta options.""" 130 | 131 | model = Profile 132 | fields = ("data",) 133 | nested_proxy_field = True 134 | 135 | 136 | class ProfileSerializer(ModelSerializer): 137 | """Profile serializer.""" 138 | 139 | information = InformationSerializer(required=False) 140 | 141 | class Meta: 142 | """Meta options.""" 143 | 144 | model = Profile 145 | fields = ( 146 | "id", 147 | "information", 148 | ) 149 | -------------------------------------------------------------------------------- /examples/requirements/documentation.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.10 3 | # To update, run: 4 | # 5 | # pip-compile documentation.in 6 | # 7 | alabaster==0.7.12 8 | # via sphinx 9 | asgiref==3.5.2 10 | # via django 11 | attrs==22.1.0 12 | # via 13 | # jsonschema 14 | # pytest 15 | babel==2.11.0 16 | # via sphinx 17 | certifi==2022.9.24 18 | # via requests 19 | charset-normalizer==2.1.1 20 | # via requests 21 | coverage[toml]==6.5.0 22 | # via 23 | # -r test.in 24 | # pytest-cov 25 | distlib==0.3.6 26 | # via virtualenv 27 | django==3.2.16 28 | # via 29 | # -r django_3_2.in 30 | # django-debug-toolbar 31 | # django-nine 32 | # djangorestframework 33 | # drf-spectacular 34 | # drf-spectacular-sidecar 35 | django-debug-toolbar==3.7.0 36 | # via -r django_3_2.in 37 | django-debug-toolbar-force==0.1.8 38 | # via -r django_3_2.in 39 | django-nine==0.2.5 40 | # via 41 | # django-debug-toolbar-force 42 | # django-ormex 43 | django-ormex==0.2.1 44 | # via -r django_3_2.in 45 | djangorestframework==3.12.4 46 | # via 47 | # -r django_3_2.in 48 | # drf-spectacular 49 | docutils==0.19 50 | # via 51 | # -r docs.in 52 | # rst2pdf 53 | # rstcheck 54 | # sphinx 55 | drf-spectacular[sidecar]==0.24.2 56 | # via -r django_3_2.in 57 | drf-spectacular-sidecar==2022.11.1 58 | # via 59 | # -r django_3_2.in 60 | # drf-spectacular 61 | factory-boy==3.2.1 62 | # via -r test.in 63 | faker==15.3.3 64 | # via 65 | # -r test.in 66 | # factory-boy 67 | filelock==3.8.0 68 | # via 69 | # tox 70 | # virtualenv 71 | idna==3.4 72 | # via requests 73 | imagesize==1.4.1 74 | # via sphinx 75 | importlib-metadata==5.0.0 76 | # via rst2pdf 77 | inflection==0.5.1 78 | # via drf-spectacular 79 | iniconfig==1.1.1 80 | # via pytest 81 | jinja2==3.1.2 82 | # via 83 | # -r docs.in 84 | # rst2pdf 85 | # sphinx 86 | jsonschema==4.17.0 87 | # via drf-spectacular 88 | markupsafe==2.1.1 89 | # via 90 | # -r docs.in 91 | # jinja2 92 | packaging==21.3 93 | # via 94 | # pytest 95 | # rst2pdf 96 | # sphinx 97 | # tox 98 | pillow==9.3.0 99 | # via 100 | # -r common.in 101 | # reportlab 102 | platformdirs==2.5.4 103 | # via virtualenv 104 | pluggy==0.13.1 105 | # via 106 | # pytest 107 | # tox 108 | py==1.11.0 109 | # via 110 | # -r test.in 111 | # pytest 112 | # tox 113 | pygments==2.13.0 114 | # via 115 | # rst2pdf 116 | # sphinx 117 | pyparsing==3.0.9 118 | # via packaging 119 | pyrsistent==0.19.2 120 | # via jsonschema 121 | pytest==6.2.4 122 | # via 123 | # -r test.in 124 | # pytest-cov 125 | # pytest-django 126 | # pytest-ordering 127 | # pytest-pythonpath 128 | pytest-cov==2.12.0 129 | # via -r test.in 130 | pytest-django==4.3.0 131 | # via -r test.in 132 | pytest-ordering==0.6 133 | # via -r test.in 134 | pytest-pythonpath==0.7.4 135 | # via -r test.in 136 | python-dateutil==2.8.2 137 | # via faker 138 | python-memcached==1.58 139 | # via -r common.in 140 | pytz==2022.6 141 | # via 142 | # -r common.in 143 | # babel 144 | # django 145 | pyyaml==6.0 146 | # via 147 | # drf-spectacular 148 | # rst2pdf 149 | reportlab==3.6.12 150 | # via rst2pdf 151 | requests==2.28.1 152 | # via sphinx 153 | rst2pdf==0.99 154 | # via -r docs.in 155 | rstcheck==3.3.1 156 | # via -r docs.in 157 | six==1.16.0 158 | # via 159 | # -r common.in 160 | # django-debug-toolbar-force 161 | # django-ormex 162 | # python-dateutil 163 | # python-memcached 164 | # tox 165 | smartypants==2.0.1 166 | # via rst2pdf 167 | snowballstemmer==2.2.0 168 | # via sphinx 169 | sphinx==5.3.0 170 | # via -r docs.in 171 | sphinxcontrib-applehelp==1.0.2 172 | # via sphinx 173 | sphinxcontrib-devhelp==1.0.2 174 | # via sphinx 175 | sphinxcontrib-htmlhelp==2.0.0 176 | # via sphinx 177 | sphinxcontrib-jsmath==1.0.1 178 | # via sphinx 179 | sphinxcontrib-qthelp==1.0.3 180 | # via sphinx 181 | sphinxcontrib-serializinghtml==1.1.5 182 | # via sphinx 183 | sqlparse==0.4.3 184 | # via 185 | # django 186 | # django-debug-toolbar 187 | toml==0.10.2 188 | # via pytest 189 | tomli==2.0.1 190 | # via 191 | # coverage 192 | # tox 193 | tox==3.27.1 194 | # via -r test.in 195 | uritemplate==4.1.1 196 | # via drf-spectacular 197 | urllib3==1.26.12 198 | # via requests 199 | virtualenv==20.16.7 200 | # via tox 201 | zipp==3.10.0 202 | # via importlib-metadata 203 | -------------------------------------------------------------------------------- /examples/simple/books/serializers/author.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author serializers. 3 | """ 4 | 5 | from rest_framework import serializers 6 | from rest_framework_tricks.serializers import ( 7 | HyperlinkedModelSerializer, 8 | ModelSerializer, 9 | ) 10 | 11 | from ..models import ( 12 | Author, 13 | AuthorProxy, 14 | ) 15 | 16 | __all__ = ( 17 | "AuthorProxySerializer", 18 | "AuthorSerializer", 19 | ) 20 | 21 | 22 | # **************************************************************************** 23 | # ******************************** Author ************************************ 24 | # **************************************************************************** 25 | 26 | 27 | class PersonalContactInformationSerializer(serializers.ModelSerializer): 28 | """Personal contact information serializer.""" 29 | 30 | class Meta: 31 | """Meta options.""" 32 | 33 | model = Author 34 | fields = ( 35 | "email", 36 | "phone_number", 37 | "website", 38 | ) 39 | nested_proxy_field = True 40 | 41 | 42 | class BusinessContactInformationSerializer(serializers.ModelSerializer): 43 | """Business contact information serializer.""" 44 | 45 | class Meta: 46 | """Meta options.""" 47 | 48 | model = Author 49 | fields = ( 50 | "company", 51 | "company_email", 52 | "company_phone_number", 53 | "company_website", 54 | ) 55 | nested_proxy_field = True 56 | 57 | 58 | class ContactInformationSerializer(serializers.ModelSerializer): 59 | """Contact information serializer.""" 60 | 61 | personal_contact_information = PersonalContactInformationSerializer( 62 | required=False 63 | ) 64 | business_contact_information = BusinessContactInformationSerializer( 65 | required=False 66 | ) 67 | 68 | class Meta: 69 | """Meta options.""" 70 | 71 | model = Author 72 | fields = ( 73 | "personal_contact_information", 74 | "business_contact_information", 75 | ) 76 | nested_proxy_field = True 77 | 78 | 79 | class AuthorSerializer(ModelSerializer): 80 | """Author serializer.""" 81 | 82 | contact_information = ContactInformationSerializer(required=False) 83 | 84 | class Meta: 85 | """Meta options.""" 86 | 87 | model = Author 88 | fields = ( 89 | "id", 90 | "salutation", 91 | "name", 92 | "birth_date", 93 | "biography", 94 | "contact_information", 95 | ) 96 | 97 | 98 | # **************************************************************************** 99 | # ****************************** AuthorProxy ********************************* 100 | # **************************************************************************** 101 | 102 | 103 | class ProxyPersonalContactInformationSerializer(serializers.ModelSerializer): 104 | """Personal contact information serializer.""" 105 | 106 | class Meta: 107 | """Meta options.""" 108 | 109 | model = AuthorProxy 110 | fields = ( 111 | "email", 112 | "phone_number", 113 | "website", 114 | ) 115 | nested_proxy_field = True 116 | 117 | 118 | class ProxyBusinessContactInformationSerializer(serializers.ModelSerializer): 119 | """Business contact information serializer.""" 120 | 121 | class Meta: 122 | """Meta options.""" 123 | 124 | model = AuthorProxy 125 | fields = ( 126 | "company", 127 | "company_email", 128 | "company_phone_number", 129 | "company_website", 130 | ) 131 | nested_proxy_field = True 132 | 133 | 134 | class ProxyContactInformationSerializer(serializers.ModelSerializer): 135 | """Contact information serializer.""" 136 | 137 | personal_contact_information = ProxyPersonalContactInformationSerializer( 138 | required=False 139 | ) 140 | business_contact_information = ProxyBusinessContactInformationSerializer( 141 | required=False 142 | ) 143 | 144 | class Meta: 145 | """Meta options.""" 146 | 147 | model = AuthorProxy 148 | fields = ( 149 | "personal_contact_information", 150 | "business_contact_information", 151 | ) 152 | nested_proxy_field = True 153 | 154 | 155 | class AuthorProxySerializer(ModelSerializer): 156 | """Author serializer.""" 157 | 158 | contact_information = ProxyContactInformationSerializer(required=False) 159 | 160 | class Meta: 161 | """Meta options.""" 162 | 163 | model = AuthorProxy 164 | fields = ( 165 | "id", 166 | "salutation", 167 | "name", 168 | "birth_date", 169 | "biography", 170 | "contact_information", 171 | ) 172 | -------------------------------------------------------------------------------- /examples/simple/factories/books_book.py: -------------------------------------------------------------------------------- 1 | """ 2 | Books Book model factory. 3 | """ 4 | 5 | import random 6 | 7 | from factory import ( 8 | SubFactory, 9 | post_generation, 10 | LazyAttribute, 11 | ) 12 | from factory.django import DjangoModelFactory 13 | from factory.fuzzy import FuzzyChoice 14 | 15 | from books.constants import BOOK_PUBLISHING_STATUS_CHOICES 16 | from books.models import Book 17 | 18 | from .factory_faker import Faker 19 | from .books_author import ( 20 | AuthorFactory, 21 | LimitedAuthorFactory, 22 | SingleAuthorFactory, 23 | ) 24 | from .books_order import OrderFactory 25 | from .books_orderline import OrderLineFactory 26 | from .books_tag import LimitedTagFactory 27 | 28 | __all__ = ( 29 | "BookFactory", 30 | "BookWithoutOrdersFactory", 31 | "BookWithoutTagsAndOrdersFactory", 32 | "BookWithoutTagsFactory", 33 | "BookWithUniqueTitleFactory", 34 | "SingleBookFactory", 35 | ) 36 | 37 | 38 | class BaseBookFactory(DjangoModelFactory): 39 | """Base book factory.""" 40 | 41 | title = Faker("text", max_nb_chars=100) 42 | summary = Faker("text") 43 | publisher = SubFactory("factories.books_publisher.LimitedPublisherFactory") 44 | publication_date = Faker("date") 45 | price = Faker("pydecimal", left_digits=2, right_digits=2, positive=True) 46 | isbn = Faker("isbn13") 47 | state = FuzzyChoice(dict(BOOK_PUBLISHING_STATUS_CHOICES).keys()) 48 | pages = LazyAttribute(lambda __x: random.randint(10, 200)) 49 | 50 | class Meta: 51 | """Meta class.""" 52 | 53 | model = Book 54 | abstract = True 55 | 56 | @post_generation 57 | def tags(obj, created, extracted, **kwargs): 58 | """Create Tag objects for the created Book instance.""" 59 | if created: 60 | # Create from 1 to 7 ``Tag`` objects. 61 | amount = random.randint(1, 7) 62 | tags = LimitedTagFactory.create_batch(amount, **kwargs) 63 | obj.tags.add(*tags) 64 | 65 | @post_generation 66 | def authors(obj, created, extracted, **kwargs): 67 | """Create `Author` objects for the created `Book` instance.""" 68 | if created: 69 | # Create random amount of `Author` objects. 70 | amount = random.randint(3, 7) 71 | authors = LimitedAuthorFactory.create_batch(amount, **kwargs) 72 | obj.authors.add(*authors) 73 | 74 | @post_generation 75 | def orders(obj, created, extracted, **kwargs): 76 | """Create `Order` objects for the created `Book` instance.""" 77 | if created: 78 | # Create 3 `Order` objects. 79 | amount = random.randint(2, 7) 80 | orders = OrderFactory.create_batch(amount, **kwargs) 81 | order_line_kwargs = dict(kwargs) 82 | order_line_kwargs["book"] = obj 83 | for order in orders: 84 | # Create 1 `OrderLine` object. 85 | amount = random.randint(1, 5) 86 | order_lines = OrderLineFactory.create_batch( 87 | amount, **order_line_kwargs 88 | ) 89 | order.lines.add(*order_lines) 90 | 91 | 92 | class BookFactory(BaseBookFactory): 93 | """Book factory.""" 94 | 95 | 96 | class BookWithUniqueTitleFactory(BaseBookFactory): 97 | """Book factory with unique title attribute.""" 98 | 99 | class Meta: 100 | """Meta class.""" 101 | 102 | django_get_or_create = ("title",) 103 | 104 | 105 | class SingleBookFactory(BaseBookFactory): 106 | """Book factory, but limited to a single book.""" 107 | 108 | id = 999999 109 | title = "Performance optimisation" 110 | publisher = SubFactory("factories.books_publisher.SinglePublisherFactory") 111 | 112 | class Meta: 113 | """Meta class.""" 114 | 115 | django_get_or_create = ("id",) 116 | 117 | @post_generation 118 | def authors(obj, created, extracted, **kwargs): 119 | """Create `Author` objects for the created `Book` instance.""" 120 | if created: 121 | # Create a single `Author` object. 122 | author = SingleAuthorFactory() 123 | obj.authors.add(author) 124 | 125 | 126 | class BookWithoutTagsFactory(BaseBookFactory): 127 | """Book without tags factory.""" 128 | 129 | @post_generation 130 | def tags(obj, created, extracted, **kwargs): 131 | """Dummy.""" 132 | 133 | 134 | class BookWithoutOrdersFactory(BaseBookFactory): 135 | """Book without orders factory.""" 136 | 137 | @post_generation 138 | def orders(obj, created, extracted, **kwargs): 139 | """Dummy.""" 140 | 141 | 142 | class BookWithoutTagsAndOrdersFactory(BaseBookFactory): 143 | """Book without tags and orders factory.""" 144 | 145 | @post_generation 146 | def tags(obj, created, extracted, **kwargs): 147 | """Dummy.""" 148 | 149 | @post_generation 150 | def orders(obj, created, extracted, **kwargs): 151 | """Dummy.""" 152 | -------------------------------------------------------------------------------- /examples/simple/books/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="Author", 17 | fields=[ 18 | ( 19 | "id", 20 | models.AutoField( 21 | verbose_name="ID", 22 | serialize=False, 23 | auto_created=True, 24 | primary_key=True, 25 | ), 26 | ), 27 | ("salutation", models.CharField(max_length=10)), 28 | ("name", models.CharField(max_length=200)), 29 | ("email", models.EmailField(max_length=254)), 30 | ( 31 | "headshot", 32 | models.ImageField( 33 | null=True, upload_to=b"author_headshots", blank=True 34 | ), 35 | ), 36 | ], 37 | options={ 38 | "ordering": ["name"], 39 | }, 40 | ), 41 | migrations.CreateModel( 42 | name="Book", 43 | fields=[ 44 | ( 45 | "id", 46 | models.AutoField( 47 | verbose_name="ID", 48 | serialize=False, 49 | auto_created=True, 50 | primary_key=True, 51 | ), 52 | ), 53 | ("title", models.CharField(max_length=100)), 54 | ("publication_date", models.DateField()), 55 | ("isbn", models.CharField(max_length=100)), 56 | ("price", models.DecimalField(max_digits=10, decimal_places=2)), 57 | ("authors", models.ManyToManyField(to="books.Author")), 58 | ], 59 | options={ 60 | "ordering": ["isbn"], 61 | }, 62 | ), 63 | migrations.CreateModel( 64 | name="Order", 65 | fields=[ 66 | ( 67 | "id", 68 | models.AutoField( 69 | verbose_name="ID", 70 | serialize=False, 71 | auto_created=True, 72 | primary_key=True, 73 | ), 74 | ), 75 | ("created", models.DateField(auto_now_add=True)), 76 | ("updated", models.DateField(auto_now=True)), 77 | ], 78 | options={ 79 | "ordering": ["-created"], 80 | }, 81 | ), 82 | migrations.CreateModel( 83 | name="OrderLine", 84 | fields=[ 85 | ( 86 | "id", 87 | models.AutoField( 88 | verbose_name="ID", 89 | serialize=False, 90 | auto_created=True, 91 | primary_key=True, 92 | ), 93 | ), 94 | ("book", models.ForeignKey(to="books.Book", on_delete=models.CASCADE)), 95 | ], 96 | options={ 97 | "ordering": ["order__created"], 98 | }, 99 | ), 100 | migrations.CreateModel( 101 | name="Publisher", 102 | fields=[ 103 | ( 104 | "id", 105 | models.AutoField( 106 | verbose_name="ID", 107 | serialize=False, 108 | auto_created=True, 109 | primary_key=True, 110 | ), 111 | ), 112 | ("name", models.CharField(max_length=30)), 113 | ("address", models.CharField(max_length=50)), 114 | ("city", models.CharField(max_length=60)), 115 | ("state_province", models.CharField(max_length=30)), 116 | ("country", models.CharField(max_length=50)), 117 | ("website", models.URLField()), 118 | ], 119 | options={ 120 | "ordering": ["name"], 121 | }, 122 | ), 123 | migrations.AddField( 124 | model_name="order", 125 | name="lines", 126 | field=models.ManyToManyField(to="books.OrderLine", blank=True), 127 | ), 128 | migrations.AddField( 129 | model_name="order", 130 | name="owner", 131 | field=models.ForeignKey( 132 | to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE 133 | ), 134 | ), 135 | migrations.AddField( 136 | model_name="book", 137 | name="publisher", 138 | field=models.ForeignKey(to="books.Publisher", on_delete=models.CASCADE), 139 | ), 140 | ] 141 | --------------------------------------------------------------------------------