django-pfx 1.4.dev52__tar.gz → 1.4.dev56__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.dev52 → django_pfx-1.4.dev56}/PKG-INFO +1 -1
  2. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/django_pfx.egg-info/PKG-INFO +1 -1
  3. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +4 -4
  4. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/models/pfx_models.py +1 -0
  5. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/filters_views.py +11 -4
  6. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/rest_views.py +72 -21
  7. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/models.py +1 -0
  8. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/basic_api_errors.py +4 -3
  9. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/basic_api_test.py +13 -0
  10. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/views.py +1 -1
  11. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/.gitignore +0 -0
  12. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/.gitlab-ci.yml +0 -0
  13. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/.pre-commit-config.yaml +0 -0
  14. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/LICENSE +0 -0
  15. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/MANIFEST.in +0 -0
  16. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/README.md +0 -0
  17. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/django_pfx.egg-info/SOURCES.txt +0 -0
  18. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/django_pfx.egg-info/dependency_links.txt +0 -0
  19. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/django_pfx.egg-info/requires.txt +0 -0
  20. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/django_pfx.egg-info/top_level.txt +0 -0
  21. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/Makefile +0 -0
  22. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/conf.py +0 -0
  23. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/index.rst +0 -0
  24. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/source/api.views.rst +0 -0
  25. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/source/authentication.md +0 -0
  26. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/source/decorator.md +0 -0
  27. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/source/generate_openapi.md +0 -0
  28. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/source/getting_started.md +0 -0
  29. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/source/internationalisation.md +0 -0
  30. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/source/model.md +0 -0
  31. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/source/pfx_views.md +0 -0
  32. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/source/profiling.md +0 -0
  33. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/source/settings.md +0 -0
  34. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/doc/source/testing.md +0 -0
  35. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/img/pfx.png +0 -0
  36. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/img/pfx.svg +0 -0
  37. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/make_messages +0 -0
  38. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/manage.py +0 -0
  39. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/__init__.py +0 -0
  40. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/__init__.py +0 -0
  41. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/apidoc/__init__.py +0 -0
  42. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/apidoc/parameters.py +0 -0
  43. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/apidoc/schema.py +0 -0
  44. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/apidoc/tags.py +0 -0
  45. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/apps.py +0 -0
  46. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/decorator/__init__.py +0 -0
  47. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/decorator/rest.py +0 -0
  48. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/default_settings.py +0 -0
  49. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/exceptions.py +0 -0
  50. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/fields.py +0 -0
  51. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/http/__init__.py +0 -0
  52. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/http/json_response.py +0 -0
  53. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  54. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/management/__init__.py +0 -0
  55. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/management/commands/__init__.py +0 -0
  56. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  57. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/management/commands/profile.py +0 -0
  58. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/middleware/__init__.py +0 -0
  59. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/middleware/authentication.py +0 -0
  60. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/middleware/locale.py +0 -0
  61. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/middleware/profiling.py +0 -0
  62. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/migrations/0001_initial.py +0 -0
  63. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/migrations/__init__.py +0 -0
  64. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/models/__init__.py +0 -0
  65. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
  66. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/models/cache_mixins.py +0 -0
  67. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/models/login_ban.py +0 -0
  68. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/models/not_null_fields.py +0 -0
  69. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/models/otp_user_mixin.py +0 -0
  70. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/models/pfx_user.py +0 -0
  71. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  72. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/serializers/__init__.py +0 -0
  73. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/serializers/json.py +0 -0
  74. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/settings.py +0 -0
  75. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/shortcuts.py +0 -0
  76. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/storage/__init__.py +0 -0
  77. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/storage/s3_storage.py +0 -0
  78. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
  79. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
  80. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  81. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  82. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  83. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  84. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/test.py +0 -0
  85. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/urls.py +0 -0
  86. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/__init__.py +0 -0
  87. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/authentication_views.py +0 -0
  88. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/fields.py +0 -0
  89. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/locale_views.py +0 -0
  90. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  91. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  92. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/groups.py +0 -0
  93. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  94. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  95. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  96. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  97. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  98. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  99. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  100. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  101. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  102. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/subset.py +0 -0
  103. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  104. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  105. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  106. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  107. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  108. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/settings/__init__.py +0 -0
  109. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pfx/settings/dev.py +0 -0
  110. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/pyproject.toml +0 -0
  111. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/requirements.txt +0 -0
  112. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/serve-doc +0 -0
  113. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/setup.cfg +0 -0
  114. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/setup.py +0 -0
  115. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/__init__.py +0 -0
  116. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
  117. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/settings/__init__.py +0 -0
  118. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/settings/ci.py +0 -0
  119. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/settings/common.py +0 -0
  120. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/settings/dev.py +0 -0
  121. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/settings/dev_custom_example.py +0 -0
  122. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/settings/dev_default.py +0 -0
  123. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/__init__.py +0 -0
  124. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_api_doc.py +0 -0
  125. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_api_doc_search.py +0 -0
  126. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_auth_api.py +0 -0
  127. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_body_mixin.py +0 -0
  128. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_cache.py +0 -0
  129. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_client.py +0 -0
  130. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_fields.py +0 -0
  131. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_filters.py +0 -0
  132. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_locale_api.py +0 -0
  133. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_perm_tests.py +0 -0
  134. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_perms_api.py +0 -0
  135. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_profiling_middleware.py +0 -0
  136. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_settings.py +0 -0
  137. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_shortcuts.py +0 -0
  138. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_timezone_middleware.py +0 -0
  139. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_tools.py +0 -0
  140. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_user_queryset.py +0 -0
  141. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_view_decorators.py +0 -0
  142. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/tests/test_view_fields.py +0 -0
  143. {django_pfx-1.4.dev52 → django_pfx-1.4.dev56}/tests/urls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-pfx
