django-pfx 1.2.dev94__tar.gz → 1.2.dev99__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 (123) hide show
  1. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/PKG-INFO +1 -1
  2. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/django_pfx.egg-info/PKG-INFO +1 -1
  3. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/doc/conf.py +3 -1
  4. django-pfx-1.2.dev99/doc/source/decorator.md +61 -0
  5. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/doc/source/getting_started.md +2 -3
  6. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/doc/source/model.md +16 -1
  7. django-pfx-1.2.dev99/doc/source/pfx_views.md +274 -0
  8. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/doc/source/testing.md +98 -22
  9. django-pfx-1.2.dev94/doc/source/decorator.md +0 -45
  10. django-pfx-1.2.dev94/doc/source/pfx_views.md +0 -100
  11. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/.gitignore +0 -0
  12. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/.gitlab-ci.yml +0 -0
  13. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/.pre-commit-config.yaml +0 -0
  14. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/LICENSE +0 -0
  15. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/MANIFEST.in +0 -0
  16. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/README.md +0 -0
  17. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/django_pfx.egg-info/SOURCES.txt +0 -0
  18. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/django_pfx.egg-info/dependency_links.txt +0 -0
  19. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/django_pfx.egg-info/requires.txt +0 -0
  20. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/django_pfx.egg-info/top_level.txt +0 -0
  21. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/doc/Makefile +0 -0
  22. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/doc/index.rst +0 -0
  23. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/doc/source/authentication.md +0 -0
  24. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/doc/source/cookbook.md +0 -0
  25. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/doc/source/internationalisation.md +0 -0
  26. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/doc/source/pfxcore.views.rst +0 -0
  27. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/img/pfx.png +0 -0
  28. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/img/pfx.svg +0 -0
  29. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/__init__.py +0 -0
  30. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/__init__.py +0 -0
  31. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/apidoc/__init__.py +0 -0
  32. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/apidoc/parameters.py +0 -0
  33. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/apidoc/schema.py +0 -0
  34. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/apidoc/tags.py +0 -0
  35. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/apps.py +0 -0
  36. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/decorator/__init__.py +0 -0
  37. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/decorator/rest.py +0 -0
  38. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/default_settings.py +0 -0
  39. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/exceptions.py +0 -0
  40. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/fields.py +0 -0
  41. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/http/__init__.py +0 -0
  42. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/http/json_response.py +0 -0
  43. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  44. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +0 -0
  45. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/management/__init__.py +0 -0
  46. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/management/commands/__init__.py +0 -0
  47. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  48. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/middleware/__init__.py +0 -0
  49. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/middleware/authentication.py +0 -0
  50. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/middleware/locale.py +0 -0
  51. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/models/__init__.py +0 -0
  52. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/models/cache_mixins.py +0 -0
  53. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/models/not_null_fields.py +0 -0
  54. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/models/pfx_models.py +0 -0
  55. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  56. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/serializers/__init__.py +0 -0
  57. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/serializers/json.py +0 -0
  58. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/settings.py +0 -0
  59. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/shortcuts.py +0 -0
  60. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/storage/__init__.py +0 -0
  61. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/storage/s3_storage.py +0 -0
  62. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  63. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  64. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  65. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  66. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/test.py +0 -0
  67. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/urls.py +0 -0
  68. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/__init__.py +0 -0
  69. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/authentication_views.py +0 -0
  70. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/fields.py +0 -0
  71. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/filters_views.py +0 -0
  72. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/locale_views.py +0 -0
  73. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  74. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  75. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/groups.py +0 -0
  76. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  77. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  78. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  79. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  80. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  81. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  82. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  83. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  84. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  85. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/subset.py +0 -0
  86. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  87. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  88. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  89. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  90. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  91. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pfx/pfxcore/views/rest_views.py +0 -0
  92. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/pyproject.toml +0 -0
  93. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/requirements.txt +0 -0
  94. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/runtest.py +0 -0
  95. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/setup.cfg +0 -0
  96. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/setup.py +0 -0
  97. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/__init__.py +0 -0
  98. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/models.py +0 -0
  99. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/settings/__init__.py +0 -0
  100. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/settings/ci.py +0 -0
  101. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/settings/common.py +0 -0
  102. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/settings/dev.py +0 -0
  103. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/settings/dev_custom_example.py +0 -0
  104. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/settings/dev_default.py +0 -0
  105. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/__init__.py +0 -0
  106. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/basic_api_errors.py +0 -0
  107. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/basic_api_test.py +0 -0
  108. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_api_doc.py +0 -0
  109. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_auth_api.py +0 -0
  110. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_cache.py +0 -0
  111. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_fields.py +0 -0
  112. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_filters.py +0 -0
  113. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_locale_api.py +0 -0
  114. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_perm_tests.py +0 -0
  115. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_perms_api.py +0 -0
  116. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_shortcuts.py +0 -0
  117. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_timezone_middleware.py +0 -0
  118. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_tools.py +0 -0
  119. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_user_queryset.py +0 -0
  120. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_view_decorators.py +0 -0
  121. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/tests/test_view_fields.py +0 -0
  122. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/urls.py +0 -0
  123. {django-pfx-1.2.dev94 → django-pfx-1.2.dev99}/tests/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-pfx
