django-pfx 1.3.dev20__tar.gz → 1.4.dev4__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 (131) hide show
  1. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/PKG-INFO +1 -1
  2. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/django_pfx.egg-info/PKG-INFO +1 -1
  3. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/django_pfx.egg-info/SOURCES.txt +1 -0
  4. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/source/pfx_views.md +44 -0
  5. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/rest_views.py +18 -1
  6. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/__init__.py +1 -0
  7. django-pfx-1.4.dev4/tests/tests/test_body_mixin.py +48 -0
  8. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/.gitignore +0 -0
  9. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/.gitlab-ci.yml +0 -0
  10. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/.pre-commit-config.yaml +0 -0
  11. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/LICENSE +0 -0
  12. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/MANIFEST.in +0 -0
  13. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/README.md +0 -0
  14. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/django-admin-test +0 -0
  15. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/django_pfx.egg-info/dependency_links.txt +0 -0
  16. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/django_pfx.egg-info/requires.txt +0 -0
  17. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/django_pfx.egg-info/top_level.txt +0 -0
  18. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/Makefile +0 -0
  19. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/conf.py +0 -0
  20. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/index.rst +0 -0
  21. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/source/api.views.rst +0 -0
  22. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/source/authentication.md +0 -0
  23. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/source/decorator.md +0 -0
  24. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/source/generate_openapi.md +0 -0
  25. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/source/getting_started.md +0 -0
  26. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/source/internationalisation.md +0 -0
  27. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/source/model.md +0 -0
  28. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/source/profiling.md +0 -0
  29. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/source/settings.md +0 -0
  30. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/doc/source/testing.md +0 -0
  31. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/img/pfx.png +0 -0
  32. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/img/pfx.svg +0 -0
  33. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/__init__.py +0 -0
  34. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/__init__.py +0 -0
  35. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/apidoc/__init__.py +0 -0
  36. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/apidoc/parameters.py +0 -0
  37. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/apidoc/schema.py +0 -0
  38. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/apidoc/tags.py +0 -0
  39. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/apps.py +0 -0
  40. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/decorator/__init__.py +0 -0
  41. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/decorator/rest.py +0 -0
  42. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/default_settings.py +0 -0
  43. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/exceptions.py +0 -0
  44. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/fields.py +0 -0
  45. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/http/__init__.py +0 -0
  46. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/http/json_response.py +0 -0
  47. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  48. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +0 -0
  49. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/management/__init__.py +0 -0
  50. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/management/commands/__init__.py +0 -0
  51. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  52. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/management/commands/profile.py +0 -0
  53. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/middleware/__init__.py +0 -0
  54. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/middleware/authentication.py +0 -0
  55. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/middleware/locale.py +0 -0
  56. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/middleware/profiling.py +0 -0
  57. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/models/__init__.py +0 -0
  58. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/models/cache_mixins.py +0 -0
  59. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/models/not_null_fields.py +0 -0
  60. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/models/pfx_models.py +0 -0
  61. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  62. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/serializers/__init__.py +0 -0
  63. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/serializers/json.py +0 -0
  64. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/settings.py +0 -0
  65. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/shortcuts.py +0 -0
  66. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/storage/__init__.py +0 -0
  67. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/storage/s3_storage.py +0 -0
  68. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  69. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  70. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  71. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  72. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/test.py +0 -0
  73. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/urls.py +0 -0
  74. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/__init__.py +0 -0
  75. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/authentication_views.py +0 -0
  76. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/fields.py +0 -0
  77. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/filters_views.py +0 -0
  78. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/locale_views.py +0 -0
  79. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  80. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  81. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/groups.py +0 -0
  82. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  83. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  84. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  85. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  86. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  87. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  88. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  89. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  90. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  91. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/subset.py +0 -0
  92. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  93. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  94. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  95. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  96. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  97. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/pyproject.toml +0 -0
  98. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/requirements.txt +0 -0
  99. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/runtest.py +0 -0
  100. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/serve-doc +0 -0
  101. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/setup.cfg +0 -0
  102. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/setup.py +0 -0
  103. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/__init__.py +0 -0
  104. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
  105. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/models.py +0 -0
  106. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/settings/__init__.py +0 -0
  107. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/settings/ci.py +0 -0
  108. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/settings/common.py +0 -0
  109. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/settings/dev.py +0 -0
  110. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/settings/dev_custom_example.py +0 -0
  111. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/settings/dev_default.py +0 -0
  112. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/basic_api_errors.py +0 -0
  113. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/basic_api_test.py +0 -0
  114. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_api_doc.py +0 -0
  115. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_auth_api.py +0 -0
  116. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_cache.py +0 -0
  117. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_client.py +0 -0
  118. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_fields.py +0 -0
  119. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_filters.py +0 -0
  120. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_locale_api.py +0 -0
  121. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_perm_tests.py +0 -0
  122. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_perms_api.py +0 -0
  123. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_profiling_middleware.py +0 -0
  124. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_shortcuts.py +0 -0
  125. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_timezone_middleware.py +0 -0
  126. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_tools.py +0 -0
  127. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_user_queryset.py +0 -0
  128. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_view_decorators.py +0 -0
  129. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/tests/test_view_fields.py +0 -0
  130. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/urls.py +0 -0
  131. {django-pfx-1.3.dev20 → django-pfx-1.4.dev4}/tests/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-pfx