3
- Version: 1.4.dev52
3
+ Version: 1.4.dev56
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.dev52
3
+ Version: 1.4.dev56
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
@@ -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-05-21 15:30+0200\n"
10
+ "POT-Creation-Date: 2024-12-10 13:34+0100\n"
11
11
  "PO-Revision-Date: 2021-06-22 23:31+0200\n"
12
12
  "Last-Translator: \n"
13
13
  "Language-Team: \n"
@@ -254,7 +254,7 @@ msgstr ""
254
254
  msgid "A new authentication code has been sent by email."
255
255
  msgstr "Un nouveau code d'authentification a été envoyé par e-mail."
256
256
 
257
- #: views/filters_views.py:79
257
+ #: views/filters_views.py:81
258
258
  #, python-brace-format
259
259
  msgid "Invalid value for {filter} filter"
260
260
  msgstr "Valeur invalide pour le filtre {filter}"
@@ -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:1077
278
+ #: views/rest_views.py:1086
279
279
  #, python-brace-format
280
280
  msgid "{model} {obj} deleted."
281
281
  msgstr "{model} {obj} supprimé."
282
282
 
283
- #: views/rest_views.py:1139 views/rest_views.py:1167
283
+ #: views/rest_views.py:1178 views/rest_views.py:1218
284
284
  msgid "Unexpected storage error"
285
285
  msgstr "Erreur de stockage inattendue"
@@ -22,6 +22,7 @@ class UniqueConstraint(models.UniqueConstraint):
22
22
 
23
23
  class JSONReprMixin():
24
24
  """A model mixin to manage JSON representation."""
25
+ api = None
25
26
  apidoc = {}
26
27
 
27
28
  def json_repr(self, **values):
@@ -34,8 +34,8 @@ class Filter():
34
34
  def __init__(
35
35
  self, name, label, type=None, filter_func=None,
36
36
  filter_func_and=False, filter_func_list=False, choices=None,
37
- related_model=None, technical=False, defaults=None,
38
- empty_value=True):
37
+ related_model=None, related_model_api=None,
38
+ technical=False, defaults=None, empty_value=True):
39
39
  self.name = name
40
40
  self.label = label
41
41
  self.type = type
@@ -44,6 +44,7 @@ class Filter():
44
44
  self.filter_func_list = filter_func_list