3
- Version: 1.2.dev94
3
+ Version: 1.2.dev99
4
4
  Summary: Django PFX is a toolkit to build web APIs dedicated to be used by React progressive web app.
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.2.dev94
3
+ Version: 1.2.dev99
4
4
  Summary: Django PFX is a toolkit to build web APIs dedicated to be used by React progressive web app.
5
5
  Author: Hervé Martinet
6
6
  Author-email: herve.martinet@gmail.com
@@ -66,4 +66,6 @@ html_theme = "sphinx_rtd_theme"
66
66
  # Add any paths that contain custom static files (such as style sheets) here,
67
67
  # relative to this directory. They are copied after the builtin static files,
68
68
  # so a file named "default.css" will overwrite the builtin "default.css".
69
- html_static_path = ['_static']
69
+ # html_static_path = ['_static']
70
+
71
+ myst_heading_anchors = 3
@@ -0,0 +1,61 @@
1
+ # View decorator and URL
2
+ ## @rest_view
3
+ Used to provide the base path of a view class
4
+ ```
5
+ @rest_view("/base-path")
6
+ class ViewClass():
7
+ ```
8
+
9
+ ## @rest_api
10
+ Used to annotate the rest services method.
11
+ Parameters are the path and the HTTP method.
12
+ ```
13
+ @rest_api("/path", method="get")
14
+ def class_method(self):
15
+ ```
16
+
17
+ A short example
18
+ ```
19
+ @rest_view("/books")
20
+ class BookRestView():
21
+
22
+ @rest_api("/list", method="get")
23
+ def get_list(self):
24
+ return JsonResponse(
25
+ dict(books=['The Man in the High Castle', 'A Scanner Darkly']))
26
+ ```
27
+
28
+ ### path parameters
29
+ Path can contain parameters that are passed to the method.
30
+ ```
31
+ @rest_api("/path/<int:pk>/test/<slug:slug>", method="get")
32
+ def class_method(self, pk, slug):
33
+ ```
34
+
35
+ ## Registering urls
36
+ To be available, annotated view class must be registered
37
+ in your urls.py file as follows.
38
+ ```
39
+ from pfx.pfxcore import register_views
40
+
41
+ from . import views
42
+
43
+ urlpatterns = register_views(
44
+ views.AuthorRestView,
45
+ views.BookRestView)
46
+ ```
47
+ You can include multiple views under one path, or add a path
48
+ wih a specific class method for each HTTP methods:
49
+ ```
50
+ from pfx.pfxcore import register_views
51
+
52
+ from . import views
53
+
54
+ urlpatterns = [
55
+ path('api/', include(register_views(
56
+ views.AuthorRestView,
57
+ views.BookRestView))),
58
+ path('other/thing', views.OtherRestView.as_view(
59
+ pfx_methods=dict(get='get_other'))),
60
+ ]
61
+ ```
@@ -70,11 +70,10 @@ from django_request_mapping import UrlPattern
70
70
 
71
71
  from book import views
72
72
 
