From 6643611535c8a2ac46f7bc55235b42393ffb6bab Mon Sep 17 00:00:00 2001 From: Andrey Shpak Date: Tue, 16 Aug 2022 18:57:28 +0300 Subject: [PATCH 01/11] Less lines in example app --- example_app/boolean_demo.py | 3 --- example_app/dates_demo.py | 3 --- example_app/dict_demo.py | 3 --- example_app/numbers_demo.py | 3 --- example_app/strings_demo.py | 3 --- 5 files changed, 15 deletions(-) diff --git a/example_app/boolean_demo.py b/example_app/boolean_demo.py index 2fbe082b..4a4368f1 100644 --- a/example_app/boolean_demo.py +++ b/example_app/boolean_demo.py @@ -17,9 +17,6 @@ class BooleanDemoModel(db.Document): ) -BooleanDemoForm = BooleanDemoModel.to_wtf_form() - - def boolean_demo_view(pk=None): """Return all fields demonstration.""" from example_app.views import demo_view diff --git a/example_app/dates_demo.py b/example_app/dates_demo.py index af0f1c5c..066b1ab3 100644 --- a/example_app/dates_demo.py +++ b/example_app/dates_demo.py @@ -22,9 +22,6 @@ class DateTimeModel(db.Document): ) -DateTimeDemoForm = DateTimeModel.to_wtf_form() - - def dates_demo_view(pk=None): """Return all fields demonstration.""" from example_app.views import demo_view diff --git a/example_app/dict_demo.py b/example_app/dict_demo.py index a7621f1e..0dfb96d9 100644 --- a/example_app/dict_demo.py +++ b/example_app/dict_demo.py @@ -24,9 +24,6 @@ class DictDemoModel(db.Document): ) -DictDemoForm = DictDemoModel.to_wtf_form() - - def dict_demo_view(pk=None): """Return all fields demonstration.""" from example_app.views import demo_view diff --git a/example_app/numbers_demo.py b/example_app/numbers_demo.py index e363166f..9bbf6403 100644 --- a/example_app/numbers_demo.py +++ b/example_app/numbers_demo.py @@ -19,9 +19,6 @@ class NumbersDemoModel(db.Document): integer_field_limited = db.IntField(min_value=1, max_value=200) -NumbersDemoForm = NumbersDemoModel.to_wtf_form() - - def numbers_demo_view(pk=None): """Return all fields demonstration.""" from example_app.views import demo_view diff --git a/example_app/strings_demo.py b/example_app/strings_demo.py index 0738a6c8..7138f324 100644 --- a/example_app/strings_demo.py +++ b/example_app/strings_demo.py @@ -25,9 +25,6 @@ class StringsDemoModel(db.Document): url_field = db.URLField() -StringsDemoForm = StringsDemoModel.to_wtf_form() - - def strings_demo_view(pk=None): """Return all fields demonstration.""" from example_app.views import demo_view From f17a964cc3878973611e064c238a503ba8b41324 Mon Sep 17 00:00:00 2001 From: Andrey Shpak Date: Tue, 16 Aug 2022 19:46:07 +0300 Subject: [PATCH 02/11] Add BinaryField demo to example app --- example_app/app.py | 3 +++ example_app/binary_demo.py | 17 +++++++++++++++++ example_app/templates/layout.html | 1 + example_app/views.py | 2 ++ 4 files changed, 23 insertions(+) create mode 100644 example_app/binary_demo.py diff --git a/example_app/app.py b/example_app/app.py index 2b424f2f..6f51b903 100644 --- a/example_app/app.py +++ b/example_app/app.py @@ -3,6 +3,7 @@ from pymongo import monitoring from example_app import views +from example_app.binary_demo import binary_demo_view from example_app.boolean_demo import boolean_demo_view from example_app.dates_demo import dates_demo_view from example_app.dict_demo import dict_demo_view @@ -55,6 +56,8 @@ app.add_url_rule("/bool//", view_func=boolean_demo_view, methods=["GET", "POST"]) app.add_url_rule("/dict", view_func=dict_demo_view, methods=["GET", "POST"]) app.add_url_rule("/dict//", view_func=dict_demo_view, methods=["GET", "POST"]) +app.add_url_rule("/binary", view_func=binary_demo_view, methods=["GET", "POST"]) +app.add_url_rule("/binary//", view_func=binary_demo_view, methods=["GET", "POST"]) if __name__ == "__main__": app.run(host="0.0.0.0", port=8000) diff --git a/example_app/binary_demo.py b/example_app/binary_demo.py new file mode 100644 index 00000000..6aede03b --- /dev/null +++ b/example_app/binary_demo.py @@ -0,0 +1,17 @@ +"""Strings and strings related fields demo model.""" + +from example_app.models import db + + +class BinaryDemoModel(db.Document): + """Documentation example model.""" + + string_field = db.StringField() + binary_field = db.BinaryField() + + +def binary_demo_view(pk=None): + """Return all fields demonstration.""" + from example_app.views import demo_view + + return demo_view(model=BinaryDemoModel, view_name=binary_demo_view.__name__, pk=pk) diff --git a/example_app/templates/layout.html b/example_app/templates/layout.html index 758ecf90..1b307770 100644 --- a/example_app/templates/layout.html +++ b/example_app/templates/layout.html @@ -23,6 +23,7 @@
  • DateTime demo
  • Booleans demo
  • Dict/Json demo
  • +
  • Binary demo
  • diff --git a/example_app/views.py b/example_app/views.py index a4c871e8..5bc04375 100644 --- a/example_app/views.py +++ b/example_app/views.py @@ -4,6 +4,7 @@ from mongoengine.context_managers import switch_db from example_app import models +from example_app.binary_demo import BinaryDemoModel from example_app.boolean_demo import BooleanDemoModel from example_app.dates_demo import DateTimeModel from example_app.dict_demo import DictDemoModel @@ -57,6 +58,7 @@ def delete_data(): DictDemoModel.objects().delete() StringsDemoModel.objects().delete() NumbersDemoModel.objects().delete() + BinaryDemoModel.objects().delete() with switch_db(models.Todo, "secondary"): models.Todo.objects().delete() From 80f48490e9263e25f09d6ff70170d8c0e51f5916 Mon Sep 17 00:00:00 2001 From: Andrey Shpak Date: Tue, 16 Aug 2022 20:28:04 +0300 Subject: [PATCH 03/11] Add in forms support of BinaryField --- example_app/binary_demo.py | 1 + flask_mongoengine/db_fields.py | 15 +-------------- flask_mongoengine/wtf/fields.py | 22 ++++++++++++++++++++-- tests/test_db_fields.py | 1 - 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/example_app/binary_demo.py b/example_app/binary_demo.py index 6aede03b..eab16655 100644 --- a/example_app/binary_demo.py +++ b/example_app/binary_demo.py @@ -8,6 +8,7 @@ class BinaryDemoModel(db.Document): string_field = db.StringField() binary_field = db.BinaryField() + binary_field_with_default = db.BinaryField(default=lambda: "foobar".encode("utf-8")) def binary_demo_view(pk=None): diff --git a/flask_mongoengine/db_fields.py b/flask_mongoengine/db_fields.py index fa7f9fb6..761ff579 100644 --- a/flask_mongoengine/db_fields.py +++ b/flask_mongoengine/db_fields.py @@ -305,20 +305,7 @@ class BinaryField(WtfFieldMixin, fields.BinaryField): All arguments should be passed as keyword arguments, to exclude unexpected behaviour. """ - DEFAULT_WTF_FIELD = custom_fields.BinaryField if custom_fields else None - - def to_wtf_field( - self, - *, - model: Optional[Type] = None, - field_kwargs: Optional[dict] = None, - ): - """ - Protection from execution of :func:`to_wtf_field` in form generation. - - :raises NotImplementedError: Field converter to WTForm Field not implemented. - """ - raise NotImplementedError("Field converter to WTForm Field not implemented.") + DEFAULT_WTF_FIELD = custom_fields.MongoBinaryField if custom_fields else None class BooleanField(WtfFieldMixin, fields.BooleanField): diff --git a/flask_mongoengine/wtf/fields.py b/flask_mongoengine/wtf/fields.py index 09b7be58..1fa76dbe 100644 --- a/flask_mongoengine/wtf/fields.py +++ b/flask_mongoengine/wtf/fields.py @@ -309,6 +309,26 @@ def process_formdata(self, valuelist): super().process_formdata(valuelist) +# noinspection PyAttributeOutsideInit +class MongoBinaryField(wtf_fields.TextAreaField): + """ + Special WTForm :class:`~.wtforms.fields.TextAreaField` that convert input to binary. + """ + + def process_formdata(self, valuelist): + """Converts string form value to binary type and ignoring empty form fields.""" + if not valuelist or valuelist[0] == "": + self.data = None + else: + self.data = valuelist[0].encode("utf-8") + + def _value(self): + """ + Ensures that encoded string data will not be encoded once more on form edit. + """ + return self.data.decode("utf-8") if self.data is not None else "" + + class MongoBooleanField(wtf_fields.SelectField): """Mongo SelectField field for BooleanFields, that correctly coerce values.""" @@ -325,8 +345,6 @@ def __init__( Replaces defaults of :class:`wtforms.fields.SelectField` with for Boolean values. Fully compatible with :class:`wtforms.fields.SelectField` and have same parameters. - - """ if coerce is None: coerce = coerce_boolean diff --git a/tests/test_db_fields.py b/tests/test_db_fields.py index 52dac9c7..4800c69c 100644 --- a/tests/test_db_fields.py +++ b/tests/test_db_fields.py @@ -148,7 +148,6 @@ def test__ensure_callable_or_list__raise_error_if_argument_not_callable_and_not_ @pytest.mark.parametrize( "FieldClass", [ - db_fields.BinaryField, db_fields.CachedReferenceField, db_fields.DynamicField, db_fields.EmbeddedDocumentField, From 2d5d52342e8056b7cb70d0840934fd2615e274c9 Mon Sep 17 00:00:00 2001 From: Andrey Shpak Date: Wed, 17 Aug 2022 10:30:22 +0300 Subject: [PATCH 04/11] Extend example app with file field --- example_app/binary_demo.py | 1 + example_app/templates/form_demo.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/example_app/binary_demo.py b/example_app/binary_demo.py index eab16655..a5400199 100644 --- a/example_app/binary_demo.py +++ b/example_app/binary_demo.py @@ -9,6 +9,7 @@ class BinaryDemoModel(db.Document): string_field = db.StringField() binary_field = db.BinaryField() binary_field_with_default = db.BinaryField(default=lambda: "foobar".encode("utf-8")) + file_field = db.FileField() def binary_demo_view(pk=None): diff --git a/example_app/templates/form_demo.html b/example_app/templates/form_demo.html index 44f2621a..8d13c6ed 100644 --- a/example_app/templates/form_demo.html +++ b/example_app/templates/form_demo.html @@ -30,7 +30,7 @@ {{ render_navigation(page, view) }}
    -
    + {% for field in form %} {{ render_field(field, style='font-weight: bold') }} {% endfor %} From 1f84fe622780e202cef353d547668718381c3830 Mon Sep 17 00:00:00 2001 From: Andrey Shpak Date: Wed, 17 Aug 2022 10:31:12 +0300 Subject: [PATCH 05/11] Add custom Mongo FileField for Forms, and related widget --- flask_mongoengine/wtf/fields.py | 68 +++++++++++++++++++++++++++----- flask_mongoengine/wtf/widgets.py | 34 ++++++++++++++++ 2 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 flask_mongoengine/wtf/widgets.py diff --git a/flask_mongoengine/wtf/fields.py b/flask_mongoengine/wtf/fields.py index 1fa76dbe..b1f0cd4c 100644 --- a/flask_mongoengine/wtf/fields.py +++ b/flask_mongoengine/wtf/fields.py @@ -9,9 +9,13 @@ from flask import json from mongoengine.queryset import DoesNotExist +from werkzeug.datastructures import FileStorage from wtforms import fields as wtf_fields from wtforms import validators as wtf_validators from wtforms import widgets as wtf_widgets +from wtforms.utils import unset_value + +from flask_mongoengine.wtf import widgets as mongo_widgets def coerce_boolean(value: Optional[str]) -> Optional[bool]: @@ -31,6 +35,14 @@ def coerce_boolean(value: Optional[str]) -> Optional[bool]: raise ValueError("Unexpected string value.") +def _is_empty_file(file_object): + """Detects empty files and file streams.""" + file_object.seek(0) + first_char = file_object.read(1) + file_object.seek(0) + return not bool(first_char) + + # noinspection PyAttributeOutsideInit,PyAbstractClass class QuerySetSelectField(wtf_fields.SelectFieldBase): """ @@ -369,6 +381,53 @@ class MongoEmailField(EmptyStringIsNoneMixin, wtf_fields.EmailField): pass +class MongoFileField(wtf_fields.FileField): + """GridFS file field.""" + + widget = mongo_widgets.MongoFileInput() + + def __init__(self, **kwargs): + """Extends base field arguments with file delete marker.""" + super().__init__(**kwargs) + + self._should_delete = False + self._marker = f"_{self.name}_delete" + + def process(self, formdata, data=unset_value, extra_filters=None): + """Extracts 'delete' marker option, if exists in request.""" + if formdata and self._marker in formdata: + self._should_delete = True + return super().process(formdata, data=data, extra_filters=extra_filters) + + def populate_obj(self, obj, name): + """Upload, replace or delete file from database, according form action.""" + field = getattr(obj, name, None) + + if field is None: + return None + + if self._should_delete: + field.delete() + return None + + if isinstance(self.data, FileStorage) and not _is_empty_file(self.data.stream): + action = field.replace if field.grid_id else field.put + action( + self.data.stream, + filename=self.data.filename, + content_type=self.data.content_type, + ) + + +class MongoFloatField(wtf_fields.FloatField): + """ + Regular :class:`wtforms.fields.FloatField`, with widget replaced to + :class:`wtforms.widgets.NumberInput`. + """ + + widget = wtf_widgets.NumberInput(step="any") + + class MongoHiddenField(EmptyStringIsNoneMixin, wtf_fields.HiddenField): """ Regular :class:`wtforms.fields.HiddenField`, that transform empty string to `None`. @@ -425,15 +484,6 @@ class MongoURLField(EmptyStringIsNoneMixin, wtf_fields.URLField): pass -class MongoFloatField(wtf_fields.FloatField): - """ - Regular :class:`wtforms.fields.FloatField`, with widget replaced to - :class:`wtforms.widgets.NumberInput`. - """ - - widget = wtf_widgets.NumberInput(step="any") - - class MongoDictField(MongoTextAreaField): """Form field to handle JSON in :class:`~flask_mongoengine.db_fields.DictField`.""" diff --git a/flask_mongoengine/wtf/widgets.py b/flask_mongoengine/wtf/widgets.py new file mode 100644 index 00000000..e5e16bb3 --- /dev/null +++ b/flask_mongoengine/wtf/widgets.py @@ -0,0 +1,34 @@ +"""Custom widgets for Mongo fields.""" +from markupsafe import Markup, escape +from mongoengine.fields import GridFSProxy +from wtforms.widgets import html_params + + +class MongoFileInput(object): + """Renders a file input field with delete option.""" + + template = """ +
    + %(name)s %(size)dk (%(content_type)s) + Delete +
    + """ + + def __call__(self, field, **kwargs): + kwargs.setdefault("id", field.id) + placeholder = "" + if field.data and isinstance(field.data, GridFSProxy): + data = field.data + placeholder = self.template % { + "name": escape(data.name), + "content_type": escape(data.content_type), + "size": data.length // 1024, + "marker": f"_{field.name}_delete", + } + + return Markup( + ( + "%s" + % (placeholder, html_params(name=field.name, type="file", **kwargs)) + ) + ) From 90742704c7790112ab8513007097fd357900dbd8 Mon Sep 17 00:00:00 2001 From: Andrey Shpak Date: Wed, 17 Aug 2022 10:31:39 +0300 Subject: [PATCH 06/11] Enable FileField --- flask_mongoengine/db_fields.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/flask_mongoengine/db_fields.py b/flask_mongoengine/db_fields.py index 761ff579..fa554669 100644 --- a/flask_mongoengine/db_fields.py +++ b/flask_mongoengine/db_fields.py @@ -577,20 +577,7 @@ class FileField(WtfFieldMixin, fields.FileField): All arguments should be passed as keyword arguments, to exclude unexpected behaviour. """ - DEFAULT_WTF_FIELD = wtf_fields.FileField if wtf_fields else None - - def to_wtf_field( - self, - *, - model: Optional[Type] = None, - field_kwargs: Optional[dict] = None, - ): - """ - Protection from execution of :func:`to_wtf_field` in form generation. - - :raises NotImplementedError: Field converter to WTForm Field not implemented. - """ - raise NotImplementedError("Field converter to WTForm Field not implemented.") + DEFAULT_WTF_FIELD = custom_fields.MongoFileField if custom_fields else None class FloatField(WtfFieldMixin, fields.FloatField): From a26b95c3b451c354c6f27fabc0560fb211efe8cd Mon Sep 17 00:00:00 2001 From: Andrey Shpak Date: Wed, 17 Aug 2022 10:32:14 +0300 Subject: [PATCH 07/11] Tests update --- tests/test_db_fields.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_db_fields.py b/tests/test_db_fields.py index 4800c69c..c2f16e68 100644 --- a/tests/test_db_fields.py +++ b/tests/test_db_fields.py @@ -153,7 +153,6 @@ def test__ensure_callable_or_list__raise_error_if_argument_not_callable_and_not_ db_fields.EmbeddedDocumentField, db_fields.EmbeddedDocumentListField, db_fields.EnumField, - db_fields.FileField, db_fields.GenericEmbeddedDocumentField, db_fields.GenericLazyReferenceField, db_fields.GenericReferenceField, From 22911291dc801919e8faac8270a95bb77a6f879c Mon Sep 17 00:00:00 2001 From: Andrey Shpak Date: Wed, 17 Aug 2022 10:45:56 +0300 Subject: [PATCH 08/11] Simplify data deletion in example app --- example_app/views.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/example_app/views.py b/example_app/views.py index 5bc04375..6e273cd8 100644 --- a/example_app/views.py +++ b/example_app/views.py @@ -4,12 +4,6 @@ from mongoengine.context_managers import switch_db from example_app import models -from example_app.binary_demo import BinaryDemoModel -from example_app.boolean_demo import BooleanDemoModel -from example_app.dates_demo import DateTimeModel -from example_app.dict_demo import DictDemoModel -from example_app.numbers_demo import NumbersDemoModel -from example_app.strings_demo import StringsDemoModel def generate_data(): @@ -51,16 +45,10 @@ def generate_data(): def delete_data(): """Clear database.""" - with switch_db(models.Todo, "default"): - models.Todo.objects().delete() - BooleanDemoModel.objects().delete() - DateTimeModel.objects().delete() - DictDemoModel.objects().delete() - StringsDemoModel.objects().delete() - NumbersDemoModel.objects().delete() - BinaryDemoModel.objects().delete() - with switch_db(models.Todo, "secondary"): - models.Todo.objects().delete() + from example_app.app import db + + db.connection["default"].drop_database("example_app") + db.connection["secondary"].drop_database("example_app_2") def index(): From 2edcdb5a555ad609864aef65af7a66b2458cf0d1 Mon Sep 17 00:00:00 2001 From: Andrey Shpak Date: Wed, 17 Aug 2022 12:53:13 +0300 Subject: [PATCH 09/11] Update MongoFileInput widget with full input nesting + Add MongoImageInput widget --- flask_mongoengine/wtf/widgets.py | 38 ++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/flask_mongoengine/wtf/widgets.py b/flask_mongoengine/wtf/widgets.py index e5e16bb3..a41ce7ac 100644 --- a/flask_mongoengine/wtf/widgets.py +++ b/flask_mongoengine/wtf/widgets.py @@ -1,10 +1,10 @@ """Custom widgets for Mongo fields.""" from markupsafe import Markup, escape -from mongoengine.fields import GridFSProxy -from wtforms.widgets import html_params +from mongoengine.fields import GridFSProxy, ImageGridFsProxy +from wtforms.widgets.core import FileInput -class MongoFileInput(object): +class MongoFileInput(FileInput): """Renders a file input field with delete option.""" template = """ @@ -14,21 +14,27 @@ class MongoFileInput(object):
    """ - def __call__(self, field, **kwargs): - kwargs.setdefault("id", field.id) + def _is_supported_file(self, field) -> bool: + """Checks type of file input.""" + return field.data and isinstance(field.data, GridFSProxy) + + def __call__(self, field, **kwargs) -> Markup: placeholder = "" - if field.data and isinstance(field.data, GridFSProxy): - data = field.data + + if self._is_supported_file(field): placeholder = self.template % { - "name": escape(data.name), - "content_type": escape(data.content_type), - "size": data.length // 1024, + "name": escape(field.data.name), + "content_type": escape(field.data.content_type), + "size": field.data.length // 1024, "marker": f"_{field.name}_delete", } - return Markup( - ( - "%s" - % (placeholder, html_params(name=field.name, type="file", **kwargs)) - ) - ) + return Markup(placeholder) + super().__call__(field, **kwargs) + + +class MongoImageInput(MongoFileInput): + """Renders an image input field with delete option.""" + + def _is_supported_file(self, field) -> bool: + """Checks type of file input.""" + return field.data and isinstance(field.data, ImageGridFsProxy) From ec3a19b3ee5c76b774a8aadeb50a60ec3098306f Mon Sep 17 00:00:00 2001 From: Andrey Shpak Date: Wed, 17 Aug 2022 12:53:37 +0300 Subject: [PATCH 10/11] Enable ImageField forms rendering --- flask_mongoengine/db_fields.py | 19 ++++++++----------- flask_mongoengine/wtf/fields.py | 6 ++++++ tests/test_db_fields.py | 1 - 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/flask_mongoengine/db_fields.py b/flask_mongoengine/db_fields.py index fa554669..05504bd5 100644 --- a/flask_mongoengine/db_fields.py +++ b/flask_mongoengine/db_fields.py @@ -725,18 +725,15 @@ class ImageField(WtfFieldMixin, fields.ImageField): All arguments should be passed as keyword arguments, to exclude unexpected behaviour. """ - def to_wtf_field( - self, - *, - model: Optional[Type] = None, - field_kwargs: Optional[dict] = None, - ): - """ - Protection from execution of :func:`to_wtf_field` in form generation. + DEFAULT_WTF_FIELD = custom_fields.MongoImageField if custom_fields else None - :raises NotImplementedError: Field converter to WTForm Field not implemented. - """ - raise NotImplementedError("Field converter to WTForm Field not implemented.") + @property + @wtf_required + def wtf_generated_options(self) -> dict: + """Inserts accepted type in widget rendering (does not do validation).""" + options = super().wtf_generated_options + options["render_kw"] = {"accept": "image/*"} + return options class IntField(WtfFieldMixin, fields.IntField): diff --git a/flask_mongoengine/wtf/fields.py b/flask_mongoengine/wtf/fields.py index b1f0cd4c..7226e8c9 100644 --- a/flask_mongoengine/wtf/fields.py +++ b/flask_mongoengine/wtf/fields.py @@ -436,6 +436,12 @@ class MongoHiddenField(EmptyStringIsNoneMixin, wtf_fields.HiddenField): pass +class MongoImageField(MongoFileField): + """GridFS image field.""" + + widget = mongo_widgets.MongoImageInput() + + class MongoPasswordField(EmptyStringIsNoneMixin, wtf_fields.PasswordField): """ Regular :class:`wtforms.fields.PasswordField`, that transform empty string to `None`. diff --git a/tests/test_db_fields.py b/tests/test_db_fields.py index c2f16e68..0cb8ded5 100644 --- a/tests/test_db_fields.py +++ b/tests/test_db_fields.py @@ -158,7 +158,6 @@ def test__ensure_callable_or_list__raise_error_if_argument_not_callable_and_not_ db_fields.GenericReferenceField, db_fields.GeoJsonBaseField, db_fields.GeoPointField, - db_fields.ImageField, db_fields.LazyReferenceField, db_fields.LineStringField, db_fields.ListField, From 3acf114454d127d527579c7d554d1deb07766876 Mon Sep 17 00:00:00 2001 From: Andrey Shpak Date: Wed, 17 Aug 2022 12:54:25 +0300 Subject: [PATCH 11/11] Add ImageField demo to example app --- example_app/binary_demo.py | 1 + example_app/templates/layout.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/example_app/binary_demo.py b/example_app/binary_demo.py index a5400199..d61857bb 100644 --- a/example_app/binary_demo.py +++ b/example_app/binary_demo.py @@ -10,6 +10,7 @@ class BinaryDemoModel(db.Document): binary_field = db.BinaryField() binary_field_with_default = db.BinaryField(default=lambda: "foobar".encode("utf-8")) file_field = db.FileField() + image_field = db.ImageField() def binary_demo_view(pk=None): diff --git a/example_app/templates/layout.html b/example_app/templates/layout.html index 1b307770..44533edc 100644 --- a/example_app/templates/layout.html +++ b/example_app/templates/layout.html @@ -23,7 +23,7 @@
  • DateTime demo
  • Booleans demo
  • Dict/Json demo
  • -
  • Binary demo
  • +
  • Binary/Files/Images demo