45
45
  self.choices = choices
46
46
  self.related_model = related_model
47
+ self.related_model_api = related_model_api
47
48
  self.technical = technical
48
49
  self.defaults = defaults or []
49
50
  self.empty_value = empty_value
@@ -62,6 +63,7 @@ class Filter():
62
63
  dict(label=_(v), value=k) for k, v in self.choices]
63
64
  if self.related_model:
64
65
  res['related_model'] = str(self.related_model.__name__)
66
+ res['api'] = self.related_model_api
65
67
  return res
66
68
 
67
69
  def _parse_value(self, value):
@@ -99,8 +101,8 @@ class ModelFilter(Filter):
99
101
  def __init__(
100
102
  self, model, name, label=None, type=None,
101
103
  filter_func=None, filter_func_and=False, filter_func_list=False,
102
- choices=None, related_model=None, technical=False, defaults=None,
103
- empty_value=None):
104
+ choices=None, related_model=None, related_model_api=None,
105
+ technical=False, defaults=None, empty_value=None):
104
106
  self.model = model
105
107
  self.field = model._meta.get_field(name)
106
108
  if empty_value is None:
@@ -113,6 +115,11 @@ class ModelFilter(Filter):
113
115
  related_model or (
114
116
  self.field.remote_field and
115
117
  self.field.remote_field.model),
118
+ related_model_api or (
119
+ self.field.remote_field and
120
+ self.field.remote_field.model and
121
+ hasattr(self.field.remote_field.model, 'api') and
122
+ self.field.remote_field.model.api),
116
123
  technical=technical, defaults=defaults, empty_value=empty_value)
117
124
 
118
125
  @property
@@ -143,8 +143,8 @@ class ModelMixin():
143
143
  def _process_fields(cls, fields):
144
144
  if not fields:
145
145
  return {
146
- f.name: ViewField.from_model_field(f.name, f)
147
- for f in cls.model._meta.fields}
146
+ _f.name: ViewField.from_model_field(_f.name, _f)
147
+ for _f in cls.model._meta.fields}
148
148
 
149
149
  def _field(e):
150
150
  if isinstance(e, ViewField):
@@ -178,8 +178,8 @@ class ModelMixin():
178
178
  return cache.get_or_set(
179
179
  class_key(cls, 'fields', 'select_related'),
180
180
  lambda: set([
181
- f for field in cls.get_fields().values()
182
- for f in field.select_related]),
181
+ _f for field in cls.get_fields().values()
182
+ for _f in field.select_related]),
183
183
  None)
184
184
 
185
185
  @classmethod
@@ -188,8 +188,8 @@ class ModelMixin():
188
188
  return cache.get_or_set(
189
189
  class_key(cls, 'fields', 'prefetch_related'),
190
190
  lambda: set([
191
- f for field in cls.get_fields().values()
192
- for f in field.prefetch_related]),
191
+ _f for field in cls.get_fields().values()
192
+ for _f in field.prefetch_related]),
193
193
  None)
194
194
 
195
195
  @property
@@ -286,8 +286,8 @@ class ModelResponseMixin(ModelMixin):
286
286
  :rtype: :class:`JsonResponse`
287
287
  """
288
288
  return JsonResponse(self.serialize_object(o, **{
289
- f.alias: f.to_json(o, self.format_date)
290
- for f in self.get_fields().values()}, meta=meta))
289
+ _f.alias: _f.to_json(o, self.format_date)
290
+ for _f in self.get_fields().values()}, meta=meta))
291
291
 
292
292
  def validate(self, obj, created=False, **kwargs):
293
293
  """Validate an object instance.