3
- Version: 1.3.dev20
3
+ Version: 1.4.dev4
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.3.dev20
3
+ Version: 1.4.dev4
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
@@ -113,6 +113,7 @@ tests/tests/basic_api_errors.py
113
113
  tests/tests/basic_api_test.py
114
114
  tests/tests/test_api_doc.py
115
115
  tests/tests/test_auth_api.py
116
+ tests/tests/test_body_mixin.py
116
117
  tests/tests/test_cache.py
117
118
  tests/tests/test_client.py
118
119
  tests/tests/test_fields.py
@@ -300,3 +300,47 @@ the list of available fields to order the list.
300
300
  In a future version, these services should return a JSON OpenAPI structure. The list
301
301
  of orderable fields is resource-intensive to compute and should be removed and moved
302
302
  in the generated API doc.
303
+
304
+ ## Custom service with body validation
305
+
306
+ You can define a custom service and load body data into a unmanaged model if you view
307
+ inherits from `BodyMixin`.
308
+
309
+ Then you can add custom validators on you model.
310
+
311
+ Define an unmanaged model (you can use fields validators or extend the `clean` method
312
+ to customize validation):
313
+ ```python
314
+ from django.db import models
315
+ from django.utils.translation import gettext_lazy as _
316
+
317
+ from pfx.pfxcore.models import PFXModelMixin
318
+
319
+
320
+ class CustomModel(PFXModelMixin, models.Model):
321
+ pks = models.IntegerField(_("An integer"))
322
+
323
+ class Meta:
324
+ managed = False
325
+ ```
326
+
327
+ Then you can load the body in a `CustomModel` instance in a service, method `body_to_model`
328
+ will call `full_clean` on model instance and raise a `ModelValidationAPIError` if
329
+ there is invalid values (`ModelValidationAPIError` will be automatically converted
330
+ into a `422/JSONResponse` including fields and/or global errors):
331
+ ```python
332
+ from pfx.pfxcore.decorator import rest_api, rest_view
333
+ from pfx.pfxcore.views import BodyMixin
334
+
335
+ from .models import CustomModel
336
+
337
+
338
+ @rest_view("/my-service")
339
+ class MyServiceView(BodyMixin):
340
+ @rest_api("", method="put")
341
+ def put(self, *args, **kwargs):
342
+ custom = self.body_to_model(CustomModel)
343
+ # …
344
+ ```
345
+
346
+ See {func}`pfx.pfxcore.views.BodyMixin.body_to_model` for method documentation.
@@ -11,7 +11,7 @@ from django.core.exceptions import (
11
11
  ValidationError,
12
12
  )
