django-pfx 1.4.dev48__tar.gz → 1.4.dev52__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.dev48 → django_pfx-1.4.dev52}/PKG-INFO +1 -1
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/django_pfx.egg-info/PKG-INFO +1 -1
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/django_pfx.egg-info/SOURCES.txt +1 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/api.views.rst +83 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/authentication.md +4 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/generate_openapi.md +49 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/apidoc/parameters.py +4 -4
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/decorator/rest.py +2 -1
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/default_settings.py +1 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +3 -3
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/management/commands/makeapidoc.py +8 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/otp_user_mixin.py +12 -3
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/test.py +6 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/groups.py +2 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/rest_views.py +2 -1
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/__init__.py +1 -0
- django_pfx-1.4.dev52/tests/tests/test_api_doc_search.py +110 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_auth_api.py +4 -1
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/.gitignore +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/.gitlab-ci.yml +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/.pre-commit-config.yaml +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/LICENSE +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/MANIFEST.in +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/README.md +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/django_pfx.egg-info/dependency_links.txt +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/django_pfx.egg-info/requires.txt +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/django_pfx.egg-info/top_level.txt +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/Makefile +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/conf.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/index.rst +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/decorator.md +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/getting_started.md +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/internationalisation.md +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/model.md +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/pfx_views.md +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/profiling.md +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/settings.md +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/testing.md +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/img/pfx.png +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/img/pfx.svg +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/make_messages +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/manage.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/apidoc/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/apidoc/schema.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/apidoc/tags.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/apps.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/decorator/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/exceptions.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/fields.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/http/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/http/json_response.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/management/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/management/commands/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/management/commands/profile.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/middleware/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/middleware/authentication.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/middleware/locale.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/middleware/profiling.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/migrations/0001_initial.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/migrations/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/cache_mixins.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/login_ban.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/not_null_fields.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/pfx_models.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/pfx_user.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/serializers/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/serializers/json.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/settings.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/shortcuts.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/storage/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/storage/s3_storage.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/urls.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/authentication_views.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/fields.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/filters_views.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/locale_views.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/date_format.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/list_count.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/list_items.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/list_order.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/list_search.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/settings/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/settings/dev.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pyproject.toml +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/requirements.txt +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/serve-doc +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/setup.cfg +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/setup.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/models.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/settings/__init__.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/settings/ci.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/settings/common.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/settings/dev.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/settings/dev_custom_example.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/settings/dev_default.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/basic_api_errors.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/basic_api_test.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_api_doc.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_body_mixin.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_cache.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_client.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_fields.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_filters.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_locale_api.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_perm_tests.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_perms_api.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_profiling_middleware.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_settings.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_shortcuts.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_timezone_middleware.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_tools.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_user_queryset.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_view_decorators.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_view_fields.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/urls.py +0 -0
- {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/views.py +0 -0
|
@@ -122,6 +122,7 @@ tests/tests/__init__.py
|
|
|
122
122
|
tests/tests/basic_api_errors.py
|
|
123
123
|
tests/tests/basic_api_test.py
|
|
124
124
|
tests/tests/test_api_doc.py
|
|
125
|
+
tests/tests/test_api_doc_search.py
|
|
125
126
|
tests/tests/test_auth_api.py
|
|
126
127
|
tests/tests/test_body_mixin.py
|
|
127
128
|
tests/tests/test_cache.py
|
|
@@ -47,6 +47,89 @@ API Reference
|
|
|
47
47
|
``pfx.pfxcore.views``
|
|
48
48
|
*********************
|
|
49
49
|
|
|
50
|
+
Query parameters & groups
|
|
51
|
+
-------------------------
|
|
52
|
+
|
|
53
|
+
.. autoclass:: pfx.pfxcore.views.parameters.ListCount
|
|
54
|
+
:members:
|
|
55
|
+
:undoc-members:
|
|
56
|
+
:show-inheritance:
|
|
57
|
+
|
|
58
|
+
.. autoclass:: pfx.pfxcore.views.parameters.ListItems
|
|
59
|
+
:members:
|
|
60
|
+
:undoc-members:
|
|
61
|
+
:show-inheritance:
|
|
62
|
+
|
|
63
|
+
.. autoclass:: pfx.pfxcore.views.parameters.ListMode
|
|
64
|
+
:members:
|
|
65
|
+
:undoc-members:
|
|
66
|
+
:show-inheritance:
|
|
67
|
+
|
|
68
|
+
.. autoclass:: pfx.pfxcore.views.parameters.ListOrder
|
|
69
|
+
:members:
|
|
70
|
+
:undoc-members:
|
|
71
|
+
:show-inheritance:
|
|
72
|
+
|
|
73
|
+
.. autoclass:: pfx.pfxcore.views.parameters.ListSearch
|
|
74
|
+
:members:
|
|
75
|
+
:undoc-members:
|
|
76
|
+
:show-inheritance:
|
|
77
|
+
|
|
78
|
+
.. autoclass:: pfx.pfxcore.views.parameters.MediaRedirect
|
|
79
|
+
:members:
|
|
80
|
+
:undoc-members:
|
|
81
|
+
:show-inheritance:
|
|
82
|
+
|
|
83
|
+
.. autoclass:: pfx.pfxcore.views.parameters.MetaFields
|
|
84
|
+
:members:
|
|
85
|
+
:undoc-members:
|
|
86
|
+
:show-inheritance:
|
|
87
|
+
|
|
88
|
+
.. autoclass:: pfx.pfxcore.views.parameters.groups.MetaFilters
|
|
89
|
+
:members:
|
|
90
|
+
:undoc-members:
|
|
91
|
+
:show-inheritance:
|
|
92
|
+
|
|
93
|
+
.. autoclass:: pfx.pfxcore.views.parameters.groups.MetaOrders
|
|
94
|
+
:members:
|
|
95
|
+
:undoc-members:
|
|
96
|
+
:show-inheritance:
|
|
97
|
+
|
|
98
|
+
.. autoclass:: pfx.pfxcore.views.parameters.groups.SubsetLimit
|
|
99
|
+
:members:
|
|
100
|
+
:undoc-members:
|
|
101
|
+
:show-inheritance:
|
|
102
|
+
|
|
103
|
+
.. autoclass:: pfx.pfxcore.views.parameters.groups.SubsetOffset
|
|
104
|
+
:members:
|
|
105
|
+
:undoc-members:
|
|
106
|
+
:show-inheritance:
|
|
107
|
+
|
|
108
|
+
.. autoclass:: pfx.pfxcore.views.parameters.groups.SubsetPageSize
|
|
109
|
+
:members:
|
|
110
|
+
:undoc-members:
|
|
111
|
+
:show-inheritance:
|
|
112
|
+
|
|
113
|
+
.. autoclass:: pfx.pfxcore.views.parameters.groups.SubsetPageSubset
|
|
114
|
+
:members:
|
|
115
|
+
:undoc-members:
|
|
116
|
+
:show-inheritance:
|
|
117
|
+
|
|
118
|
+
.. autoclass:: pfx.pfxcore.views.parameters.groups.SubsetPage
|
|
119
|
+
:members:
|
|
120
|
+
:undoc-members:
|
|
121
|
+
:show-inheritance:
|
|
122
|
+
|
|
123
|
+
.. autoclass:: pfx.pfxcore.views.parameters.groups.MetaList
|
|
124
|
+
:members:
|
|
125
|
+
:undoc-members:
|
|
126
|
+
:show-inheritance:
|
|
127
|
+
|
|
128
|
+
.. autoclass:: pfx.pfxcore.views.parameters.groups.List
|
|
129
|
+
:members:
|
|
130
|
+
:undoc-members:
|
|
131
|
+
:show-inheritance:
|
|
132
|
+
|
|
50
133
|
Base services
|
|
51
134
|
-------------
|
|
52
135
|
|
|
@@ -97,6 +97,10 @@ class MyUser(OtpUserMixin, AbstractPFXBaseUser):
|
|
|
97
97
|
* `PFX_TOKEN_OTP_VALIDITY`: Validity for OTP tokens (corresponds to the maximum time to enter
|
|
98
98
|
an OTP code after logging in with a password) (optional, default `{'minutes': 15}`)
|
|
99
99
|
* `PFX_HOTP_CODE_VALIDITY`: Validity of HOTP codes in minutes (used to send code by email) (optional, default `15`).
|
|
100
|
+
* `PFX_OTP_VALID_WINDOW`: TOTP valid window (optional, default `1`).
|
|
101
|
+
According to [RFC 6238 section 5.2](https://www.ietf.org/rfc/rfc6238.html#section-5.2).
|
|
102
|
+
* `PFX_OTP_IMAGE`: An image https URL used by FreeOTP. See [FreeOTP URI](https://github.com/npmccallum/freeotp-android/blob/master/URI.md).
|
|
103
|
+
* `PFX_OTP_COLOR`: A brand color (in RRGGBB format) for used by FreeOTP. See [FreeOTP URI](https://github.com/npmccallum/freeotp-android/blob/master/URI.md).
|
|
100
104
|
|
|
101
105
|
The user can then enable or disable the OTP auth using the [services documented below](#enable-mfa-otp).
|
|
102
106
|
|
|
@@ -158,6 +158,55 @@ from .parameters import ListGroup
|
|
|
158
158
|
@rest_api("/", method="get", parameters=[ListGroup])
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
+
#### Pre defined query parameters
|
|
162
|
+
|
|
163
|
+
A number of predefined parameters and groups are used by view mixins, and are reusable and inheritable:
|
|
164
|
+
See [API reference | Query parameters & groups](api.views.rst#query-parameters-groups) for details.
|
|
165
|
+
|
|
166
|
+
For {class}`pfx.pfxcore.views.parameters.ListSearch`, you can redefine the behaviour by annotation:
|
|
167
|
+
either with `@rest_doc` on the view (useful if the view inherits from {class}`pfx.pfxcore.views.ListRestViewMixin`),
|
|
168
|
+
or with `@rest_api` on the service (useful if you reuse the {class}`pfx.pfxcore.views.parameters.ListSearch` parameter
|
|
169
|
+
or the {class}`pfx.pfxcore.views.parameters.groups.List` group).
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from pfx.pfxcore.decorator.rest import rest_doc, rest_view
|
|
173
|
+
from pfx.pfxcore.views import BaseRestView, ListRestViewMixin
|
|
174
|
+
|
|
175
|
+
# Do not add the search param in api doc:
|
|
176
|
+
@rest_doc("", "get", search=False)
|
|
177
|
+
@rest_view("/my-view")
|
|
178
|
+
class MyView(ListRestViewMixin, BaseRestView):
|
|
179
|
+
pass
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from pfx.pfxcore.decorator.rest import rest_doc, rest_view
|
|
184
|
+
from pfx.pfxcore.views import BaseRestView, ListRestViewMixin
|
|
185
|
+
|
|
186
|
+
# Customize the search param description in api doc:
|
|
187
|
+
@rest_doc("", "get", search="Search the string in name and summary fields.")
|
|
188
|
+
@rest_view("/my-view")
|
|
189
|
+
class MyView(ListRestViewMixin, BaseRestView):
|
|
190
|
+
pass
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from pfx.pfxcore.decorator.rest import rest_doc, rest_view
|
|
195
|
+
from pfx.pfxcore.views import BaseRestView
|
|
196
|
+
|
|
197
|
+
@rest_view("/custom")
|
|
198
|
+
class MyView(BaseRestView):
|
|
199
|
+
|
|
200
|
+
# Use parameters.groups.List with custom description for search param:
|
|
201
|
+
@rest_api(
|
|
202
|
+
"", method="get",
|
|
203
|
+
parameters=[parameters.groups.List],
|
|
204
|
+
search="A custom description.")
|
|
205
|
+
def get(self, *args, **kwargs):
|
|
206
|
+
return JsonResponse({})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
|
|
161
210
|
### Body and response schema
|
|
162
211
|
|
|
163
212
|
When using standard mixins to provide basic Rest services, the body
|
|
@@ -21,12 +21,12 @@ class Parameter:
|
|
|
21
21
|
return cls.__name__
|
|
22
22
|
|
|
23
23
|
@classmethod
|
|
24
|
-
def as_parameter(cls):
|
|
24
|
+
def as_parameter(cls, doc=None):
|
|
25
25
|
res = dict(name=cls.name)
|
|
26
26
|
res['in'] = cls.location
|
|
27
|
-
|
|
28
|
-
if
|
|
29
|
-
res['description'] =
|
|
27
|
+
description = doc or inspect.getdoc(cls)
|
|
28
|
+
if description:
|
|
29
|
+
res['description'] = description
|
|
30
30
|
members = inspect.getmembers(
|
|
31
31
|
cls, predicate=lambda x: not (
|
|
32
32
|
inspect.ismethod(x)))
|
|
@@ -17,7 +17,7 @@ def rest_api(
|
|
|
17
17
|
path, method='get', public=None, priority=0, priority_doc=0,
|
|
18
18
|
parameters=None,
|
|
19
19
|
request_schema=None, response_schema=None, filters=False,
|
|
20
|
-
groups=None):
|
|
20
|
+
search=False, groups=None):
|
|
21
21
|
def decorator(func):
|
|
22
22
|
@wraps(func)
|
|
23
23
|
def wrapper(self, request, *args, **kwargs):
|
|
@@ -47,6 +47,7 @@ def rest_api(
|
|
|
47
47
|
wrapper.rest_api_request_schema = request_schema
|
|
48
48
|
wrapper.rest_api_response_schema = response_schema
|
|
49
49
|
wrapper.rest_api_filters = filters
|
|
50
|
+
wrapper.rest_api_search = search
|
|
50
51
|
wrapper.rest_api_groups = set(groups or [])
|
|
51
52
|
wrapper.rest_api_public = public
|
|
52
53
|
return wrapper
|
|
@@ -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: 2024-
|
|
10
|
+
"POT-Creation-Date: 2024-05-21 15:30+0200\n"
|
|
11
11
|
"PO-Revision-Date: 2021-06-22 23:31+0200\n"
|
|
12
12
|
"Last-Translator: \n"
|
|
13
13
|
"Language-Team: \n"
|
|
@@ -275,11 +275,11 @@ msgstr "{model} {obj} créé."
|
|
|
275
275
|
msgid "{model} {obj} updated."
|
|
276
276
|
msgstr "{model} {obj} modifié."
|
|
277
277
|
|
|
278
|
-
#: views/rest_views.py:
|
|
278
|
+
#: views/rest_views.py:1077
|
|
279
279
|
#, python-brace-format
|
|
280
280
|
msgid "{model} {obj} deleted."
|
|
281
281
|
msgstr "{model} {obj} supprimé."
|
|
282
282
|
|
|
283
|
-
#: views/rest_views.py:
|
|
283
|
+
#: views/rest_views.py:1139 views/rest_views.py:1167
|
|
284
284
|
msgid "Unexpected storage error"
|
|
285
285
|
msgstr "Erreur de stockage inattendue"
|
|
@@ -63,6 +63,14 @@ def global_parameters(spec, method):
|
|
|
63
63
|
p.get('name', '#N/A') for p in spec.get('parameters', [])
|
|
64
64
|
if isinstance(p, dict)}
|
|
65
65
|
for qp in extend_groups(method.rest_api_params):
|
|
66
|
+
if qp.name == 'search':
|
|
67
|
+
search = (
|
|
68
|
+
spec['search'] if 'search' in spec else method.rest_api_search)
|
|
69
|
+
if not search:
|
|
70
|
+
continue
|
|
71
|
+
if isinstance(search, str):
|
|
72
|
+
yield qp.as_parameter(doc=search)
|
|
73
|
+
continue
|
|
66
74
|
if qp.name in existings:
|
|
67
75
|
# Ignore path parameters that are manually described.
|
|
68
76
|
continue
|
|
@@ -65,7 +65,7 @@ class OtpUserMixin(models.Model):
|
|
|
65
65
|
self.otp_secret_token = None
|
|
66
66
|
self.save(update_fields=['otp_secret_token'])
|
|
67
67
|
|
|
68
|
-
def get_otp_setup_uri(self, tmp=False):
|
|
68
|
+
def get_otp_setup_uri(self, tmp=False, with_color=True):
|
|
69
69
|
"""Return the setup URL for OTP activation.
|
|
70
70
|
"""
|
|
71
71
|
import pyotp
|
|
@@ -73,9 +73,14 @@ class OtpUserMixin(models.Model):
|
|
|
73
73
|
name=self.get_username(), issuer_name=settings.PFX_SITE_NAME)
|
|
74
74
|
if settings.PFX_OTP_IMAGE:
|
|
75
75
|
args['image'] = settings.PFX_OTP_IMAGE
|
|
76
|
-
|
|
76
|
+
uri = pyotp.totp.TOTP(
|
|
77
77
|
tmp and self.otp_secret_token_tmp or
|
|
78
78
|
self.otp_secret_token).provisioning_uri(**args)
|
|
79
|
+
if with_color and settings.PFX_OTP_COLOR:
|
|
80
|
+
# TODO: Can be put in provisioning_uri args if
|
|
81
|
+
# https://github.com/pyauth/pyotp/pull/164 is merge and published.
|
|
82
|
+
uri = f"{uri}&color={settings.PFX_OTP_COLOR}"
|
|
83
|
+
return uri
|
|
79
84
|
|
|
80
85
|
def is_otp_valid(self, otp_code, tmp=False):
|
|
81
86
|
"""Verify an OTP code.
|
|
@@ -86,7 +91,11 @@ class OtpUserMixin(models.Model):
|
|
|
86
91
|
:returns: `True` if the code is valid, `False` otherwise.
|
|
87
92
|
"""
|
|
88
93
|
import pyotp
|
|
89
|
-
|
|
94
|
+
|
|
95
|
+
# TODO : The with_color paramter can be removed if
|
|
96
|
+
# https://github.com/pyauth/pyotp/pull/164 is merge and published.
|
|
97
|
+
totp = pyotp.parse_uri(
|
|
98
|
+
self.get_otp_setup_uri(tmp=tmp, with_color=False))
|
|
90
99
|
valid = totp.verify(
|
|
91
100
|
otp_code, valid_window=settings.PFX_OTP_VALID_WINDOW)
|
|
92
101
|
if not valid and timezone.now() <= self.hotp_expiry:
|
|
@@ -255,6 +255,12 @@ class TestAssertMixin:
|
|
|
255
255
|
return self.assertIn(
|
|
256
256
|
element, self.get_val(src, key, msg=msg), msg=msg)
|
|
257
257
|
|
|
258
|
+
# assert JSON array contains
|
|
259
|
+
def assertJNotIn(self, src, key, element, msg=None):
|
|
260
|
+
msg = '\n'.join([msg or '', self.format_json(src)])
|
|
261
|
+
return self.assertNotIn(
|
|
262
|
+
element, self.get_val(src, key, msg=msg), msg=msg)
|
|
263
|
+
|
|
258
264
|
|
|
259
265
|
class TestPermsAssertMixin(TestAssertMixin):
|
|
260
266
|
USER_TESTS = {}
|
|
@@ -26,10 +26,12 @@ class ModelSerialization(ParameterGroup):
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class MetaList(ParameterGroup):
|
|
29
|
+
"""Parameters group for meta list services."""
|
|
29
30
|
parameters = [MetaFields, MetaFilters, MetaOrders]
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
class List(ParameterGroup):
|
|
34
|
+
"""Parameters group for list services."""
|
|
33
35
|
parameters = [
|
|
34
36
|
ListCount, ListItems, ListSearch, ListOrder, ListMode,
|
|
35
37
|
Subset, SubsetPage, SubsetPageSize,
|
|
@@ -808,7 +808,8 @@ class ListRestViewMixin(ModelResponseMixin):
|
|
|
808
808
|
return JsonResponse(res)
|
|
809
809
|
|
|
810
810
|
@rest_api(
|
|
811
|
-
"", method="get", parameters=[parameters.groups.List],
|
|
811
|
+
"", method="get", parameters=[parameters.groups.List],
|
|
812
|
+
filters=True, search=True,
|
|
812
813
|
response_schema='model_list_schema', priority_doc=-20)
|
|
813
814
|
def get_list(self, *args, **kwargs):
|
|
814
815
|
"""Entrypoint for :code:`GET /` route.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .basic_api_errors import BasicAPIErrorTest
|
|
2
2
|
from .basic_api_test import BasicAPITest
|
|
3
3
|
from .test_api_doc import ApiDocTest
|
|
4
|
+
from .test_api_doc_search import TestAPIDocSearch
|
|
4
5
|
from .test_auth_api import AuthAPITest
|
|
5
6
|
from .test_body_mixin import TestBodyMixin
|
|
6
7
|
from .test_cache import TestCache
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from django.http import JsonResponse
|
|
2
|
+
from django.test import TestCase, override_settings
|
|
3
|
+
from django.urls import include, path
|
|
4
|
+
|
|
5
|
+
from pfx.pfxcore.decorator.rest import rest_api, rest_doc, rest_view
|
|
6
|
+
from pfx.pfxcore.management.commands.makeapidoc import get_spec
|
|
7
|
+
from pfx.pfxcore.shortcuts import register_views
|
|
8
|
+
from pfx.pfxcore.test import APIClient, TestAssertMixin
|
|
9
|
+
from pfx.pfxcore.views import (
|
|
10
|
+
BaseRestView,
|
|
11
|
+
ListRestViewMixin,
|
|
12
|
+
ModelResponseMixin,
|
|
13
|
+
parameters,
|
|
14
|
+
)
|
|
15
|
+
from tests.models import Author
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@rest_view("/list/default")
|
|
19
|
+
class DefaultListView(ListRestViewMixin, ModelResponseMixin, BaseRestView):
|
|
20
|
+
model = Author
|
|
21
|
+
default_public = True
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@rest_doc("", "get", search=False)
|
|
25
|
+
@rest_view("/list/no-search")
|
|
26
|
+
class NoSearchListView(ListRestViewMixin, ModelResponseMixin, BaseRestView):
|
|
27
|
+
model = Author
|
|
28
|
+
default_public = True
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@rest_doc("", "get", search="A custom description.")
|
|
32
|
+
@rest_view("/list/custom-description")
|
|
33
|
+
class CustomDescriptionListView(
|
|
34
|
+
ListRestViewMixin, ModelResponseMixin, BaseRestView):
|
|
35
|
+
model = Author
|
|
36
|
+
default_public = True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@rest_view("/custom")
|
|
40
|
+
class CustomServiceView(BaseRestView):
|
|
41
|
+
model = Author
|
|
42
|
+
default_public = True
|
|
43
|
+
|
|
44
|
+
@rest_api(
|
|
45
|
+
"/default", method="get", parameters=[parameters.groups.List],
|
|
46
|
+
search=True)
|
|
47
|
+
def get_default(self, *args, **kwargs):
|
|
48
|
+
return JsonResponse({})
|
|
49
|
+
|
|
50
|
+
@rest_api(
|
|
51
|
+
"/no-search", method="get", parameters=[parameters.groups.List],
|
|
52
|
+
search=False)
|
|
53
|
+
def get_no_search(self, *args, **kwargs):
|
|
54
|
+
return JsonResponse({})
|
|
55
|
+
|
|
56
|
+
@rest_api(
|
|
57
|
+
"/custom-description", method="get",
|
|
58
|
+
parameters=[parameters.groups.List],
|
|
59
|
+
search="A custom description.")
|
|
60
|
+
def get_custom_description(self, *args, **kwargs):
|
|
61
|
+
return JsonResponse({})
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
urlpatterns = [
|
|
65
|
+
path('api/', include(register_views(
|
|
66
|
+
DefaultListView,
|
|
67
|
+
NoSearchListView,
|
|
68
|
+
CustomDescriptionListView,
|
|
69
|
+
CustomServiceView))),
|
|
70
|
+
path('api/', include('pfx.pfxcore.urls'))
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@override_settings(ROOT_URLCONF=__name__)
|
|
75
|
+
class TestAPIDocSearch(TestAssertMixin, TestCase):
|
|
76
|
+
def setUp(self):
|
|
77
|
+
self.client = APIClient(default_locale='en')
|
|
78
|
+
|
|
79
|
+
def test_body_to_model(self):
|
|
80
|
+
def get_params(spec, path):
|
|
81
|
+
for p in self.get_val(spec, f'paths.{path}.get.parameters'):
|
|
82
|
+
yield p.get('$ref') or p.get('name'), p
|
|
83
|
+
|
|
84
|
+
spec = get_spec(set()).to_dict()
|
|
85
|
+
|
|
86
|
+
params = dict(get_params(spec, '/list/default'))
|
|
87
|
+
self.assertJEExists(params, '#/components/parameters/ListSearch')
|
|
88
|
+
self.assertJENotExists(params, 'search')
|
|
89
|
+
|
|
90
|
+
params = dict(get_params(spec, '/list/no-search'))
|
|
91
|
+
self.assertJENotExists(params, '#/components/parameters/ListSearch')
|
|
92
|
+
self.assertJENotExists(params, 'search')
|
|
93
|
+
|
|
94
|
+
params = dict(get_params(spec, '/list/custom-description'))
|
|
95
|
+
self.assertJENotExists(params, '#/components/parameters/ListSearch')
|
|
96
|
+
self.assertJEExists(params, 'search')
|
|
97
|
+
self.assertJE(params, 'search.description', "A custom description.")
|
|
98
|
+
|
|
99
|
+
params = dict(get_params(spec, '/custom/default'))
|
|
100
|
+
self.assertJEExists(params, '#/components/parameters/ListSearch')
|
|
101
|
+
self.assertJENotExists(params, 'search')
|
|
102
|
+
|
|
103
|
+
params = dict(get_params(spec, '/custom/no-search'))
|
|
104
|
+
self.assertJENotExists(params, '#/components/parameters/ListSearch')
|
|
105
|
+
self.assertJENotExists(params, 'search')
|
|
106
|
+
|
|
107
|
+
params = dict(get_params(spec, '/custom/custom-description'))
|
|
108
|
+
self.assertJENotExists(params, '#/components/parameters/ListSearch')
|
|
109
|
+
self.assertJEExists(params, 'search')
|
|
110
|
+
self.assertJE(params, 'search.description', "A custom description.")
|
|
@@ -895,7 +895,9 @@ class AuthAPITest(TestAssertMixin, TransactionTestCase):
|
|
|
895
895
|
self.assertRC(response, 200)
|
|
896
896
|
self.client.token = self.get_val(response, 'token')
|
|
897
897
|
|
|
898
|
-
@override_settings(
|
|
898
|
+
@override_settings(
|
|
899
|
+
PFX_OTP_IMAGE="https://example.org/fake.png",
|
|
900
|
+
PFX_OTP_COLOR="FF0000")
|
|
899
901
|
def test_otp_enable(self):
|
|
900
902
|
self.client.login(username='jrr.tolkien', password='RIGHT PASSWORD')
|
|
901
903
|
|
|
@@ -905,6 +907,7 @@ class AuthAPITest(TestAssertMixin, TransactionTestCase):
|
|
|
905
907
|
self.assertJIn(response, 'setup_uri', "otpauth://totp/")
|
|
906
908
|
self.assertJIn(response, 'setup_uri', "Books%20Demo:jrr.tolkien")
|
|
907
909
|
self.assertJIn(response, 'setup_uri', "issuer=Books%20Demo")
|
|
910
|
+
self.assertJIn(response, 'setup_uri', "color=FF0000")
|
|
908
911
|
self.assertJIn(
|
|
909
912
|
response, 'setup_uri',
|
|
910
913
|
"image=https%3A%2F%2Fexample.org%2Ffake.png")
|
|
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
|
|
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
|
{django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/user_filtered_queryset_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/otp_code_email.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/welcome_email.txt
RENAMED
|
File without changes
|
{django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/welcome_subject.txt
RENAMED
|
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
|
{django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/media_redirect.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset_page_size.py
RENAMED
|
File without changes
|
{django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset_page_subset.py
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|