@@ -434,8 +434,8 @@ class BodyMixin:
434
434
  """
435
435
  if fields is None:
436
436
  fields = [
437
- f.name for f in model._meta.get_fields()
438
- if not isinstance(f, AutoFieldMixin)]
437
+ _f.name for _f in model._meta.get_fields()
438
+ if not isinstance(_f, AutoFieldMixin)]
439
439
  obj = model(**{
440
440
  k: v for k, v in self.deserialize_body().items() if k in fields})
441
441
  if validate:
@@ -531,6 +531,15 @@ class ListRestViewMixin(ModelResponseMixin):
531
531
  self.list_fields or self.fields)), None))
532
532
  return self._list_fields
533
533
 
534
+ def get_list_meta_filters(self):
535
+ """Return the filters metadata for lists.
536
+
537
+ :returns: The filters metadata generator
538
+ :rtype: :class:`generator`
539
+ """
540
+ for _f in self.filters:
541
+ yield _f.meta
542
+
534
543
  def search_filter(self, search): # pragma: no cover
535
544
  """Return the django filters for the default text search.
536
545
 
@@ -577,7 +586,7 @@ class ListRestViewMixin(ModelResponseMixin):
577
586
  if get_bool(self.request.GET, 'filters', default_all):
578
587
  meta['filters'] = cache.get_or_set(
579
588
  class_key(self.__class__, 'meta', 'filters'),
580
- lambda: [f.meta for f in self.filters],
589
+ lambda: [_f.meta for _f in self.filters],
581
590
  None)
582
591
  if get_bool(self.request.GET, 'orders', default_all):
583
592
  meta['orders'] = cache.get_or_set(
@@ -681,8 +690,8 @@ class ListRestViewMixin(ModelResponseMixin):
681
690
  return cache.get_or_set(
682
691
  class_key(self.__class__, 'list_fields', 'select_related'),
683
692
  lambda: set([
684
- f for field in self.get_list_fields().values()
685
- for f in field.select_related]),
693
+ _f for field in self.get_list_fields().values()
694
+ for _f in field.select_related]),
686
695
  None)
687
696
 
688
697
  def get_list_fields_prefetch_related(self):
@@ -690,8 +699,8 @@ class ListRestViewMixin(ModelResponseMixin):
690
699
  return cache.get_or_set(
691
700
  class_key(self.__class__, 'list_fields', 'prefetch_related'),
692
701
  lambda: set([
693
- f for field in self.get_list_fields().values()
694
- for f in field.prefetch_related]),
702
+ _f for field in self.get_list_fields().values()
703
+ for _f in field.prefetch_related]),
695
704
  None)
696
705
 
697
706
  def get_list_result(self, qs):
@@ -708,8 +717,8 @@ class ListRestViewMixin(ModelResponseMixin):
708
717
  *self.get_list_fields_prefetch_related())
709
718
  for o in qs:
710
719
  yield self.serialize_object(o, **{
711
- f.alias: f.to_json(o, self.format_date)
712
- for f in self.get_list_fields().values()})
720
+ _f.alias: _f.to_json(o, self.format_date)
721
+ for _f in self.get_list_fields().values()})
713
722
 
714
723
  def get_short_list_result(self, qs):
715
724
  """Get a generator to serialize each result in a queryset.
@@ -1124,11 +1133,41 @@ class MediaRestViewMixin(ModelMixin):
1124
1133
  ---
1125
1134
  get:
1126
1135
  summary: Get upload URL
1127
- description: Get upload URL for a file field.
1136
+ description: |
1137
+ Get upload URL for a `MediaField` field.
1138
+
1139
+ You can upload a file ont the received URL. When the upload
1140
+ query is done, you have to confirm the process with an
1141
+ update request (`PUT`) on {model}. The body of this request
1142
+ must contain the name of the `MediaField` with the contents
1143
+ of the `file` value in the response of this request.
1144
+
1145
+ 1. `GET /<int:pk>/<str:field>/upload-url/<str:filename>`
1146
+ → `data`
1147
+ 2. `PUT data.url aFile Content-Type: aFile.type`
1148
+ 3. `PUT /<int:pk> {{field: data.file}}`
1128
1149
  parameters extras:
1129
- pk: the {model} pk
1130
- field: the {model} field name
1131
- filename: the desired filename
1150
+ pk: The {model} pk.
1151
+ field: The {model} field name. Must be the name of
1152
+ a `MediaField` field.
1153
+ filename: The desired filename.
1154
+ responses:
1155
+ 200:
1156
+ description: The upload URL
1157
+ content:
1158
+ application/json:
1159
+ schema:
1160
+ properties:
1161
+ url:
1162
+ type: string
1163
+ format: uri
1164
+ file:
1165
+ type: object
1166
+ properties:
1167
+ name:
1168
+ type: string
1169
+ key:
1170
+ type: string
1132
1171
  """