73
- urlpatterns = UrlPattern()
74
- urlpatterns.register(views.BookRestView)
73
+ apipatterns = register_views(views.BookRestView)
75
74
 
76
75
  urlpatterns = [
77
- path('api/', include(urlpatterns)),
76
+ path('api/', include(apipatterns)),
78
77
  ]
79
78
  ```
80
79
 
@@ -5,7 +5,7 @@ Django PFX use plain Django Models classes.
5
5
  This library only provides some helpers for properties
6
6
  and foreign keys representations.
7
7
 
8
- ## @rest_property
8
+ ## `@rest_property`
9
9
  If you want to use properties in your model,
10
10
  you have to annotate them with @rest_property.
11
11
 
@@ -95,3 +95,18 @@ Which give the following result on the book service :
95
95
  "resource_name": "The Fellowship of the Ring",
96
96
  }
97
97
  ```
98
+
99
+ ## Not null char fields
100
+
101
+ To avoid storing a mixture of empty strings and `null` values, while automatically
102
+ converting `null` values to empty strings, you can use the following model fields:
103
+
104
+ * `NotNullCharField`
105
+ * `NotNullTextField`
106
+ * `NotNullURLField`
107
+ ```
108
+ a_string_field = NotNullCharField(
109
+ _("A string"), max_length=255, blank=True, default="")
110
+ ```
111
+
112
+ The `null` parameter of these fields is automatically set to `False`.
@@ -0,0 +1,274 @@
1
+ # Django PFX Views
2
+ REST services are provided as `ViewMixin` for `BaseRestView`.
3
+ Each of these views must set the queryset used to query the model data either
4
+ by defining the queryset attribute or by overriding the `get_queryset` method.
5
+
6
+ Fields can be listed in the fields attribute, else all the fields are provided.
7
+
8
+ ## DetailRestViewMixin
9
+ Provide a get detail service for a model class.
10
+
11
+ By default, all model fields are included in response. You can customize
12
+ the response by specifying the fields attribute (see [Define Fields](pfx_views.md#define-fields) for details).
13
+ ```
14
+ from pfx.pfxcore.views import BaseRestView, DetailRestViewMixin
15
+
16
+ @rest_view("/books")
17
+ class BookRestView(DetailRestViewMixin, BaseRestView):
18
+ queryset = Book.objects
19
+ fields = ['name', 'author', 'pub_date', 'created_at', 'type']
20
+ ```
21
+
22
+ ## SlugDetailRestViewMixin
23
+ Provide a get detail service for a model class with a slug.
24
+ Slug field is searched in the `slug` field of the model by default,
25
+ but it can be overridden with the `SLUG_FIELD` attribute.
26
+
27
+ ```
28
+ from pfx.pfxcore.views import BaseRestView, SlugDetailRestViewMixin
29
+
30
+ @rest_view("/authors")
31
+ class AuthorRestView(SlugDetailRestViewMixin, BaseRestView):
32
+ queryset = Author.objects
33
+ SLUG_FIELD = "slug"
34
+ fields = ['name', 'slug', 'created_at', 'type']
35
+ ```
36
+ ## ListRestViewMixin
37
+ Provide a list service for a model class.
38
+
39
+ By default, list fields are taken from fields attributes.
40
+ They can also be listed in the list_fields attribute if you need to have
41
+ different fields in the list than in other views.
42
+ ```
43
+ from pfx.pfxcore.views import BaseRestView, ListRestViewMixin
44
+
45
+ @rest_view("/books")
46
+ class BookRestView(ListRestViewMixin, BaseRestView):
47
+ queryset = Book.objects
48
+ list_fields = ['name', 'author', 'pub_date', 'created_at', 'type']
49
+ ```
50
+ ### Pagination
51
+ If you pass `?subset=pagination`, the response will include pagination data in meta:
52
+ ```
53
+ {
54
+ "items": […],
55
+ "meta": {
56
+ "page": 1,
57
+ "page_size": 10,
58
+ "count": 200,
59
+ "page_count": 20,
60
+ "subset": [1, 2, 3, 4, 5],
61
+ "page_subset": 5
62
+ }
63
+ }
64
+ ```
65
+ The page_size cannot be greater than `PFX_MAX_LIST_RESULT_SIZE` settings.
66
+ Override `pagination_result(self, qs)` to customize the behavior.
67
+
68
+ If you pass `?subset=offset`, the response will include offset/limit data in meta:
69
+ ```
70
+ {
71
+ "items": […],
72
+ "meta": {
73
+ "count": 200,
74
+ "page_count": 20,
75
+ "limit": 10,
76
+ "offset": 0
77
+ }
78
+ }
79
+ ```
80
+ The limit cannot be greater than `PFX_MAX_LIST_RESULT_SIZE` settings.
81
+ Override `offset_result(self, qs)` to customize the behavior.
82
+ ### Filters
83
+ List view can have filters.
84
+ #### ModelFilter
85
+ Use `ModelFilter` to add a filter on a ORM field:
86
+ ```
87
+ from pfx.pfxcore.views import (
88
+ RestView,
89
+ ModelFilter)
90
+
91
+
92
+ class AuthorRestView(RestView):
93
+ filters = [
94
+ ModelFilter(Author, 'name'),
95
+ ]
96
+ ```
97
+ #### Filter
98
+ Use `Filter` to add a custom filter:
99
+ ```
100
+ from django.db.models import Q
101
+
102
+ from pfx.pfxcore.views import (
103
+ RestView,
104
+ FieldType,
105
+ Filter)
106
+
107
+
108
+ def name_filter(value):
109
+ return Q(first_name__icontains=value) | Q(last_name__icontains=value)
110
+
111
+
112
+ class AuthorRestView(RestView):
113
+ filters = [
114
+ Filter('name', _("Name"), FieldType.CharField, name_filter),
115
+ ]
116
+ ```
117
+ ## CreateRestViewMixin
118
+ Provide a creation service for a model class.
119
+ ```
120
+ from pfx.pfxcore.views import BaseRestView, CreateRestViewMixin
121
+
122
+ @rest_view("/books")
123
+ class BookRestView(CreateRestViewMixin, BaseRestView):
124
+ queryset = Book.objects
125
+ fields = ['name', 'author', 'pub_date', 'created_at', 'type']
126
+ ```
127
+ ### Default values
128
+ You can set default values for fields in the ORM object field. But if
129
+ you need to set it at view level, you can use the `default_values`
130
+ class attribute.
131
+ ```
132
+ @rest_view("/books")
133
+ class BookRestView(CreateRestViewMixin, BaseRestView):
134
+ queryset = Book.objects
135
+ default_values = dict(
136
+ format='octavo'
137
+ )
138
+ ```
139
+ If you need to set dynamics default values,
140
+ you can override following methods (depending of your needs):
141
+ * `get_default_values(self)`: return `default_values`.
142
+ * `new_object(self)`: return a new object instance with `get_default_values()`.
143
+ * `is_valid(self, obj, created=False, rel_data=None)`: persist the instance after validation.
144
+
145
+ ## UpdateRestViewMixin
146
+ Provide an update service for a model class.
147
+ ```
148
+ from pfx.pfxcore.views import BaseRestView, UpdateRestViewMixin
149
+
150
+ @rest_view("/books")
151
+ class BookRestView(UpdateRestViewMixin, BaseRestView):
152
+ queryset = Book.objects
153
+ fields = ['name', 'author', 'pub_date', 'created_at', 'type']
154
+ ```
155
+
156
+ ## DeleteRestViewMixin
157
+ Provide a delete service for a model class.
158
+ ```
159
+ from pfx.pfxcore.views import BaseRestView, DeleteRestViewMixin
160
+
161
+ @rest_view("/books")
162
+ class BookRestView(DeleteRestViewMixin, BaseRestView):
163
+ queryset = Book.objects
164
+ ```
165
+
166
+ ## SecuredRestViewMixin
167
+ `SecuredRestViewMixin` allows you to define whether methods
168
+ are public or private (requires a logged-in user),
169
+ and to check access conditions to the method for a user.
170
+
171
+ If you inherit `BaseRestView` or `RestView`, `SecuredRestViewMixin` is
172
+ already included. The only way to ignore it or defining your custom security
173
+ system from scratch id to inherit Django original `View` instead.
174
+
175
+ ### Default behavior
176
+ By default, all methods are private. You can modify
177
+ the `default_public` attribute to change this.
178
+ ```
179
+ @rest_view("/books")
180
+ class BookRestView(RestView):
181
+ queryset = Book.objects
182
+ default_public = True
183
+ ```
184
+ ### Public method
185
+ If you want to define specific methods as a public methods,
186
+ add corresponding attributes `${method_name}_public`:
187
+ ```
188
+ @rest_view("/books")
189
+ class BookRestView(RestView):
190
+ queryset = Book.objects
191
+ get_public = True
192
+ get_list_public = True
193
+ ```
194
+ ### Check user access
195
+ For private methods, you can verify user access in two steps:
196
+ * By overriding the `perm(self)` method.
197
+ * By overriding the `${method_name}_perm(self)` method.
198
+
199
+ the `perm` method is called first, and if it returns `false`, access is denied.
200
+ If it returns `true` (default behavior), access is allowed
201
+ if `${method_name}_perm(self)` method does not exists.
202
+ If `${method_name}_perm` exists, it is called and must
203
+ return `true` to allow access.
204
+ ```
205
+ @rest_view("/books")
206
+ class BookRestView(RestView):
207
+ queryset = Book.objects
208
+
209
+ def my_method_perm(self)
210
+ return self.request.user.is_admin
211
+ ```
212
+ ### Check user access based on data
213
+ You can check user access based on data by overriding following methods:
214
+ * `object_create_perm(self, data)`
215
+ * `object_update_perm(self, obj, data)`
216
+ * `object_delete_perm(self, obj)`
217
+ Where `data` is the dictionary of new values and `obj` the existing object.
218
+
219
+ These methods are called by the put/post/delete methods of standard mixins
220
+ before validation. If you write a custom method and you want to use
221
+ one of these method, you have to call it yourself.
222
+
223
+ ## Base classes
224
+ You can use PFX view mixins with following base classes:
225
+
226
+ * `View`: the Django base view class.
227
+ * `BaseRestView`: The PFX base view for API, inherits `SecuredRestViewMixin` and `View`.
228
+ * `RestView`: The PFX base view for a Rest API, inherits:
229
+ * `ListRestViewMixin`
230
+ * `DetailRestViewMixin`
231
+ * `CreateRestViewMixin`
232
+ * `UpdateRestViewMixin`
233
+ * `DeleteRestViewMixin`
234
+ * `BaseRestView`
235
+
236
+ ## Define Fields
237
+ Fields in `fields` and `list_fields` attributes can be:
238
+ * A string: the field attribute name.
239
+ * A `pfx.pfxcore.views.VF` object.
240
+
241
+ `VF` object can be used to customize field behavior and must define at least the field name:
242
+ ```
243
+ field = [VF('name')]
244
+ ```
245
+ You can specify following optional attributes:
246
+ * `verbose_name`
247
+ * `field_type`
248
+ * `alias`
249
+ * `readonly`
250
+ * `readonly_create`
251
+ * `readonly_update`
252
+ * `choices`
253
+ * `select`
254
+ * `json_repr`
255
+
256
+ Refer to method documentation for details.
257
+
258
+ ### Meta
259
+
260
+ The `ModelResponseMixin` (included in every mixin with a service whose response is a simple object),
261
+ exposes a `/meta` service to identify fields (and their characteristics: type, required, readonly, ...),
262
+ to enable automatic generation of forms.
263
+
264
+ See the generated API documentation for more details.
265
+
266
+ On the same principle, `ListRestViewMixin` exposes the `/meta/list` service, which,
267
+ in addition to the fields, returns the list of available filters and
268
+ the list of available fields to order the list.
269
+
270
+ #### Notes for future releases
271
+
272
+ In a future version, these services should return a JSON OpenAPI structure. The list
273
+ of orderable fields is resource-intensive to compute and should be removed and moved
274
+ in the generated API doc.
@@ -18,12 +18,12 @@ so that each request header contains
18
18
  the HTTP_X_CUSTOM_LANGUAGE attribute with the locale.
19
19
 
20
20
  Example :
21
- ```
21
+ ```python
22
22
  client = APIClient(default_locale='en_GB')
23
23
  ```
24
24
  Each request to a service can also override the locale.
25
25
  Example :
26
- ```
26
+ ```python
27
27
  client = APIClient()
28
28
  response = client.get('/api/authors', locale='fr_CH')
29
29
  ```
@@ -33,7 +33,7 @@ response = client.get('/api/authors', locale='fr_CH')
33
33
  Send a get request.
34
34
 
35
35
  Example :
36
- ```
36
+ ```python
37
37
  client = APIClient()
38
38
  response = client.get('/api/authors')
39
39
  ```
@@ -42,7 +42,7 @@ response = client.get('/api/authors')
42
42
  Send a post request with content type 'application/json'.
43
43
 
44
44
  Example :
45
- ```
45
+ ```python
46
46
  client = APIClient()
47
47
  response = client.post(
48
48
  '/api/authors', {
@@ -54,7 +54,7 @@ response = client.post(
54
54
  Send a put request with content type 'application/json'.
55
55
 
56
56
  Example :
57
- ```
57
+ ```python
58
58
  client = APIClient()
59
59
  response = self.client.put(
60
60
  f'/api/authors/{author_pk}',
@@ -67,13 +67,12 @@ response = self.client.put(
67
67
  Send a delete request with content type 'application/json'.
68
68
 
69
69
  Example :
70
- ```
70
+ ```python
71
71
  client = APIClient()
72
72
  response = client.delete(
73
73
  f'/api/authors/{author_pk}')
74
74
  ```
75
75
 
76
-
77
76
  ### Login
78
77
  If you use PFX authentication views you can use this login method.
79
78
 
@@ -81,7 +80,7 @@ Once you called login, you can call any other method
81
80
  listed above, the authentication token will be sent with the requests.
82
81
 
83
82
  Example :
84
- ```
83
+ ```python
85
84
  client = APIClient()
86
85
  client.login(
87
86
  username='jrr.tolkien',
@@ -98,7 +97,7 @@ Test the response code of a response.
98
97
  It takes two parameters, the response and the expected status code.
99
98
 
100
99
  Example :
101
- ```
100
+ ```python
102
101
  class ATestClass(TestAssertMixin, TransactionTestCase):
103
102
 
104
103
  def a_test(self):
@@ -110,12 +109,12 @@ class ATestClass(TestAssertMixin, TransactionTestCase):
110
109
  self.assertRC(response, 200)
111
110
  ```
112
111
 
113
- ### assertJE
114
- Test the value of a json property in the json_content of the response.
115
- It takes 3 parameters, the response, the key and the expected value.
116
- It allows you to specify the path to reach an element in the json structure.
112
+ ### get_val
113
+
114
+ Get a value for a python dictionary or a json HTTP response by a string path key.
115
+ The src parameter can be a Python dictionary or an object with `json_content` attribute.
117
116
  For instance if you have a response like
118
- ```
117
+ ```python
119
118
  {
120
119
  "author": {
121
120
  "pk": 2,
@@ -125,32 +124,58 @@ For instance if you have a response like
125
124
  "pk": 6,
126
125
  }
127
126
  ```
128
- you can reach the author pk by providing "author.pk" as the key.
127
+ you can reach the author pk by providing `"author.pk"` as the key.
129
128
 
130
- AssertJE also allows to specify the index in an array.
131
- The syntax is "@index".
129
+ get_val also allows to specify the index in an array.
130
+ The syntax is `"@index"`.
132
131
 
133
132
  For instance if you want the pk of the author of the
134
- third item in an array of books you can specify "items.@3.author.pk"
133
+ third item in an array of books you can specify `"items.@3.author.pk"`
135
134
 
136
135
  Example :
136
+ ```python
137
+ class ATestClass(TestAssertMixin, TransactionTestCase):
138
+
139
+ def a_test(self):
140
+ client = APIClient()
141
+ response = client.get('/api/books')
142
+ author_pk = self.get_val(response, 'items.@3.author.pk')
137
143
  ```
144
+
145
+ ### assertJE
146
+ Test the value of a dictionary property (using `get_val`).
147
+ It takes 3 parameters, the source, the key and the expected value.
148
+
149
+ Example :
150
+ ```python
138
151
  class ATestClass(TestAssertMixin, TransactionTestCase):
139
152
 
140
153
  def a_test(self):
141
154
  client = APIClient()
142
155
  response = client.get('/api/books')
143
- self.assertJE(response, 'items.@3.author.pk', 6)
156
+ author_pk = self.assertJE(response, 'items.@3.author.pk', 6)
144
157
  ```
145
158
 
146
159
  ### assertNJE
147
- Assert NJE is the same as AssertJE, but it tests that the value is not equal.
160
+ `AssertNJE` is the same as `AssertJE`, but it tests that the value is not equal.
161
+
162
+ ### assertJEExists
163
+ Assert the path exists in the source.
164
+
165
+ ### assertJENotExists
166
+ Assert the path does not exists in the source.
167
+
168
+ ### assertSize
169
+ Test the size of a value (if the value is a collection).
170
+
171
+ ### assertJIn
172
+ Test that an element is part of a collection value.
148
173
 
149
174
  ## Print Response
150
175
  You can use the print_response helper to print a response in the console.
151
176
 
152
177
  Example :
153
- ```
178
+ ```python
154
179
  client = APIClient()
155
180
  response = client.get('/api/books')
156
181
  print_response(response)
@@ -158,7 +183,7 @@ print_response(response)
158
183
 
159
184
  will produce
160
185
 
161
- ```
186
+ ```plain
162
187
  *********************http response*********************
163
188
  Status : 200 OK
164
189
  Headers :
@@ -186,3 +211,54 @@ Content :
186
211
  }
187
212
  *******************************************************
188
213
  ```
214
+
215
+ ## PermsAPITest
216
+
217
+ This test class can be used to test permissions on services with multiple users.
218
+
219
+ Create multiple users in `setUpTestData` and add a method for each service
220
+ you want to test.
221
+
222
+ You can test the response status code or the response list count for items list responses.
223
+
224
+ Example :
225
+ ```python
226
+ class BookPermsAPITest(PermsAPITest):
227
+ def setUp(self):
228
+ self.client = APIClient(with_cookie=True)
229
+
230
+ @classmethod
231
+ def setUpTestData(cls):
232
+ # Create your users here
233
+
234
+ def list(self):
235
+ # Create a book
236
+ return self.client.get('/api/books?items=1&count=1')
237
+
238
+ def get(self):
239
+ # book = … (create a book)
240
+ return self.client.get(f'/api/books/{book.pk}')
241
+
242
+ def post(self):
243
+ return self.client.post('/api/books', dict(
244
+ name="Test new"))
245
+
246
+ def put(self):
247
+ return self.client.put(
248
+ f'/api/books/{self.organization.pk}', dict(
249
+ name="Updated"))
250
+
251
+ def delete(self):
252
+ # book = … (create a book)
253
+ return self.client.delete(f'/api/books/{book.pk}')
254
+
255
+ USER_TESTS = {
256
+ "admin@user.org": dict(
257
+ list=200, list__count=1, get=200
258
+ post=200, put=200, delete=200),
259
+ "user@user.org": dict(
260
+ list=200, list__count=1, get=200,
261
+ post=403, put=403, delete=403),
262
+ }
263
+
264
+ ```
@@ -1,45 +0,0 @@
1
- # View decorator and URL
2
- ## @rest_view
3
- Used to provide the base path of a view class
4
- ```
5
- @rest_view("/base-path")
6
- class ViewClass():
7
- ```
8
-
9
- ## @rest_api
10
- Used to annotate the rest services method.
11
- Parameters are the path and the HTTP method.
12
- ```
13
- @rest_api("/path", method="get")
14
- def class_method(self):
15
- ```
16
-
17
- A short example
18
- ```
19
- @rest_view("/books")
20
- class BookRestView():
21
-
22
- @rest_api("/list", method="get")
23
- def get_list(self):
24
- return JsonResponse(
25
- dict(books=['The Man in the High Castle', 'A Scanner Darkly']))
26
- ```
27
-
28
- ### path parameters
29
- Path can contain parameters that are passed to the method.
30
- ```
31
- @rest_api("/path/<int:id>/test/<slug:slug>", method="get")
32
- def class_method(self, id, slug):
33
- ```
34
-
35
- ## Registering urls
36
- To be available, annotated view class must be registered
37
- in your urls.py file as follows.
38
- ```
39
- from django_request_mapping import UrlPattern
40
-
41
- from . import views
42
-
43
- urlpatterns = UrlPattern()
44
- urlpatterns.register(views.BookRestView)
45
- ```
@@ -1,100 +0,0 @@
1
- # Django PFX Views
2
- REST services are provided as ViewMixin.
3
- Each of these views must set the queryset used to query the model data either
4
- by defining the queryset attribute or by overriding the get_queryset method.
5
-
6
- Fields can be listed in the fields attribute, else all the fields are provided.
7
-
8
- ## ListRestViewMixin
9
- Provide a list service for a model class.
10
-
11
- By default, list fields are taken from fields attributes if defined.
12
- They can also be listed in the list_fields attribute if you need to have
13
- different fields in the list than in other views.
14
- ```
15
- @rest_view("/books")
16
- class BookRestView(ListRestViewMixinRestView):
17
- queryset = Book.objects
18
- list_fields = ['name', 'author', 'pub_date', 'created_at', 'type']
19
- ```
20
- ### Meta
21
- ### Pagination
22
-
23
- ### Filters
24
- List view can have filters.
25
-
26
- TODO explain filters.
27
-
28
- ## DetailRestViewMixin
29
- Provide a get detail service for a model class.
30
-
31
- ```
32
- @rest_view("/books")
33
- class BookRestView(DetailRestViewMixin):
34
- queryset = Book.objects
35
- fields = ['name', 'author', 'pub_date', 'created_at', 'type']
36
- ```
37
- ### Meta
38
-
39
- ## CreateRestViewMixin
40
- Provide a creation service for a model class.
41
- Readonly fields can be provided in the read_only attribute.
42
-
43
- TODO : explain "default_values"
44
- ```
45
- @rest_view("/books")
46
- class BookRestView(CreateRestViewMixin):
47
- queryset = Book.objects
48
- fields = ['name', 'author', 'pub_date', 'created_at', 'type']
49
- readonly_fields = ['created_at']
50
- ```
51
-
52
- ## UpdateRestViewMixin
53
- Provide an update service for a model class.
54
- Readonly fields can be provided in the read_only attribute.
55
- ```
56
- @rest_view("/books")
57
- class BookRestView(UpdateRestViewMixin):
58
- queryset = Book.objects
59
- fields = ['name', 'author', 'pub_date', 'created_at', 'type']
60
- readonly_fields = ['created_at']
61
- ```
62
-
63
- ## DeleteRestViewMixin
64
- Provide a delete service for a model class.
65
-
66
- ```
67
- @rest_view("/books")
68
- class BookRestView(DeleteRestViewMixin):
69
- queryset = Book.objects
70
- ```
71
-
72
- ## SlugDetailRestViewMixin
73
- Provide a get detail service for a model class with a slug.
74
- Slug field is searched in the "slug" field of the model by default,
75
- but it can be overridden with the "SLUG_FIELD" attribute.
76
-
77
- ```
78
- @rest_view("/authors")
79
- class AuthorRestView(SlugDetailRestViewMixin):
80
- queryset = Author.objects
81
- SLUG_FIELD = "slug"
82
- fields = ['name', 'slug', 'created_at', 'type']
83
- ```
84
-
85
- ## SecuredRestViewMixin
86
- TODO Explain :
87
- - default_public = True
88
- - def perm(self):
89
- - {func_name}_perm
90
-
91
-
92
- ## RestView
93
- A base class that inherits :
94
- ListRestViewMixin,
95
- DetailRestViewMixin,
96
- CreateRestViewMixin,
97
- UpdateRestViewMixin,
98
- DeleteRestViewMixin,
99
- SecuredRestViewMixin,
100
- django.views.View
File without changes
File without changes
File without changes
File without changes