django-pfx 1.4.dev104__tar.gz → 1.4.dev108__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/PKG-INFO +4 -2
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/django_pfx.egg-info/PKG-INFO +4 -2
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/django_pfx.egg-info/SOURCES.txt +5 -1
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/django_pfx.egg-info/requires.txt +1 -0
- django_pfx-1.4.dev108/pfx/pfxcore/fields/__init__.py +3 -0
- django_pfx-1.4.dev108/pfx/pfxcore/fields/media_field.py +60 -0
- django_pfx-1.4.dev104/pfx/pfxcore/fields.py → django_pfx-1.4.dev108/pfx/pfxcore/fields/minutes_duration_field.py +0 -55
- django_pfx-1.4.dev108/pfx/pfxcore/fields/nh3_field.py +83 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +7 -7
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/fields.py +11 -11
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/rest_views.py +16 -21
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/requirements.txt +1 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/setup.cfg +1 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/__init__.py +1 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/basic_api_test.py +12 -9
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_fields_minutes_duration.py +1 -1
- django_pfx-1.4.dev108/tests/tests/test_fields_nh3.py +38 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/.gitignore +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/.gitlab-ci.yml +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/.pre-commit-config.yaml +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/LICENSE +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/MANIFEST.in +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/README.md +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/django_pfx.egg-info/dependency_links.txt +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/django_pfx.egg-info/top_level.txt +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/Makefile +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/conf.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/index.rst +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/source/api.views.rst +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/source/authentication.md +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/source/decorator.md +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/source/generate_openapi.md +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/source/getting_started.md +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/source/internationalisation.md +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/source/model.md +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/source/pfx_views.md +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/source/profiling.md +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/source/settings.md +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/doc/source/testing.md +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/img/pfx.png +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/img/pfx.svg +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/make_messages +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/manage.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/apidoc/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/apidoc/parameters.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/apidoc/schema.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/apidoc/tags.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/apps.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/decorator/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/decorator/rest.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/default_settings.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/exceptions.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/http/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/http/json_response.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/management/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/management/commands/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/management/commands/profile.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/middleware/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/middleware/authentication.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/middleware/locale.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/middleware/profiling.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/migrations/0001_initial.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/migrations/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/migrations/operations/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/migrations/operations/permissions.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/models/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/models/cache_mixins.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/models/login_ban.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/models/not_null_fields.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/models/ordered_model_mixin.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/models/otp_user_mixin.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/models/pfx_models.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/models/pfx_user.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/serializers/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/serializers/json.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/settings.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/shortcuts.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/storage/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/storage/exceptions.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/storage/local_storage.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/storage/s3_storage.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/test.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/urls.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/authentication_views.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/filters_views.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/locale_views.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/media_rest_view_mixin.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/ordered_rest_view_mixin.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/date_format.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/groups.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/list_count.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/list_items.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/list_order.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/list_search.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/subset.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/settings/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pfx/settings/dev.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/pyproject.toml +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/serve-doc +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/setup.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/apps.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/migrations/0001_initial.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/migrations/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/models.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/settings/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/settings/ci.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/settings/common.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/settings/dev.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/settings/dev_custom_example.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/settings/dev_default.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/basic_api_errors.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_api_doc.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_api_doc_search.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_auth_api.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_body_mixin.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_cache.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_client.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_fields_choices.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_filters.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_locale_api.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_ordered_rest_view_mixin.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_perm_tests.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_permissions.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_perms_api.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_post_migrate_groups_update.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_profiling_middleware.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_settings.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_shortcuts.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_timezone_middleware.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_tools.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_user_queryset.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_view_decorators.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/tests/test_view_fields.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/urls.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests/views.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/migrations/0001_initial.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/migrations/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/settings/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/settings/ci.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/settings/common.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/settings/dev.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/settings/dev_custom_example.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/settings/dev_default.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/tests/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/tests/test_api.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/tests/test_auth_api.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/urls.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_base_user/views.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/migrations/0001_initial.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/migrations/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/models.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/settings/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/settings/ci.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/settings/common.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/settings/dev.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/settings/dev_custom_example.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/settings/dev_default.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/tests/__init__.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/tests/test_api.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/tests/test_auth_api.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/urls.py +0 -0
- {django_pfx-1.4.dev104 → django_pfx-1.4.dev108}/tests_custom_user/views.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: django-pfx
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.dev108
|
|
4
4
|
Summary: Django PFX is a toolkit designed to streamline the development of RESTful APIs using the Django framework.
|
|
5
5
|
Author: Hervé Martinet
|
|
6
6
|
Author-email: herve.martinet@gmail.com
|
|
@@ -34,8 +34,10 @@ Requires-Dist: apispec
|
|
|
34
34
|
Requires-Dist: pyyaml
|
|
35
35
|
Requires-Dist: pytz
|
|
36
36
|
Requires-Dist: dill
|
|
37
|
+
Requires-Dist: nh3
|
|
37
38
|
Provides-Extra: otp
|
|
38
39
|
Requires-Dist: pyotp; extra == "otp"
|
|
40
|
+
Dynamic: license-file
|
|
39
41
|
|
|
40
42
|
# Django PFX
|
|
41
43
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: django-pfx
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.dev108
|
|
4
4
|
Summary: Django PFX is a toolkit designed to streamline the development of RESTful APIs using the Django framework.
|
|
5
5
|
Author: Hervé Martinet
|
|
6
6
|
Author-email: herve.martinet@gmail.com
|
|
@@ -34,8 +34,10 @@ Requires-Dist: apispec
|
|
|
34
34
|
Requires-Dist: pyyaml
|
|
35
35
|
Requires-Dist: pytz
|
|
36
36
|
Requires-Dist: dill
|
|
37
|
+
Requires-Dist: nh3
|
|
37
38
|
Provides-Extra: otp
|
|
38
39
|
Requires-Dist: pyotp; extra == "otp"
|
|
40
|
+
Dynamic: license-file
|
|
39
41
|
|
|
40
42
|
# Django PFX
|
|
41
43
|
|
|
@@ -37,7 +37,6 @@ pfx/pfxcore/__init__.py
|
|
|
37
37
|
pfx/pfxcore/apps.py
|
|
38
38
|
pfx/pfxcore/default_settings.py
|
|
39
39
|
pfx/pfxcore/exceptions.py
|
|
40
|
-
pfx/pfxcore/fields.py
|
|
41
40
|
pfx/pfxcore/settings.py
|
|
42
41
|
pfx/pfxcore/shortcuts.py
|
|
43
42
|
pfx/pfxcore/test.py
|
|
@@ -48,6 +47,10 @@ pfx/pfxcore/apidoc/schema.py
|
|
|
48
47
|
pfx/pfxcore/apidoc/tags.py
|
|
49
48
|
pfx/pfxcore/decorator/__init__.py
|
|
50
49
|
pfx/pfxcore/decorator/rest.py
|
|
50
|
+
pfx/pfxcore/fields/__init__.py
|
|
51
|
+
pfx/pfxcore/fields/media_field.py
|
|
52
|
+
pfx/pfxcore/fields/minutes_duration_field.py
|
|
53
|
+
pfx/pfxcore/fields/nh3_field.py
|
|
51
54
|
pfx/pfxcore/http/__init__.py
|
|
52
55
|
pfx/pfxcore/http/json_response.py
|
|
53
56
|
pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo
|
|
@@ -141,6 +144,7 @@ tests/tests/test_cache.py
|
|
|
141
144
|
tests/tests/test_client.py
|
|
142
145
|
tests/tests/test_fields_choices.py
|
|
143
146
|
tests/tests/test_fields_minutes_duration.py
|
|
147
|
+
tests/tests/test_fields_nh3.py
|
|
144
148
|
tests/tests/test_filters.py
|
|
145
149
|
tests/tests/test_locale_api.py
|
|
146
150
|
tests/tests/test_ordered_rest_view_mixin.py
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
|
|
4
|
+
from django.db import models
|
|
5
|
+
from django.db.models.signals import post_delete
|
|
6
|
+
from django.dispatch import receiver
|
|
7
|
+
|
|
8
|
+
from pfx.pfxcore.shortcuts import settings
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_storage_class(class_path):
|
|
14
|
+
ps = class_path.split('.')
|
|
15
|
+
return getattr(import_module('.'.join(ps[:-1])), ps[-1])()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MediaField(models.JSONField):
|
|
19
|
+
def __init__(
|
|
20
|
+
self, *args, max_length=255, get_key=None, storage=None,
|
|
21
|
+
auto_delete=False, **kwargs):
|
|
22
|
+
self.get_key = get_key or self.get_default_key
|
|
23
|
+
if not storage and not settings.STORAGE_DEFAULT:
|
|
24
|
+
raise Exception(
|
|
25
|
+
"Missing storage. You have to set a storage "
|
|
26
|
+
"class on the field or define STORAGE_DEFAULT settings.")
|
|
27
|
+
self.storage = storage or get_storage_class(settings.STORAGE_DEFAULT)
|
|
28
|
+
self.auto_delete = auto_delete
|
|
29
|
+
super().__init__(
|
|
30
|
+
*args, max_length=max_length,
|
|
31
|
+
default=kwargs.pop('default', dict),
|
|
32
|
+
blank=kwargs.pop('blank', True),
|
|
33
|
+
**kwargs)
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def get_default_key(obj, filename):
|
|
37
|
+
return f"{type(obj).__name__}/{obj.pk}/{filename}"
|
|
38
|
+
|
|
39
|
+
def to_python(self, value):
|
|
40
|
+
return super().to_python(self.storage.to_python(value))
|
|
41
|
+
|
|
42
|
+
def get_upload_url(self, request, obj, filename):
|
|
43
|
+
key = self.get_key(obj, filename)
|
|
44
|
+
url = self.storage.get_upload_url(request, key)
|
|
45
|
+
return dict(url=url, file=dict(name=filename, key=key))
|
|
46
|
+
|
|
47
|
+
def get_url(self, request, obj):
|
|
48
|
+
return self.storage.get_url(
|
|
49
|
+
request, self.value_from_object(obj)['key'])
|
|
50
|
+
|
|
51
|
+
def upload(self, obj, file, filename, **kwargs):
|
|
52
|
+
key = self.get_key(obj, filename)
|
|
53
|
+
return self.to_python(self.storage.upload(key, file, **kwargs))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@receiver(post_delete)
|
|
57
|
+
def post_delete_media(sender, instance, **kwargs):
|
|
58
|
+
for field in sender._meta.fields:
|
|
59
|
+
if isinstance(field, MediaField) and field.auto_delete:
|
|
60
|
+
field.storage.delete(field.value_from_object(instance))
|
|
@@ -1,69 +1,14 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import re
|
|
3
3
|
from datetime import timedelta
|
|
4
|
-
from importlib import import_module
|
|
5
4
|
|
|
6
5
|
from django.core.exceptions import ValidationError
|
|
7
6
|
from django.db import models
|
|
8
|
-
from django.db.models.signals import post_delete
|
|
9
|
-
from django.dispatch import receiver
|
|
10
7
|
from django.utils.translation import gettext_lazy as _
|
|
11
8
|
|
|
12
|
-
from pfx.pfxcore.shortcuts import settings
|
|
13
|
-
|
|
14
9
|
logger = logging.getLogger(__name__)
|
|
15
10
|
|
|
16
11
|
|
|
17
|
-
def get_storage_class(class_path):
|
|
18
|
-
ps = class_path.split('.')
|
|
19
|
-
return getattr(import_module('.'.join(ps[:-1])), ps[-1])()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class MediaField(models.JSONField):
|
|
23
|
-
def __init__(
|
|
24
|
-
self, *args, max_length=255, get_key=None, storage=None,
|
|
25
|
-
auto_delete=False, **kwargs):
|
|
26
|
-
self.get_key = get_key or self.get_default_key
|
|
27
|
-
if not storage and not settings.STORAGE_DEFAULT:
|
|
28
|
-
raise Exception(
|
|
29
|
-
"Missing storage. You have to set a storage "
|
|
30
|
-
"class on the field or define STORAGE_DEFAULT settings.")
|
|
31
|
-
self.storage = storage or get_storage_class(settings.STORAGE_DEFAULT)
|
|
32
|
-
self.auto_delete = auto_delete
|
|
33
|
-
super().__init__(
|
|
34
|
-
*args, max_length=max_length,
|
|
35
|
-
default=kwargs.pop('default', dict),
|
|
36
|
-
blank=kwargs.pop('blank', True),
|
|
37
|
-
**kwargs)
|
|
38
|
-
|
|
39
|
-
@staticmethod
|
|
40
|
-
def get_default_key(obj, filename):
|
|
41
|
-
return f"{type(obj).__name__}/{obj.pk}/{filename}"
|
|
42
|
-
|
|
43
|
-
def to_python(self, value):
|
|
44
|
-
return super().to_python(self.storage.to_python(value))
|
|
45
|
-
|
|
46
|
-
def get_upload_url(self, request, obj, filename):
|
|
47
|
-
key = self.get_key(obj, filename)
|
|
48
|
-
url = self.storage.get_upload_url(request, key)
|
|
49
|
-
return dict(url=url, file=dict(name=filename, key=key))
|
|
50
|
-
|
|
51
|
-
def get_url(self, request, obj):
|
|
52
|
-
return self.storage.get_url(
|
|
53
|
-
request, self.value_from_object(obj)['key'])
|
|
54
|
-
|
|
55
|
-
def upload(self, obj, file, filename, **kwargs):
|
|
56
|
-
key = self.get_key(obj, filename)
|
|
57
|
-
return self.to_python(self.storage.upload(key, file, **kwargs))
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@receiver(post_delete)
|
|
61
|
-
def post_delete_media(sender, instance, **kwargs):
|
|
62
|
-
for field in sender._meta.fields:
|
|
63
|
-
if isinstance(field, MediaField) and field.auto_delete:
|
|
64
|
-
field.storage.delete(field.value_from_object(instance))
|
|
65
|
-
|
|
66
|
-
|
|
67
12
|
class MinutesDurationField(models.DurationField):
|
|
68
13
|
RE_FLOAT = re.compile(r'^[0-9]*(\.[0-9]*)?$')
|
|
69
14
|
RE_HH_MM = re.compile(r'^([0-9]*):([0-5][0-9])?$')
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from django.db import models
|
|
3
|
+
from django.utils.safestring import mark_safe
|
|
4
|
+
|
|
5
|
+
import nh3
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_nh3_default_options():
|
|
9
|
+
nh3_args = {}
|
|
10
|
+
nh3_settings = {
|
|
11
|
+
"NH3_ALLOWED_TAGS": "tags",
|
|
12
|
+
"NH3_ALLOWED_ATTRIBUTES": "attributes",
|
|
13
|
+
"NH3_STRIP_COMMENTS": "strip_comments",
|
|
14
|
+
"NH3_URL_SCHEMES": "url_schemes",
|
|
15
|
+
"NH3_ATTRIBUTE_FILTER": "attribute_filter",
|
|
16
|
+
"NH3_LINK_REL": "link_rel",
|
|
17
|
+
"NH3_GENERIC_ATTRIBUTE_PREFIXES": "generic_attribute_prefixes",
|
|
18
|
+
"NH3_TAG_ATTRIBUTE_VALUES": "tag_attribute_values",
|
|
19
|
+
"NH3_SET_TAG_ATTRIBUTE_VALUES": "set_tag_attribute_values",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for setting, kwarg in nh3_settings.items():
|
|
23
|
+
if hasattr(settings, setting):
|
|
24
|
+
attr = getattr(settings, setting)
|
|
25
|
+
nh3_args[kwarg] = attr
|
|
26
|
+
|
|
27
|
+
return nh3_args
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NH3Field(models.TextField):
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
allowed_tags=None,
|
|
34
|
+
allowed_attributes=None,
|
|
35
|
+
url_schemes=None,
|
|
36
|
+
strip_comments=None,
|
|
37
|
+
attribute_filter=None,
|
|
38
|
+
link_rel=None,
|
|
39
|
+
generic_attribute_prefixes=None,
|
|
40
|
+
tag_attribute_values=None,
|
|
41
|
+
set_tag_attribute_values=None,
|
|
42
|
+
*args,
|
|
43
|
+
**kwargs,
|
|
44
|
+
):
|
|
45
|
+
super().__init__(*args, **kwargs)
|
|
46
|
+
|
|
47
|
+
self.nh3_kwargs = get_nh3_default_options()
|
|
48
|
+
|
|
49
|
+
if allowed_tags:
|
|
50
|
+
self.nh3_kwargs["tags"] = allowed_tags
|
|
51
|
+
if allowed_attributes:
|
|
52
|
+
self.nh3_kwargs["attributes"] = allowed_attributes
|
|
53
|
+
if url_schemes:
|
|
54
|
+
self.nh3_kwargs["url_schemes"] = url_schemes
|
|
55
|
+
if strip_comments:
|
|
56
|
+
self.nh3_kwargs["strip_comments"] = strip_comments
|
|
57
|
+
if attribute_filter:
|
|
58
|
+
self.nh3_kwargs["attribute_filter"] = attribute_filter
|
|
59
|
+
if link_rel:
|
|
60
|
+
self.nh3_kwargs["link_rel"] = link_rel
|
|
61
|
+
if generic_attribute_prefixes:
|
|
62
|
+
self.nh3_kwargs["generic_attribute_prefixes"] = (
|
|
63
|
+
generic_attribute_prefixes)
|
|
64
|
+
if tag_attribute_values:
|
|
65
|
+
self.nh3_kwargs["tag_attribute_values"] = tag_attribute_values
|
|
66
|
+
if set_tag_attribute_values:
|
|
67
|
+
self.nh3_kwargs["set_tag_attribute_values"] = (
|
|
68
|
+
set_tag_attribute_values)
|
|
69
|
+
|
|
70
|
+
def pre_save(self, model_instance, add):
|
|
71
|
+
data = getattr(model_instance, self.attname)
|
|
72
|
+
if data is None:
|
|
73
|
+
return data
|
|
74
|
+
clean_value = nh3.clean(data, **self.nh3_kwargs) if data else ""
|
|
75
|
+
setattr(model_instance, self.attname, mark_safe(clean_value))
|
|
76
|
+
return clean_value
|
|
77
|
+
|
|
78
|
+
def from_db_value(self, value, expression, connection):
|
|
79
|
+
if value is None:
|
|
80
|
+
return value
|
|
81
|
+
# Values are sanitised before saving, so any value returned from the DB
|
|
82
|
+
# is safe to render unescaped.
|
|
83
|
+
return mark_safe(value)
|
|
@@ -7,7 +7,7 @@ msgid ""
|
|
|
7
7
|
msgstr ""
|
|
8
8
|
"Project-Id-Version: \n"
|
|
9
9
|
"Report-Msgid-Bugs-To: \n"
|
|
10
|
-
"POT-Creation-Date: 2025-03-
|
|
10
|
+
"POT-Creation-Date: 2025-03-31 12:17+0200\n"
|
|
11
11
|
"PO-Revision-Date: 2021-06-22 23:31+0200\n"
|
|
12
12
|
"Last-Translator: \n"
|
|
13
13
|
"Language-Team: \n"
|
|
@@ -47,11 +47,11 @@ msgstr "Interdit"
|
|
|
47
47
|
msgid "Resource not found"
|
|
48
48
|
msgstr "Ressource non trouvée"
|
|
49
49
|
|
|
50
|
-
#: fields.py:
|
|
50
|
+
#: fields/minutes_duration_field.py:31
|
|
51
51
|
msgid "Invalid value."
|
|
52
52
|
msgstr "Valeur invalide."
|
|
53
53
|
|
|
54
|
-
#: fields.py:
|
|
54
|
+
#: fields/minutes_duration_field.py:46
|
|
55
55
|
msgid ""
|
|
56
56
|
"Invalid format, it can be a number in hours, “1:05”, “:05”, “1h 5m”, “1.5h” "
|
|
57
57
|
"or “30m”."
|
|
@@ -281,23 +281,23 @@ msgstr "Le paramètre object est requis pour move={move}."
|
|
|
281
281
|
msgid "object {pk} does not exists in this move context."
|
|
282
282
|
msgstr "object {pk} n'existe pas dans ce contexte de déplacement."
|
|
283
283
|
|
|
284
|
-
#: views/rest_views.py:
|
|
284
|
+
#: views/rest_views.py:234
|
|
285
285
|
#, python-brace-format
|
|
286
286
|
msgid "{obj} cannot be deleted because it is referenced by other objects."
|
|
287
287
|
msgstr ""
|
|
288
288
|
"{obj} ne peut pas être supprimé car il est référencé par d’autres objets."
|
|
289
289
|
|
|
290
|
-
#: views/rest_views.py:
|
|
290
|
+
#: views/rest_views.py:319
|
|
291
291
|
#, python-brace-format
|
|
292
292
|
msgid "{model} {obj} created."
|
|
293
293
|
msgstr "{model} {obj} créé."
|
|
294
294
|
|
|
295
|
-
#: views/rest_views.py:
|
|
295
|
+
#: views/rest_views.py:320
|
|
296
296
|
#, python-brace-format
|
|
297
297
|
msgid "{model} {obj} updated."
|
|
298
298
|
msgstr "{model} {obj} modifié."
|
|
299
299
|
|
|
300
|
-
#: views/rest_views.py:
|
|
300
|
+
#: views/rest_views.py:1165
|
|
301
301
|
#, python-brace-format
|
|
302
302
|
msgid "{model} {obj} deleted."
|
|
303
303
|
msgstr "{model} {obj} supprimé."
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import logging
|
|
3
3
|
import operator
|
|
4
|
-
from datetime import date
|
|
5
4
|
|
|
5
|
+
from django.core.exceptions import ValidationError
|
|
6
6
|
from django.db import models
|
|
7
7
|
from django.db.models.constants import LOOKUP_SEP
|
|
8
|
-
from django.utils.formats import date_format
|
|
9
8
|
from django.utils.functional import cached_property
|
|
10
9
|
|
|
11
10
|
from apispec.utils import deepupdate
|
|
@@ -144,13 +143,8 @@ class ViewField:
|
|
|
144
143
|
return self.json_repr(value)
|
|
145
144
|
return value.json_repr()
|
|
146
145
|
|
|
147
|
-
def to_json(self, obj
|
|
146
|
+
def to_json(self, obj):
|
|
148
147
|
value = self.get_value(obj)
|
|
149
|
-
if format_date and isinstance(value, date):
|
|
150
|
-
return dict(
|
|
151
|
-
value=value,
|
|
152
|
-
formatted=date_format(
|
|
153
|
-
value, format='SHORT_DATE_FORMAT', use_l10n=True))
|
|
154
148
|
if self.field_type == FieldType.MinutesDurationField:
|
|
155
149
|
return pfx_fields.MinutesDurationField.to_json(value)
|
|
156
150
|
if self.field_type == FieldType.ModelObject:
|
|
@@ -343,9 +337,15 @@ class ViewModelField(ViewField):
|
|
|
343
337
|
if isinstance(value, dict) and 'value' in value
|
|
344
338
|
else value)
|
|
345
339
|
elif self.field_type == FieldType.DateField:
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
340
|
+
try:
|
|
341
|
+
value = value and field.to_python(value)
|
|
342
|
+
except ValidationError as e:
|
|
343
|
+
raise ValidationError({field.name: e})
|
|
344
|
+
elif self.field_type == FieldType.DateTimeField:
|
|
345
|
+
try:
|
|
346
|
+
value = value and field.to_python(value)
|
|
347
|
+
except ValidationError as e:
|
|
348
|
+
raise ValidationError({field.name: e})
|
|
349
349
|
elif self.field_type == FieldType.IntegerField:
|
|
350
350
|
value = value if value != '' else None
|
|
351
351
|
elif self.field_type == FieldType.FloatField:
|
|
@@ -205,11 +205,6 @@ class ModelMixin():
|
|
|
205
205
|
"""
|
|
206
206
|
return self.model._meta.verbose_name
|
|
207
207
|
|
|
208
|
-
@property
|
|
209
|
-
def format_date(self):
|
|
210
|
-
""":meta private: Undocumented because it must be changed."""
|
|
211
|
-
return get_bool(self.request.GET, 'date_format')
|
|
212
|
-
|
|
213
208
|
def message_response(self, message, **kwargs):
|
|
214
209
|
"""Build a message JSON response.
|
|
215
210
|
|
|
@@ -296,7 +291,7 @@ class ModelResponseMixin(ModelMixin):
|
|
|
296
291
|
:rtype: :class:`JsonResponse`
|
|
297
292
|
"""
|
|
298
293
|
return JsonResponse(self.serialize_object(o, **{
|
|
299
|
-
_f.alias: _f.to_json(o
|
|
294
|
+
_f.alias: _f.to_json(o)
|
|
300
295
|
for _f in self.get_fields().values()}, meta=meta))
|
|
301
296
|
|
|
302
297
|
def validate(self, obj, rel_data=None, created=False, **kwargs):
|
|
@@ -759,7 +754,7 @@ class ListRestViewMixin(ModelResponseMixin):
|
|
|
759
754
|
*self.get_list_fields_prefetch_related())
|
|
760
755
|
for o in qs:
|
|
761
756
|
yield self.serialize_object(o, **{
|
|
762
|
-
_f.alias: _f.to_json(o
|
|
757
|
+
_f.alias: _f.to_json(o)
|
|
763
758
|
for _f in self.get_list_fields().values()})
|
|
764
759
|
|
|
765
760
|
def get_short_list_result(self, qs):
|
|
@@ -1043,14 +1038,14 @@ class CreateRestViewMixin(ModelBodyMixin, ModelResponseMixin):
|
|
|
1043
1038
|
return True
|
|
1044
1039
|
|
|
1045
1040
|
def _post(self, *args, **kwargs):
|
|
1046
|
-
obj = self.new_object()
|
|
1047
|
-
data, rel_data = self.get_model_data(
|
|
1048
|
-
obj, self.deserialize_body(), created=True)
|
|
1049
|
-
forbidden = False
|
|
1050
|
-
if not self.object_create_perm(data):
|
|
1051
|
-
forbidden = True
|
|
1052
|
-
self.set_values(obj, **data)
|
|
1053
1041
|
try:
|
|
1042
|
+
obj = self.new_object()
|
|
1043
|
+
data, rel_data = self.get_model_data(
|
|
1044
|
+
obj, self.deserialize_body(), created=True)
|
|
1045
|
+
forbidden = False
|
|
1046
|
+
if not self.object_create_perm(data):
|
|
1047
|
+
forbidden = True
|
|
1048
|
+
self.set_values(obj, **data)
|
|
1054
1049
|
self.validate(obj, rel_data=rel_data, created=True)
|
|
1055
1050
|
if forbidden:
|
|
1056
1051
|
raise ForbiddenError
|
|
@@ -1101,14 +1096,14 @@ class UpdateRestViewMixin(ModelBodyMixin, ModelResponseMixin):
|
|
|
1101
1096
|
return True
|
|
1102
1097
|
|
|
1103
1098
|
def _put(self, id, *args, **kwargs):
|
|
1104
|
-
obj = self.get_object(pk=id)
|
|
1105
|
-
data, rel_data = self.get_model_data(
|
|
1106
|
-
obj, self.deserialize_body(), created=False)
|
|
1107
|
-
forbidden = False
|
|
1108
|
-
if not self.object_update_perm(obj, data):
|
|
1109
|
-
forbidden = True
|
|
1110
|
-
self.set_values(obj, **data)
|
|
1111
1099
|
try:
|
|
1100
|
+
obj = self.get_object(pk=id)
|
|
1101
|
+
data, rel_data = self.get_model_data(
|
|
1102
|
+
obj, self.deserialize_body(), created=False)
|
|
1103
|
+
forbidden = False
|
|
1104
|
+
if not self.object_update_perm(obj, data):
|
|
1105
|
+
forbidden = True
|
|
1106
|
+
self.set_values(obj, **data)
|
|
1112
1107
|
self.validate(obj, rel_data=rel_data, created=False)
|
|
1113
1108
|
if forbidden:
|
|
1114
1109
|
raise ForbiddenError
|
|
@@ -8,6 +8,7 @@ from .test_cache import TestCache
|
|
|
8
8
|
from .test_client import TestApiClient
|
|
9
9
|
from .test_fields_choices import TestFieldsChoices
|
|
10
10
|
from .test_fields_minutes_duration import TestFieldsMinutesDuration
|
|
11
|
+
from .test_fields_nh3 import TestFieldsNh3
|
|
11
12
|
from .test_filters import FiltersTest
|
|
12
13
|
from .test_locale_api import LocaleAPITest
|
|
13
14
|
from .test_ordered_rest_view_mixin import TestOrderedRestViewMixin
|
|
@@ -632,15 +632,6 @@ class BasicAPITest(TestAssertMixin, TestCase):
|
|
|
632
632
|
response = self.client.get(f'/api/books/{self.author1_book1.pk}')
|
|
633
633
|
self.assertRC(response, 200)
|
|
634
634
|
self.assertJE(response, 'pub_date', '1937-01-01')
|
|
635
|
-
response = self.client.get(
|
|
636
|
-
f'/api/books/{self.author1_book1.pk}?date_format=0')
|
|
637
|
-
self.assertRC(response, 200)
|
|
638
|
-
self.assertJE(response, 'pub_date', '1937-01-01')
|
|
639
|
-
response = self.client.get(
|
|
640
|
-
f'/api/books/{self.author1_book1.pk}?date_format=1')
|
|
641
|
-
self.assertRC(response, 200)
|
|
642
|
-
self.assertJE(response, 'pub_date.value', '1937-01-01')
|
|
643
|
-
self.assertJE(response, 'pub_date.formatted', '01/01/1937')
|
|
644
635
|
|
|
645
636
|
def test_get_detail_with_rel_object(self):
|
|
646
637
|
response = self.client.get(f'/api/books/{self.author3_book1.pk}')
|
|
@@ -764,6 +755,18 @@ class BasicAPITest(TestAssertMixin, TestCase):
|
|
|
764
755
|
response, '__all__.@0',
|
|
765
756
|
'The Hobbit already exists for John Ronald Reuel Tolkien')
|
|
766
757
|
|
|
758
|
+
def test_create_invalid_date(self):
|
|
759
|
+
response = self.client.post(
|
|
760
|
+
'/api/books', dict(
|
|
761
|
+
name="The Silmarillion",
|
|
762
|
+
author=self.author1.pk,
|
|
763
|
+
pub_date="19777-09-15"))
|
|
764
|
+
self.assertRC(response, 422)
|
|
765
|
+
self.assertJE(
|
|
766
|
+
response, 'pub_date.@0',
|
|
767
|
+
'“19777-09-15” value has an invalid date format. '
|
|
768
|
+
'It must be in YYYY-MM-DD format.')
|
|
769
|
+
|
|
767
770
|
def test_update(self):
|
|
768
771
|
response = self.client.put(
|
|
769
772
|
f'/api/authors/{self.author1.pk}',
|
|
@@ -29,7 +29,7 @@ class TestFieldsMinutesDuration(TestAssertMixin, TestCase):
|
|
|
29
29
|
pub_date=date(1937, 1, 1)
|
|
30
30
|
)
|
|
31
31
|
|
|
32
|
-
@patch('pfx.pfxcore.fields.logger', MagicMock())
|
|
32
|
+
@patch('pfx.pfxcore.fields.minutes_duration_field.logger', MagicMock())
|
|
33
33
|
def test_to_python(self):
|
|
34
34
|
d = MinutesDurationField()
|
|
35
35
|
self.assertIsNone(d.to_python(None))
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from django.db import connection, models
|
|
2
|
+
from django.test import TestCase
|
|
3
|
+
from django.test.utils import override_settings
|
|
4
|
+
|
|
5
|
+
from pfx.pfxcore.fields import NH3Field
|
|
6
|
+
from pfx.pfxcore.models import JSONReprMixin
|
|
7
|
+
from pfx.pfxcore.test import TestAssertMixin
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestNh3Model(JSONReprMixin, models.Model):
|
|
11
|
+
html = NH3Field()
|
|
12
|
+
custom_html = NH3Field(
|
|
13
|
+
allowed_tags={'h1', 'p', 'span'},
|
|
14
|
+
allowed_attributes={'*': {'class'}})
|
|
15
|
+
|
|
16
|
+
class Meta:
|
|
17
|
+
verbose_name = "TestModel"
|
|
18
|
+
verbose_name_plural = "TestModels"
|
|
19
|
+
ordering = ['pk']
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@override_settings(ROOT_URLCONF=__name__)
|
|
23
|
+
class TestFieldsNh3(TestAssertMixin, TestCase):
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def setUpTestData(cls):
|
|
27
|
+
with connection.schema_editor() as schema_editor:
|
|
28
|
+
schema_editor.create_model(TestNh3Model)
|
|
29
|
+
|
|
30
|
+
def test_save(self):
|
|
31
|
+
t = TestNh3Model.objects.create(
|
|
32
|
+
html="<unknown>AAA</unknown>",
|
|
33
|
+
custom_html='<h1>TEST</h1>'
|
|
34
|
+
'<script>alert("hello")</script>'
|
|
35
|
+
'<p><span class="test" style="font-family: noto;"></span></p>')
|
|
36
|
+
self.assertEqual(t.html, "AAA")
|
|
37
|
+
self.assertEqual(
|
|
38
|
+
t.custom_html, '<h1>TEST</h1><p><span class="test"></span></p>')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|