1133
1172
  obj = self.get_object(pk=pk)
1134
1173
  try:
@@ -1158,6 +1197,18 @@ class MediaRestViewMixin(ModelMixin):
1158
1197
  parameters extras:
1159
1198
  pk: the {model} pk
1160
1199
  field: the {model} field name
1200
+ responses:
1201
+ 200:
1202
+ description: The file URL, if `redirect` is `false`.
1203
+ content:
1204
+ application/json:
1205
+ schema:
1206
+ properties:
1207
+ url:
1208
+ type: string
1209
+ format: uri
1210
+ 302:
1211
+ description: The redirect, if `redirect` is `true`.
1161
1212
  """
1162
1213
  obj = self.get_object(pk=pk)
1163
1214
  try:
@@ -109,6 +109,7 @@ class UserAuthorQuerySet(UserFilteredQuerySetMixin, models.QuerySet):
109
109
 
110
110
  class Author(CacheableMixin, JSONReprMixin, models.Model):
111
111
  CACHED_PROPERTIES = ['books_count']
112
+ api = '/authors'
112
113
 
113
114
  first_name = models.CharField(_("First Name"), max_length=30)
114
115
  last_name = models.CharField(_("Last Name"), max_length=30)
@@ -59,7 +59,8 @@ class BasicAPIErrorTest(TestAssertMixin, TestCase):
59
59
  "new_password": "Wrong stuffs",
60
60
  }''')
61
61
  self.assertRC(response, 422)
62
- self.assertJE(
63
- response, 'message',
62
+ self.assertIn(self.get_val(response, 'message'), [
63
+ "JSON Malformed Illegal trailing comma before end "
64
+ "of object: line 3 column 47 (char 96)", # python >= 3.13
64
65
  "JSON Malformed Expecting property name enclosed in "
65
- "double quotes: line 4 column 17 (char 114)")
66
+ "double quotes: line 4 column 17 (char 114)"]) # python <= 3.12
@@ -224,6 +224,7 @@ class BasicAPITest(TestAssertMixin, TestCase):
224
224
  "label": "Types",
225
225
  "name": "types",
226
226
  "related_model": "BookType",
227
+ "api": "/book-types",
227
228
  "technical": False,
228
229
  "type": "ModelObject"
229
230
  }
@@ -286,6 +287,18 @@ class BasicAPITest(TestAssertMixin, TestCase):
286
287
 
287
288
  response = self.client.get('/api/books/meta/list')
288
289
  self.assertRC(response, 200)
290
+
291
+ self.assertJE(response, 'filters.@0.items.@0', {
292
+ "empty_value": False,
293
+ "is_group": False,
294
+ "label": "Author",
295
+ "name": "author",
296
+ "related_model": "Author",
297
+ "api": "/authors",
298
+ "technical": False,
299
+ "type": "ModelObject"
300
+ })
301
+
289
302
  self.assertJIn(response, 'orders', 'pk')
290
303
  self.assertJIn(response, 'orders', 'name')
291
304
  self.assertJIn(response, 'orders', 'author')
@@ -64,7 +64,7 @@ class AuthorRestView(AuthorRestViewMixin, SlugDetailRestViewMixin, RestView):
64
64
  Filter(
65
65
  'heroic_fantasy', _("Heroic Fantasy"),
66
66
  FieldType.BooleanField, heroic_fantasy_filter),
67
- ModelFilter(Author, 'types')
67
+ ModelFilter(Author, 'types', related_model_api='/book-types')
68
68
  ]),
69
69
  FilterGroup('custom', _("Custom"), [
70
70
  ModelFilter(
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes