django-pfx 1.7.6.dev21__tar.gz → 1.7.6.dev25__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.7.6.dev21 → django_pfx-1.7.6.dev25}/PKG-INFO +1 -1
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/django_pfx.egg-info/PKG-INFO +1 -1
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/django_pfx.egg-info/SOURCES.txt +6 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/django_pfx.egg-info/scm_file_list.json +6 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/django_pfx.egg-info/scm_version.json +2 -2
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/source/authentication.md +2 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/source/testing.md +90 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/email_backend/__init__.py +1 -0
- django_pfx-1.7.6.dev25/pfx/emails/email_backend/memory_email_backend.py +14 -0
- django_pfx-1.7.6.dev25/pfx/emails/test.py +40 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/fields/media_field.py +31 -3
- django_pfx-1.7.6.dev25/pfx/pfxcore/sms/backends/memory.py +15 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/test.py +99 -0
- django_pfx-1.7.6.dev25/pfx/pfxcore/xml_test_runner.py +8 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/basic_api_test.py +31 -0
- django_pfx-1.7.6.dev25/tests/tests/test_emails_test_tools.py +60 -0
- django_pfx-1.7.6.dev25/tests/tests/test_test_runner.py +117 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/.gitignore +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/.gitlab-ci.yml +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/.pre-commit-config.yaml +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/LICENSE +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/MANIFEST.in +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/README.md +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/django_pfx.egg-info/dependency_links.txt +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/django_pfx.egg-info/requires.txt +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/django_pfx.egg-info/top_level.txt +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/Makefile +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/conf.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/index.rst +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/source/api.views.rst +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/source/decorator.md +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/source/generate_openapi.md +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/source/getting_started.md +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/source/internationalisation.md +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/source/model.md +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/source/pfx_views.md +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/source/profiling.md +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/source/settings.md +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/doc/source/user_jobs.md +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/img/pfx.png +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/img/pfx.svg +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/make_messages +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/manage.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/apps.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/email_backend/q_mailjet_email_backend.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/exceptions.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/mailjet/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/mailjet/mailjet.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/mailjet/message_result.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/migrations/0002_alter_email_headers.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/migrations/0003_alter_email_options.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/migrations/0004_alter_email_mail_from.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/migrations/0005_lowercase_verbose_names.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/migrations/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/models/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/models/email.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/providers/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/providers/email_django_q_executor.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/shortcuts.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/tasks.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/templates/emails/base_template.html +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/templates/emails/base_template.txt +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/templates/emails/base_template_subject.txt +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/urls.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/views/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/emails/views/email_rest_view.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/apidoc/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/apidoc/parameters.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/apidoc/schema.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/apidoc/tags.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/apps.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/decorator/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/decorator/rest.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/default_settings.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/exceptions.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/fields/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/fields/decimal_field.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/fields/minutes_duration_field.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/fields/rich_text_field.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/http/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/http/json_response.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/management/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/management/commands/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/management/commands/profile.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/mfa/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/mfa/authenticator.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/mfa/base.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/mfa/email.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/mfa/legacy.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/mfa/sms.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/middleware/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/middleware/authentication.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/middleware/locale.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/middleware/profiling.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/middleware/request_too_big_middleware.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/middleware/ws_cookie_authentication_middleware.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/migrations/0004_alter_loginban_failed_counter_and_more.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/migrations/0005_mfadevice_mfadevice_mfa_device_unique_backend.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/migrations/0006_alter_pfxuser_managers.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/migrations/0007_alter_pfxuser_options.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/migrations/0008_lowercase_verbose_names.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/migrations/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/migrations/operations/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/migrations/operations/permissions.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/models/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/models/attachment_mixin.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/models/cache_mixins.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/models/login_ban.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/models/mfa_device.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/models/mfa_user_mixin.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/models/not_null_fields.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/models/ordered_model_mixin.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/models/pfx_models.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/models/pfx_user.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/serializers/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/serializers/json.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/settings.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/shortcuts.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/sms/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/sms/backends/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/sms/backends/base.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/sms/backends/console.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/storage/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/storage/exceptions.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/storage/local_storage.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/storage/s3_storage.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/urls.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/version_check.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/authentication_views.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/fields.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/filters_views.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/locale_views.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/media_rest_view_mixin.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/mfa_authentication_views.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/ordered_rest_view_mixin.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/date_format.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/groups.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/list_count.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/list_items.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/list_order.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/list_search.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/subset.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/pfxcore/views/rest_views.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/settings/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/settings/dev.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/apps.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/migrations/0002_alter_userjob_options_alter_userjob_auto_queued_and_more.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/migrations/0003_clean_scheduled_tasks.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/migrations/0004_userjob_summary_url.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/migrations/0005_swappable_user_job.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/migrations/0006_lowercase_verbose_names.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/migrations/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/models/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/models/user_job.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/models/user_job_attachment.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/providers/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/providers/user_job_django_q_executor.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/providers/user_job_websocket_channel_provider.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/tasks.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/urls.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/views/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/views/user_job_attachment_view.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/views/user_job_view.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pfx/userjobs/views/user_websocket_consumer.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/pyproject.toml +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/requirements.txt +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/serve-doc +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/setup.cfg +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/setup.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/apps.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/channel_group_send_calls.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/mfa_urls.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/migrations/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/models.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/settings/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/settings/ci.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/settings/common.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/settings/dev.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/settings/dev_default.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/basic_api_errors.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/mfa_test_utils.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_api_doc.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_api_doc_search.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_auth_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_body_mixin.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_cache.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_client.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_emails_email_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_emails_email_model.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_emails_q_mailjet_email_backend.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_emails_send_email_message.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_fields_choices.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_fields_date.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_fields_decimal.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_fields_help_text.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_fields_minutes_duration.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_fields_one2many.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_fields_rich_text.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_fields_time.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_filters.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_locale_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_mfa_legacy.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_mfa_login_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_mfa_setup_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_ordered_rest_view_mixin.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_perm_tests.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_permissions.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_perms_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_post_migrate_groups_update.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_profiling_middleware.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_settings.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_shortcuts.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_timezone_middleware.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_tools.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_user_job_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_user_job_messages.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_user_job_model.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_user_queryset.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_user_websocket.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_verbose_names.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_version_check.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_view_decorators.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_view_fields.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/tests/test_ws_cookie_middleware.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/urls.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests/views.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/migrations/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/settings/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/settings/ci.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/settings/common.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/settings/dev.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/settings/dev_default.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/tests/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/tests/test_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/tests/test_auth_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/urls.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_base_user/views.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/migrations/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/models.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/settings/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/settings/ci.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/settings/common.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/settings/dev.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/settings/dev_default.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/tests/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/tests/test_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/tests/test_auth_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/urls.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user/views.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/migrations/0002_lowercase_verbose_names.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/migrations/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/models.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/providers.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/scope.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/settings/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/settings/ci.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/settings/common.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/settings/dev.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/settings/dev_default.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/tests/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/tests/test_user_job.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_custom_user_job/urls.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/migrations/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/models.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/settings/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/settings/ci.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/settings/common.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/settings/dev.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/settings/dev_default.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/tests/__init__.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/tests/test_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/tests/test_auth_api.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/urls.py +0 -0
- {django_pfx-1.7.6.dev21 → django_pfx-1.7.6.dev25}/tests_user_email_login/views.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-pfx
|
|
3
|
-
Version: 1.7.6.
|
|
3
|
+
Version: 1.7.6.dev25
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-pfx
|
|
3
|
-
Version: 1.7.6.
|
|
3
|
+
Version: 1.7.6.dev25
|
|
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
|
|
@@ -41,8 +41,10 @@ pfx/emails/apps.py
|
|
|
41
41
|
pfx/emails/exceptions.py
|
|
42
42
|
pfx/emails/shortcuts.py
|
|
43
43
|
pfx/emails/tasks.py
|
|
44
|
+
pfx/emails/test.py
|
|
44
45
|
pfx/emails/urls.py
|
|
45
46
|
pfx/emails/email_backend/__init__.py
|
|
47
|
+
pfx/emails/email_backend/memory_email_backend.py
|
|
46
48
|
pfx/emails/email_backend/q_mailjet_email_backend.py
|
|
47
49
|
pfx/emails/locale/fr/LC_MESSAGES/django.mo
|
|
48
50
|
pfx/emails/locale/fr/LC_MESSAGES/django.po
|
|
@@ -73,6 +75,7 @@ pfx/pfxcore/shortcuts.py
|
|
|
73
75
|
pfx/pfxcore/test.py
|
|
74
76
|
pfx/pfxcore/urls.py
|
|
75
77
|
pfx/pfxcore/version_check.py
|
|
78
|
+
pfx/pfxcore/xml_test_runner.py
|
|
76
79
|
pfx/pfxcore/apidoc/__init__.py
|
|
77
80
|
pfx/pfxcore/apidoc/parameters.py
|
|
78
81
|
pfx/pfxcore/apidoc/schema.py
|
|
@@ -133,6 +136,7 @@ pfx/pfxcore/sms/__init__.py
|
|
|
133
136
|
pfx/pfxcore/sms/backends/__init__.py
|
|
134
137
|
pfx/pfxcore/sms/backends/base.py
|
|
135
138
|
pfx/pfxcore/sms/backends/console.py
|
|
139
|
+
pfx/pfxcore/sms/backends/memory.py
|
|
136
140
|
pfx/pfxcore/storage/__init__.py
|
|
137
141
|
pfx/pfxcore/storage/exceptions.py
|
|
138
142
|
pfx/pfxcore/storage/local_storage.py
|
|
@@ -225,6 +229,7 @@ tests/tests/test_emails_email_api.py
|
|
|
225
229
|
tests/tests/test_emails_email_model.py
|
|
226
230
|
tests/tests/test_emails_q_mailjet_email_backend.py
|
|
227
231
|
tests/tests/test_emails_send_email_message.py
|
|
232
|
+
tests/tests/test_emails_test_tools.py
|
|
228
233
|
tests/tests/test_fields_choices.py
|
|
229
234
|
tests/tests/test_fields_date.py
|
|
230
235
|
tests/tests/test_fields_decimal.py
|
|
@@ -246,6 +251,7 @@ tests/tests/test_post_migrate_groups_update.py
|
|
|
246
251
|
tests/tests/test_profiling_middleware.py
|
|
247
252
|
tests/tests/test_settings.py
|
|
248
253
|
tests/tests/test_shortcuts.py
|
|
254
|
+
tests/tests/test_test_runner.py
|
|
249
255
|
tests/tests/test_timezone_middleware.py
|
|
250
256
|
tests/tests/test_tools.py
|
|
251
257
|
tests/tests/test_user_job_api.py
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"pfx/emails/__init__.py",
|
|
30
30
|
"pfx/emails/apps.py",
|
|
31
31
|
"pfx/emails/email_backend/__init__.py",
|
|
32
|
+
"pfx/emails/email_backend/memory_email_backend.py",
|
|
32
33
|
"pfx/emails/email_backend/q_mailjet_email_backend.py",
|
|
33
34
|
"pfx/emails/exceptions.py",
|
|
34
35
|
"pfx/emails/locale/fr/LC_MESSAGES/django.po",
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
"pfx/emails/templates/emails/base_template.html",
|
|
51
52
|
"pfx/emails/templates/emails/base_template.txt",
|
|
52
53
|
"pfx/emails/templates/emails/base_template_subject.txt",
|
|
54
|
+
"pfx/emails/test.py",
|
|
53
55
|
"pfx/emails/urls.py",
|
|
54
56
|
"pfx/emails/views/__init__.py",
|
|
55
57
|
"pfx/emails/views/email_rest_view.py",
|
|
@@ -118,6 +120,7 @@
|
|
|
118
120
|
"pfx/pfxcore/sms/backends/__init__.py",
|
|
119
121
|
"pfx/pfxcore/sms/backends/base.py",
|
|
120
122
|
"pfx/pfxcore/sms/backends/console.py",
|
|
123
|
+
"pfx/pfxcore/sms/backends/memory.py",
|
|
121
124
|
"pfx/pfxcore/storage/__init__.py",
|
|
122
125
|
"pfx/pfxcore/storage/exceptions.py",
|
|
123
126
|
"pfx/pfxcore/storage/local_storage.py",
|
|
@@ -158,6 +161,7 @@
|
|
|
158
161
|
"pfx/pfxcore/views/parameters/subset_page_size.py",
|
|
159
162
|
"pfx/pfxcore/views/parameters/subset_page_subset.py",
|
|
160
163
|
"pfx/pfxcore/views/rest_views.py",
|
|
164
|
+
"pfx/pfxcore/xml_test_runner.py",
|
|
161
165
|
"pfx/settings/__init__.py",
|
|
162
166
|
"pfx/settings/dev.py",
|
|
163
167
|
"pfx/userjobs/__init__.py",
|
|
@@ -215,6 +219,7 @@
|
|
|
215
219
|
"tests/tests/test_emails_email_model.py",
|
|
216
220
|
"tests/tests/test_emails_q_mailjet_email_backend.py",
|
|
217
221
|
"tests/tests/test_emails_send_email_message.py",
|
|
222
|
+
"tests/tests/test_emails_test_tools.py",
|
|
218
223
|
"tests/tests/test_fields_choices.py",
|
|
219
224
|
"tests/tests/test_fields_date.py",
|
|
220
225
|
"tests/tests/test_fields_decimal.py",
|
|
@@ -236,6 +241,7 @@
|
|
|
236
241
|
"tests/tests/test_profiling_middleware.py",
|
|
237
242
|
"tests/tests/test_settings.py",
|
|
238
243
|
"tests/tests/test_shortcuts.py",
|
|
244
|
+
"tests/tests/test_test_runner.py",
|
|
239
245
|
"tests/tests/test_timezone_middleware.py",
|
|
240
246
|
"tests/tests/test_tools.py",
|
|
241
247
|
"tests/tests/test_user_job_api.py",
|
|
@@ -183,6 +183,8 @@ removed.
|
|
|
183
183
|
* `PFX_SMS_BACKEND`: Dotted path to the SMS backend class (optional, default
|
|
184
184
|
`'pfx.pfxcore.sms.backends.console.ConsoleSMSBackend'`).
|
|
185
185
|
Implement {class}`pfx.pfxcore.sms.backends.base.BaseSMSBackend` to integrate a real SMS provider.
|
|
186
|
+
In the tests, `pfx.pfxcore.test.TestRunner` replaces it with
|
|
187
|
+
{class}`pfx.pfxcore.sms.backends.memory.MemorySMSBackend` (see [Testing](testing.md)).
|
|
186
188
|
* `PFX_TOKEN_OTP_VALIDITY`: Validity for OTP tokens (corresponds to the maximum time to enter
|
|
187
189
|
an OTP code after logging in with a password) (optional, default `{'minutes': 15}`)
|
|
188
190
|
* `PFX_HOTP_CODE_VALIDITY`: Validity of HOTP codes in minutes (used to send code by email or SMS) (optional, default `15`).
|
|
@@ -340,3 +340,93 @@ class BookPermsAPITest(TestPermsAssertMixin):
|
|
|
340
340
|
post=403, put=403, delete=403),
|
|
341
341
|
}
|
|
342
342
|
```
|
|
343
|
+
|
|
344
|
+
## Test runner
|
|
345
|
+
`pfx.pfxcore.test.TestRunner` replaces the SMS and email backends for the
|
|
346
|
+
whole run, parallel workers included: nothing leaves the tests, whatever
|
|
347
|
+
the local settings contain, and what would be sent is kept for the
|
|
348
|
+
assertions.
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
TEST_RUNNER = 'pfx.pfxcore.test.TestRunner'
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
- `PFX_SMS_BACKEND` becomes
|
|
355
|
+
`pfx.pfxcore.sms.backends.memory.MemorySMSBackend`, which collects the
|
|
356
|
+
SMS in memory.
|
|
357
|
+
- If `pfx.emails` is installed, `EMAIL_BACKEND` becomes
|
|
358
|
+
`pfx.emails.email_backend.MemoryEmailBackend` instead of the `locmem`
|
|
359
|
+
backend set by Django. `locmem` puts the emails in
|
|
360
|
+
`django.core.mail.outbox`: the email model is never built nor validated,
|
|
361
|
+
and a sending path that would create an invalid email goes unnoticed.
|
|
362
|
+
`MemoryEmailBackend` builds, validates and saves the email as
|
|
363
|
+
`QMailjetEmailBackend` does, with the `manual` send mode: nothing is
|
|
364
|
+
sent.
|
|
365
|
+
|
|
366
|
+
A project without `pfx.emails` can use the runner as well: only the SMS
|
|
367
|
+
backend is replaced, and the emails stay in Django's `locmem` outbox.
|
|
368
|
+
|
|
369
|
+
To use subclasses of these backends (to fill extra fields of your email
|
|
370
|
+
model, for instance):
|
|
371
|
+
```python
|
|
372
|
+
PFX_TEST_SMS_BACKEND = 'myapp.tests.MyMemorySMSBackend'
|
|
373
|
+
PFX_TEST_EMAIL_BACKEND = 'myapp.tests.MyMemoryEmailBackend'
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
For the XML reports of `unittest-xml-reporting` (to install in the
|
|
377
|
+
project), use the XML variant:
|
|
378
|
+
```python
|
|
379
|
+
TEST_RUNNER = 'pfx.pfxcore.xml_test_runner.XMLTestRunner'
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
With another runner, combine it with `MemoryBackendsRunnerMixin`:
|
|
383
|
+
```python
|
|
384
|
+
from pfx.pfxcore.test import MemoryBackendsRunnerMixin
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class MyTestRunner(MemoryBackendsRunnerMixin, OtherTestRunner):
|
|
388
|
+
pass
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
A test class that needs another backend keeps its
|
|
392
|
+
`override_settings(EMAIL_BACKEND=…)` or `override_settings(PFX_SMS_BACKEND=…)`.
|
|
393
|
+
|
|
394
|
+
## SMSTestMixin
|
|
395
|
+
Helpers to check the SMS of a test. The outbox is emptied before each
|
|
396
|
+
test. They fail if the SMS backend is not a `MemorySMSBackend`, rather
|
|
397
|
+
than reporting an empty outbox.
|
|
398
|
+
|
|
399
|
+
- `sms_outbox()`: the SMS sent so far, as `(to, message)` tuples.
|
|
400
|
+
- `clear_sms_outbox()`: forget the SMS sent so far.
|
|
401
|
+
|
|
402
|
+
## EmailTestMixin
|
|
403
|
+
`pfx.emails.test.EmailTestMixin` gives helpers to check the emails of a
|
|
404
|
+
test. They fail if the emails are not saved in the email model (with
|
|
405
|
+
`locmem`, for instance), rather than reporting an empty outbox.
|
|
406
|
+
|
|
407
|
+
- `outbox()`: the emails created so far, in creation order.
|
|
408
|
+
- `clear_outbox()`: delete the emails created so far.
|
|
409
|
+
- `email_to(email)`: the addresses of the `To`, without the names.
|
|
410
|
+
- `get_email_html(email)`: the HTML part, fails if there is none.
|
|
411
|
+
|
|
412
|
+
Example :
|
|
413
|
+
```python
|
|
414
|
+
from django.test import TestCase
|
|
415
|
+
|
|
416
|
+
from pfx.emails.test import EmailTestMixin
|
|
417
|
+
from pfx.pfxcore.test import SMSTestMixin
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
class ATestClass(EmailTestMixin, SMSTestMixin, TestCase):
|
|
421
|
+
|
|
422
|
+
def a_test(self):
|
|
423
|
+
# … something that sends an email to jrr@example.com
|
|
424
|
+
[email] = self.outbox()
|
|
425
|
+
self.assertEqual(self.email_to(email), ['jrr@example.com'])
|
|
426
|
+
self.assertIn('Welcome', self.get_email_html(email))
|
|
427
|
+
|
|
428
|
+
def another_test(self):
|
|
429
|
+
# … something that sends an SMS
|
|
430
|
+
[(to, message)] = self.sms_outbox()
|
|
431
|
+
self.assertEqual(to, '+41791234567')
|
|
432
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .q_mailjet_email_backend import QMailjetEmailBackend
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MemoryEmailBackend(QMailjetEmailBackend):
|
|
5
|
+
"""Create the emails as :class:`QMailjetEmailBackend` does, but never
|
|
6
|
+
send them.
|
|
7
|
+
|
|
8
|
+
The email model is built, validated and saved, so a sending path that
|
|
9
|
+
would create an invalid email fails. The ``manual`` send mode stops
|
|
10
|
+
there: nothing is queued nor sent to Mailjet."""
|
|
11
|
+
|
|
12
|
+
def create_mail(self, message):
|
|
13
|
+
message.send_mode = 'manual'
|
|
14
|
+
super().create_mail(message)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from django.utils.module_loading import import_string
|
|
3
|
+
|
|
4
|
+
from pfx.emails.email_backend import QMailjetEmailBackend
|
|
5
|
+
from pfx.emails.models import get_email_model
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EmailTestMixin:
|
|
9
|
+
"""Helpers to check the emails created by a test."""
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def _check_email_backend(cls):
|
|
13
|
+
backend = import_string(settings.EMAIL_BACKEND)
|
|
14
|
+
if not issubclass(backend, QMailjetEmailBackend):
|
|
15
|
+
raise AssertionError(
|
|
16
|
+
f"EMAIL_BACKEND is {settings.EMAIL_BACKEND}, the emails are "
|
|
17
|
+
f"not saved in the email model. Run the tests with a runner "
|
|
18
|
+
f"using pfx.pfxcore.test.MemoryBackendsRunnerMixin.")
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def outbox(cls):
|
|
22
|
+
"""The emails created so far, in creation order."""
|
|
23
|
+
cls._check_email_backend()
|
|
24
|
+
return list(get_email_model().objects.order_by('pk'))
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def clear_outbox(cls):
|
|
28
|
+
cls._check_email_backend()
|
|
29
|
+
get_email_model().objects.all().delete()
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def email_to(cls, email):
|
|
33
|
+
"""The addresses of the ``To`` of ``email``, without the names."""
|
|
34
|
+
return [r['Email'] for r in email.mail_to]
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def get_email_html(cls, email):
|
|
38
|
+
if not email.html_part:
|
|
39
|
+
raise AssertionError("Email does not contain an HTML part.")
|
|
40
|
+
return email.html_part
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import binascii
|
|
1
3
|
import io
|
|
2
4
|
import logging
|
|
3
5
|
from importlib import import_module
|
|
4
|
-
from urllib.
|
|
6
|
+
from urllib.parse import unquote_to_bytes
|
|
5
7
|
|
|
6
8
|
from django.core.exceptions import ValidationError
|
|
7
9
|
from django.db import models
|
|
@@ -15,6 +17,30 @@ logger = logging.getLogger(__name__)
|
|
|
15
17
|
DEFAULT_KEYS = {"key", "name", "content-type", "content-length"}
|
|
16
18
|
|
|
17
19
|
|
|
20
|
+
def decode_data_uri(value):
|
|
21
|
+
"""Return the bytes carried by a ``data:`` URI, and only that.
|
|
22
|
+
|
|
23
|
+
The uploaded content reaches the server as this string. It used to be
|
|
24
|
+
handed to ``urlopen``, which also opens ``file://`` and ``http://``:
|
|
25
|
+
a client could then have the server read a local file or reach the
|
|
26
|
+
internal network. Only the ``data:`` scheme is accepted here, and it
|
|
27
|
+
is decoded in place, never opened.
|
|
28
|
+
|
|
29
|
+
:raises ValidationError: when the value is not a valid ``data:`` URI
|
|
30
|
+
"""
|
|
31
|
+
if not isinstance(value, str) or not value.startswith('data:'):
|
|
32
|
+
raise ValidationError("Le contenu doit être une URI « data: ».")
|
|
33
|
+
header, sep, data = value[len('data:'):].partition(',')
|
|
34
|
+
if not sep:
|
|
35
|
+
raise ValidationError("URI « data: » invalide.")
|
|
36
|
+
if ';base64' in header:
|
|
37
|
+
try:
|
|
38
|
+
return base64.b64decode(data, validate=True)
|
|
39
|
+
except (binascii.Error, ValueError):
|
|
40
|
+
raise ValidationError("URI « data: » invalide.")
|
|
41
|
+
return unquote_to_bytes(data)
|
|
42
|
+
|
|
43
|
+
|
|
18
44
|
def get_storage_class(class_path):
|
|
19
45
|
ps = class_path.split('.')
|
|
20
46
|
return getattr(import_module('.'.join(ps[:-1])), ps[-1])()
|
|
@@ -91,8 +117,10 @@ class MediaField(models.JSONField):
|
|
|
91
117
|
after_save.append(lambda: self.delete(self._db_value))
|
|
92
118
|
setattr(obj, self.name, None)
|
|
93
119
|
if b64:
|
|
94
|
-
|
|
95
|
-
file =
|
|
120
|
+
try:
|
|
121
|
+
file = decode_data_uri(b64)
|
|
122
|
+
except ValidationError as e:
|
|
123
|
+
raise ValidationError({self.name: e.messages})
|
|
96
124
|
setattr(obj, self.name, dict(key=self.get_key(obj, name)))
|
|
97
125
|
after_save.append(lambda: save_file(obj, file, name))
|
|
98
126
|
return after_save
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .base import BaseSMSBackend
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MemorySMSBackend(BaseSMSBackend):
|
|
5
|
+
"""Collect the SMS in :attr:`outbox` instead of sending them.
|
|
6
|
+
|
|
7
|
+
A new backend instance is built for each send (see
|
|
8
|
+
:func:`pfx.pfxcore.sms.get_sms_backend`), so the outbox is a class
|
|
9
|
+
attribute."""
|
|
10
|
+
|
|
11
|
+
#: The sent SMS, as ``(to, message)`` tuples.
|
|
12
|
+
outbox = []
|
|
13
|
+
|
|
14
|
+
def send_sms(self, to, message):
|
|
15
|
+
self.outbox.append((to, message))
|
|
@@ -5,8 +5,16 @@ from functools import reduce
|
|
|
5
5
|
from http.cookies import SimpleCookie
|
|
6
6
|
from json import JSONDecodeError
|
|
7
7
|
|
|
8
|
+
from django.apps import apps
|
|
8
9
|
from django.db import transaction
|
|
10
|
+
from django.test import override_settings
|
|
9
11
|
from django.test.client import Client as DjangoClient
|
|
12
|
+
from django.test.runner import DiscoverRunner, ParallelTestSuite
|
|
13
|
+
from django.test.runner import _init_worker as django_init_worker
|
|
14
|
+
from django.utils.module_loading import import_string
|
|
15
|
+
|
|
16
|
+
from pfx.pfxcore.settings import settings
|
|
17
|
+
from pfx.pfxcore.sms.backends.memory import MemorySMSBackend
|
|
10
18
|
|
|
11
19
|
logger = logging.getLogger(__name__)
|
|
12
20
|
|
|
@@ -322,3 +330,94 @@ class MockBoto3Client:
|
|
|
322
330
|
|
|
323
331
|
def delete_object(self, *args, **kwargs):
|
|
324
332
|
pass
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def get_test_backends():
|
|
336
|
+
"""The backends that replace the real ones during the tests."""
|
|
337
|
+
backends = dict(PFX_SMS_BACKEND=getattr(
|
|
338
|
+
settings, 'PFX_TEST_SMS_BACKEND',
|
|
339
|
+
'pfx.pfxcore.sms.backends.memory.MemorySMSBackend'))
|
|
340
|
+
if apps.is_installed('pfx.emails'):
|
|
341
|
+
backends['EMAIL_BACKEND'] = getattr(
|
|
342
|
+
settings, 'PFX_TEST_EMAIL_BACKEND',
|
|
343
|
+
'pfx.emails.email_backend.MemoryEmailBackend')
|
|
344
|
+
return backends
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _init_worker(*args, **kwargs):
|
|
348
|
+
# A spawned worker calls setup_test_environment() again, which puts
|
|
349
|
+
# locmem back.
|
|
350
|
+
django_init_worker(*args, **kwargs)
|
|
351
|
+
override_settings(**get_test_backends()).enable()
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class MemoryBackendsParallelTestSuite(ParallelTestSuite):
|
|
355
|
+
init_worker = _init_worker
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class MemoryBackendsRunnerMixin:
|
|
359
|
+
"""Replace the SMS and email backends for the whole run.
|
|
360
|
+
|
|
361
|
+
Nothing leaves the tests, whatever the local settings contain, and
|
|
362
|
+
what would be sent is kept for the assertions (see
|
|
363
|
+
:class:`SMSTestMixin` and :class:`pfx.emails.test.EmailTestMixin`).
|
|
364
|
+
|
|
365
|
+
- ``PFX_SMS_BACKEND`` becomes ``PFX_TEST_SMS_BACKEND`` (default
|
|
366
|
+
:class:`pfx.pfxcore.sms.backends.memory.MemorySMSBackend`).
|
|
367
|
+
- If ``pfx.emails`` is installed, ``EMAIL_BACKEND`` becomes
|
|
368
|
+
``PFX_TEST_EMAIL_BACKEND`` (default
|
|
369
|
+
:class:`pfx.emails.email_backend.MemoryEmailBackend`) instead of
|
|
370
|
+
the ``locmem`` backend set by Django: the email model is built and
|
|
371
|
+
validated, which ``locmem`` never does.
|
|
372
|
+
|
|
373
|
+
A test class decorated with ``override_settings`` keeps its own
|
|
374
|
+
backends."""
|
|
375
|
+
|
|
376
|
+
parallel_test_suite = MemoryBackendsParallelTestSuite
|
|
377
|
+
|
|
378
|
+
def setup_test_environment(self, **kwargs):
|
|
379
|
+
super().setup_test_environment(**kwargs)
|
|
380
|
+
self._test_backends = override_settings(**get_test_backends())
|
|
381
|
+
self._test_backends.enable()
|
|
382
|
+
|
|
383
|
+
def teardown_test_environment(self, **kwargs):
|
|
384
|
+
self._test_backends.disable()
|
|
385
|
+
super().teardown_test_environment(**kwargs)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
class TestRunner(MemoryBackendsRunnerMixin, DiscoverRunner):
|
|
389
|
+
pass
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class SMSTestMixin:
|
|
393
|
+
"""Helpers to check the SMS sent by a test.
|
|
394
|
+
|
|
395
|
+
The outbox is emptied before each test, as Django does for
|
|
396
|
+
``mail.outbox``."""
|
|
397
|
+
|
|
398
|
+
@classmethod
|
|
399
|
+
def _sms_backend(cls):
|
|
400
|
+
backend = import_string(settings.PFX_SMS_BACKEND)
|
|
401
|
+
if not issubclass(backend, MemorySMSBackend):
|
|
402
|
+
raise AssertionError(
|
|
403
|
+
f"PFX_SMS_BACKEND is {settings.PFX_SMS_BACKEND}, the SMS "
|
|
404
|
+
f"are not collected. Run the tests with a runner using "
|
|
405
|
+
f"pfx.pfxcore.test.MemoryBackendsRunnerMixin.")
|
|
406
|
+
return backend
|
|
407
|
+
|
|
408
|
+
# __call__ rather than _pre_setup: a classmethod in Django 5.2, an
|
|
409
|
+
# instance method in 4.2.
|
|
410
|
+
def __call__(self, *args, **kwargs):
|
|
411
|
+
backend = import_string(settings.PFX_SMS_BACKEND)
|
|
412
|
+
if issubclass(backend, MemorySMSBackend):
|
|
413
|
+
backend.outbox.clear()
|
|
414
|
+
return super().__call__(*args, **kwargs)
|
|
415
|
+
|
|
416
|
+
@classmethod
|
|
417
|
+
def sms_outbox(cls):
|
|
418
|
+
"""The SMS sent so far, as ``(to, message)`` tuples."""
|
|
419
|
+
return list(cls._sms_backend().outbox)
|
|
420
|
+
|
|
421
|
+
@classmethod
|
|
422
|
+
def clear_sms_outbox(cls):
|
|
423
|
+
cls._sms_backend().outbox.clear()
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from xmlrunner.extra.djangotestrunner import XMLTestRunner as BaseXMLTestRunner
|
|
2
|
+
|
|
3
|
+
from pfx.pfxcore.test import MemoryBackendsRunnerMixin
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class XMLTestRunner(MemoryBackendsRunnerMixin, BaseXMLTestRunner):
|
|
7
|
+
""":class:`pfx.pfxcore.test.TestRunner` with the XML reports of
|
|
8
|
+
``unittest-xml-reporting``, which must be installed."""
|
|
@@ -847,6 +847,37 @@ class BasicAPITest(TestAssertMixin, TestCase):
|
|
|
847
847
|
self.assertJE(response, 'local_file', None)
|
|
848
848
|
self.assertFalse(Path(f'/tmp/django-pfx-filestore/{key2}').exists())
|
|
849
849
|
|
|
850
|
+
def test_create_file_rejects_non_data_uri(self):
|
|
851
|
+
"""A media upload only accepts a ``data:`` URI.
|
|
852
|
+
|
|
853
|
+
The content used to be handed to ``urlopen``, so ``file://`` made
|
|
854
|
+
the server read a local file and ``http://`` reach the network.
|
|
855
|
+
Both are refused now, with a field error, before anything is read.
|
|
856
|
+
"""
|
|
857
|
+
for uri in (
|
|
858
|
+
'file:///etc/hostname',
|
|
859
|
+
'http://169.254.169.254/latest/meta-data/',
|
|
860
|
+
'not a uri at all'):
|
|
861
|
+
response = self.client.post('/api/books', dict(
|
|
862
|
+
name="Test Book",
|
|
863
|
+
author=self.author1.pk,
|
|
864
|
+
pub_date='2000-01-01',
|
|
865
|
+
local_file=dict(name='x.txt', base64=uri)))
|
|
866
|
+
self.assertRC(response, 422)
|
|
867
|
+
self.assertJEExists(response, 'local_file')
|
|
868
|
+
|
|
869
|
+
def test_create_file_url_encoded_data_uri(self):
|
|
870
|
+
"""A ``data:`` URI without ``;base64`` is percent-decoded, as
|
|
871
|
+
``urlopen`` did, so that path keeps working without it."""
|
|
872
|
+
response = self.client.post('/api/books', dict(
|
|
873
|
+
name="Test Book",
|
|
874
|
+
author=self.author1.pk,
|
|
875
|
+
pub_date='2000-01-01',
|
|
876
|
+
local_file=dict(
|
|
877
|
+
name='test.txt', base64='data:text/plain,Hello%20world')))
|
|
878
|
+
self.assertRC(response, 200)
|
|
879
|
+
self.assertJE(response, 'local_file.content-length', '11')
|
|
880
|
+
|
|
850
881
|
def test_get_file_content_disposition(self):
|
|
851
882
|
"""The file name must survive both local serving paths.
|
|
852
883
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from django.core import mail
|
|
2
|
+
from django.core.exceptions import ValidationError
|
|
3
|
+
from django.core.mail import EmailMultiAlternatives, send_mail
|
|
4
|
+
from django.test import TestCase, override_settings
|
|
5
|
+
|
|
6
|
+
from pfx.emails.models import Email
|
|
7
|
+
from pfx.emails.test import EmailTestMixin
|
|
8
|
+
|
|
9
|
+
MEMORY_BACKEND = 'pfx.emails.email_backend.MemoryEmailBackend'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@override_settings(EMAIL_BACKEND=MEMORY_BACKEND)
|
|
13
|
+
class TestMemoryEmailBackend(EmailTestMixin, TestCase):
|
|
14
|
+
"""The backend goes as far as the saved email, never further."""
|
|
15
|
+
|
|
16
|
+
def test_the_email_is_saved_and_not_sent(self):
|
|
17
|
+
send_mail(
|
|
18
|
+
'Subject', 'Content', 'from@example.com', ['to@example.com'])
|
|
19
|
+
email = Email.objects.get()
|
|
20
|
+
self.assertEqual(email.send_mode, 'manual')
|
|
21
|
+
self.assertEqual(email.status, 'to_send')
|
|
22
|
+
self.assertEqual(mail.outbox, [])
|
|
23
|
+
|
|
24
|
+
def test_an_invalid_email_fails(self):
|
|
25
|
+
"""What locmem cannot see: the email model refuses the message."""
|
|
26
|
+
with self.assertRaises(ValidationError):
|
|
27
|
+
send_mail(
|
|
28
|
+
'Subject', 'Content', 'from@example.com', ['not-an-email'])
|
|
29
|
+
|
|
30
|
+
def test_outbox_helpers(self):
|
|
31
|
+
send_mail(
|
|
32
|
+
'First', 'Content', 'from@example.com',
|
|
33
|
+
['"Jean Dupont" <jean@example.com>', 'paul@example.com'])
|
|
34
|
+
message = EmailMultiAlternatives(
|
|
35
|
+
'Second', 'Text', 'from@example.com', ['marie@example.com'])
|
|
36
|
+
message.attach_alternative('<p>HTML</p>', 'text/html')
|
|
37
|
+
message.send()
|
|
38
|
+
|
|
39
|
+
first, second = self.outbox()
|
|
40
|
+
self.assertEqual(
|
|
41
|
+
[first.subject, second.subject], ['First', 'Second'])
|
|
42
|
+
self.assertEqual(
|
|
43
|
+
self.email_to(first), ['jean@example.com', 'paul@example.com'])
|
|
44
|
+
self.assertEqual(self.get_email_html(second), '<p>HTML</p>')
|
|
45
|
+
with self.assertRaises(AssertionError):
|
|
46
|
+
self.get_email_html(first)
|
|
47
|
+
|
|
48
|
+
self.clear_outbox()
|
|
49
|
+
self.assertEqual(self.outbox(), [])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TestEmailTestMixinWithLocmem(EmailTestMixin, TestCase):
|
|
53
|
+
"""Without the runner, locmem is still there: the helpers refuse to
|
|
54
|
+
answer instead of reporting an empty outbox."""
|
|
55
|
+
|
|
56
|
+
def test_the_helpers_refuse_locmem(self):
|
|
57
|
+
with self.assertRaises(AssertionError):
|
|
58
|
+
self.outbox()
|
|
59
|
+
with self.assertRaises(AssertionError):
|
|
60
|
+
self.clear_outbox()
|