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.
Files changed (143) hide show
  1. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/PKG-INFO +1 -1
  2. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/django_pfx.egg-info/PKG-INFO +1 -1
  3. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/django_pfx.egg-info/SOURCES.txt +1 -0
  4. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/api.views.rst +83 -0
  5. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/authentication.md +4 -0
  6. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/generate_openapi.md +49 -0
  7. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/apidoc/parameters.py +4 -4
  8. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/decorator/rest.py +2 -1
  9. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/default_settings.py +1 -0
  10. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +3 -3
  11. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/management/commands/makeapidoc.py +8 -0
  12. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/otp_user_mixin.py +12 -3
  13. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/test.py +6 -0
  14. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/groups.py +2 -0
  15. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/rest_views.py +2 -1
  16. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/__init__.py +1 -0
  17. django_pfx-1.4.dev52/tests/tests/test_api_doc_search.py +110 -0
  18. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_auth_api.py +4 -1
  19. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/.gitignore +0 -0
  20. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/.gitlab-ci.yml +0 -0
  21. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/.pre-commit-config.yaml +0 -0
  22. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/LICENSE +0 -0
  23. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/MANIFEST.in +0 -0
  24. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/README.md +0 -0
  25. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/django_pfx.egg-info/dependency_links.txt +0 -0
  26. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/django_pfx.egg-info/requires.txt +0 -0
  27. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/django_pfx.egg-info/top_level.txt +0 -0
  28. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/Makefile +0 -0
  29. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/conf.py +0 -0
  30. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/index.rst +0 -0
  31. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/decorator.md +0 -0
  32. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/getting_started.md +0 -0
  33. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/internationalisation.md +0 -0
  34. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/model.md +0 -0
  35. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/pfx_views.md +0 -0
  36. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/profiling.md +0 -0
  37. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/settings.md +0 -0
  38. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/doc/source/testing.md +0 -0
  39. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/img/pfx.png +0 -0
  40. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/img/pfx.svg +0 -0
  41. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/make_messages +0 -0
  42. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/manage.py +0 -0
  43. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/__init__.py +0 -0
  44. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/__init__.py +0 -0
  45. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/apidoc/__init__.py +0 -0
  46. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/apidoc/schema.py +0 -0
  47. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/apidoc/tags.py +0 -0
  48. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/apps.py +0 -0
  49. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/decorator/__init__.py +0 -0
  50. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/exceptions.py +0 -0
  51. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/fields.py +0 -0
  52. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/http/__init__.py +0 -0
  53. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/http/json_response.py +0 -0
  54. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  55. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/management/__init__.py +0 -0
  56. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/management/commands/__init__.py +0 -0
  57. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/management/commands/profile.py +0 -0
  58. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/middleware/__init__.py +0 -0
  59. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/middleware/authentication.py +0 -0
  60. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/middleware/locale.py +0 -0
  61. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/middleware/profiling.py +0 -0
  62. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/migrations/0001_initial.py +0 -0
  63. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/migrations/__init__.py +0 -0
  64. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/__init__.py +0 -0
  65. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
  66. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/cache_mixins.py +0 -0
  67. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/login_ban.py +0 -0
  68. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/not_null_fields.py +0 -0
  69. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/pfx_models.py +0 -0
  70. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/pfx_user.py +0 -0
  71. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  72. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/serializers/__init__.py +0 -0
  73. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/serializers/json.py +0 -0
  74. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/settings.py +0 -0
  75. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/shortcuts.py +0 -0
  76. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/storage/__init__.py +0 -0
  77. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/storage/s3_storage.py +0 -0
  78. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
  79. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
  80. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  81. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  82. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  83. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  84. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/urls.py +0 -0
  85. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/__init__.py +0 -0
  86. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/authentication_views.py +0 -0
  87. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/fields.py +0 -0
  88. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/filters_views.py +0 -0
  89. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/locale_views.py +0 -0
  90. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  91. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  92. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  93. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  94. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  95. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  96. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  97. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  98. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  99. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  100. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  101. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset.py +0 -0
  102. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  103. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  104. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  105. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  106. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  107. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/settings/__init__.py +0 -0
  108. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pfx/settings/dev.py +0 -0
  109. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/pyproject.toml +0 -0
  110. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/requirements.txt +0 -0
  111. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/serve-doc +0 -0
  112. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/setup.cfg +0 -0
  113. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/setup.py +0 -0
  114. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/__init__.py +0 -0
  115. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
  116. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/models.py +0 -0
  117. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/settings/__init__.py +0 -0
  118. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/settings/ci.py +0 -0
  119. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/settings/common.py +0 -0
  120. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/settings/dev.py +0 -0
  121. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/settings/dev_custom_example.py +0 -0
  122. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/settings/dev_default.py +0 -0
  123. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/basic_api_errors.py +0 -0
  124. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/basic_api_test.py +0 -0
  125. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_api_doc.py +0 -0
  126. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_body_mixin.py +0 -0
  127. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_cache.py +0 -0
  128. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_client.py +0 -0
  129. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_fields.py +0 -0
  130. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_filters.py +0 -0
  131. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_locale_api.py +0 -0
  132. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_perm_tests.py +0 -0
  133. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_perms_api.py +0 -0
  134. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_profiling_middleware.py +0 -0
  135. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_settings.py +0 -0
  136. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_shortcuts.py +0 -0
  137. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_timezone_middleware.py +0 -0
  138. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_tools.py +0 -0
  139. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_user_queryset.py +0 -0
  140. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_view_decorators.py +0 -0
  141. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/tests/test_view_fields.py +0 -0
  142. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/urls.py +0 -0
  143. {django_pfx-1.4.dev48 → django_pfx-1.4.dev52}/tests/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-pfx
3
- Version: 1.4.dev48
3
+ Version: 1.4.dev52
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.1
2
2
  Name: django-pfx
3
- Version: 1.4.dev48
3
+ Version: 1.4.dev52
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
@@ -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
- doc = inspect.getdoc(cls)
28
- if doc:
29
- res['description'] = doc
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
@@ -4,6 +4,7 @@ PFX_TOKEN_OTP_VALIDITY = {'minutes': 15}
4
4
  PFX_HOTP_CODE_VALIDITY = 15
5
5
  PFX_OTP_VALID_WINDOW = 1
6
6
  PFX_OTP_IMAGE = None
7
+ PFX_OTP_COLOR = None
7
8
 
8
9
  PFX_COOKIE_DOMAIN = None
9
10
  PFX_COOKIE_SECURE = True
@@ -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-04-23 14:41+0200\n"
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:1076
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:1138 views/rest_views.py:1166
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
- return pyotp.totp.TOTP(
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
- totp = pyotp.parse_uri(self.get_otp_setup_uri(tmp=tmp))
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], filters=True,
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(PFX_OTP_IMAGE="https://example.org/fake.png")
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