django-pfx 1.4.dev54__tar.gz → 1.4.dev58__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/PKG-INFO +1 -1
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/django_pfx.egg-info/PKG-INFO +1 -1
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +4 -4
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/models/pfx_models.py +1 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/filters_views.py +25 -5
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/rest_views.py +36 -20
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/models.py +10 -1
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/basic_api_errors.py +4 -3
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/basic_api_test.py +37 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/views.py +1 -6
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/.gitignore +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/.gitlab-ci.yml +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/.pre-commit-config.yaml +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/LICENSE +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/MANIFEST.in +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/README.md +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/django_pfx.egg-info/SOURCES.txt +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/django_pfx.egg-info/dependency_links.txt +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/django_pfx.egg-info/requires.txt +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/django_pfx.egg-info/top_level.txt +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/Makefile +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/conf.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/index.rst +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/source/api.views.rst +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/source/authentication.md +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/source/decorator.md +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/source/generate_openapi.md +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/source/getting_started.md +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/source/internationalisation.md +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/source/model.md +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/source/pfx_views.md +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/source/profiling.md +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/source/settings.md +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/doc/source/testing.md +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/img/pfx.png +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/img/pfx.svg +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/make_messages +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/manage.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/apidoc/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/apidoc/parameters.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/apidoc/schema.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/apidoc/tags.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/apps.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/decorator/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/decorator/rest.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/default_settings.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/exceptions.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/fields.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/http/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/http/json_response.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/management/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/management/commands/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/management/commands/profile.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/middleware/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/middleware/authentication.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/middleware/locale.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/middleware/profiling.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/migrations/0001_initial.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/migrations/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/models/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/models/cache_mixins.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/models/login_ban.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/models/not_null_fields.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/models/otp_user_mixin.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/models/pfx_user.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/serializers/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/serializers/json.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/settings.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/shortcuts.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/storage/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/storage/s3_storage.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/test.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/urls.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/authentication_views.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/fields.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/locale_views.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/date_format.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/groups.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/list_count.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/list_items.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/list_order.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/list_search.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/subset.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/settings/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/settings/dev.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pyproject.toml +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/requirements.txt +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/serve-doc +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/setup.cfg +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/setup.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/settings/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/settings/ci.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/settings/common.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/settings/dev.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/settings/dev_custom_example.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/settings/dev_default.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/__init__.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_api_doc.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_api_doc_search.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_auth_api.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_body_mixin.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_cache.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_client.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_fields.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_filters.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_locale_api.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_perm_tests.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_perms_api.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_profiling_middleware.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_settings.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_shortcuts.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_timezone_middleware.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_tools.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_user_queryset.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_view_decorators.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/tests/test_view_fields.py +0 -0
- {django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/tests/urls.py +0 -0
|
@@ -7,7 +7,7 @@ msgid ""
|
|
|
7
7
|
msgstr ""
|
|
8
8
|
"Project-Id-Version: \n"
|
|
9
9
|
"Report-Msgid-Bugs-To: \n"
|
|
10
|
-
"POT-Creation-Date: 2024-
|
|
10
|
+
"POT-Creation-Date: 2024-12-11 12:51+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:
|
|
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:
|
|
278
|
+
#: views/rest_views.py:1093
|
|
279
279
|
#, python-brace-format
|
|
280
280
|
msgid "{model} {obj} deleted."
|
|
281
281
|
msgstr "{model} {obj} supprimé."
|
|
282
282
|
|
|
283
|
-
#: views/rest_views.py:
|
|
283
|
+
#: views/rest_views.py:1185 views/rest_views.py:1225
|
|
284
284
|
msgid "Unexpected storage error"
|
|
285
285
|
msgstr "Erreur de stockage inattendue"
|
|
@@ -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,
|
|
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):
|
|
@@ -87,7 +89,20 @@ class Filter():
|
|
|
87
89
|
[self.filter_func(v) for v in values])
|
|
88
90
|
|
|
89
91
|
def _get_values(self, params):
|
|
90
|
-
values = [
|
|
92
|
+
values = []
|
|
93
|
+
if (self.type == FieldType.ModelObject and self.related_model and
|
|
94
|
+
hasattr(self.related_model.objects, 'default_search')):
|
|
95
|
+
q = None
|
|
96
|
+
for search in params.getlist(f'{self.name}*'):
|
|
97
|
+
crit = self.related_model.objects.default_search(search)
|
|
98
|
+
if q is None:
|
|
99
|
+
q = crit
|
|
100
|
+
q |= crit
|
|
101
|
+
if q:
|
|
102
|
+
values.extend(self.related_model.objects.filter(
|
|
103
|
+
q).values_list('pk', flat=True))
|
|
104
|
+
values.extend([
|
|
105
|
+
self._parse_value(v) for v in params.getlist(self.name)])
|
|
91
106
|
return values or self.defaults
|
|
92
107
|
|
|
93
108
|
def query(self, params):
|
|
@@ -99,8 +114,8 @@ class ModelFilter(Filter):
|
|
|
99
114
|
def __init__(
|
|
100
115
|
self, model, name, label=None, type=None,
|
|
101
116
|
filter_func=None, filter_func_and=False, filter_func_list=False,
|
|
102
|
-
choices=None, related_model=None,
|
|
103
|
-
empty_value=None):
|
|
117
|
+
choices=None, related_model=None, related_model_api=None,
|
|
118
|
+
technical=False, defaults=None, empty_value=None):
|
|
104
119
|
self.model = model
|
|
105
120
|
self.field = model._meta.get_field(name)
|
|
106
121
|
if empty_value is None:
|
|
@@ -113,6 +128,11 @@ class ModelFilter(Filter):
|
|
|
113
128
|
related_model or (
|
|
114
129
|
self.field.remote_field and
|
|
115
130
|
self.field.remote_field.model),
|
|
131
|
+
related_model_api or (
|
|
132
|
+
self.field.remote_field and
|
|
133
|
+
self.field.remote_field.model and
|
|
134
|
+
hasattr(self.field.remote_field.model, 'api') and
|
|
135
|
+
self.field.remote_field.model.api),
|
|
116
136
|
technical=technical, defaults=defaults, empty_value=empty_value)
|
|
117
137
|
|
|
118
138
|
@property
|
|
@@ -143,8 +143,8 @@ class ModelMixin():
|
|
|
143
143
|
def _process_fields(cls, fields):
|
|
144
144
|
if not fields:
|
|
145
145
|
return {
|
|
146
|
-
|
|
147
|
-
for
|
|
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
|
-
|
|
182
|
-
for
|
|
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
|
-
|
|
192
|
-
for
|
|
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
|
-
|
|
290
|
-
for
|
|
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
|
-
|
|
438
|
-
if not isinstance(
|
|
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
|
|
|
@@ -541,6 +550,8 @@ class ListRestViewMixin(ModelResponseMixin):
|
|
|
541
550
|
:returns: The django filters
|
|
542
551
|
:rtype: :class:`django.db.models.Q`
|
|
543
552
|
"""
|
|
553
|
+
if hasattr(self.model.objects, 'default_search'):
|
|
554
|
+
return self.model.objects.default_search(search)
|
|
544
555
|
return Q()
|
|
545
556
|
|
|
546
557
|
def orderable_fields(self, model, models=None):
|
|
@@ -577,7 +588,7 @@ class ListRestViewMixin(ModelResponseMixin):
|
|
|
577
588
|
if get_bool(self.request.GET, 'filters', default_all):
|
|
578
589
|
meta['filters'] = cache.get_or_set(
|
|
579
590
|
class_key(self.__class__, 'meta', 'filters'),
|
|
580
|
-
lambda: [
|
|
591
|
+
lambda: [_f.meta for _f in self.filters],
|
|
581
592
|
None)
|
|
582
593
|
if get_bool(self.request.GET, 'orders', default_all):
|
|
583
594
|
meta['orders'] = cache.get_or_set(
|
|
@@ -620,9 +631,14 @@ class ListRestViewMixin(ModelResponseMixin):
|
|
|
620
631
|
:returns: The filtered queryset
|
|
621
632
|
:rtype: :class:`django.db.models.QuerySet`
|
|
622
633
|
"""
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
634
|
+
q = None
|
|
635
|
+
for search in self.request.GET.getlist('search'):
|
|
636
|
+
crit = self.search_filter(search)
|
|
637
|
+
if q is None:
|
|
638
|
+
q = crit
|
|
639
|
+
q |= crit
|
|
640
|
+
if q:
|
|
641
|
+
return qs.filter(q)
|
|
626
642
|
return qs
|
|
627
643
|
|
|
628
644
|
def get_order_mapping(self):
|
|
@@ -681,8 +697,8 @@ class ListRestViewMixin(ModelResponseMixin):
|
|
|
681
697
|
return cache.get_or_set(
|
|
682
698
|
class_key(self.__class__, 'list_fields', 'select_related'),
|
|
683
699
|
lambda: set([
|
|
684
|
-
|
|
685
|
-
for
|
|
700
|
+
_f for field in self.get_list_fields().values()
|
|
701
|
+
for _f in field.select_related]),
|
|
686
702
|
None)
|
|
687
703
|
|
|
688
704
|
def get_list_fields_prefetch_related(self):
|
|
@@ -690,8 +706,8 @@ class ListRestViewMixin(ModelResponseMixin):
|
|
|
690
706
|
return cache.get_or_set(
|
|
691
707
|
class_key(self.__class__, 'list_fields', 'prefetch_related'),
|
|
692
708
|
lambda: set([
|
|
693
|
-
|
|
694
|
-
for
|
|
709
|
+
_f for field in self.get_list_fields().values()
|
|
710
|
+
for _f in field.prefetch_related]),
|
|
695
711
|
None)
|
|
696
712
|
|
|
697
713
|
def get_list_result(self, qs):
|
|
@@ -708,8 +724,8 @@ class ListRestViewMixin(ModelResponseMixin):
|
|
|
708
724
|
*self.get_list_fields_prefetch_related())
|
|
709
725
|
for o in qs:
|
|
710
726
|
yield self.serialize_object(o, **{
|
|
711
|
-
|
|
712
|
-
for
|
|
727
|
+
_f.alias: _f.to_json(o, self.format_date)
|
|
728
|
+
for _f in self.get_list_fields().values()})
|
|
713
729
|
|
|
714
730
|
def get_short_list_result(self, qs):
|
|
715
731
|
"""Get a generator to serialize each result in a queryset.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from django.contrib.auth.base_user import BaseUserManager
|
|
2
2
|
from django.core.mail import send_mail
|
|
3
3
|
from django.db import models
|
|
4
|
+
from django.db.models import Q
|
|
4
5
|
from django.utils.functional import cached_property
|
|
5
6
|
from django.utils.translation import gettext_lazy as _
|
|
6
7
|
|
|
@@ -96,6 +97,13 @@ class User(CacheableMixin, JSONReprMixin, OtpUserMixin, AbstractPFXBaseUser):
|
|
|
96
97
|
send_mail(subject, message, from_email, [self.email], **kwargs)
|
|
97
98
|
|
|
98
99
|
|
|
100
|
+
class AuthorQuerySet(models.QuerySet):
|
|
101
|
+
def default_search(self, search):
|
|
102
|
+
return (
|
|
103
|
+
Q(first_name__unaccent__icontains=search) |
|
|
104
|
+
Q(last_name__unaccent__icontains=search))
|
|
105
|
+
|
|
106
|
+
|
|
99
107
|
class BadUserAuthorQuerySet(UserFilteredQuerySetMixin, models.QuerySet):
|
|
100
108
|
pass
|
|
101
109
|
|
|
@@ -109,6 +117,7 @@ class UserAuthorQuerySet(UserFilteredQuerySetMixin, models.QuerySet):
|
|
|
109
117
|
|
|
110
118
|
class Author(CacheableMixin, JSONReprMixin, models.Model):
|
|
111
119
|
CACHED_PROPERTIES = ['books_count']
|
|
120
|
+
api = '/authors'
|
|
112
121
|
|
|
113
122
|
first_name = models.CharField(_("First Name"), max_length=30)
|
|
114
123
|
last_name = models.CharField(_("Last Name"), max_length=30)
|
|
@@ -126,7 +135,7 @@ class Author(CacheableMixin, JSONReprMixin, models.Model):
|
|
|
126
135
|
'tests.BookType', related_name='authors',
|
|
127
136
|
verbose_name="Types")
|
|
128
137
|
|
|
129
|
-
objects =
|
|
138
|
+
objects = AuthorQuerySet.as_manager()
|
|
130
139
|
bad_user_objects = BadUserAuthorQuerySet.as_manager()
|
|
131
140
|
user_objects = UserAuthorQuerySet.as_manager()
|
|
132
141
|
|
|
@@ -59,7 +59,8 @@ class BasicAPIErrorTest(TestAssertMixin, TestCase):
|
|
|
59
59
|
"new_password": "Wrong stuffs",
|
|
60
60
|
}''')
|
|
61
61
|
self.assertRC(response, 422)
|
|
62
|
-
self.
|
|
63
|
-
|
|
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
|
|
@@ -197,6 +197,12 @@ class BasicAPITest(TestAssertMixin, TestCase):
|
|
|
197
197
|
self.assertJE(response, 'items.@0.gender.value', 'male')
|
|
198
198
|
self.assertJE(response, 'items.@0.gender.label', 'Male')
|
|
199
199
|
|
|
200
|
+
response = self.client.get(
|
|
201
|
+
'/api/authors?search=isaac&search=tolkien&items=1&count=1')
|
|
202
|
+
|
|
203
|
+
self.assertRC(response, 200)
|
|
204
|
+
self.assertJE(response, 'meta.count', 2)
|
|
205
|
+
|
|
200
206
|
def test_meta_list(self):
|
|
201
207
|
response = self.client.get('/api/authors/meta/list')
|
|
202
208
|
self.assertRC(response, 200)
|
|
@@ -224,6 +230,7 @@ class BasicAPITest(TestAssertMixin, TestCase):
|
|
|
224
230
|
"label": "Types",
|
|
225
231
|
"name": "types",
|
|
226
232
|
"related_model": "BookType",
|
|
233
|
+
"api": "/book-types",
|
|
227
234
|
"technical": False,
|
|
228
235
|
"type": "ModelObject"
|
|
229
236
|
}
|
|
@@ -286,6 +293,18 @@ class BasicAPITest(TestAssertMixin, TestCase):
|
|
|
286
293
|
|
|
287
294
|
response = self.client.get('/api/books/meta/list')
|
|
288
295
|
self.assertRC(response, 200)
|
|
296
|
+
|
|
297
|
+
self.assertJE(response, 'filters.@0.items.@0', {
|
|
298
|
+
"empty_value": False,
|
|
299
|
+
"is_group": False,
|
|
300
|
+
"label": "Author",
|
|
301
|
+
"name": "author",
|
|
302
|
+
"related_model": "Author",
|
|
303
|
+
"api": "/authors",
|
|
304
|
+
"technical": False,
|
|
305
|
+
"type": "ModelObject"
|
|
306
|
+
})
|
|
307
|
+
|
|
289
308
|
self.assertJIn(response, 'orders', 'pk')
|
|
290
309
|
self.assertJIn(response, 'orders', 'name')
|
|
291
310
|
self.assertJIn(response, 'orders', 'author')
|
|
@@ -470,6 +489,24 @@ class BasicAPITest(TestAssertMixin, TestCase):
|
|
|
470
489
|
self.assertTrue(
|
|
471
490
|
item['type'] is None or item['type']['pk'] == book_type.pk)
|
|
472
491
|
|
|
492
|
+
def test_model_filter_foreign_key_text_search(self):
|
|
493
|
+
response = self.client.get(
|
|
494
|
+
'/api/books?author*=asimov&items=1&count=1')
|
|
495
|
+
self.assertRC(response, 200)
|
|
496
|
+
self.assertJE(response, 'meta.count', 3)
|
|
497
|
+
|
|
498
|
+
# Test multiple values
|
|
499
|
+
response = self.client.get(
|
|
500
|
+
'/api/books?author*=asimov&author*=tolkien&items=1&count=1')
|
|
501
|
+
self.assertRC(response, 200)
|
|
502
|
+
self.assertJE(response, 'meta.count', 7)
|
|
503
|
+
|
|
504
|
+
# Test empty search
|
|
505
|
+
response = self.client.get(
|
|
506
|
+
'/api/books?author*=&items=1&count=1')
|
|
507
|
+
self.assertRC(response, 200)
|
|
508
|
+
self.assertJE(response, 'meta.count', 7)
|
|
509
|
+
|
|
473
510
|
def test_model_filter_char_choices(self):
|
|
474
511
|
response = self.client.get('/api/authors?gender=male&items=1&count=1')
|
|
475
512
|
|
|
@@ -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(
|
|
@@ -80,11 +80,6 @@ class AuthorRestView(AuthorRestViewMixin, SlugDetailRestViewMixin, RestView):
|
|
|
80
80
|
]),
|
|
81
81
|
]
|
|
82
82
|
|
|
83
|
-
def search_filter(self, search):
|
|
84
|
-
return (
|
|
85
|
-
Q(first_name__unaccent__icontains=search) |
|
|
86
|
-
Q(last_name__unaccent__icontains=search))
|
|
87
|
-
|
|
88
83
|
@rest_api("/cache/<int:id>", method="get", groups=['cache'])
|
|
89
84
|
def cache_get(self, id, *args, **kwargs):
|
|
90
85
|
book = Author.cache_get(id)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/models/user_filtered_queryset_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/templates/registration/otp_code_email.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/templates/registration/welcome_email.txt
RENAMED
|
File without changes
|
{django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/templates/registration/welcome_subject.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/media_redirect.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/subset_page_size.py
RENAMED
|
File without changes
|
{django_pfx-1.4.dev54 → django_pfx-1.4.dev58}/pfx/pfxcore/views/parameters/subset_page_subset.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|