diff --git a/examples/tabler/.python-version b/examples/tabler/.python-version new file mode 100644 index 0000000000..e4fba21835 --- /dev/null +++ b/examples/tabler/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/examples/tabler/README.md b/examples/tabler/README.md new file mode 100644 index 0000000000..3936080c65 --- /dev/null +++ b/examples/tabler/README.md @@ -0,0 +1,24 @@ +# Tabler Example + +This example shows how you can enable the look & feel of the Tabler admin +interface. + +The current example uses `TablerTheme(layout="fluid")`. Supported layout values +are `vertical`, `fluid`, and `condensed`. + +## How to run this example + +Clone the repository and navigate to this example: + +```shell +git clone https://github.com/pallets-eco/flask-admin.git +cd flask-admin/examples/tabler +``` + +> This example uses [`uv`](https://docs.astral.sh/uv/) to manage its dependencies and developer environment. + +Run the example using `uv`, which will manage the environment and dependencies automatically: + +```shell +uv run main.py +``` diff --git a/examples/tabler/__init__.py b/examples/tabler/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/tabler/data.py b/examples/tabler/data.py new file mode 100644 index 0000000000..0c172ae5b9 --- /dev/null +++ b/examples/tabler/data.py @@ -0,0 +1,133 @@ +def build_sample_db(db, User, Page): + """ + Populate a small db with some example entries. + """ + + db.drop_all() + db.create_all() + + first_names = [ + "Harry", + "Amelia", + "Oliver", + "Jack", + "Isabella", + "Charlie", + "Sophie", + "Mia", + "Jacob", + "Thomas", + "Emily", + "Lily", + "Ava", + "Isla", + "Alfie", + "Olivia", + "Jessica", + "Riley", + "William", + "James", + "Geoffrey", + "Lisa", + "Benjamin", + "Stacey", + "Lucy", + ] + last_names = [ + "Brown", + "Smith", + "Patel", + "Jones", + "Williams", + "Johnson", + "Taylor", + "Thomas", + "Roberts", + "Khan", + "Lewis", + "Jackson", + "Clarke", + "James", + "Phillips", + "Wilson", + "Ali", + "Mason", + "Mitchell", + "Rose", + "Davis", + "Davies", + "Rodriguez", + "Cox", + "Alexander", + ] + + for i in range(len(first_names)): + user = User() + user.name = first_names[i] + " " + last_names[i] + user.email = first_names[i].lower() + "@example.com" + db.session.add(user) + + sample_text = [ + { + "title": "de Finibus Bonorum et Malorum - Part I", + "content": ( + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim " + "ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " + "aliquip ex ea commodo consequat. Duis aute irure dolor in " + "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla " + "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in " + "culpa qui officia deserunt mollit anim id est laborum." + ), + "meta_data": {"a":1, "b":"a"}, + }, + { + "title": "de Finibus Bonorum et Malorum - Part II", + "content": ( + "Sed ut perspiciatis unde omnis iste natus error sit voluptatem " + "accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae " + "ab illo inventore veritatis et quasi architecto beatae vitae dicta " + "sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit " + "aspernatur aut odit aut fugit, sed quia consequuntur magni dolores " + "eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, " + "qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, " + "sed quia non numquam eius modi tempora incidunt ut labore et dolore " + "magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis " + "nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut " + "aliquid ex ea commodi consequatur? Quis autem vel eum iure " + "reprehenderit qui in ea voluptate velit esse quam nihil molestiae " + "consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla " + "pariatur?" + ), + "meta_data": {"a":1, "b":"a"}, + }, + { + "title": "de Finibus Bonorum et Malorum - Part III", + "content": ( + "At vero eos et accusamus et iusto odio dignissimos ducimus qui " + "blanditiis praesentium voluptatum deleniti atque corrupti quos " + "dolores et quas molestias excepturi sint occaecati cupiditate non " + "provident, similique sunt in culpa qui officia deserunt mollitia " + "animi, id est laborum et dolorum fuga. Et harum quidem rerum " + "facilis est et expedita distinctio. Nam libero tempore, cum soluta " + "nobis est eligendi optio cumque nihil impedit quo minus id quod " + "maxime placeat facere possimus, omnis voluptas assumenda est, omnis " + "dolor repellendus. Temporibus autem quibusdam et aut officiis debitis " + "aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae " + "sint et molestiae non recusandae. Itaque earum rerum hic tenetur a " + "sapiente delectus, ut aut reiciendis voluptatibus maiores alias " + "consequatur aut perferendis doloribus asperiores repellat." + ), + "meta_data": {"a":1, "b":"a"}, + }, + ] + + for entry in sample_text: + page = Page() + page.title = entry["title"] + page.content = entry["content"] + page.meta_data = entry["meta_data"] + db.session.add(page) + + db.session.commit() + return diff --git a/examples/tabler/files/d1/dummy.txt b/examples/tabler/files/d1/dummy.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/tabler/main.py b/examples/tabler/main.py new file mode 100644 index 0000000000..c533a3ff92 --- /dev/null +++ b/examples/tabler/main.py @@ -0,0 +1,192 @@ +import datetime +import os.path as op + +from flask import Flask +from flask import redirect +from flask import request +from flask import url_for +from flask_admin import Admin +from flask_admin import AdminIndexView +from flask_admin import expose +from flask_admin.contrib.fileadmin import FileAdmin +from flask_admin.contrib.sqla import ModelView +from flask_admin.menu import MenuDivider +from flask_admin.menu import MenuLink +from flask_admin.theme import TablerTheme +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import Boolean +from sqlalchemy import DateTime +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Text +from sqlalchemy import JSON +from sqlalchemy import text +from sqlalchemy.orm import Mapped +from sqlalchemy.orm import mapped_column +from flask_debugtoolbar import DebugToolbarExtension + +from examples.tabler.data import build_sample_db + +app = Flask(__name__) +app.config["DEBUG"] = True +app.config["SECRET_KEY"] = "secret" +app.config["DATABASE_FILE"] = "db.sqlite" +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + app.config["DATABASE_FILE"] +app.config["SQLALCHEMY_ECHO"] = False +app.config["EXPLAIN_TEMPLATE_LOADING"] = True + +db = SQLAlchemy(app) + +toolbar = DebugToolbarExtension() +toolbar.init_app(app) + + +admin = Admin( + app, + name="Example: Tabler", + theme=TablerTheme(layout="condensed") +) + + +@app.route("/") +def index(): + return 'Click me to get to Admin!' + + +class User(db.Model): + id: Mapped[int] = mapped_column(Integer, primary_key=True) + name: Mapped[str] = mapped_column(String(64)) + email: Mapped[str] = mapped_column(String(64)) + active: Mapped[bool] = mapped_column(Boolean, default=True) + created_at: Mapped[DateTime] = mapped_column( + DateTime, default=datetime.datetime.now + ) + + def __repr__(self): + return self.name + + +class Page(db.Model): + id: Mapped[int] = mapped_column(Integer, primary_key=True) + title: Mapped[str] = mapped_column(String(64)) + content: Mapped[Text] = mapped_column(Text) + meta_data: Mapped[dict] = mapped_column(JSON, default=dict, server_default=text("'{}'")) + + def __repr__(self): + return self.title + + +class UserAdmin(ModelView): + column_searchable_list = ("name",) + column_filters = ("name", "email") + can_export = True + export_types = ["csv", "xlsx"] + can_set_page_size = True + page_size_options = (3, 5, 7, 10, 20, 50, 100) + page_size = 7 + + +class SimplePageView(ModelView): + can_view_details = True + + +class FileAdminModal(FileAdmin): + rename_modal = True + edit_modal = True + mkdir_modal = True + upload_modal = True + + +class PageWithModalView(ModelView): + create_modal = True + edit_modal = True + details_modal = True + can_view_details = True + + +with app.app_context(): + build_sample_db(db, User, Page) + +if __name__ == "__main__": + admin.add_view( + UserAdmin( + User, + db, + category="Menu", + menu_icon_type="ti", + menu_icon_value="user", + menu_class_name="text-warning", + ) + ) + admin.add_menu_item(MenuDivider(), target_category="Menu") + admin.add_view(SimplePageView(Page, db, category="Menu", name="Simple Page")) + + admin.add_view( + PageWithModalView( + Page, db, category="Menu", endpoint="page-modal", name="Page-Modal" + ) + ) + + admin.add_view( + ModelView( + Page, + db, + name="Page-with-icon", + endpoint="page2", + menu_class_name="text-danger", + menu_icon_type="ti", + menu_icon_value="file", + ) + ) + + admin.add_view(FileAdmin("files/", name="Local Files", category="Menu")) + admin.add_view( + FileAdminModal("files/", name="Local Files with Modals", category="Menu") + ) + + admin.add_link( + MenuLink( + name="link1", + url="http://www.example.com/", + class_name="text-warning bg-danger", + icon_type="ti", + icon_value="link", + ) + ) + admin.add_link( + MenuLink(name="link2", url="http://www.example.com/", class_name="text-danger") + ) + admin.add_link(MenuLink(name="Link3", url="http://www.example.com/")) + + # admin.add_sub_category(name="Links", parent_name="Menu") + # admin.add_link( + # MenuLink( + # name="External link", + # url="http://www.example.com/", + # category="Links", + # class_name="text-info", + # icon_type="ti", + # icon_value="link", + # ) + # ) + # admin.add_link( + # MenuLink( + # name="External link", + # url="http://www.example.com/", + # category="Links", + # class_name="text-success", + # ) + # ) + # admin.add_menu_item(MenuDivider(), target_category="Links") + # admin.add_link( + # MenuLink(name="External link", url="http://www.example.com/", category="Links") + # ) + + + app_dir = op.realpath(op.dirname(__file__)) + database_path = op.join(app_dir, app.config["DATABASE_FILE"]) + if not op.exists(database_path): + with app.app_context(): + build_sample_db(db, User, Page) + + app.run(debug=True) diff --git a/examples/tabler/pyproject.toml b/examples/tabler/pyproject.toml new file mode 100644 index 0000000000..f9b6dd22c0 --- /dev/null +++ b/examples/tabler/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "example-tabler" +version = "0.1.0" +description = "Example Tabler UI." +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "flask-admin[sqlalchemy-with-utils]", + "flask-debugtoolbar", + "flask-debugtoolbar-extrapanels", +] + +[tool.uv.sources] +flask-admin = { path = "../../", editable = true } diff --git a/examples/tabler/static/d1/afile.txt b/examples/tabler/static/d1/afile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/tabler/uv.lock b/examples/tabler/uv.lock new file mode 100644 index 0000000000..d939442d6d --- /dev/null +++ b/examples/tabler/uv.lock @@ -0,0 +1,544 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" + +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "types-python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960, upload-time = "2023-09-30T22:11:18.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "colour" +version = "0.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/d4/5911a7618acddc3f594ddf144ecd8a03c29074a540f4494670ad8f153efe/colour-0.1.5.tar.gz", hash = "sha256:af20120fefd2afede8b001fbef2ea9da70ad7d49fafdb6489025dae8745c3aee", size = 24776, upload-time = "2017-11-19T23:20:08.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/46/e81907704ab203206769dee1385dc77e1407576ff8f50a0681d0a6b541be/colour-0.1.5-py2.py3-none-any.whl", hash = "sha256:33f6db9d564fadc16e59921a56999b79571160ce09916303d35346dddc17978c", size = 23772, upload-time = "2017-11-19T23:20:04.56Z" }, +] + +[[package]] +name = "dnspython" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, +] + +[[package]] +name = "email-validator" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967, upload-time = "2024-06-20T11:30:30.034Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload-time = "2024-06-20T11:30:28.248Z" }, +] + +[[package]] +name = "example-tabler" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "flask-admin", extra = ["sqlalchemy-with-utils"] }, + { name = "flask-debugtoolbar" }, + { name = "flask-debugtoolbar-extrapanels" }, +] + +[package.metadata] +requires-dist = [ + { name = "flask-admin", extras = ["sqlalchemy-with-utils"], editable = "../../" }, + { name = "flask-debugtoolbar" }, + { name = "flask-debugtoolbar-extrapanels" }, +] + +[[package]] +name = "flask" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, +] + +[[package]] +name = "flask-admin" +version = "2.1.0" +source = { editable = "../../" } +dependencies = [ + { name = "flask" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "werkzeug" }, + { name = "wtforms" }, +] + +[package.optional-dependencies] +sqlalchemy-with-utils = [ + { name = "arrow" }, + { name = "colour" }, + { name = "email-validator" }, + { name = "flask-sqlalchemy" }, + { name = "sqlalchemy" }, + { name = "sqlalchemy-citext" }, + { name = "sqlalchemy-utils" }, +] + +[package.metadata] +requires-dist = [ + { name = "arrow", marker = "extra == 'sqlalchemy-with-utils'", specifier = ">=0.14.0" }, + { name = "azure-storage-blob", marker = "extra == 'azure-blob-storage'", specifier = ">=12.0.0" }, + { name = "boto3", marker = "extra == 's3'", specifier = ">=1.33" }, + { name = "colour", marker = "extra == 'sqlalchemy-with-utils'", specifier = ">=0.1.5" }, + { name = "email-validator", marker = "extra == 'sqlalchemy-with-utils'", specifier = ">=2" }, + { name = "flask", specifier = ">=2.0" }, + { name = "flask-admin", extras = ["azure-blob-storage"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-admin", extras = ["export"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-admin", extras = ["geoalchemy"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-admin", extras = ["images"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-admin", extras = ["mongoengine"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-admin", extras = ["peewee"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-admin", extras = ["pymongo"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-admin", extras = ["rediscli"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-admin", extras = ["s3"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-admin", extras = ["sqlalchemy"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-admin", extras = ["sqlalchemy"], marker = "extra == 'geoalchemy'", editable = "../../" }, + { name = "flask-admin", extras = ["sqlalchemy"], marker = "extra == 'sqlalchemy-with-utils'", editable = "../../" }, + { name = "flask-admin", extras = ["sqlalchemy-lite"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-admin", extras = ["sqlalchemy-with-utils"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-admin", extras = ["translation"], marker = "extra == 'all'", editable = "../../" }, + { name = "flask-babel", marker = "extra == 'translation'", specifier = ">=3.0.1" }, + { name = "flask-sqlalchemy", marker = "extra == 'sqlalchemy'", specifier = ">=3" }, + { name = "flask-sqlalchemy-lite", marker = "extra == 'sqlalchemy-lite'" }, + { name = "geoalchemy2", marker = "extra == 'geoalchemy'", specifier = ">=0.14.0" }, + { name = "jinja2", specifier = ">=3.0" }, + { name = "markupsafe", specifier = ">=2.0" }, + { name = "mongoengine", marker = "extra == 'mongoengine'", specifier = ">=0.29.0" }, + { name = "peewee", marker = "extra == 'peewee'", specifier = ">=3.14.0" }, + { name = "pillow", marker = "extra == 'images'", specifier = ">=10.0.0" }, + { name = "pymongo", marker = "extra == 'pymongo'", specifier = ">=3.10.0" }, + { name = "redis", marker = "extra == 'rediscli'", specifier = ">=4.0.0" }, + { name = "shapely", marker = "extra == 'geoalchemy'", specifier = ">=2" }, + { name = "sqlalchemy", marker = "extra == 'sqlalchemy'", specifier = ">=1.4" }, + { name = "sqlalchemy-citext", marker = "extra == 'sqlalchemy-with-utils'", specifier = ">=1.8.0" }, + { name = "sqlalchemy-utils", marker = "extra == 'sqlalchemy-with-utils'", specifier = ">=0.38.0" }, + { name = "tablib", marker = "extra == 'export'", specifier = ">=3.0.0" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "werkzeug", specifier = ">=2.0" }, + { name = "wtf-peewee", marker = "extra == 'peewee'", specifier = ">=3.0.4" }, + { name = "wtforms", specifier = ">=2.3" }, +] +provides-extras = ["sqlalchemy", "sqlalchemy-lite", "sqlalchemy-with-utils", "geoalchemy", "pymongo", "mongoengine", "peewee", "s3", "azure-blob-storage", "images", "export", "rediscli", "translation", "all"] + +[package.metadata.requires-dev] +dev = [ + { name = "beautifulsoup4" }, + { name = "botocore", specifier = ">=1.35" }, + { name = "flake8" }, + { name = "flask", extras = ["async"] }, + { name = "flask-admin", extras = ["all"], editable = "../../" }, + { name = "moto" }, + { name = "mypy" }, + { name = "pallets-sphinx-themes" }, + { name = "pre-commit" }, + { name = "pre-commit-uv" }, + { name = "psycopg2-binary" }, + { name = "pylint" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "sphinx" }, + { name = "sphinx-intl", specifier = ">=2.3.1" }, + { name = "sphinxcontrib-log-cabinet" }, + { name = "tox", specifier = "<4.30" }, + { name = "tox-uv" }, + { name = "types-beautifulsoup4" }, + { name = "types-boto3" }, + { name = "types-peewee" }, + { name = "types-pillow" }, + { name = "types-shapely" }, + { name = "types-wtforms" }, +] +docs = [ + { name = "flask-admin", extras = ["all"], editable = "../../" }, + { name = "pallets-sphinx-themes" }, + { name = "sphinx" }, + { name = "sphinx-intl", specifier = ">=2.3.1" }, + { name = "sphinxcontrib-log-cabinet" }, +] +pre-commit = [ + { name = "pre-commit" }, + { name = "pre-commit-uv" }, +] +tests = [ + { name = "beautifulsoup4" }, + { name = "botocore", specifier = ">=1.35" }, + { name = "flake8" }, + { name = "flask", extras = ["async"] }, + { name = "moto" }, + { name = "psycopg2-binary" }, + { name = "pylint" }, + { name = "pytest" }, + { name = "pytest-cov" }, +] +typing = [ + { name = "mypy" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "types-beautifulsoup4" }, + { name = "types-boto3" }, + { name = "types-peewee" }, + { name = "types-pillow" }, + { name = "types-shapely" }, + { name = "types-wtforms" }, +] + +[[package]] +name = "flask-debugtoolbar" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/0b/19a29b9354b3c00102a475791093358a30afba43e8b676294e7d01964592/flask_debugtoolbar-0.16.0.tar.gz", hash = "sha256:3b925d4dcc09205471e5021019dfeb0eb6dabd6c184de16a3496dfb1f342afe1", size = 335258, upload-time = "2024-09-28T14:55:35.345Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/17/f2a647152315561787d2dfc7dcaf452ec83930a31de9d083a7094da404de/flask_debugtoolbar-0.16.0-py3-none-any.whl", hash = "sha256:2857a58ef20b88cf022a88bb7f0c6f6be1fb91a2e8b2d9fcc9079357a692083e", size = 413047, upload-time = "2024-09-28T14:55:33.928Z" }, +] + +[[package]] +name = "flask-debugtoolbar-extrapanels" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask-debugtoolbar" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/e7/e6a8d8adab0be5588dd7f49a664f55dc6629366d12cbeb50e9a08b654eab/flask_debugtoolbar_extrapanels-0.1.0.tar.gz", hash = "sha256:35e9d8c97e2ce19393b66dedabde9cc64d78f7046e64a6c79cf28484fd7c1eff", size = 10112, upload-time = "2026-03-10T01:14:41.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/0b/90b10e2f827195fc9f2eabfa40ac4c688a6e6bc1ccfa7761f820793c3862/flask_debugtoolbar_extrapanels-0.1.0-py3-none-any.whl", hash = "sha256:4ca8114cdd8ffd20894cc112975befabd0cdef76ae5a135f6171f5d9eb1aa826", size = 5694, upload-time = "2026-03-10T01:14:42.169Z" }, +] + +[[package]] +name = "flask-sqlalchemy" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/53/b0a9fcc1b1297f51e68b69ed3b7c3c40d8c45be1391d77ae198712914392/flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312", size = 81899, upload-time = "2023-09-11T21:42:36.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/6a/89963a5c6ecf166e8be29e0d1bf6806051ee8fe6c82e232842e3aeac9204/flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0", size = 25125, upload-time = "2023-09-11T21:42:34.514Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", size = 185752, upload-time = "2025-06-05T16:16:09.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/db/b4c12cff13ebac2786f4f217f06588bccd8b53d260453404ef22b121fc3a/greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be", size = 268977, upload-time = "2025-06-05T16:10:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/52/61/75b4abd8147f13f70986df2801bf93735c1bd87ea780d70e3b3ecda8c165/greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac", size = 627351, upload-time = "2025-06-05T16:38:50.685Z" }, + { url = "https://files.pythonhosted.org/packages/35/aa/6894ae299d059d26254779a5088632874b80ee8cf89a88bca00b0709d22f/greenlet-3.2.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392", size = 638599, upload-time = "2025-06-05T16:41:34.057Z" }, + { url = "https://files.pythonhosted.org/packages/47/48/ff9ca8ba9772d083a4f5221f7b4f0ebe8978131a9ae0909cf202f94cd879/greenlet-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db", size = 633284, upload-time = "2025-06-05T16:13:01.599Z" }, + { url = "https://files.pythonhosted.org/packages/e9/45/626e974948713bc15775b696adb3eb0bd708bec267d6d2d5c47bb47a6119/greenlet-3.2.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b", size = 582206, upload-time = "2025-06-05T16:12:48.51Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8e/8b6f42c67d5df7db35b8c55c9a850ea045219741bb14416255616808c690/greenlet-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712", size = 1111412, upload-time = "2025-06-05T16:36:45.479Z" }, + { url = "https://files.pythonhosted.org/packages/05/46/ab58828217349500a7ebb81159d52ca357da747ff1797c29c6023d79d798/greenlet-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00", size = 1135054, upload-time = "2025-06-05T16:12:36.478Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/d1b537be5080721c0f0089a8447d4ef72839039cdb743bdd8ffd23046e9a/greenlet-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302", size = 296573, upload-time = "2025-06-05T16:34:26.521Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2e/d4fcb2978f826358b673f779f78fa8a32ee37df11920dc2bb5589cbeecef/greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822", size = 270219, upload-time = "2025-06-05T16:10:10.414Z" }, + { url = "https://files.pythonhosted.org/packages/16/24/929f853e0202130e4fe163bc1d05a671ce8dcd604f790e14896adac43a52/greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83", size = 630383, upload-time = "2025-06-05T16:38:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b2/0320715eb61ae70c25ceca2f1d5ae620477d246692d9cc284c13242ec31c/greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf", size = 642422, upload-time = "2025-06-05T16:41:35.259Z" }, + { url = "https://files.pythonhosted.org/packages/7e/c8/ca19760cf6eae75fa8dc32b487e963d863b3ee04a7637da77b616703bc37/greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147", size = 637627, upload-time = "2025-06-05T16:13:02.858Z" }, + { url = "https://files.pythonhosted.org/packages/65/89/77acf9e3da38e9bcfca881e43b02ed467c1dedc387021fc4d9bd9928afb8/greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5", size = 585502, upload-time = "2025-06-05T16:12:49.642Z" }, + { url = "https://files.pythonhosted.org/packages/97/c6/ae244d7c95b23b7130136e07a9cc5aadd60d59b5951180dc7dc7e8edaba7/greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc", size = 1114498, upload-time = "2025-06-05T16:36:46.598Z" }, + { url = "https://files.pythonhosted.org/packages/89/5f/b16dec0cbfd3070658e0d744487919740c6d45eb90946f6787689a7efbce/greenlet-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba", size = 1139977, upload-time = "2025-06-05T16:12:38.262Z" }, + { url = "https://files.pythonhosted.org/packages/66/77/d48fb441b5a71125bcac042fc5b1494c806ccb9a1432ecaa421e72157f77/greenlet-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34", size = 297017, upload-time = "2025-06-05T16:25:05.225Z" }, + { url = "https://files.pythonhosted.org/packages/f3/94/ad0d435f7c48debe960c53b8f60fb41c2026b1d0fa4a99a1cb17c3461e09/greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d", size = 271992, upload-time = "2025-06-05T16:11:23.467Z" }, + { url = "https://files.pythonhosted.org/packages/93/5d/7c27cf4d003d6e77749d299c7c8f5fd50b4f251647b5c2e97e1f20da0ab5/greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b", size = 638820, upload-time = "2025-06-05T16:38:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7e/807e1e9be07a125bb4c169144937910bf59b9d2f6d931578e57f0bce0ae2/greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d", size = 653046, upload-time = "2025-06-05T16:41:36.343Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0d/93729068259b550d6a0288da4ff72b86ed05626eaf1eb7c0d3466a2571de/greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688", size = 649747, upload-time = "2025-06-05T16:13:04.628Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f6/c82ac1851c60851302d8581680573245c8fc300253fc1ff741ae74a6c24d/greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb", size = 605461, upload-time = "2025-06-05T16:12:50.792Z" }, + { url = "https://files.pythonhosted.org/packages/98/82/d022cf25ca39cf1200650fc58c52af32c90f80479c25d1cbf57980ec3065/greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c", size = 1121190, upload-time = "2025-06-05T16:36:48.59Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e1/25297f70717abe8104c20ecf7af0a5b82d2f5a980eb1ac79f65654799f9f/greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163", size = 1149055, upload-time = "2025-06-05T16:12:40.457Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8f/8f9e56c5e82eb2c26e8cde787962e66494312dc8cb261c460e1f3a9c88bc/greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849", size = 297817, upload-time = "2025-06-05T16:29:49.244Z" }, + { url = "https://files.pythonhosted.org/packages/b1/cf/f5c0b23309070ae93de75c90d29300751a5aacefc0a3ed1b1d8edb28f08b/greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", size = 270732, upload-time = "2025-06-05T16:10:08.26Z" }, + { url = "https://files.pythonhosted.org/packages/48/ae/91a957ba60482d3fecf9be49bc3948f341d706b52ddb9d83a70d42abd498/greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", size = 639033, upload-time = "2025-06-05T16:38:53.983Z" }, + { url = "https://files.pythonhosted.org/packages/6f/df/20ffa66dd5a7a7beffa6451bdb7400d66251374ab40b99981478c69a67a8/greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", size = 652999, upload-time = "2025-06-05T16:41:37.89Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6a/1e1b5aa10dced4ae876a322155705257748108b7fd2e4fae3f2a091fe81a/greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", size = 650037, upload-time = "2025-06-05T16:13:06.402Z" }, + { url = "https://files.pythonhosted.org/packages/26/f2/ad51331a157c7015c675702e2d5230c243695c788f8f75feba1af32b3617/greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", size = 608402, upload-time = "2025-06-05T16:12:51.91Z" }, + { url = "https://files.pythonhosted.org/packages/26/bc/862bd2083e6b3aff23300900a956f4ea9a4059de337f5c8734346b9b34fc/greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", size = 1119577, upload-time = "2025-06-05T16:36:49.787Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/1fc0cc068cfde885170e01de40a619b00eaa8f2916bf3541744730ffb4c3/greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36", size = 1147121, upload-time = "2025-06-05T16:12:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/27/1a/199f9587e8cb08a0658f9c30f3799244307614148ffe8b1e3aa22f324dea/greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3", size = 297603, upload-time = "2025-06-05T16:20:12.651Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ca/accd7aa5280eb92b70ed9e8f7fd79dc50a2c21d8c73b9a0856f5b564e222/greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86", size = 271479, upload-time = "2025-06-05T16:10:47.525Z" }, + { url = "https://files.pythonhosted.org/packages/55/71/01ed9895d9eb49223280ecc98a557585edfa56b3d0e965b9fa9f7f06b6d9/greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97", size = 683952, upload-time = "2025-06-05T16:38:55.125Z" }, + { url = "https://files.pythonhosted.org/packages/ea/61/638c4bdf460c3c678a0a1ef4c200f347dff80719597e53b5edb2fb27ab54/greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728", size = 696917, upload-time = "2025-06-05T16:41:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/67/10/b2a4b63d3f08362662e89c103f7fe28894a51ae0bc890fabf37d1d780e52/greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892", size = 692995, upload-time = "2025-06-05T16:13:07.972Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c6/ad82f148a4e3ce9564056453a71529732baf5448ad53fc323e37efe34f66/greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141", size = 655320, upload-time = "2025-06-05T16:12:53.453Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.41" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/12/d7c445b1940276a828efce7331cb0cb09d6e5f049651db22f4ebb0922b77/sqlalchemy-2.0.41-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1f09b6821406ea1f94053f346f28f8215e293344209129a9c0fcc3578598d7b", size = 2117967, upload-time = "2025-05-14T17:48:15.841Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b8/cb90f23157e28946b27eb01ef401af80a1fab7553762e87df51507eaed61/sqlalchemy-2.0.41-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1936af879e3db023601196a1684d28e12f19ccf93af01bf3280a3262c4b6b4e5", size = 2107583, upload-time = "2025-05-14T17:48:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c2/eef84283a1c8164a207d898e063edf193d36a24fb6a5bb3ce0634b92a1e8/sqlalchemy-2.0.41-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2ac41acfc8d965fb0c464eb8f44995770239668956dc4cdf502d1b1ffe0d747", size = 3186025, upload-time = "2025-05-14T17:51:51.226Z" }, + { url = "https://files.pythonhosted.org/packages/bd/72/49d52bd3c5e63a1d458fd6d289a1523a8015adedbddf2c07408ff556e772/sqlalchemy-2.0.41-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81c24e0c0fde47a9723c81d5806569cddef103aebbf79dbc9fcbb617153dea30", size = 3186259, upload-time = "2025-05-14T17:55:22.526Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9e/e3ffc37d29a3679a50b6bbbba94b115f90e565a2b4545abb17924b94c52d/sqlalchemy-2.0.41-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23a8825495d8b195c4aa9ff1c430c28f2c821e8c5e2d98089228af887e5d7e29", size = 3126803, upload-time = "2025-05-14T17:51:53.277Z" }, + { url = "https://files.pythonhosted.org/packages/8a/76/56b21e363f6039978ae0b72690237b38383e4657281285a09456f313dd77/sqlalchemy-2.0.41-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:60c578c45c949f909a4026b7807044e7e564adf793537fc762b2489d522f3d11", size = 3148566, upload-time = "2025-05-14T17:55:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/3b/92/11b8e1b69bf191bc69e300a99badbbb5f2f1102f2b08b39d9eee2e21f565/sqlalchemy-2.0.41-cp310-cp310-win32.whl", hash = "sha256:118c16cd3f1b00c76d69343e38602006c9cfb9998fa4f798606d28d63f23beda", size = 2086696, upload-time = "2025-05-14T17:55:59.136Z" }, + { url = "https://files.pythonhosted.org/packages/5c/88/2d706c9cc4502654860f4576cd54f7db70487b66c3b619ba98e0be1a4642/sqlalchemy-2.0.41-cp310-cp310-win_amd64.whl", hash = "sha256:7492967c3386df69f80cf67efd665c0f667cee67032090fe01d7d74b0e19bb08", size = 2110200, upload-time = "2025-05-14T17:56:00.757Z" }, + { url = "https://files.pythonhosted.org/packages/37/4e/b00e3ffae32b74b5180e15d2ab4040531ee1bef4c19755fe7926622dc958/sqlalchemy-2.0.41-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6375cd674fe82d7aa9816d1cb96ec592bac1726c11e0cafbf40eeee9a4516b5f", size = 2121232, upload-time = "2025-05-14T17:48:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/ef/30/6547ebb10875302074a37e1970a5dce7985240665778cfdee2323709f749/sqlalchemy-2.0.41-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f8c9fdd15a55d9465e590a402f42082705d66b05afc3ffd2d2eb3c6ba919560", size = 2110897, upload-time = "2025-05-14T17:48:21.634Z" }, + { url = "https://files.pythonhosted.org/packages/9e/21/59df2b41b0f6c62da55cd64798232d7349a9378befa7f1bb18cf1dfd510a/sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f9dc8c44acdee06c8fc6440db9eae8b4af8b01e4b1aee7bdd7241c22edff4f", size = 3273313, upload-time = "2025-05-14T17:51:56.205Z" }, + { url = "https://files.pythonhosted.org/packages/62/e4/b9a7a0e5c6f79d49bcd6efb6e90d7536dc604dab64582a9dec220dab54b6/sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c11ceb9a1f482c752a71f203a81858625d8df5746d787a4786bca4ffdf71c6", size = 3273807, upload-time = "2025-05-14T17:55:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/d8/79f2427251b44ddee18676c04eab038d043cff0e764d2d8bb08261d6135d/sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:911cc493ebd60de5f285bcae0491a60b4f2a9f0f5c270edd1c4dbaef7a38fc04", size = 3209632, upload-time = "2025-05-14T17:51:59.384Z" }, + { url = "https://files.pythonhosted.org/packages/d4/16/730a82dda30765f63e0454918c982fb7193f6b398b31d63c7c3bd3652ae5/sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03968a349db483936c249f4d9cd14ff2c296adfa1290b660ba6516f973139582", size = 3233642, upload-time = "2025-05-14T17:55:29.901Z" }, + { url = "https://files.pythonhosted.org/packages/04/61/c0d4607f7799efa8b8ea3c49b4621e861c8f5c41fd4b5b636c534fcb7d73/sqlalchemy-2.0.41-cp311-cp311-win32.whl", hash = "sha256:293cd444d82b18da48c9f71cd7005844dbbd06ca19be1ccf6779154439eec0b8", size = 2086475, upload-time = "2025-05-14T17:56:02.095Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8e/8344f8ae1cb6a479d0741c02cd4f666925b2bf02e2468ddaf5ce44111f30/sqlalchemy-2.0.41-cp311-cp311-win_amd64.whl", hash = "sha256:3d3549fc3e40667ec7199033a4e40a2f669898a00a7b18a931d3efb4c7900504", size = 2110903, upload-time = "2025-05-14T17:56:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/3e/2a/f1f4e068b371154740dd10fb81afb5240d5af4aa0087b88d8b308b5429c2/sqlalchemy-2.0.41-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9", size = 2119645, upload-time = "2025-05-14T17:55:24.854Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e8/c664a7e73d36fbfc4730f8cf2bf930444ea87270f2825efbe17bf808b998/sqlalchemy-2.0.41-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1", size = 2107399, upload-time = "2025-05-14T17:55:28.097Z" }, + { url = "https://files.pythonhosted.org/packages/5c/78/8a9cf6c5e7135540cb682128d091d6afa1b9e48bd049b0d691bf54114f70/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70", size = 3293269, upload-time = "2025-05-14T17:50:38.227Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/f74add3978c20de6323fb11cb5162702670cc7a9420033befb43d8d5b7a4/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6145afea51ff0af7f2564a05fa95eb46f542919e6523729663a5d285ecb3cf5e", size = 3303364, upload-time = "2025-05-14T17:51:49.829Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d4/c990f37f52c3f7748ebe98883e2a0f7d038108c2c5a82468d1ff3eec50b7/sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b46fa6eae1cd1c20e6e6f44e19984d438b6b2d8616d21d783d150df714f44078", size = 3229072, upload-time = "2025-05-14T17:50:39.774Z" }, + { url = "https://files.pythonhosted.org/packages/15/69/cab11fecc7eb64bc561011be2bd03d065b762d87add52a4ca0aca2e12904/sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41836fe661cc98abfae476e14ba1906220f92c4e528771a8a3ae6a151242d2ae", size = 3268074, upload-time = "2025-05-14T17:51:51.736Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0c19ec16858585d37767b167fc9602593f98998a68a798450558239fb04a/sqlalchemy-2.0.41-cp312-cp312-win32.whl", hash = "sha256:a8808d5cf866c781150d36a3c8eb3adccfa41a8105d031bf27e92c251e3969d6", size = 2084514, upload-time = "2025-05-14T17:55:49.915Z" }, + { url = "https://files.pythonhosted.org/packages/7f/23/4c2833d78ff3010a4e17f984c734f52b531a8c9060a50429c9d4b0211be6/sqlalchemy-2.0.41-cp312-cp312-win_amd64.whl", hash = "sha256:5b14e97886199c1f52c14629c11d90c11fbb09e9334fa7bb5f6d068d9ced0ce0", size = 2111557, upload-time = "2025-05-14T17:55:51.349Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ad/2e1c6d4f235a97eeef52d0200d8ddda16f6c4dd70ae5ad88c46963440480/sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", size = 2115491, upload-time = "2025-05-14T17:55:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8d/be490e5db8400dacc89056f78a52d44b04fbf75e8439569d5b879623a53b/sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", size = 2102827, upload-time = "2025-05-14T17:55:34.921Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/c97ad430f0b0e78efaf2791342e13ffeafcbb3c06242f01a3bb8fe44f65d/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", size = 3225224, upload-time = "2025-05-14T17:50:41.418Z" }, + { url = "https://files.pythonhosted.org/packages/5e/51/5ba9ea3246ea068630acf35a6ba0d181e99f1af1afd17e159eac7e8bc2b8/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", size = 3230045, upload-time = "2025-05-14T17:51:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/78/2f/8c14443b2acea700c62f9b4a8bad9e49fc1b65cfb260edead71fd38e9f19/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", size = 3159357, upload-time = "2025-05-14T17:50:43.483Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b2/43eacbf6ccc5276d76cea18cb7c3d73e294d6fb21f9ff8b4eef9b42bbfd5/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", size = 3197511, upload-time = "2025-05-14T17:51:57.308Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/677c17c5d6a004c3c45334ab1dbe7b7deb834430b282b8a0f75ae220c8eb/sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", size = 2082420, upload-time = "2025-05-14T17:55:52.69Z" }, + { url = "https://files.pythonhosted.org/packages/e9/61/e8c1b9b6307c57157d328dd8b8348ddc4c47ffdf1279365a13b2b98b8049/sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", size = 2108329, upload-time = "2025-05-14T17:55:54.495Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224, upload-time = "2025-05-14T17:39:42.154Z" }, +] + +[[package]] +name = "sqlalchemy-citext" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/c3/404caceaffdf0dcfa822e8b068aebc4fef328bf85af42b6cd8fdd2b2555b/sqlalchemy-citext-1.8.0.tar.gz", hash = "sha256:a1740e693a9a334e7c8f60ae731083fe75ce6c1605bb9ca6644a6f1f63b15b77", size = 3601, upload-time = "2021-03-02T18:14:03.539Z" } + +[[package]] +name = "sqlalchemy-utils" +version = "0.41.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/bf/abfd5474cdd89ddd36dbbde9c6efba16bfa7f5448913eba946fed14729da/SQLAlchemy-Utils-0.41.2.tar.gz", hash = "sha256:bc599c8c3b3319e53ce6c5c3c471120bd325d0071fb6f38a10e924e3d07b9990", size = 138017, upload-time = "2024-03-24T15:17:28.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/f0/dc4757b83ac1ab853cf222df8535ed73973e0c203d983982ba7b8bc60508/SQLAlchemy_Utils-0.41.2-py3-none-any.whl", hash = "sha256:85cf3842da2bf060760f955f8467b87983fb2e30f1764fd0e24a48307dc8ec6e", size = 93083, upload-time = "2024-03-24T15:17:24.533Z" }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20250516" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/88/d65ed807393285204ab6e2801e5d11fbbea811adcaa979a2ed3b67a5ef41/types_python_dateutil-2.9.0.20250516.tar.gz", hash = "sha256:13e80d6c9c47df23ad773d54b2826bd52dbbb41be87c3f339381c1700ad21ee5", size = 13943, upload-time = "2025-05-16T03:06:58.385Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/3f/b0e8db149896005adc938a1e7f371d6d7e9eca4053a29b108978ed15e0c2/types_python_dateutil-2.9.0.20250516-py3-none-any.whl", hash = "sha256:2b2b3f57f9c6a61fba26a9c0ffb9ea5681c9b83e69cd897c6b5f668d9c0cab93", size = 14356, upload-time = "2025-05-16T03:06:57.249Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" }, +] + +[[package]] +name = "wtforms" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/e4/633d080897e769ed5712dcfad626e55dbd6cf45db0ff4d9884315c6a82da/wtforms-3.2.1.tar.gz", hash = "sha256:df3e6b70f3192e92623128123ec8dca3067df9cfadd43d59681e210cfb8d4682", size = 137801, upload-time = "2024-10-21T11:34:00.108Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/c9/2088fb5645cd289c99ebe0d4cdcc723922a1d8e1beaefb0f6f76dff9b21c/wtforms-3.2.1-py3-none-any.whl", hash = "sha256:583bad77ba1dd7286463f21e11aa3043ca4869d03575921d1a1698d0715e0fd4", size = 152454, upload-time = "2024-10-21T11:33:58.44Z" }, +] diff --git a/flask_admin/base.py b/flask_admin/base.py index bce8e242a5..443d3cf200 100644 --- a/flask_admin/base.py +++ b/flask_admin/base.py @@ -234,6 +234,7 @@ def __init__( - `flask_admin.consts.ICON_TYPE_GLYPH` - Bootstrap glyph icon - `flask_admin.consts.ICON_TYPE_FONT_AWESOME` - Font Awesome icon + - `flask_admin.consts.ICON_TYPE_TABLER` - Tabler icon - `flask_admin.consts.ICON_TYPE_IMAGE` - Image relative to Flask static directory - `flask_admin.consts.ICON_TYPE_IMAGE_URL` - Image with full URL diff --git a/flask_admin/consts.py b/flask_admin/consts.py index 8419864606..95563d264d 100644 --- a/flask_admin/consts.py +++ b/flask_admin/consts.py @@ -2,6 +2,8 @@ ICON_TYPE_GLYPH = "glyph" # font awesome glyph icon ICON_TYPE_FONT_AWESOME = "fa" +# Tabler icons +ICON_TYPE_TABLER = "ti" # image relative to Flask static folder ICON_TYPE_IMAGE = "image" # external image diff --git a/flask_admin/model/base.py b/flask_admin/model/base.py index 7938227f41..4b5738eb1f 100644 --- a/flask_admin/model/base.py +++ b/flask_admin/model/base.py @@ -941,6 +941,7 @@ def __init__( - `flask_admin.consts.ICON_TYPE_GLYPH` - Bootstrap glyph icon - `flask_admin.consts.ICON_TYPE_FONT_AWESOME` - Font Awesome icon + - `flask_admin.consts.ICON_TYPE_TABLER` - Tabler icon - `flask_admin.consts.ICON_TYPE_IMAGE` - Image relative to Flask static directory - `flask_admin.consts.ICON_TYPE_IMAGE_URL` - Image with full URL diff --git a/flask_admin/model/typefmt.py b/flask_admin/model/typefmt.py index 12eb6bd48c..458e62bf34 100644 --- a/flask_admin/model/typefmt.py +++ b/flask_admin/model/typefmt.py @@ -38,9 +38,11 @@ def bool_formatter(view: T_MODEL_VIEW, value: t.Any, name: str) -> str: """ glyph = "ok-circle" if value else "minus-sign" fa = "fa-check-circle" if value else "fa-minus-circle" + ti = "circle-check" if value else "circle-minus" label = f'{name}: {"true" if value else "false"}' + color = "text-success" if value else "text-muted" return Markup( - f'' ) diff --git a/flask_admin/static/admin/css/tabler/admin.css b/flask_admin/static/admin/css/tabler/admin.css new file mode 100644 index 0000000000..4379e42474 --- /dev/null +++ b/flask_admin/static/admin/css/tabler/admin.css @@ -0,0 +1,101 @@ +.admin-form fieldset { + border: none; + padding: 0; + margin: 0; +} + +.tabler-admin-header .navbar-brand { + margin-right: 1rem; +} + +.tabler-admin-navbar-collapse { + justify-content: space-between; +} + +.tabler-admin-navbar-secondary .navbar-nav, +.tabler-admin-navbar-collapse .navbar-nav { + flex-wrap: wrap; +} + +.tabler-admin-navbar-secondary .nav-item.dropdown .dropdown-menu, +.tabler-admin-navbar-collapse .nav-item.dropdown .dropdown-menu { + position: absolute; +} + +.submit-row { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + +.model-list th.list-checkbox-column, +.model-list td.list-checkbox-column { + width: 2rem; + text-align: center; +} + +.list-buttons-column { + white-space: nowrap; +} + +.list-buttons-column form.icon { + display: inline; +} + +.list-buttons-column form.icon button { + background: none; + border: none; + padding: 0 0.25rem; + cursor: pointer; + color: var(--tblr-secondary); +} + +.list-buttons-column form.icon button:hover { + color: var(--tblr-danger); +} + +.list-buttons-column a.icon { + padding: 0 0.25rem; + color: var(--tblr-secondary); +} + +.list-buttons-column a.icon:hover { + color: var(--tblr-primary); +} + + +.inline-field .inline-field-list .inline-field { + margin-bottom: 1rem; +} + +.inline-remove-field { + color: var(--tblr-danger); +} + + +.filters td { + padding: 0.25rem; +} + +.field-filters .filter { + cursor: pointer; +} + + +.select2-container { + min-width: 100%; +} + + +.nav-tabs { + margin-bottom: 1rem; +} + + +.pagination { + margin-top: 1rem; +} + +.alert-dismissible .btn-close { + padding: 0.75rem 1rem; +} diff --git a/flask_admin/static/admin/css/tabler/rediscli.css b/flask_admin/static/admin/css/tabler/rediscli.css new file mode 100644 index 0000000000..c1d9f1a644 --- /dev/null +++ b/flask_admin/static/admin/css/tabler/rediscli.css @@ -0,0 +1,48 @@ +.console { + position: relative; + width: 100%; + min-height: 400px; +} + +.console-container { + border-radius: 4px; + position: absolute; + border: 1px solid #d4d4d4; + padding: 2px; + overflow: scroll; + top: 2px; + left: 2px; + right: 2px; + bottom: 5em; +} + +.console-line { + position: absolute; + left: 2px; + right: 2px; + bottom: 2px; +} + +.console-line input { + width: 100%; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + height: 2em; +} + +.console .cmd { + background-color: #f5f5f5; + padding: 2px; + margin: 1px; +} + +.console .response { + background-color: #f0f0f0; + padding: 2px; + margin: 1px; +} + +.console .error { + color: red; +} diff --git a/flask_admin/static/admin/js/tabler_filters.js b/flask_admin/static/admin/js/tabler_filters.js new file mode 100644 index 0000000000..921ad1c5d1 --- /dev/null +++ b/flask_admin/static/admin/js/tabler_filters.js @@ -0,0 +1,184 @@ +// bs4 converted to tabler + +const AdminFilters = function (element, filtersElement, filterGroups, activeFilters) { + const root = document.querySelector(element); + const filterTable = root.querySelector('.filters'); + let lastCount = 0; + + function getCount(name) { + const idx = name.indexOf('_'); + if (idx === -1) return 0; + return parseInt(name.substr(3, idx - 3), 10); + } + + function makeName(name) { + return 'flt' + (lastCount++) + '_' + name; + } + + function showApplyButton() { + root.querySelectorAll('button[type="submit"]').forEach(function (b) { + b.classList.remove('d-none'); + }); + } + + function hideApplyButton() { + root.querySelectorAll('button[type="submit"]').forEach(function (b) { + b.classList.add('d-none'); + }); + } + + // Return correct + function inputTypeFor(filter) { + switch (filter.type) { + case 'datepicker': + case 'daterangepicker': + return 'date'; + case 'datetimepicker': + case 'datetimerangepicker': + return 'datetime-local'; + case 'timepicker': + case 'timerangepicker': + return 'time'; + default: + return 'text'; + } + } + + function createFilterInput(placeholderTd, filterValue, filter) { + const td = document.createElement('td'); + let field; + + if (filter.options) { + field = document.createElement('select'); + field.className = 'filter-val form-select form-select-sm'; + filter.options.forEach(function (pair) { + const opt = document.createElement('option'); + opt.value = pair[0]; + opt.textContent = pair[1]; + if (filterValue != null && filterValue == pair[0]) { + opt.selected = true; + } + field.appendChild(opt); + }); + } else { + field = document.createElement('input'); + field.type = inputTypeFor(filter); + field.className = 'filter-val form-control form-control-sm'; + if (filterValue != null) field.value = filterValue; + } + + field.name = makeName(filter.arg); + field.addEventListener('input', showApplyButton); + field.addEventListener('change', showApplyButton); + td.appendChild(field); + placeholderTd.replaceWith(td); + return field; + } + + function changeOperation(subfilters, row, opSelect) { + const selectedFilter = subfilters[opSelect.selectedIndex]; + const lastTd = row.querySelector('td:last-child'); + createFilterInput(lastTd, null, selectedFilter); + showApplyButton(); + } + + function removeFilter(row) { + row.remove(); + const remaining = filterTable.querySelectorAll('tr'); + if (remaining.length === 0) { + hideApplyButton(); + } else { + showApplyButton(); + } + } + + function addFilter(name, subfilters, selectedIndex, filterValue) { + let tbody = filterTable.querySelector('tbody'); + if (!tbody) { + tbody = document.createElement('tbody'); + filterTable.appendChild(tbody); + } + + const row = document.createElement('tr'); + tbody.appendChild(row); + + const labelTd = document.createElement('td'); + const removeBtn = document.createElement('a'); + removeBtn.href = '#'; + removeBtn.className = 'btn btn-secondary btn-sm remove-filter'; + removeBtn.innerHTML = '× ' + name; + removeBtn.addEventListener('click', function (e) { + e.preventDefault(); + removeFilter(row); + }); + labelTd.appendChild(removeBtn); + row.appendChild(labelTd); + + const opTd = document.createElement('td'); + const opSelect = document.createElement('select'); + opSelect.className = 'filter-op form-select form-select-sm'; + + let filterSelection = 0; + subfilters.forEach(function (subfilter, i) { + const opt = document.createElement('option'); + opt.value = subfilter.arg; + opt.textContent = subfilter.operation; + if (subfilter.index == selectedIndex) { + opt.selected = true; + filterSelection = i; + } + opSelect.appendChild(opt); + }); + opTd.appendChild(opSelect); + row.appendChild(opTd); + + const valueTd = document.createElement('td'); + row.appendChild(valueTd); + + const filter = subfilters[filterSelection]; + const field = createFilterInput(valueTd, filterValue, filter); + + opSelect.addEventListener('change', function () { + changeOperation(subfilters, row, opSelect); + }); + + field.focus(); + return field; + } + + const filtersMenu = document.querySelector(filtersElement); + if (filtersMenu) { + filtersMenu.addEventListener('click', function (e) { + const link = e.target.closest('a.filter'); + if (!link) return; + const name = link.textContent.trim(); + addFilter(name, filterGroups[name], false, null); + showApplyButton(); + }); + } + + activeFilters.forEach(function (activeFilter) { + const idx = activeFilter[0]; + const name = activeFilter[1]; + const filterValue = activeFilter[2]; + addFilter(name, filterGroups[name], idx, filterValue); + }); + + root.querySelectorAll('.filter-val').forEach(function (el) { + const count = getCount(el.name || ''); + if (count > lastCount) lastCount = count; + }); + lastCount += 1; +}; + +document.addEventListener('DOMContentLoaded', function () { + const filterGroupsEl = document.getElementById('filter-groups-data'); + if (filterGroupsEl) { + new AdminFilters( + '#filter_form', + '.field-filters', + JSON.parse(filterGroupsEl.textContent), + JSON.parse(document.getElementById('active-filters-data').textContent) + ); + } +}); diff --git a/flask_admin/static/admin/js/tabler_modal.js b/flask_admin/static/admin/js/tabler_modal.js new file mode 100644 index 0000000000..da35c26c3b --- /dev/null +++ b/flask_admin/static/admin/js/tabler_modal.js @@ -0,0 +1,14 @@ +document.addEventListener('show.bs.modal', function (event) { + const trigger = event.relatedTarget; + if (!trigger) return; + + const href = trigger.getAttribute('href'); + if (!href || href === '#' || href.startsWith('javascript')) return; + + const modalContent = event.target.querySelector('.modal-content'); + if (!modalContent) return; + + fetch(href) + .then(function (response) { return response.text(); }) + .then(function (html) { modalContent.innerHTML = html; }); +}); diff --git a/flask_admin/templates/tabler/admin/_theme_toggle.html b/flask_admin/templates/tabler/admin/_theme_toggle.html new file mode 100644 index 0000000000..39381755b5 --- /dev/null +++ b/flask_admin/templates/tabler/admin/_theme_toggle.html @@ -0,0 +1,23 @@ +{% if icon_only %} + +{% else %} + +{% endif %} diff --git a/flask_admin/templates/tabler/admin/actions.html b/flask_admin/templates/tabler/admin/actions.html new file mode 100644 index 0000000000..54d3f65ed9 --- /dev/null +++ b/flask_admin/templates/tabler/admin/actions.html @@ -0,0 +1,38 @@ +{% import 'admin/static.html' as admin_static with context %} + +{% macro dropdown(actions, btn_class='nav-link dropdown-toggle') -%} + + +{% endmacro %} + +{% macro form(actions, url) %} + {% if actions %} +
+ {% if action_form.csrf_token is defined and action_form.csrf_token %} + {{ action_form.csrf_token }} + {% elif csrf_token is defined and csrf_token %} + + {% endif %} + {% if return_url is defined and return_url %} + {{ action_form.url(value=return_url) }} + {% else %} + {{ action_form.url() }} + {% endif %} + {{ action_form.action() }} +
+ {% endif %} +{% endmacro %} + +{% macro script(message, actions, actions_confirmation) %} + {% if actions %} +
{{ actions_confirmation|tojson|safe }}
+
{{ message|tojson|safe }}
+ + {% endif %} +{% endmacro %} diff --git a/flask_admin/templates/tabler/admin/base.html b/flask_admin/templates/tabler/admin/base.html new file mode 100644 index 0000000000..fd119628fb --- /dev/null +++ b/flask_admin/templates/tabler/admin/base.html @@ -0,0 +1,228 @@ +{% import 'admin/layout.html' as layout with context -%} +{% import 'admin/static.html' as admin_static with context %} +{% set _t = admin_view.admin.theme %} +{% set menu_class = 'navbar-nav pt-lg-3' if _t.is_sidebar_layout else 'navbar-nav' %} +{% if _t.is_sidebar_layout %} +{% set menu_links_class = 'navbar-nav' %} +{% elif _t.is_condensed_layout %} +{% set menu_links_class = 'navbar-nav ms-md-auto' %} +{% else %} +{% set menu_links_class = 'navbar-nav' %} +{% endif %} +{% set brand_markup %} +{% block brand %} + + Flask Admin Tabler + +{% endblock %} +{% endset %} +{% set main_menu_markup %} +{% block main_menu %} + +{% endblock %} +{% endset %} +{% set menu_links_markup %} +{% block menu_links %} + +{% endblock %} +{% endset %} +{% set access_control_markup %} +{% block access_control %} +{% endblock %} +{% endset %} + + + + + + {% block title %}{% if admin_view.category %}{{ admin_view.category }} - {% endif %}{{ admin_view.name }} - {{ admin_view.admin.name }}{% endblock %} + {% block head_meta %} + + + + + + {% endblock %} + {% block head_css %} + {% if _t.use_cdn %} + + + + {% else %} + + + + {% endif %} + + {% if admin_view.extra_css %} + {% for css_url in admin_view.extra_css %} + + {% endfor %} + {% endif %} + {% endblock %} + {% block head %} + {% endblock %} + {% block head_tail %} + {% endblock %} + + + + {# Apply stored dark/light preference immediately to prevent flash. + Only dark/light is user-switchable; other theme settings are server-side. #} + + +
+ {% block page_body %} + {% if _t.is_sidebar_layout %} + + {% else %} + + {% if _t.is_fluid_layout %} + + {% endif %} + {% endif %} + +
+ + +
+
+ {% block messages %} + {{ layout.messages() }} + {% endblock %} + + {# store the jinja2 context for form_rules rendering logic #} + {% set render_ctx = h.resolve_ctx() %} + + {% block body %}{% endblock %} +
+
+
+ {% endblock %} +
+ + {% block tail_js %} + {% if _t.use_cdn %} + + {% else %} + + {% endif %} + + + + + + {% if admin_view.extra_js %} + {% for js_url in admin_view.extra_js %} + + {% endfor %} + {% endif %} + {% endblock %} + + {% block tail %} + {% endblock %} + + + diff --git a/flask_admin/templates/tabler/admin/file/form.html b/flask_admin/templates/tabler/admin/file/form.html new file mode 100644 index 0000000000..162427cda3 --- /dev/null +++ b/flask_admin/templates/tabler/admin/file/form.html @@ -0,0 +1,29 @@ +{% extends 'admin/master.html' %} +{% import 'admin/lib.html' as lib with context %} + +{% block head %} + {{ super() }} + {{ lib.form_css() }} +{% endblock %} + +{% block body %} + + + {% block fa_form %} + {{ lib.render_form(form, dir_url) }} + {% endblock %} +{% endblock %} + +{% block tail %} + {{ super() }} + {{ lib.form_js() }} +{% endblock %} diff --git a/flask_admin/templates/tabler/admin/file/list.html b/flask_admin/templates/tabler/admin/file/list.html new file mode 100644 index 0000000000..7b18f39db8 --- /dev/null +++ b/flask_admin/templates/tabler/admin/file/list.html @@ -0,0 +1,210 @@ +{% extends 'admin/master.html' %} +{% import 'admin/lib.html' as lib with context %} +{% import 'admin/actions.html' as actionslib with context %} + +{% block body %} + {# ── Breadcrumb ───────────────────────────────────────────────────────── #} + {% block breadcrums %} + + {% endblock %} + + {# ── Toolbar ──────────────────────────────────────────────────────────── #} + {% block toolbar %} +
+ {% if admin_view.can_upload %} + {%- if admin_view.upload_modal -%} + {{ lib.add_modal_button(url=get_dir_url('.upload', path=dir_path, modal=True), + btn_class="btn btn-primary", + content='' ~ _gettext('Upload File')) }} + {% else %} + + {{ _gettext('Upload File') }} + + {%- endif -%} + {% endif %} + {% if admin_view.can_mkdir %} + {%- if admin_view.mkdir_modal -%} + {{ lib.add_modal_button(url=get_dir_url('.mkdir', path=dir_path, modal=True), + btn_class="btn btn-secondary", + content='' ~ _gettext('Create Directory')) }} + {% else %} + + {{ _gettext('Create Directory') }} + + {%- endif -%} + {% endif %} + {% if actions %} + {{ actionslib.dropdown(actions, 'dropdown-toggle btn btn-outline-secondary') }} + {% endif %} +
+ {% endblock %} + + {# ── File table ───────────────────────────────────────────────────────── #} + {% block file_list_table %} +
+ + + + {% block list_header scoped %} + {% if actions %} + + {% endif %} + + {% for column in admin_view.column_list %} + + {% endfor %} + {% endblock %} + + + + {% for name, path, is_dir, size, date in items %} + + {% block list_row scoped %} + {% if actions %} + + {% endif %} + + {% if is_dir %} + + {% else %} + + {% if admin_view.is_column_visible('size') %} + + {% endif %} + {% endif %} + {% if admin_view.is_column_visible('date') %} + + {% endif %} + {% endblock %} + + {% endfor %} + +
+ +   + {% if admin_view.is_column_sortable(column) %} + {% if sort_column == column %} + + {{ admin_view.column_label(column) }} + {% if sort_desc %} + + {% else %} + + {% endif %} + + {% else %} + + {{ admin_view.column_label(column) }} + + {% endif %} + {% else %} + {{ _gettext(admin_view.column_label(column)) }} + {% endif %} +
+ {% if not is_dir %} + + {% endif %} + + {% block list_row_actions scoped %} +
+ {% if admin_view.can_rename and path and name != '..' %} + {%- if admin_view.rename_modal -%} + {{ lib.add_modal_button(url=get_url('.rename', path=path, modal=True), + title=_gettext('Rename File'), + content='') }} + {% else %} + + + + {%- endif -%} + {% endif %} + {%- if admin_view.can_delete and path -%} + {% if is_dir %} + {% if name != '..' and admin_view.can_delete_dirs %} +
+ {{ delete_form.path(value=path) }} + {% if delete_form.csrf_token is defined and delete_form.csrf_token %} + {{ delete_form.csrf_token }} + {% endif %} + +
+ {% endif %} + {% else %} +
+ {{ delete_form.path(value=path) }} + {% if delete_form.csrf_token is defined and delete_form.csrf_token %} + {{ delete_form.csrf_token }} + {% endif %} + +
+ {% endif %} + {%- endif -%} +
+ {% endblock %} +
+ + {{ name }} + + + {% if admin_view.can_download %} + {%- if admin_view.edit_modal and admin_view.is_file_editable(path) -%} + {{ lib.add_modal_button(url=get_file_url(path, modal=True)|safe, + btn_class='', content=name) }} + {% else %} + + {{ name }} + + {%- endif -%} + {% else %} + {{ name }} + {% endif %} + {{ size|filesizeformat }}{{ timestamp_format(date) }}
+
+ {% endblock %} + + {% block actions %} + {{ actionslib.form(actions, get_url('.action_view')) }} + {% endblock %} + + {%- if admin_view.rename_modal or admin_view.mkdir_modal + or admin_view.upload_modal or admin_view.edit_modal -%} + {{ lib.add_modal_window() }} + {%- endif -%} +{% endblock %} + +{% block tail %} + {{ super() }} + {{ actionslib.script(_gettext('Please select at least one file.'), + actions, + actions_confirmation) }} + +{% endblock %} diff --git a/flask_admin/templates/tabler/admin/file/modals/form.html b/flask_admin/templates/tabler/admin/file/modals/form.html new file mode 100644 index 0000000000..5838a6d4fe --- /dev/null +++ b/flask_admin/templates/tabler/admin/file/modals/form.html @@ -0,0 +1,19 @@ +{% import 'admin/static.html' as admin_static with context %} +{% import 'admin/lib.html' as lib with context %} + +{% block body %} + {# content added to modal-content #} + + +{% endblock %} + +{% block tail %} + +{% endblock %} diff --git a/flask_admin/templates/tabler/admin/index.html b/flask_admin/templates/tabler/admin/index.html new file mode 100644 index 0000000000..fbfdf4c0b8 --- /dev/null +++ b/flask_admin/templates/tabler/admin/index.html @@ -0,0 +1,4 @@ +{% extends 'admin/master.html' %} + +{% block body %} +{% endblock %} diff --git a/flask_admin/templates/tabler/admin/layout.html b/flask_admin/templates/tabler/admin/layout.html new file mode 100644 index 0000000000..f76963462c --- /dev/null +++ b/flask_admin/templates/tabler/admin/layout.html @@ -0,0 +1,107 @@ +{% macro menu_icon(item) -%} + +{% if item %} +{% set icon_type = item.get_icon_type() %} +{%- if icon_type %} +{% set icon_value = item.get_icon_value() %} +{% if icon_type == 'glyph' %} + +{% elif icon_type == 'fa' %} + +{% elif icon_type == 'ti' %} + +{% elif icon_type == 'image' %} +menu image +{% elif icon_type == 'image-url' %} +menu image +{% endif %} +{% endif %} +{% endif %} + +{%- endmacro %} + +{% macro menu(menu_root=None) %} +{% set is_main_nav = menu_root == None %} +{% if menu_root is none %}{% set menu_root = admin_view.admin.menu() %}{% endif %} +{%- for item in menu_root %} +{%- if item.is_category() -%} +{% set children = item.get_children() %} +{%- if children %} +{% set class_name = item.get_class_name() or '' %} + +{% set _sidebar = admin_view.admin.theme.is_sidebar_layout %} + +{% endif %} +{%- else %} +{%- if item.is_accessible() and item.is_visible() -%} +{% set class_name = item.get_class_name() %} + +{%- endif -%} +{% endif -%} +{% endfor %} +{% endmacro %} + +{% macro menu_links(links=None) %} +{% if links is none %}{% set links = admin_view.admin.menu_links() %}{% endif %} +{% for item in links %} +{% set class_name = item.get_class_name() %} +{% if item.is_accessible() and item.is_visible() %} + +{% endif %} +{% endfor %} +{% endmacro %} + +{% macro messages() %} +{% with messages = get_flashed_messages(with_categories=True) %} +{% if messages %} +{% for category, m in messages %} +{% if category %} +{% set mapping = {'message': 'info', 'error': 'danger'} %} +