Skip to content

Commit

Permalink
Release 5.10.5
Browse files Browse the repository at this point in the history
### BREAKING CHANGES:
* ``Pagination-Identifiers`` used to send duplicate values before, but now they can't.
* ``Pagination-Identifiers`` are now sorted independently of the order in the results.

### Changelog:
* Feature(backend): Provide valid schema array ``collectionFormat`` for filters.
* Fix(backend): ``Pagination-Identifiers`` wasn't uniq and sorted.
* Fix(docs): There is no translations for new fields.
* Chore(backend): Update dependencies.

See merge request vst/vst-utils!667
  • Loading branch information
onegreyonewhite committed Sep 12, 2024
2 parents e9d1f24 + 9896960 commit a218f20
Show file tree
Hide file tree
Showing 11 changed files with 68 additions and 12 deletions.
10 changes: 9 additions & 1 deletion doc/locale/ru/LC_MESSAGES/backend.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VST Utils 5.0.4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-16 02:46+0000\n"
"POT-Creation-Date: 2024-09-12 01:25+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down Expand Up @@ -1216,6 +1216,10 @@ msgstr ""
"Список MIME-типов, доступных для выбора пользователем. Поддерживается "
"синтаксис с использованием ``*``. По умолчанию ``['text/csv']``"

#: of vstutils.api.fields.CheckboxBooleanField:1
msgid "Boolean field that renders checkbox."
msgstr "Булево поле, которое отрисовывается как чекбокс."

#: of vstutils.api.fields.CommaMultiSelect:1
msgid ""
"Field that allows users to input multiple values, separated by a "
Expand Down Expand Up @@ -2281,6 +2285,10 @@ msgstr ""
"Поле будет отображаться в клиентском приложении в виде поля ввода для "
"ввода номера телефона, включая код страны."

#: of vstutils.api.fields.PlusMinusIntegerField:1
msgid "Integer field that renders +/- buttons."
msgstr "Целочисленное поле, которое отрисовывается как кнопки +/-."

#: of vstutils.api.fields.QrCodeField:1
msgid "A versatile field for encoding various types of data into QR codes."
msgstr "Универсальное поле для кодирования различных типов данных в QR-коды."
Expand Down
2 changes: 1 addition & 1 deletion requirements-rpc.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Packages needed for delayed jobs.
celery[redis]==5.4.0
django-celery-beat~=2.6.0
django-celery-beat~=2.7.0
2 changes: 1 addition & 1 deletion requirements-stubs.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
django-stubs[compatible-mypy]~=5.0.2
django-stubs[compatible-mypy]==5.0.2
djangorestframework-stubs[compatible-mypy]~=3.15.0
celery-stubs~=0.1.3
drf-yasg-stubs~=0.1.4
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ uvicorn~=0.30.6
pyuwsgi~=2.0.26
# Restore it if some problems with pyuwsgi
# uwsgi==2.0.23
fastapi-slim~=0.112.1
fastapi-slim~=0.114.1
aiofiles~=24.1.0
asgiref>=3.8.1

Expand Down
24 changes: 23 additions & 1 deletion test_src/test_proj/models/fields_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

from django.db import models
from django.http.response import FileResponse
from django.forms.widgets import SelectMultiple
from django.forms.fields import IntegerField
from django.utils import timezone
from django_filters import Filter

from vstutils.api import fields, filters, actions
from vstutils.models import BModel, BaseModel
Expand All @@ -20,7 +24,6 @@
WYSIWYGField,
CrontabField,
)
from django.utils import timezone
from rest_framework.fields import DecimalField, CharField


Expand Down Expand Up @@ -204,6 +207,24 @@ class SomeDataCsvSerializer(BaseSerializer):
some_data = CharField(max_length=300, required=True)


class MultipleSelectField(IntegerField):
widget = SelectMultiple

def clean(self, value):
return [super(IntegerField, self).clean(v) for v in value]


class InFilter(Filter):
field_class = MultipleSelectField

def __init__(
self,
field_name=None,
lookup_expr='in',
**kwargs):
super().__init__(field_name, lookup_expr, **kwargs)