13
13
  from django.db import IntegrityError, transaction
14
- from django.db.models import ForeignKey, Q
14
+ from django.db.models import ForeignKey, Model, Q
15
15
  from django.shortcuts import redirect
16
16
  from django.urls import path
17
17
  from django.utils.translation import gettext_lazy as _
@@ -26,6 +26,7 @@ from pfx.pfxcore.exceptions import (
26
26
  APIError,
27
27
  ForbiddenError,
28
28
  JsonErrorAPIError,
29
+ ModelValidationAPIError,
29
30
  NotFoundError,
30
31
  UnauthorizedError,
31
32
  )
@@ -421,6 +422,22 @@ class BodyMixin:
421
422
  except JSONDecodeError as e:
422
423
  raise JsonErrorAPIError(e)
423
424
 
425
+ def body_to_model(self, model: Model, validate=True, **kwargs):
426
+ """Return a new model instance built with request body.
427
+
428
+ :param model: The model to instantiate
429
+ :param validate: Activate validation
430
+ :param \\**kwargs: The optional arguments for `full_clean`
431
+ :rtype: :class:`django.db.models.Model`
432
+ """
433
+ obj = model(**self.deserialize_body())
434
+ if validate:
435
+ try:
436
+ obj.full_clean(**kwargs)
437
+ except ValidationError as e:
438
+ raise ModelValidationAPIError(e)
439
+ return obj
440
+
424
441
 
425
442
  class ModelBodyMixin(BodyMixin, ModelMixin):
426
443
  """Extension mixin to process object in body."""
@@ -2,6 +2,7 @@ from .basic_api_errors import BasicAPIErrorTest
2
2
  from .basic_api_test import BasicAPITest
3
3
  from .test_api_doc import ApiDocTest
4
4
  from .test_auth_api import AuthAPITest
5
+ from .test_body_mixin import TestBodyMixin
5
6
  from .test_cache import TestCache
6
7
  from .test_client import TestApiClient
7
8
  from .test_fields import FieldsTest
@@ -0,0 +1,48 @@
1
+ import json
2
+ from unittest.mock import MagicMock
3
+
4
+ from django.db import models
5
+ from django.test import TestCase
6
+
7
+ from pfx.pfxcore.exceptions import ModelValidationAPIError
8
+ from pfx.pfxcore.models import PFXModelMixin
9
+ from pfx.pfxcore.test import TestAssertMixin
10
+ from pfx.pfxcore.views import BodyMixin
11
+
12
+
13
+ class CustomModel(PFXModelMixin, models.Model):
14
+ value = models.IntegerField("An integer")
15
+
16
+ class Meta:
17
+ managed = False
18
+
19
+
20
+ class CustomView(BodyMixin):
21
+ def __init__(self, **body):
22
+ super().__init__()
23
+ self.request = MagicMock()
24
+ self.request.body = json.dumps(body)
25
+
26
+
27
+ class TestBodyMixin(TestAssertMixin, TestCase):
28
+ def test_body_to_model(self):
29
+ view = CustomView(value=7)
30
+ custom = view.body_to_model(CustomModel)
31
+
32
+ self.assertIsInstance(custom, CustomModel)
33
+ self.assertEqual(custom.value, 7)
34
+
35
+ def test_body_to_model_validation(self):
36
+ view = CustomView()
37
+ with self.assertRaises(ModelValidationAPIError) as cm:
38
+ view.body_to_model(CustomModel)
39
+ self.assertJE(
40
+ cm.exception.data.message_dict, "value",
41
+ ["This field cannot be null."])
42
+
43
+ def test_body_to_model_disable_validation(self):
44
+ view = CustomView()
45
+ custom = view.body_to_model(CustomModel, validate=False)
46
+
47
+ self.assertIsInstance(custom, CustomModel)
48
+ self.assertEqual(custom.value, None)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes