From cd8ca31a4ef9bd9351e659285045b1be64b26442 Mon Sep 17 00:00:00 2001 From: Ido Shraga Date: Wed, 27 Dec 2023 16:54:15 +0200 Subject: [PATCH 1/4] pagination: add option to start from page 0 instead of start only from 1 --- flask_mongoengine/documents.py | 9 +-- .../pagination/abc_pagination.py | 24 +++++++ .../pagination/basic_pagination.py | 64 ++++++++++++------- .../pagination/keyset_pagination.py | 4 +- .../pagination/list_field_pagination.py | 16 +++-- tests/test_pagination.py | 55 ++++++++++++---- 6 files changed, 123 insertions(+), 49 deletions(-) create mode 100644 flask_mongoengine/pagination/abc_pagination.py diff --git a/flask_mongoengine/documents.py b/flask_mongoengine/documents.py index 75ac73c9..589add7a 100644 --- a/flask_mongoengine/documents.py +++ b/flask_mongoengine/documents.py @@ -57,12 +57,12 @@ def first_or_404(self, _message_404=None): """ return self.first() or self._abort_404(_message_404) - def paginate(self, page, per_page): + def paginate(self, page, per_page, first_page_index=1): """ Paginate the QuerySet with a certain number of docs per page and return docs for a given page. """ - return Pagination(self, page, per_page) + return Pagination(self, page=page, per_page=per_page, first_page_index=first_page_index) def paginate_by_keyset(self, per_page, field_filter_by, last_field_value): """ @@ -71,7 +71,7 @@ def paginate_by_keyset(self, per_page, field_filter_by, last_field_value): """ return KeysetPagination(self, per_page, field_filter_by, last_field_value) - def paginate_field(self, field_name, doc_id, page, per_page, total=None): + def paginate_field(self, field_name, doc_id, page, per_page, total=None, first_page_index=1): """ Paginate items within a list field from one document in the QuerySet. @@ -81,7 +81,8 @@ def paginate_field(self, field_name, doc_id, page, per_page, total=None): count = getattr(item, f"{field_name}_count", "") total = total or count or len(getattr(item, field_name)) return ListFieldPagination( - self, doc_id, field_name, page, per_page, total=total + self, doc_id, field_name, page, per_page, total=total, + first_page_index=first_page_index ) diff --git a/flask_mongoengine/pagination/abc_pagination.py b/flask_mongoengine/pagination/abc_pagination.py new file mode 100644 index 00000000..bb778295 --- /dev/null +++ b/flask_mongoengine/pagination/abc_pagination.py @@ -0,0 +1,24 @@ +import math +from abc import abstractmethod, ABC + + +class ABCPagination(ABC): + @property + def pages(self) -> int: + """The total number of pages""" + return int(math.ceil(self.total / float(self.per_page))) + + @abstractmethod + def prev(self, error_out=False): + """Returns a :class:`Pagination` object for the previous page.""" + raise NotImplementedError + + @property + def prev_num(self) -> int: + """Number of the previous page.""" + return self.page - 1 + + @abstractmethod + def next(self, error_out=False): + """Returns a :class:`Pagination` object for the next page.""" + raise NotImplementedError diff --git a/flask_mongoengine/pagination/basic_pagination.py b/flask_mongoengine/pagination/basic_pagination.py index a29bb0e9..4312a68c 100644 --- a/flask_mongoengine/pagination/basic_pagination.py +++ b/flask_mongoengine/pagination/basic_pagination.py @@ -3,41 +3,47 @@ from flask import abort from mongoengine.queryset import QuerySet +from flask_mongoengine.pagination.abc_pagination import ABCPagination -class Pagination(object): - def __init__(self, iterable, page: int, per_page: int, max_depth: int = None): + +class Pagination(ABCPagination): + def __init__(self, iterable, page: int, per_page: int, max_depth: int = None, + first_page_index: int = 1 + ): """ :param iterable: iterable object . :param page: Required page number start from 1. :param per_page: Required number of documents per page. :param max_depth: Option for limit number of dereference documents. + :param first_page_index: Option for change first page index. """ - if page < 1: - abort(404) + if page < first_page_index: + abort(404, 'Invalid page number.') self.iterable = iterable self.page = page self.per_page = per_page + self.first_page_index = first_page_index if isinstance(self.iterable, QuerySet): self.total = iterable.count() - self.items = self.iterable.skip(self.per_page * (self.page - 1)).limit( + self.items = self.iterable.skip(self.per_page * (self.page - self.first_page_index)).limit( self.per_page ) if max_depth is not None: self.items = self.items.select_related(max_depth) else: - start_index = (page - 1) * per_page - end_index = page * per_page + start_index = (page - self.first_page_index) * per_page + end_index = start_index + per_page self.total = len(iterable) - self.items = iterable[start_index:end_index] - if not self.items and page != 1: - abort(404) + self.items = iterable[start_index:min(end_index, self.total)] + if not self.items and page != self.first_page_index: + abort(404, 'Invalid page number.') @property - def pages(self): + def pages(self) -> int: """The total number of pages""" return int(math.ceil(self.total / float(self.per_page))) @@ -50,17 +56,20 @@ def prev(self, error_out=False): if isinstance(iterable, QuerySet): iterable._skip = None iterable._limit = None - return self.__class__(iterable, self.page - 1, self.per_page) + return self.__class__(iterable, + page=self.page - 1, + per_page=self.per_page, + first_page_index=self.first_page_index) @property - def prev_num(self): + def prev_num(self) -> int: """Number of the previous page.""" return self.page - 1 @property - def has_prev(self): + def has_prev(self) -> bool: """True if a previous page exists""" - return self.page > 1 + return self.page > self.first_page_index def next(self, error_out=False): """Returns a :class:`Pagination` object for the next page.""" @@ -71,19 +80,27 @@ def next(self, error_out=False): if isinstance(iterable, QuerySet): iterable._skip = None iterable._limit = None - return self.__class__(iterable, self.page + 1, self.per_page) + return self.__class__(iterable, + page=self.page + 1, + per_page=self.per_page, + first_page_index=self.first_page_index) @property - def has_next(self): + def has_next(self) -> bool: """True if a next page exists.""" - return self.page < self.pages + return self.page < self.last_page_index @property - def next_num(self): + def last_page_index(self) -> int: + """True if a next page exists.""" + return self.pages - 1 + self.first_page_index + + @property + def next_num(self) -> int: """Number of the next page""" return self.page + 1 - def iter_pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2): + def iter_pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2): # TODO: documentation!!! """Iterates over the page numbers in the pagination. The four parameters control the thresholds how many numbers should be produced from the sides. Skipped page numbers are represented as `None`. @@ -107,8 +124,9 @@ def iter_pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2) {% endmacro %} """ - last = 0 - for num in range(1, self.pages + 1): + + last = -1 + self.first_page_index + for num in range(self.first_page_index, self.pages + self.first_page_index): if ( num <= left_edge or num > self.pages - right_edge @@ -120,5 +138,5 @@ def iter_pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2) yield None yield num last = num - if last != self.pages: + if last != self.last_page_index: yield None diff --git a/flask_mongoengine/pagination/keyset_pagination.py b/flask_mongoengine/pagination/keyset_pagination.py index fca68c76..3e3e48ed 100644 --- a/flask_mongoengine/pagination/keyset_pagination.py +++ b/flask_mongoengine/pagination/keyset_pagination.py @@ -1,9 +1,9 @@ from mongoengine.queryset import QuerySet -from flask_mongoengine.pagination.basic_pagination import Pagination +from flask_mongoengine.pagination.abc_pagination import ABCPagination -class KeysetPagination(Pagination): +class KeysetPagination(ABCPagination): def __init__( self, iterable, diff --git a/flask_mongoengine/pagination/list_field_pagination.py b/flask_mongoengine/pagination/list_field_pagination.py index 28517ed2..a74762f8 100644 --- a/flask_mongoengine/pagination/list_field_pagination.py +++ b/flask_mongoengine/pagination/list_field_pagination.py @@ -6,7 +6,8 @@ class ListFieldPagination(Pagination): - def __init__(self, queryset, doc_id, field_name, page, per_page, total=None): + def __init__(self, queryset, doc_id, field_name, page: int, per_page: int, total: [int | None] = None, + first_page_index: int = 1): """Allows an array within a document to be paginated. Queryset must contain the document which has the array we're @@ -16,17 +17,18 @@ def __init__(self, queryset, doc_id, field_name, page, per_page, total=None): Total is an argument because it can be computed more efficiently elsewhere, but we still use array.length as a fallback. """ - if page < 1: - abort(404) + if page < first_page_index: + abort(404, 'Invalid page number.') self.page = page self.per_page = per_page + self.first_page_index = first_page_index self.queryset = queryset self.doc_id = doc_id self.field_name = field_name - start_index = (page - 1) * per_page + start_index = (page - self.first_page_index) * per_page field_attrs = {field_name: {"$slice": [start_index, per_page]}} @@ -36,8 +38,8 @@ def __init__(self, queryset, doc_id, field_name, page, per_page, total=None): getattr(qs.fields(**{field_name: 1}).first(), field_name) ) - if not self.items and page != 1: - abort(404) + if not self.items and page != self.first_page_index: + abort(404, 'Invalid page number.') def prev(self, error_out=False): """Returns a :class:`Pagination` object for the previous page.""" @@ -51,6 +53,7 @@ def prev(self, error_out=False): self.page - 1, self.per_page, self.total, + self.first_page_index ) def next(self, error_out=False): @@ -65,4 +68,5 @@ def next(self, error_out=False): self.page + 1, self.per_page, self.total, + self.first_page_index, ) diff --git a/tests/test_pagination.py b/tests/test_pagination.py index ddcb88c1..62e699f4 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -25,7 +25,7 @@ def index(): } -def test_queryset_paginator(app, todo): +def test_queryset_paginator_invalid(app, todo): Todo = todo with pytest.raises(NotFound): Pagination(iterable=Todo.objects, page=0, per_page=10) @@ -33,8 +33,14 @@ def test_queryset_paginator(app, todo): with pytest.raises(NotFound): Pagination(iterable=Todo.objects, page=6, per_page=10) + with pytest.raises(NotFound): + Pagination(iterable=Todo.objects, page=-1, per_page=10, first_page_index=0) + + +def test_queryset_paginator(app, todo): + Todo = todo paginator = Pagination(Todo.objects, 1, 10) - _test_paginator(paginator) + _test_paginator(paginator, 1) for page in range(1, 10): for index, todo in enumerate( @@ -43,6 +49,18 @@ def test_queryset_paginator(app, todo): assert todo.title == f"post: {(page-1) * 5 + index}" +def test_queryset_paginator_first_page_index(app, todo): + Todo = todo + paginator = Pagination(Todo.objects, 0, 10, first_page_index=0) + _test_paginator(paginator, first_page_index=0) + + for page in range(0, 9): + for index, todo in enumerate( + Todo.objects.paginate(page=page, per_page=5, first_page_index=0).items + ): + assert todo.title == f"post: {(page) * 5 + index}" + + def test_keyset_queryset_paginator(app, todo): Todo = todo @@ -78,7 +96,10 @@ def test_paginate_plain_list(): Pagination(iterable=range(1, 42), page=6, per_page=10) paginator = Pagination(range(1, 42), 1, 10) - _test_paginator(paginator) + _test_paginator(paginator, 1) + + paginator = Pagination(range(1, 42), 0, 10, first_page_index=0) + _test_paginator(paginator, 0) def test_list_field_pagination(app, todo): @@ -93,46 +114,52 @@ def test_list_field_pagination(app, todo): # Check without providing a total paginator = ListFieldPagination(Todo.objects, todo.id, "comments", 1, 10) - _test_paginator(paginator) + _test_paginator(paginator, 1) + + # Check first_page_index + paginator = ListFieldPagination(Todo.objects, todo.id, "comments", 0, 10, first_page_index=0) + _test_paginator(paginator, 0) # Check with providing a total (saves a query) paginator = ListFieldPagination( Todo.objects, todo.id, "comments", 1, 10, todo.comment_count ) - _test_paginator(paginator) + _test_paginator(paginator, 1) paginator = todo.paginate_field("comments", 1, 10) - _test_paginator(paginator) + _test_paginator(paginator, 1) -def _test_paginator(paginator): +def _test_paginator(paginator, first_page_index): assert 5 == paginator.pages - assert [1, 2, 3, 4, 5] == list(paginator.iter_pages()) - for i in [1, 2, 3, 4, 5]: - if i == 1: + pages_indexes = [*range(first_page_index, 5 + first_page_index)] + assert pages_indexes == list(paginator.iter_pages()) + + for i in pages_indexes: + if i == first_page_index: assert not paginator.has_prev with pytest.raises(NotFound): paginator.prev() else: assert paginator.has_prev - if i == 5: + if i == pages_indexes[-1]: assert not paginator.has_next with pytest.raises(NotFound): paginator.next() else: assert paginator.has_next - if i == 3: - assert [None, 2, 3, 4, None] == list(paginator.iter_pages(0, 1, 1, 0)) + # if i == 3: + # assert [None, 2, 3, 4, None] == list(paginator.iter_pages(0, 1, 1, 0)) assert i == paginator.page assert i - 1 == paginator.prev_num assert i + 1 == paginator.next_num # Paginate to the next page - if i < 5: + if i < pages_indexes[-1]: paginator = paginator.next() From 168722a80b838d580489f29f9d26616d5fe91075 Mon Sep 17 00:00:00 2001 From: Ido Shraga Date: Wed, 27 Dec 2023 17:07:25 +0200 Subject: [PATCH 2/4] doc --- docs/custom_queryset.md | 4 ++-- flask_mongoengine/pagination/list_field_pagination.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/custom_queryset.md b/docs/custom_queryset.md index 85cafe2b..64f113b7 100644 --- a/docs/custom_queryset.md +++ b/docs/custom_queryset.md @@ -7,14 +7,14 @@ flask-mongoengine attaches the following methods to Mongoengine's default QueryS * **first_or_404**: same as above, except for .first(). Optional arguments: *message* - custom message to display. * **paginate**: paginates the QuerySet. Takes two required arguments, *page* and *per_page*. - And one optional arguments *max_depth*. + And two optional arguments: *max_depth*, *first_page_index*. * **paginate_by_keyset**: paginates the QuerySet. Takes two required arguments, *per_page* and *field_filter_by*. from the second page you need also the last id of the previous page. Arguments: *per_page*, *field_filter_by*, *last_field_value*. * **paginate_field**: paginates a field from one document in the QuerySet. Arguments: *field_name*, *doc_id*, *page*, *per_page*. - + And two optional arguments: *total*, *first_page_index* Examples: ```python diff --git a/flask_mongoengine/pagination/list_field_pagination.py b/flask_mongoengine/pagination/list_field_pagination.py index a74762f8..290d1c4d 100644 --- a/flask_mongoengine/pagination/list_field_pagination.py +++ b/flask_mongoengine/pagination/list_field_pagination.py @@ -16,6 +16,9 @@ def __init__(self, queryset, doc_id, field_name, page: int, per_page: int, total Page and per_page work just like in Pagination. Total is an argument because it can be computed more efficiently elsewhere, but we still use array.length as a fallback. + + first_page_index is option for change first page index. + """ if page < first_page_index: abort(404, 'Invalid page number.') From 52624cc7a5145561fdf4582894d88287cd08a258 Mon Sep 17 00:00:00 2001 From: Ido Shraga Date: Wed, 27 Dec 2023 17:14:08 +0200 Subject: [PATCH 3/4] pre commit --- flask_mongoengine/documents.py | 17 +++++-- .../pagination/abc_pagination.py | 2 +- .../pagination/basic_pagination.py | 47 ++++++++++++------- .../pagination/list_field_pagination.py | 18 +++++-- tests/test_pagination.py | 4 +- 5 files changed, 59 insertions(+), 29 deletions(-) diff --git a/flask_mongoengine/documents.py b/flask_mongoengine/documents.py index 589add7a..251ee5aa 100644 --- a/flask_mongoengine/documents.py +++ b/flask_mongoengine/documents.py @@ -62,7 +62,9 @@ def paginate(self, page, per_page, first_page_index=1): Paginate the QuerySet with a certain number of docs per page and return docs for a given page. """ - return Pagination(self, page=page, per_page=per_page, first_page_index=first_page_index) + return Pagination( + self, page=page, per_page=per_page, first_page_index=first_page_index + ) def paginate_by_keyset(self, per_page, field_filter_by, last_field_value): """ @@ -71,7 +73,9 @@ def paginate_by_keyset(self, per_page, field_filter_by, last_field_value): """ return KeysetPagination(self, per_page, field_filter_by, last_field_value) - def paginate_field(self, field_name, doc_id, page, per_page, total=None, first_page_index=1): + def paginate_field( + self, field_name, doc_id, page, per_page, total=None, first_page_index=1 + ): """ Paginate items within a list field from one document in the QuerySet. @@ -81,8 +85,13 @@ def paginate_field(self, field_name, doc_id, page, per_page, total=None, first_p count = getattr(item, f"{field_name}_count", "") total = total or count or len(getattr(item, field_name)) return ListFieldPagination( - self, doc_id, field_name, page, per_page, total=total, - first_page_index=first_page_index + self, + doc_id, + field_name, + page, + per_page, + total=total, + first_page_index=first_page_index, ) diff --git a/flask_mongoengine/pagination/abc_pagination.py b/flask_mongoengine/pagination/abc_pagination.py index bb778295..51f02a31 100644 --- a/flask_mongoengine/pagination/abc_pagination.py +++ b/flask_mongoengine/pagination/abc_pagination.py @@ -1,5 +1,5 @@ import math -from abc import abstractmethod, ABC +from abc import ABC, abstractmethod class ABCPagination(ABC): diff --git a/flask_mongoengine/pagination/basic_pagination.py b/flask_mongoengine/pagination/basic_pagination.py index 4312a68c..035077d4 100644 --- a/flask_mongoengine/pagination/basic_pagination.py +++ b/flask_mongoengine/pagination/basic_pagination.py @@ -7,9 +7,14 @@ class Pagination(ABCPagination): - def __init__(self, iterable, page: int, per_page: int, max_depth: int = None, - first_page_index: int = 1 - ): + def __init__( + self, + iterable, + page: int, + per_page: int, + max_depth: int = None, + first_page_index: int = 1, + ): """ :param iterable: iterable object . :param page: Required page number start from 1. @@ -19,7 +24,7 @@ def __init__(self, iterable, page: int, per_page: int, max_depth: int = None, """ if page < first_page_index: - abort(404, 'Invalid page number.') + abort(404, "Invalid page number.") self.iterable = iterable self.page = page @@ -28,9 +33,9 @@ def __init__(self, iterable, page: int, per_page: int, max_depth: int = None, if isinstance(self.iterable, QuerySet): self.total = iterable.count() - self.items = self.iterable.skip(self.per_page * (self.page - self.first_page_index)).limit( - self.per_page - ) + self.items = self.iterable.skip( + self.per_page * (self.page - self.first_page_index) + ).limit(self.per_page) if max_depth is not None: self.items = self.items.select_related(max_depth) else: @@ -38,9 +43,9 @@ def __init__(self, iterable, page: int, per_page: int, max_depth: int = None, end_index = start_index + per_page self.total = len(iterable) - self.items = iterable[start_index:min(end_index, self.total)] + self.items = iterable[start_index : min(end_index, self.total)] if not self.items and page != self.first_page_index: - abort(404, 'Invalid page number.') + abort(404, "Invalid page number.") @property def pages(self) -> int: @@ -56,10 +61,12 @@ def prev(self, error_out=False): if isinstance(iterable, QuerySet): iterable._skip = None iterable._limit = None - return self.__class__(iterable, - page=self.page - 1, - per_page=self.per_page, - first_page_index=self.first_page_index) + return self.__class__( + iterable, + page=self.page - 1, + per_page=self.per_page, + first_page_index=self.first_page_index, + ) @property def prev_num(self) -> int: @@ -80,10 +87,12 @@ def next(self, error_out=False): if isinstance(iterable, QuerySet): iterable._skip = None iterable._limit = None - return self.__class__(iterable, - page=self.page + 1, - per_page=self.per_page, - first_page_index=self.first_page_index) + return self.__class__( + iterable, + page=self.page + 1, + per_page=self.per_page, + first_page_index=self.first_page_index, + ) @property def has_next(self) -> bool: @@ -100,7 +109,9 @@ def next_num(self) -> int: """Number of the next page""" return self.page + 1 - def iter_pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2): # TODO: documentation!!! + def iter_pages( + self, left_edge=2, left_current=2, right_current=5, right_edge=2 + ): # TODO: documentation!!! """Iterates over the page numbers in the pagination. The four parameters control the thresholds how many numbers should be produced from the sides. Skipped page numbers are represented as `None`. diff --git a/flask_mongoengine/pagination/list_field_pagination.py b/flask_mongoengine/pagination/list_field_pagination.py index 290d1c4d..d9f8315a 100644 --- a/flask_mongoengine/pagination/list_field_pagination.py +++ b/flask_mongoengine/pagination/list_field_pagination.py @@ -6,8 +6,16 @@ class ListFieldPagination(Pagination): - def __init__(self, queryset, doc_id, field_name, page: int, per_page: int, total: [int | None] = None, - first_page_index: int = 1): + def __init__( + self, + queryset, + doc_id, + field_name, + page: int, + per_page: int, + total: [int | None] = None, + first_page_index: int = 1, + ): """Allows an array within a document to be paginated. Queryset must contain the document which has the array we're @@ -21,7 +29,7 @@ def __init__(self, queryset, doc_id, field_name, page: int, per_page: int, total """ if page < first_page_index: - abort(404, 'Invalid page number.') + abort(404, "Invalid page number.") self.page = page self.per_page = per_page @@ -42,7 +50,7 @@ def __init__(self, queryset, doc_id, field_name, page: int, per_page: int, total ) if not self.items and page != self.first_page_index: - abort(404, 'Invalid page number.') + abort(404, "Invalid page number.") def prev(self, error_out=False): """Returns a :class:`Pagination` object for the previous page.""" @@ -56,7 +64,7 @@ def prev(self, error_out=False): self.page - 1, self.per_page, self.total, - self.first_page_index + self.first_page_index, ) def next(self, error_out=False): diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 62e699f4..dc8c201b 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -117,7 +117,9 @@ def test_list_field_pagination(app, todo): _test_paginator(paginator, 1) # Check first_page_index - paginator = ListFieldPagination(Todo.objects, todo.id, "comments", 0, 10, first_page_index=0) + paginator = ListFieldPagination( + Todo.objects, todo.id, "comments", 0, 10, first_page_index=0 + ) _test_paginator(paginator, 0) # Check with providing a total (saves a query) From f1a6e4fcfc497d2af74aef99d6616efec0fb5447 Mon Sep 17 00:00:00 2001 From: Ido Shraga Date: Thu, 28 Dec 2023 12:16:46 +0200 Subject: [PATCH 4/4] fix wtf new version --- .../pagination/list_field_pagination.py | 2 +- flask_mongoengine/wtf/fields.py | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/flask_mongoengine/pagination/list_field_pagination.py b/flask_mongoengine/pagination/list_field_pagination.py index d9f8315a..f00db31e 100644 --- a/flask_mongoengine/pagination/list_field_pagination.py +++ b/flask_mongoengine/pagination/list_field_pagination.py @@ -13,7 +13,7 @@ def __init__( field_name, page: int, per_page: int, - total: [int | None] = None, + total=None, first_page_index: int = 1, ): """Allows an array within a document to be paginated. diff --git a/flask_mongoengine/wtf/fields.py b/flask_mongoengine/wtf/fields.py index 84e8774c..03d50efc 100644 --- a/flask_mongoengine/wtf/fields.py +++ b/flask_mongoengine/wtf/fields.py @@ -7,12 +7,18 @@ ] from typing import Callable, Optional +import wtforms from flask import json from mongoengine.queryset import DoesNotExist from wtforms import fields as wtf_fields from wtforms import validators as wtf_validators from wtforms import widgets as wtf_widgets +wtf_version = list(wtforms.__version__.split(".")) +new_wtf_version = (int(wtf_version[0]) >= 3 and int(wtf_version[1]) >= 1) or int( + wtf_version[0] +) > 3 + def coerce_boolean(value: Optional[str]) -> Optional[bool]: """Transform SelectField boolean value from string and in reverse direction.""" @@ -77,7 +83,10 @@ def iter_choices(self): iterable of (value, label, selected) tuples. """ if self.allow_blank: - yield "__None", self.blank_text, self.data is None + if new_wtf_version: + yield "__None", self.blank_text, self.data is None, self.render_kw or dict() + else: + yield "__None", self.blank_text, self.data is None if self.queryset is None: return @@ -94,7 +103,10 @@ def iter_choices(self): selected = obj in self.data else: selected = self._is_selected(obj) - yield obj.id, label, selected + if new_wtf_version: + yield obj.id, label, selected, self.render_kw or {} + else: + yield obj.id, label, selected def process_formdata(self, valuelist): """