class Post(BModel):
author = models.ForeignKey(Author, on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=255)
Expand All @@ -229,6 +250,7 @@ class Meta:
}
_filterset_fields = {
'__authors': filters.CharFilter(method=filters.extra_filter, field_name='author'),
'extra_author': InFilter(field_name='author'),
'author': None,
'author__not': filters.CharFilter(method=filters.FkFilterHandler()),
'title': None,
Expand Down
10 changes: 10 additions & 0 deletions test_src/test_proj/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2135,6 +2135,13 @@ def has_deep_parent_filter(params):
# Check public centrifugo address when absolute path is provided
self.assertEqual(api['info']['x-centrifugo-address'], 'wss://vstutilstestserver/notify/connection/websocket')

param_filter_csv = api['paths']['/post/']['get']['parameters'][1]
self.assertEqual(param_filter_csv['name'], 'extra_author')
self.assertEqual(param_filter_csv['in'], 'query')
self.assertEqual(param_filter_csv['type'], 'array')
self.assertEqual(param_filter_csv['items']['type'], 'integer')
self.assertEqual(param_filter_csv['collectionFormat'], 'multi')

# Check csvfile schema
self.assertDictEqual(
api['definitions']['OnePost']['properties']['some_data'],
Expand Down Expand Up @@ -4231,6 +4238,7 @@ def test_pagination_identifiers(self):
{"method": "get", "path": ['post'], "query": "__authors=<<2[headers][Pagination-Identifiers]>>"},
{"method": "get", "path": ['author'], "headers": {"Identifiers-List-Name": "id"}, "query": f"id={author_2.id}"},
{"method": "get", "path": ['post'], "query": "__authors=<<4[headers][Pagination-Identifiers]>>"},
{"method": "get", "path": ['post'], "headers": {"Identifiers-List-Name": "author"}, "query": f"extra_author={author_1.id}&extra_author={author_3.id}"},
])
self.assertEqual(results[0]['status'], 200)
self.assertTrue('Pagination-Identifiers' not in results[0]['headers'])
Expand All @@ -4243,6 +4251,8 @@ def test_pagination_identifiers(self):
self.assertEqual(results[4]['data']['count'], 1)
self.assertEqual(results[5]['status'], 200, results[5])
self.assertEqual(results[5]['data']['count'], 0)
self.assertEqual(results[6]['status'], 200)
self.assertEqual(results[6]['headers']['Pagination-Identifiers'], expected_authors_identifiers)

def test_model_rating_field(self):
date = '2021-01-20T00:26:38Z'
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ changedir = ./
setenv =
DONT_YARN = true
deps =
mypy==1.7.1
mypy==1.10.1
commands =
pip uninstall vstutils -y
pip install -U -e .[stubs]
Expand Down
2 changes: 1 addition & 1 deletion vstutils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# pylint: disable=django-not-available
__version__: str = '5.10.4'
__version__: str = '5.10.5'
2 changes: 1 addition & 1 deletion vstutils/api/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,7 @@ def to_internal_value(self, data: _t.Union[models.Model, int]) -> _t.Union[model

def to_representation(self, value: _t.Union[int, models.Model]) -> _t.Any:
self.model_class = get_if_lazy(self.model_class)
if self.model_class is not None and isinstance(value, self.model_class._meta.pk.model): # type: ignore
if self.model_class is not None and isinstance(value, self.model_class._meta.pk.model):
return self.field_type(getattr(value, self.autocomplete_property))
else: # nocv
# Uses only if value got from `.values()`
Expand Down
20 changes: 17 additions & 3 deletions vstutils/api/filter_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pydantic
from django.db import models
from django.forms import BooleanField, DecimalField, IntegerField
from django.utils.encoding import force_str
from django_filters import filters, filterset
from django_filters.rest_framework.backends import DjangoFilterBackend as BaseDjangoFilterBackend
Expand Down Expand Up @@ -38,10 +39,13 @@ def get_openapi_field_schema(self, field_name, field, queryset):
# pylint: disable=unused-variable,comparison-with-callable
field_type: str = openapi.TYPE_STRING
kwargs: dict = {}
filter_field = field.field_class

if isinstance(field, filters.NumberFilter):
if issubclass(filter_field, DecimalField):
field_type = openapi.TYPE_NUMBER
elif isinstance(field, filters.BooleanFilter):
elif issubclass(filter_field, IntegerField):
field_type = openapi.TYPE_INTEGER
elif issubclass(filter_field, BooleanField):
field_type = openapi.TYPE_BOOLEAN
elif isinstance(field, (filters.ChoiceFilter, filters.MultipleChoiceFilter)):
kwargs['enum'] = tuple(dict(field.field.choices).keys())
Expand All @@ -50,7 +54,17 @@ def get_openapi_field_schema(self, field_name, field, queryset):
m_field = next((f for f in queryset.model._meta.fields if f.name == search_field), None)
field_type, kwargs_update = get_field_type_from_queryset(m_field)

if field.method == extra_filter or isinstance(field, (filters.MultipleChoiceFilter, filters.BaseCSVFilter)):
if getattr(field.field_class.widget, 'allow_multiple_selected', False):
kwargs = {
'items': {
'type': field_type,
**kwargs,
},
'uniqueItems': True,
'collectionFormat': 'multi',
}
field_type = openapi.TYPE_ARRAY
elif field.method == extra_filter or isinstance(field, filters.BaseCSVFilter):
kwargs = {
'items': {
'type': field_type,
Expand Down
4 changes: 3 additions & 1 deletion vstutils/api/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ def get_response_data(self, data):
def get_paginated_response(self, data):
response = self.response_class(self.get_response_data(data))
if self.identifier is not None:
response.headers['Pagination-Identifiers'] = ','.join(map(str, (d[self.identifier] for d in data)))
response.headers['Pagination-Identifiers'] = ','.join(
sorted(set(map(str, (d[self.identifier] for d in data))))
)
return response

def get_paginated_response_schema(self, schema):
Expand Down

0 comments on commit a218f20

Please sign in to comment.