litedmin 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- litedmin-0.4.0/CHANGELOG.md +76 -0
- litedmin-0.4.0/LICENSE +21 -0
- litedmin-0.4.0/MANIFEST.in +10 -0
- litedmin-0.4.0/PKG-INFO +302 -0
- litedmin-0.4.0/README.md +242 -0
- litedmin-0.4.0/litedmin/__init__.py +136 -0
- litedmin-0.4.0/litedmin/__main__.py +6 -0
- litedmin-0.4.0/litedmin/backend/__init__.py +1 -0
- litedmin-0.4.0/litedmin/backend/admin/__init__.py +17 -0
- litedmin-0.4.0/litedmin/backend/admin/base.py +902 -0
- litedmin-0.4.0/litedmin/backend/admin/changelist.py +211 -0
- litedmin-0.4.0/litedmin/backend/admin/checks.py +119 -0
- litedmin-0.4.0/litedmin/backend/admin/display_columns.py +221 -0
- litedmin-0.4.0/litedmin/backend/admin/export.py +57 -0
- litedmin-0.4.0/litedmin/backend/admin/formsets.py +299 -0
- litedmin-0.4.0/litedmin/backend/admin/list_projection.py +147 -0
- litedmin-0.4.0/litedmin/backend/admin/mixins.py +75 -0
- litedmin-0.4.0/litedmin/backend/admin/pagination.py +149 -0
- litedmin-0.4.0/litedmin/backend/admin/permissions.py +103 -0
- litedmin-0.4.0/litedmin/backend/admin/scope.py +8 -0
- litedmin-0.4.0/litedmin/backend/api/__init__.py +26 -0
- litedmin-0.4.0/litedmin/backend/api/autocomplete.py +214 -0
- litedmin-0.4.0/litedmin/backend/api/core.py +1017 -0
- litedmin-0.4.0/litedmin/backend/api/endpoints.py +352 -0
- litedmin-0.4.0/litedmin/backend/api/history/__init__.py +11 -0
- litedmin-0.4.0/litedmin/backend/api/history/repository.py +273 -0
- litedmin-0.4.0/litedmin/backend/api/history/service.py +105 -0
- litedmin-0.4.0/litedmin/backend/api/responses.py +67 -0
- litedmin-0.4.0/litedmin/backend/auth/__init__.py +69 -0
- litedmin-0.4.0/litedmin/backend/auth/admin.py +298 -0
- litedmin-0.4.0/litedmin/backend/auth/auth.py +172 -0
- litedmin-0.4.0/litedmin/backend/auth/backends.py +191 -0
- litedmin-0.4.0/litedmin/backend/auth/cookies.py +37 -0
- litedmin-0.4.0/litedmin/backend/auth/csrf.py +99 -0
- litedmin-0.4.0/litedmin/backend/auth/dependencies.py +205 -0
- litedmin-0.4.0/litedmin/backend/auth/gateway_web.py +266 -0
- litedmin-0.4.0/litedmin/backend/auth/hashers.py +186 -0
- litedmin-0.4.0/litedmin/backend/auth/identity.py +118 -0
- litedmin-0.4.0/litedmin/backend/auth/models/__init__.py +75 -0
- litedmin-0.4.0/litedmin/backend/auth/models/registry.py +162 -0
- litedmin-0.4.0/litedmin/backend/auth/models/sqlalchemy/__init__.py +21 -0
- litedmin-0.4.0/litedmin/backend/auth/models/sqlalchemy/base.py +5 -0
- litedmin-0.4.0/litedmin/backend/auth/models/sqlalchemy/builder.py +148 -0
- litedmin-0.4.0/litedmin/backend/auth/models/sqlalchemy/models.py +193 -0
- litedmin-0.4.0/litedmin/backend/auth/models/tortoise/__init__.py +19 -0
- litedmin-0.4.0/litedmin/backend/auth/models/tortoise/builder.py +144 -0
- litedmin-0.4.0/litedmin/backend/auth/models/tortoise/core.py +66 -0
- litedmin-0.4.0/litedmin/backend/auth/models/tortoise/models.py +125 -0
- litedmin-0.4.0/litedmin/backend/auth/permission_service.py +424 -0
- litedmin-0.4.0/litedmin/backend/auth/resolver.py +34 -0
- litedmin-0.4.0/litedmin/backend/auth/session.py +253 -0
- litedmin-0.4.0/litedmin/backend/auth/settings.py +17 -0
- litedmin-0.4.0/litedmin/backend/auth/user_model.py +101 -0
- litedmin-0.4.0/litedmin/backend/auth/utils.py +197 -0
- litedmin-0.4.0/litedmin/backend/core/__init__.py +1 -0
- litedmin-0.4.0/litedmin/backend/core/action.py +91 -0
- litedmin-0.4.0/litedmin/backend/core/config.py +110 -0
- litedmin-0.4.0/litedmin/backend/core/decorators.py +62 -0
- litedmin-0.4.0/litedmin/backend/core/display.py +90 -0
- litedmin-0.4.0/litedmin/backend/core/enums.py +80 -0
- litedmin-0.4.0/litedmin/backend/core/exceptions.py +43 -0
- litedmin-0.4.0/litedmin/backend/core/fields.py +224 -0
- litedmin-0.4.0/litedmin/backend/core/introspection.py +118 -0
- litedmin-0.4.0/litedmin/backend/core/orm_settings.py +42 -0
- litedmin-0.4.0/litedmin/backend/core/request.py +55 -0
- litedmin-0.4.0/litedmin/backend/core/schema_generator.py +895 -0
- litedmin-0.4.0/litedmin/backend/core/site.py +370 -0
- litedmin-0.4.0/litedmin/backend/core/utils.py +162 -0
- litedmin-0.4.0/litedmin/backend/core/widgets.py +100 -0
- litedmin-0.4.0/litedmin/backend/dto/__init__.py +137 -0
- litedmin-0.4.0/litedmin/backend/dto/auth.py +82 -0
- litedmin-0.4.0/litedmin/backend/dto/base.py +39 -0
- litedmin-0.4.0/litedmin/backend/dto/config.py +80 -0
- litedmin-0.4.0/litedmin/backend/dto/permissions.py +19 -0
- litedmin-0.4.0/litedmin/backend/dto/records.py +214 -0
- litedmin-0.4.0/litedmin/backend/dto/requests.py +519 -0
- litedmin-0.4.0/litedmin/backend/dto/responses.py +367 -0
- litedmin-0.4.0/litedmin/backend/integration/__init__.py +56 -0
- litedmin-0.4.0/litedmin/backend/integration/admin_lifecycle.py +136 -0
- litedmin-0.4.0/litedmin/backend/integration/asgi/__init__.py +5 -0
- litedmin-0.4.0/litedmin/backend/integration/asgi/admin.py +307 -0
- litedmin-0.4.0/litedmin/backend/integration/asgi/request.py +43 -0
- litedmin-0.4.0/litedmin/backend/integration/asgi/responses.py +43 -0
- litedmin-0.4.0/litedmin/backend/integration/asgi/routes.py +475 -0
- litedmin-0.4.0/litedmin/backend/integration/asgi/spa.py +71 -0
- litedmin-0.4.0/litedmin/backend/integration/asgi/trailing_slash.py +49 -0
- litedmin-0.4.0/litedmin/backend/integration/csrf_middleware.py +101 -0
- litedmin-0.4.0/litedmin/backend/integration/di/__init__.py +21 -0
- litedmin-0.4.0/litedmin/backend/integration/di/container.py +49 -0
- litedmin-0.4.0/litedmin/backend/integration/di/fastapi_integration.py +45 -0
- litedmin-0.4.0/litedmin/backend/integration/di/providers.py +100 -0
- litedmin-0.4.0/litedmin/backend/integration/fastapi/__init__.py +7 -0
- litedmin-0.4.0/litedmin/backend/integration/fastapi/admin.py +35 -0
- litedmin-0.4.0/litedmin/backend/integration/frontend_paths.py +35 -0
- litedmin-0.4.0/litedmin/backend/integration/handlers.py +168 -0
- litedmin-0.4.0/litedmin/backend/integration/litestar/__init__.py +7 -0
- litedmin-0.4.0/litedmin/backend/integration/litestar/admin.py +40 -0
- litedmin-0.4.0/litedmin/backend/integration/msgspec_support.py +63 -0
- litedmin-0.4.0/litedmin/backend/integration/orm_registry.py +99 -0
- litedmin-0.4.0/litedmin/backend/integration/static.py +50 -0
- litedmin-0.4.0/litedmin/backend/orm/__init__.py +35 -0
- litedmin-0.4.0/litedmin/backend/orm/base.py +130 -0
- litedmin-0.4.0/litedmin/backend/orm/batch.py +42 -0
- litedmin-0.4.0/litedmin/backend/orm/date_filters.py +68 -0
- litedmin-0.4.0/litedmin/backend/orm/pk.py +80 -0
- litedmin-0.4.0/litedmin/backend/orm/protocols.py +144 -0
- litedmin-0.4.0/litedmin/backend/orm/registry.py +156 -0
- litedmin-0.4.0/litedmin/backend/orm/sqlalchemy/__init__.py +50 -0
- litedmin-0.4.0/litedmin/backend/orm/sqlalchemy/adapter.py +8 -0
- litedmin-0.4.0/litedmin/backend/orm/sqlalchemy/admin.py +40 -0
- litedmin-0.4.0/litedmin/backend/orm/sqlalchemy/backend.py +56 -0
- litedmin-0.4.0/litedmin/backend/orm/sqlalchemy/init.py +251 -0
- litedmin-0.4.0/litedmin/backend/orm/sqlalchemy/introspector.py +254 -0
- litedmin-0.4.0/litedmin/backend/orm/sqlalchemy/repository.py +740 -0
- litedmin-0.4.0/litedmin/backend/orm/sqlalchemy/schema_generator.py +15 -0
- litedmin-0.4.0/litedmin/backend/orm/sqlalchemy/session.py +94 -0
- litedmin-0.4.0/litedmin/backend/orm/sqlalchemy/types.py +53 -0
- litedmin-0.4.0/litedmin/backend/orm/tortoise/__init__.py +44 -0
- litedmin-0.4.0/litedmin/backend/orm/tortoise/adapter.py +9 -0
- litedmin-0.4.0/litedmin/backend/orm/tortoise/admin.py +42 -0
- litedmin-0.4.0/litedmin/backend/orm/tortoise/backend.py +56 -0
- litedmin-0.4.0/litedmin/backend/orm/tortoise/init.py +103 -0
- litedmin-0.4.0/litedmin/backend/orm/tortoise/introspector.py +176 -0
- litedmin-0.4.0/litedmin/backend/orm/tortoise/repository.py +284 -0
- litedmin-0.4.0/litedmin/backend/orm/tortoise/schema_generator.py +15 -0
- litedmin-0.4.0/litedmin/backend/orm/tortoise/types.py +81 -0
- litedmin-0.4.0/litedmin/backend/storage/__init__.py +5 -0
- litedmin-0.4.0/litedmin/backend/storage/protocol.py +33 -0
- litedmin-0.4.0/litedmin/cli.py +195 -0
- litedmin-0.4.0/litedmin/py.typed +0 -0
- litedmin-0.4.0/litedmin/static/asset-manifest.json +85 -0
- litedmin-0.4.0/litedmin/static/favicon.ico +0 -0
- litedmin-0.4.0/litedmin/static/index.html +1 -0
- litedmin-0.4.0/litedmin/static/logo192.png +0 -0
- litedmin-0.4.0/litedmin/static/logo512.png +0 -0
- litedmin-0.4.0/litedmin/static/manifest.json +25 -0
- litedmin-0.4.0/litedmin/static/robots.txt +3 -0
- litedmin-0.4.0/litedmin/static/static/css/main.c336e45a.css +2 -0
- litedmin-0.4.0/litedmin/static/static/css/main.c336e45a.css.map +1 -0
- litedmin-0.4.0/litedmin/static/static/js/453.7b4f4f73.chunk.js +2 -0
- litedmin-0.4.0/litedmin/static/static/js/453.7b4f4f73.chunk.js.map +1 -0
- litedmin-0.4.0/litedmin/static/static/js/main.06b3b9a6.js +156 -0
- litedmin-0.4.0/litedmin/static/static/js/main.06b3b9a6.js.LICENSE.txt +91 -0
- litedmin-0.4.0/litedmin/static/static/js/main.06b3b9a6.js.map +1 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-300-normal.ead2b63c30510e752332.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-300-normal.f9e5f604a45c00a532f2.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-400-normal.cb04b2ee2f7d192478b8.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-400-normal.e2841352dd9512ff455b.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-500-normal.b9dcbca183ac93f02061.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-500-normal.e824ac3c6c579cd0fe85.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-600-normal.c556b5d042ebf128e47c.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-600-normal.ce60970447cde0dd1582.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-700-normal.36e9cf9ecc07ac6bce28.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-700-normal.534fd15b796bf021d0be.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-ext-300-normal.04c5d2c480c226920a9b.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-ext-300-normal.eb97c8e4e9062ed5dfd2.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-ext-400-normal.e89451624c6caf35687f.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-ext-400-normal.fd1478dcc74de00ee4c0.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-ext-500-normal.023db878f10be2203b60.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-ext-500-normal.9160d5ba9e6099f95032.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-ext-600-normal.25b7c495f686777a1c08.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-ext-600-normal.aa900fea9aed1ba3c903.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-ext-700-normal.3cf42e401103d3211022.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-cyrillic-ext-700-normal.5631f054acb5a33a716a.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-300-normal.3703eb6abf02d92c4c6d.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-300-normal.e35098a79e23df5c2282.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-400-normal.a42da273bb968bf2058d.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-400-normal.a8de720a6f13c66c8a4e.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-500-normal.0d91a975f010051e7150.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-500-normal.cbc71955e9956dcd2882.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-600-normal.5c46a357ea91911fc65d.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-600-normal.ba3b8b69a69b0e83d453.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-700-normal.f47ebf852d0b0e251a80.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-700-normal.f5fbdfc39c2431160896.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-ext-300-normal.6479663fa09debba7d48.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-ext-300-normal.c0dba9f477d8b8e0198f.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-ext-400-normal.b9e1e89444a313a63b91.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-ext-400-normal.f2fa0d9ef98f59d4a75b.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-ext-500-normal.7f67cfd4832dde0e0494.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-ext-500-normal.b187635a3d8a0b90faa0.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-ext-600-normal.7e7594e0432b84536054.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-ext-600-normal.c8a7cddec49294a9e8ba.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-ext-700-normal.4c7cc2277f187ed0ef37.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-greek-ext-700-normal.4cdf2b4e817dade3d4aa.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-300-normal.6e6082ec285b38897e2f.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-300-normal.b7ec8f2cad1cc72e39fa.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-400-normal.9698cc7dbf147ed363b6.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-400-normal.b0c8fe9db56dd415a25a.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-500-normal.5b7156f802c053798d95.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-500-normal.f46567788e464ac34d10.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-600-normal.28c8f6060760ec1a5d25.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-600-normal.9d111139a583baef3053.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-700-normal.d42de9b25ac31d2a2a8b.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-700-normal.fc47ca451598e4b97249.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-ext-300-normal.1230c922d601129d8b4c.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-ext-300-normal.f95a500034ca27bc4af3.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-ext-400-normal.1c20f7dc20d731a18fcd.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-ext-400-normal.3d10c85fbd16969509a2.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-ext-500-normal.2b8be079a0e965b2f035.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-ext-500-normal.667f22c9b36deaea0f54.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-ext-600-normal.0acb54c755dec53de476.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-ext-600-normal.ae5f9691ebe48c908e8c.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-ext-700-normal.149d81a49bd1ff0e6711.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-latin-ext-700-normal.96e3bd6bb1f980376cf1.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-vietnamese-300-normal.2a4055b17286ecdd503d.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-vietnamese-300-normal.39c118d908e573021d87.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-vietnamese-400-normal.44c9df135eec470b0ab6.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-vietnamese-400-normal.96f8adc74b63e1a3655f.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-vietnamese-500-normal.11aeae6e31a0d8c24c83.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-vietnamese-500-normal.63d02ea896a7406849b6.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-vietnamese-600-normal.6984cf5e8583fac83be3.woff2 +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-vietnamese-600-normal.a10931d3a643f48663b9.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-vietnamese-700-normal.5b5cd0c77cad23651d04.woff +0 -0
- litedmin-0.4.0/litedmin/static/static/media/inter-vietnamese-700-normal.5d9bfcacc44f345662ff.woff2 +0 -0
- litedmin-0.4.0/litedmin.egg-info/PKG-INFO +302 -0
- litedmin-0.4.0/litedmin.egg-info/SOURCES.txt +220 -0
- litedmin-0.4.0/litedmin.egg-info/dependency_links.txt +1 -0
- litedmin-0.4.0/litedmin.egg-info/entry_points.txt +2 -0
- litedmin-0.4.0/litedmin.egg-info/requires.txt +36 -0
- litedmin-0.4.0/litedmin.egg-info/top_level.txt +1 -0
- litedmin-0.4.0/pyproject.toml +127 -0
- litedmin-0.4.0/setup.cfg +4 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.4.0 — 2026-10-30
|
|
6
|
+
|
|
7
|
+
Public beta on [PyPI](https://pypi.org/project/litedmin/).
|
|
8
|
+
|
|
9
|
+
### Breaking changes
|
|
10
|
+
- List actions must be declared with **`@action`** — undecorated callables in `ModelAdmin.actions` raise `ImproperlyConfigured`
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `@action` decorator (Django `@admin.action` style): `description`, `row`, `bulk`, `toolbar`, `requires_confirmation`, `allow_empty`
|
|
14
|
+
- Row-level action buttons when `row=True`; page toolbar buttons when `toolbar=True`
|
|
15
|
+
- `BulkActionDTO.row` / `bulk` / `toolbar` in list schema
|
|
16
|
+
- Action return value (`str` / `{"message": ...}` / `ActionResponse`) becomes the SPA toast message
|
|
17
|
+
- `Textarea(collapse_chars=N)` widget helper — readonly preview + “Show full text” dialog
|
|
18
|
+
- Unified Starlette ASGI admin core; FastAPI/Litestar as thin mount adapters
|
|
19
|
+
- `run_action` enforces `allow_empty` / toolbar empty selection and `requires_confirmation` / `confirmed`
|
|
20
|
+
|
|
21
|
+
## 0.3.0 — 2026-10-01
|
|
22
|
+
|
|
23
|
+
### Breaking changes
|
|
24
|
+
- `ModelAdmin.get_queryset_filters()` renamed to **`get_list_scope_filters()`** — ORM-agnostic dict filters for `repository.list(filters=...)`
|
|
25
|
+
- Bulk action signature: third argument is **`objects`** (list of instances), not `queryset`
|
|
26
|
+
- `ChangeList.get_queryset_params()` renamed to **`get_list_params()`** (internal)
|
|
27
|
+
- Removed oauth2-proxy / edge-cookie PoC (`ExternalSessionAuthBackend`, trust-center, proxy headers, `infra/keycloak-oauth2-poc`); SSO is **gateway web handoff only**
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- Global staff gate: non-`is_staff` users cannot enter admin (login 403 / Access Denied stub; `/me` and `/logout` still work for identity)
|
|
31
|
+
- `GatewayWebAuthBackend` + `GatewayWebAuthConfig` — Keycloak SSO via shared gateway (HMAC `state=web:<app_id>:…`, HTTP handoff redeem, local `sessionid`)
|
|
32
|
+
- `IdentityProvisionConfig` / `resolve_user_from_identity` for JIT user mapping from gateway claims
|
|
33
|
+
- Routes `GET /admin/sso/login`, `GET /admin/sso/complete` (Litestar + FastAPI)
|
|
34
|
+
- `/admin/config` auth flags: `login_url`, `session_auth`, `logout_url`
|
|
35
|
+
- Example: `examples/tortoise/app_gateway_web.py`
|
|
36
|
+
- `resolve_list_scope_filters()` helper in `litedmin.backend.admin.scope`
|
|
37
|
+
- `ListEditableSaveResponse.skipped` — rows skipped due to missing object or `has_change_permission`
|
|
38
|
+
- Row-level scope applied to autocomplete options on related models
|
|
39
|
+
- Swappable `AUTH_USER_MODEL` / `auth_user_model`, CLI `createsuperuser`
|
|
40
|
+
- `PATCH /admin/{model}/{id}` for partial updates by primary key
|
|
41
|
+
- Enhanced rich text editor with media uploads
|
|
42
|
+
- JSON tree view for form values (wrap, copy, dialog for long strings)
|
|
43
|
+
- Changelist query projection to `list_display` columns only
|
|
44
|
+
- View-only users can open list rows in read-only form mode
|
|
45
|
+
- E2E tests: foreign PUT 403, export scope, list_editable skip, comment/post autocomplete scope
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
- Optional ORM / framework extras are lazy at import time
|
|
49
|
+
- List and autocomplete pagination aligned; dropped fake fetch-all page sizes
|
|
50
|
+
- Auth forms, SSO profile sync, and create-form field filtering improved
|
|
51
|
+
- Dashboard layout widened; disabled fields lightened
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
- `save_list_editable` now checks `has_change_permission(request, obj)` per row
|
|
55
|
+
- Session `expire_date` timezone handling for SQLAlchemy/asyncpg
|
|
56
|
+
- FastAPI admin: SPA serving, missing routes, and datetime writes
|
|
57
|
+
- String primary keys in admin instance routes and inline formset load/save
|
|
58
|
+
- List/autocomplete primary key resolution beyond attribute `id`
|
|
59
|
+
- Autocomplete `selected_ids` parsing ignores null values
|
|
60
|
+
- Admin SPA boot and existing inline rows
|
|
61
|
+
- Standalone ASGI run mode
|
|
62
|
+
|
|
63
|
+
## 0.2.0 — 2026-07-06
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
- `@display` decorator for computed list columns with optional ORM sorter
|
|
67
|
+
- CSV/JSON export with `has_export_permission` and list UI export buttons
|
|
68
|
+
- `StorageBackend` protocol and `ModelAdmin.upload_file` / `get_file_url` hooks
|
|
69
|
+
- File upload endpoint `POST /{model}/{id}/upload/{field}` and `file_upload` form widget
|
|
70
|
+
- Reference `LocalFileStorage` in `examples/shared/local_storage.py`
|
|
71
|
+
- Pytest markers, CI workflow, expanded test suite
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
- `ChangeList.get_full_response` is async (serializes `@display` columns)
|
|
75
|
+
- `ModelPermissionsDTO` includes `export` permission
|
|
76
|
+
- `OrmSettings` / app-owned DB initialization (from prior refactor)
|
litedmin-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mikhail Logunov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
litedmin-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: litedmin
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Modern admin interface inspired by Django Admin with React Material UI frontend
|
|
5
|
+
Author-email: Mikhail Logunov <milog7890@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ggb0yyyyyyy/litedmin
|
|
8
|
+
Project-URL: Repository, https://github.com/ggb0yyyyyyy/litedmin
|
|
9
|
+
Project-URL: Issues, https://github.com/ggb0yyyyyyy/litedmin/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/ggb0yyyyyyy/litedmin#readme
|
|
11
|
+
Keywords: admin,interface,django,react,material-ui,litestar,fastapi,sqlalchemy,tortoise
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: msgspec>=0.18.0
|
|
30
|
+
Requires-Dist: bcrypt>=4.0.0
|
|
31
|
+
Requires-Dist: starlette>=0.36.0
|
|
32
|
+
Requires-Dist: python-multipart>=0.0.6
|
|
33
|
+
Provides-Extra: tortoise
|
|
34
|
+
Requires-Dist: tortoise-orm>=0.20.0; extra == "tortoise"
|
|
35
|
+
Requires-Dist: aiosqlite>=0.16.0; extra == "tortoise"
|
|
36
|
+
Provides-Extra: sqlalchemy
|
|
37
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.0; extra == "sqlalchemy"
|
|
38
|
+
Requires-Dist: aiosqlite>=0.16.0; extra == "sqlalchemy"
|
|
39
|
+
Requires-Dist: greenlet>=3.0.0; extra == "sqlalchemy"
|
|
40
|
+
Provides-Extra: litestar
|
|
41
|
+
Requires-Dist: litestar>=2.0.0; extra == "litestar"
|
|
42
|
+
Requires-Dist: uvicorn>=0.24.0; extra == "litestar"
|
|
43
|
+
Requires-Dist: httpx>=0.24.0; extra == "litestar"
|
|
44
|
+
Provides-Extra: fastapi
|
|
45
|
+
Requires-Dist: fastapi>=0.104.0; extra == "fastapi"
|
|
46
|
+
Requires-Dist: uvicorn>=0.24.0; extra == "fastapi"
|
|
47
|
+
Requires-Dist: python-multipart>=0.0.6; extra == "fastapi"
|
|
48
|
+
Requires-Dist: dishka>=1.2.0; extra == "fastapi"
|
|
49
|
+
Requires-Dist: httpx>=0.24.0; extra == "fastapi"
|
|
50
|
+
Provides-Extra: dev
|
|
51
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
52
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
53
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
54
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
55
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
56
|
+
Requires-Dist: httpx>=0.24.0; extra == "dev"
|
|
57
|
+
Provides-Extra: all
|
|
58
|
+
Requires-Dist: litedmin[dev,fastapi,litestar,sqlalchemy,tortoise]; extra == "all"
|
|
59
|
+
Dynamic: license-file
|
|
60
|
+
|
|
61
|
+
# LiteAdmin
|
|
62
|
+
|
|
63
|
+
[](https://pypi.org/project/litedmin/)
|
|
64
|
+
[](https://pypi.org/project/litedmin/)
|
|
65
|
+
[](LICENSE)
|
|
66
|
+
|
|
67
|
+
Django Admin–style admin panel for **Litestar** and **FastAPI**, with **Tortoise ORM** and **SQLAlchemy**.
|
|
68
|
+
|
|
69
|
+
React UI ships in `litedmin/static/` — **no npm** needed for normal use.
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install litedmin
|
|
75
|
+
pip install "litedmin[fastapi,sqlalchemy]" # FastAPI + SQLAlchemy
|
|
76
|
+
pip install "litedmin[litestar,tortoise]" # Litestar + Tortoise
|
|
77
|
+
pip install "litedmin[all]" # both ORMs + frameworks + dev tools
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Core symbols (`AdminConfig`, `OrmSettings`, `register`, `ModelAdmin`) import without ORM/framework extras.
|
|
81
|
+
ORM and web stacks load only when you import their modules (or touch lazy attributes on `litedmin`).
|
|
82
|
+
|
|
83
|
+
### From source (examples / development)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
git clone https://github.com/ggb0yyyyyyy/litedmin.git
|
|
87
|
+
cd litedmin
|
|
88
|
+
pip install -e ".[litestar,tortoise]" # examples/tortoise (port 8000)
|
|
89
|
+
pip install -e ".[litestar,sqlalchemy]" # examples/sqlalchemy (port 8011)
|
|
90
|
+
pip install -e ".[fastapi,tortoise]"
|
|
91
|
+
pip install -e ".[all]"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Repository layout
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
liteDmin/
|
|
98
|
+
litedmin/ installable package
|
|
99
|
+
backend/
|
|
100
|
+
core/ AdminSite, config, OrmSettings, decorators
|
|
101
|
+
admin/ ModelAdmin, changelist, formsets, permissions
|
|
102
|
+
api/ AdminAPI / endpoints
|
|
103
|
+
auth/ session, backends, UserAdminMixin, system models
|
|
104
|
+
orm/ Tortoise / SQLAlchemy backends + repositories
|
|
105
|
+
integration/ Litestar & FastAPI Admin mounts, DI helpers
|
|
106
|
+
dto/ storage/
|
|
107
|
+
static/ bundled React UI
|
|
108
|
+
frontend/ React sources (rebuild → litedmin/static/)
|
|
109
|
+
examples/ Litestar + Tortoise / SQLAlchemy demos
|
|
110
|
+
tests/
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Quick start (Litestar + Tortoise)
|
|
114
|
+
|
|
115
|
+
Superuser is **not** created on app startup. Create it once via CLI, then run the example:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# optional credentials for CLI
|
|
119
|
+
export ADMIN_USERNAME=admin
|
|
120
|
+
export ADMIN_PASSWORD=admin
|
|
121
|
+
|
|
122
|
+
py -3.11 -m litedmin createsuperuser --settings examples.tortoise.settings:orm_settings
|
|
123
|
+
py -3.11 -m uvicorn examples.tortoise.app:app --host 0.0.0.0 --port 8000
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Open http://localhost:8000/admin
|
|
127
|
+
|
|
128
|
+
SQLAlchemy example: same flow with `examples.sqlalchemy.settings:orm_settings` and port **8011**.
|
|
129
|
+
Details: [examples/README.md](examples/README.md), [examples/CREDENTIALS.md](examples/CREDENTIALS.md).
|
|
130
|
+
|
|
131
|
+
### Minimal wiring
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from litestar import Litestar, get
|
|
135
|
+
|
|
136
|
+
# Shared Starlette Admin ASGI app; Litestar/FastAPI only provide mount helpers.
|
|
137
|
+
from litedmin.backend.integration.litestar import Admin
|
|
138
|
+
from litedmin.backend.integration import init_tortoise_with_litedmin, close_tortoise
|
|
139
|
+
# FastAPI: from litedmin.backend.integration.fastapi import Admin
|
|
140
|
+
# or: from litedmin import Admin
|
|
141
|
+
# Then: app.mount(admin.prefix, admin.as_asgi_app())
|
|
142
|
+
|
|
143
|
+
from examples.tortoise.admin_config import ExampleAdminConfig
|
|
144
|
+
from examples.tortoise.settings import AUTO_DISCOVER_MODULES, orm_settings
|
|
145
|
+
|
|
146
|
+
admin = Admin(
|
|
147
|
+
prefix='/admin',
|
|
148
|
+
config=ExampleAdminConfig(), # AdminConfig: theme / UI / auth_user_model
|
|
149
|
+
auto_discover_modules=AUTO_DISCOVER_MODULES,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
async def on_startup():
|
|
153
|
+
await init_tortoise_with_litedmin(orm_settings) # OrmSettings: ORM + DB
|
|
154
|
+
await admin.ensure_started() # validate admin + sync permissions (no user seed)
|
|
155
|
+
|
|
156
|
+
async def on_shutdown():
|
|
157
|
+
await close_tortoise()
|
|
158
|
+
|
|
159
|
+
@get('/')
|
|
160
|
+
async def root() -> dict:
|
|
161
|
+
return {'admin': '/admin'}
|
|
162
|
+
|
|
163
|
+
app = Litestar(
|
|
164
|
+
route_handlers=[root, admin.as_mount()],
|
|
165
|
+
on_startup=[on_startup],
|
|
166
|
+
on_shutdown=[on_shutdown],
|
|
167
|
+
)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### File / rich-text media storage
|
|
171
|
+
|
|
172
|
+
Pass any `StorageBackend` (`save` / `delete` / `url`) into `Admin(storage=...)`. Rich editor uploads go to `POST /admin/media/upload` and embed the returned `url` in HTML.
|
|
173
|
+
|
|
174
|
+
Examples use `LocalFileStorage` + `/media` static route — see [examples/README.md](examples/README.md). Swap in S3/MinIO by implementing the same protocol in your app.
|
|
175
|
+
|
|
176
|
+
SQLAlchemy apps also call `admin.configure_sqlalchemy(...)` after `init_sqlalchemy_with_litedmin_async` — see `examples/sqlalchemy/app.py`.
|
|
177
|
+
|
|
178
|
+
## ModelAdmin
|
|
179
|
+
|
|
180
|
+
- Prefer **`TortoiseModelAdmin`** / **`SQLAlchemyModelAdmin`** (repository save/delete).
|
|
181
|
+
- Register with `@register` / `AdminSite.register` — **`admin_site` is always injected**; do not construct ModelAdmin without a site.
|
|
182
|
+
- Permission / persist hooks are **`async`** (`has_*`, `get_permissions`, `get_list_scope_filters`, `save_model`, `delete_model`, `process_extra_fields`). Sync getters (`get_list_display`, …) stay sync.
|
|
183
|
+
- Users with password field: **`UserAdminMixin`**.
|
|
184
|
+
- Long readonly textareas: pass options on the widget itself (SPA preview + “Show full text” dialog):
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from litedmin import Textarea
|
|
188
|
+
|
|
189
|
+
class AnalysisAdmin(SQLAlchemyModelAdmin):
|
|
190
|
+
widgets = {
|
|
191
|
+
'error': Textarea(collapse_chars=280),
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Field references (SQLAlchemy)
|
|
196
|
+
|
|
197
|
+
Settings accept plain names (`'title'`, `'author__username'`) for any ORM. For SQLAlchemy you can also pass the model's own attributes (and `Link(...)` for relation paths), so a type checker catches a misspelt field:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from litedmin import Link, action, display, register
|
|
201
|
+
from litedmin.backend.orm.sqlalchemy import SQLAlchemyModelAdmin
|
|
202
|
+
|
|
203
|
+
@register(Post)
|
|
204
|
+
class PostAdmin(SQLAlchemyModelAdmin):
|
|
205
|
+
list_display = [Post.id, Post.title, Link(Post.author, User.username), 'title_upper']
|
|
206
|
+
search_fields = [Post.title, Link(Post.author, User.email)]
|
|
207
|
+
ordering = [Post.created_at.desc()]
|
|
208
|
+
list_filter = [Post.published, Post.status]
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Tortoise models have no field attributes on the class — keep using strings there (`'title'`, `'author__username'`). Computed `@display` columns are always strings.
|
|
212
|
+
|
|
213
|
+
### Tortoise example
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from litedmin import action, register, display
|
|
217
|
+
from litedmin.backend.orm.tortoise import TortoiseModelAdmin
|
|
218
|
+
|
|
219
|
+
@action(description='Publish selected', row=True)
|
|
220
|
+
async def make_published(modeladmin, request, objects):
|
|
221
|
+
# ``objects`` is a list of instances (not an ORM queryset)
|
|
222
|
+
...
|
|
223
|
+
# Optional: return a status string for the SPA toast (e.g. background job accepted)
|
|
224
|
+
# return 'Export queued'
|
|
225
|
+
|
|
226
|
+
@action(description='Refresh cache', toolbar=True, bulk=False, row=False)
|
|
227
|
+
async def refresh_cache(modeladmin, request, objects):
|
|
228
|
+
# Page toolbar button (no selection); same POST /{model}/action with ids=[]
|
|
229
|
+
...
|
|
230
|
+
return 'Refresh queued'
|
|
231
|
+
|
|
232
|
+
@register(Post)
|
|
233
|
+
class PostAdmin(TortoiseModelAdmin):
|
|
234
|
+
list_display = ['title', 'title_upper']
|
|
235
|
+
actions = [make_published, refresh_cache] # each entry must use @action
|
|
236
|
+
|
|
237
|
+
@display(label='Upper title')
|
|
238
|
+
def title_upper(self, obj):
|
|
239
|
+
return obj.title.upper()
|
|
240
|
+
|
|
241
|
+
async def has_export_permission(self, request=None, obj=None):
|
|
242
|
+
return True
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
List actions require **`@action`** (Django `@admin.action` style). Use `row=True` for per-row buttons; `bulk=False` to hide from the bulk dropdown; `toolbar=True` for page-level tools above the list (always empty selection). Signature is always `(modeladmin, request, objects)`.
|
|
246
|
+
|
|
247
|
+
## Auth
|
|
248
|
+
|
|
249
|
+
- Default: **session cookie** login (`SessionAuthBackend` + `SessionAuth`).
|
|
250
|
+
- SSO (Keycloak gateway handoff): `GatewayWebAuthBackend` — see `examples/tortoise/app_gateway_web.py` and [examples/README.md](examples/README.md).
|
|
251
|
+
|
|
252
|
+
## CLI
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
litedmin createsuperuser --settings examples.tortoise.settings:orm_settings
|
|
256
|
+
# or
|
|
257
|
+
python -m litedmin createsuperuser --settings module.path:orm_settings
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`--username` / `--password` / `--email`, or `ADMIN_USERNAME` / `ADMIN_PASSWORD` / `ADMIN_EMAIL`.
|
|
261
|
+
|
|
262
|
+
## Config split
|
|
263
|
+
|
|
264
|
+
| Object | Role |
|
|
265
|
+
|--------|------|
|
|
266
|
+
| **`AdminConfig`** | Theme, UI, sidebar groups, optional `auth_user_model` |
|
|
267
|
+
| **`OrmSettings`** | `orm_type`, database URL, app modules for ORM init |
|
|
268
|
+
|
|
269
|
+
## Features
|
|
270
|
+
|
|
271
|
+
- `@register` / `AdminSite.register` ModelAdmin API
|
|
272
|
+
- SQLAlchemy field attributes / `Link(...)` in ModelAdmin settings (strings for Tortoise)
|
|
273
|
+
- `@display` computed list columns
|
|
274
|
+
- `@action` list actions (bulk dropdown + optional `row=True` buttons)
|
|
275
|
+
- CSV/JSON export (`has_export_permission`)
|
|
276
|
+
- Django-like permissions, groups, LogEntry audit trail
|
|
277
|
+
- `list_editable`, `date_hierarchy`, bulk actions, inlines
|
|
278
|
+
- Row-level scope via `get_list_scope_filters` + object-level `has_*`
|
|
279
|
+
- Double-submit CSRF cookie for session auth (`AdminConfig.csrf_protect`)
|
|
280
|
+
- `StorageBackend` protocol for file uploads
|
|
281
|
+
|
|
282
|
+
## Typing
|
|
283
|
+
|
|
284
|
+
Package is typed; project mypy config is in `pyproject.toml` (`[tool.mypy]`).
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
pip install -e ".[dev]"
|
|
288
|
+
python -m mypy litedmin
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Change the React UI
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
pip install -e ".[all]"
|
|
295
|
+
# or: pip install -r requirements-dev.txt
|
|
296
|
+
bash scripts/build_frontend.sh
|
|
297
|
+
git add litedmin/static/
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
MIT
|
litedmin-0.4.0/README.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# LiteAdmin
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/litedmin/)
|
|
4
|
+
[](https://pypi.org/project/litedmin/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Django Admin–style admin panel for **Litestar** and **FastAPI**, with **Tortoise ORM** and **SQLAlchemy**.
|
|
8
|
+
|
|
9
|
+
React UI ships in `litedmin/static/` — **no npm** needed for normal use.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install litedmin
|
|
15
|
+
pip install "litedmin[fastapi,sqlalchemy]" # FastAPI + SQLAlchemy
|
|
16
|
+
pip install "litedmin[litestar,tortoise]" # Litestar + Tortoise
|
|
17
|
+
pip install "litedmin[all]" # both ORMs + frameworks + dev tools
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Core symbols (`AdminConfig`, `OrmSettings`, `register`, `ModelAdmin`) import without ORM/framework extras.
|
|
21
|
+
ORM and web stacks load only when you import their modules (or touch lazy attributes on `litedmin`).
|
|
22
|
+
|
|
23
|
+
### From source (examples / development)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/ggb0yyyyyyy/litedmin.git
|
|
27
|
+
cd litedmin
|
|
28
|
+
pip install -e ".[litestar,tortoise]" # examples/tortoise (port 8000)
|
|
29
|
+
pip install -e ".[litestar,sqlalchemy]" # examples/sqlalchemy (port 8011)
|
|
30
|
+
pip install -e ".[fastapi,tortoise]"
|
|
31
|
+
pip install -e ".[all]"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Repository layout
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
liteDmin/
|
|
38
|
+
litedmin/ installable package
|
|
39
|
+
backend/
|
|
40
|
+
core/ AdminSite, config, OrmSettings, decorators
|
|
41
|
+
admin/ ModelAdmin, changelist, formsets, permissions
|
|
42
|
+
api/ AdminAPI / endpoints
|
|
43
|
+
auth/ session, backends, UserAdminMixin, system models
|
|
44
|
+
orm/ Tortoise / SQLAlchemy backends + repositories
|
|
45
|
+
integration/ Litestar & FastAPI Admin mounts, DI helpers
|
|
46
|
+
dto/ storage/
|
|
47
|
+
static/ bundled React UI
|
|
48
|
+
frontend/ React sources (rebuild → litedmin/static/)
|
|
49
|
+
examples/ Litestar + Tortoise / SQLAlchemy demos
|
|
50
|
+
tests/
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick start (Litestar + Tortoise)
|
|
54
|
+
|
|
55
|
+
Superuser is **not** created on app startup. Create it once via CLI, then run the example:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# optional credentials for CLI
|
|
59
|
+
export ADMIN_USERNAME=admin
|
|
60
|
+
export ADMIN_PASSWORD=admin
|
|
61
|
+
|
|
62
|
+
py -3.11 -m litedmin createsuperuser --settings examples.tortoise.settings:orm_settings
|
|
63
|
+
py -3.11 -m uvicorn examples.tortoise.app:app --host 0.0.0.0 --port 8000
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Open http://localhost:8000/admin
|
|
67
|
+
|
|
68
|
+
SQLAlchemy example: same flow with `examples.sqlalchemy.settings:orm_settings` and port **8011**.
|
|
69
|
+
Details: [examples/README.md](examples/README.md), [examples/CREDENTIALS.md](examples/CREDENTIALS.md).
|
|
70
|
+
|
|
71
|
+
### Minimal wiring
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from litestar import Litestar, get
|
|
75
|
+
|
|
76
|
+
# Shared Starlette Admin ASGI app; Litestar/FastAPI only provide mount helpers.
|
|
77
|
+
from litedmin.backend.integration.litestar import Admin
|
|
78
|
+
from litedmin.backend.integration import init_tortoise_with_litedmin, close_tortoise
|
|
79
|
+
# FastAPI: from litedmin.backend.integration.fastapi import Admin
|
|
80
|
+
# or: from litedmin import Admin
|
|
81
|
+
# Then: app.mount(admin.prefix, admin.as_asgi_app())
|
|
82
|
+
|
|
83
|
+
from examples.tortoise.admin_config import ExampleAdminConfig
|
|
84
|
+
from examples.tortoise.settings import AUTO_DISCOVER_MODULES, orm_settings
|
|
85
|
+
|
|
86
|
+
admin = Admin(
|
|
87
|
+
prefix='/admin',
|
|
88
|
+
config=ExampleAdminConfig(), # AdminConfig: theme / UI / auth_user_model
|
|
89
|
+
auto_discover_modules=AUTO_DISCOVER_MODULES,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
async def on_startup():
|
|
93
|
+
await init_tortoise_with_litedmin(orm_settings) # OrmSettings: ORM + DB
|
|
94
|
+
await admin.ensure_started() # validate admin + sync permissions (no user seed)
|
|
95
|
+
|
|
96
|
+
async def on_shutdown():
|
|
97
|
+
await close_tortoise()
|
|
98
|
+
|
|
99
|
+
@get('/')
|
|
100
|
+
async def root() -> dict:
|
|
101
|
+
return {'admin': '/admin'}
|
|
102
|
+
|
|
103
|
+
app = Litestar(
|
|
104
|
+
route_handlers=[root, admin.as_mount()],
|
|
105
|
+
on_startup=[on_startup],
|
|
106
|
+
on_shutdown=[on_shutdown],
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### File / rich-text media storage
|
|
111
|
+
|
|
112
|
+
Pass any `StorageBackend` (`save` / `delete` / `url`) into `Admin(storage=...)`. Rich editor uploads go to `POST /admin/media/upload` and embed the returned `url` in HTML.
|
|
113
|
+
|
|
114
|
+
Examples use `LocalFileStorage` + `/media` static route — see [examples/README.md](examples/README.md). Swap in S3/MinIO by implementing the same protocol in your app.
|
|
115
|
+
|
|
116
|
+
SQLAlchemy apps also call `admin.configure_sqlalchemy(...)` after `init_sqlalchemy_with_litedmin_async` — see `examples/sqlalchemy/app.py`.
|
|
117
|
+
|
|
118
|
+
## ModelAdmin
|
|
119
|
+
|
|
120
|
+
- Prefer **`TortoiseModelAdmin`** / **`SQLAlchemyModelAdmin`** (repository save/delete).
|
|
121
|
+
- Register with `@register` / `AdminSite.register` — **`admin_site` is always injected**; do not construct ModelAdmin without a site.
|
|
122
|
+
- Permission / persist hooks are **`async`** (`has_*`, `get_permissions`, `get_list_scope_filters`, `save_model`, `delete_model`, `process_extra_fields`). Sync getters (`get_list_display`, …) stay sync.
|
|
123
|
+
- Users with password field: **`UserAdminMixin`**.
|
|
124
|
+
- Long readonly textareas: pass options on the widget itself (SPA preview + “Show full text” dialog):
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from litedmin import Textarea
|
|
128
|
+
|
|
129
|
+
class AnalysisAdmin(SQLAlchemyModelAdmin):
|
|
130
|
+
widgets = {
|
|
131
|
+
'error': Textarea(collapse_chars=280),
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Field references (SQLAlchemy)
|
|
136
|
+
|
|
137
|
+
Settings accept plain names (`'title'`, `'author__username'`) for any ORM. For SQLAlchemy you can also pass the model's own attributes (and `Link(...)` for relation paths), so a type checker catches a misspelt field:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from litedmin import Link, action, display, register
|
|
141
|
+
from litedmin.backend.orm.sqlalchemy import SQLAlchemyModelAdmin
|
|
142
|
+
|
|
143
|
+
@register(Post)
|
|
144
|
+
class PostAdmin(SQLAlchemyModelAdmin):
|
|
145
|
+
list_display = [Post.id, Post.title, Link(Post.author, User.username), 'title_upper']
|
|
146
|
+
search_fields = [Post.title, Link(Post.author, User.email)]
|
|
147
|
+
ordering = [Post.created_at.desc()]
|
|
148
|
+
list_filter = [Post.published, Post.status]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Tortoise models have no field attributes on the class — keep using strings there (`'title'`, `'author__username'`). Computed `@display` columns are always strings.
|
|
152
|
+
|
|
153
|
+
### Tortoise example
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from litedmin import action, register, display
|
|
157
|
+
from litedmin.backend.orm.tortoise import TortoiseModelAdmin
|
|
158
|
+
|
|
159
|
+
@action(description='Publish selected', row=True)
|
|
160
|
+
async def make_published(modeladmin, request, objects):
|
|
161
|
+
# ``objects`` is a list of instances (not an ORM queryset)
|
|
162
|
+
...
|
|
163
|
+
# Optional: return a status string for the SPA toast (e.g. background job accepted)
|
|
164
|
+
# return 'Export queued'
|
|
165
|
+
|
|
166
|
+
@action(description='Refresh cache', toolbar=True, bulk=False, row=False)
|
|
167
|
+
async def refresh_cache(modeladmin, request, objects):
|
|
168
|
+
# Page toolbar button (no selection); same POST /{model}/action with ids=[]
|
|
169
|
+
...
|
|
170
|
+
return 'Refresh queued'
|
|
171
|
+
|
|
172
|
+
@register(Post)
|
|
173
|
+
class PostAdmin(TortoiseModelAdmin):
|
|
174
|
+
list_display = ['title', 'title_upper']
|
|
175
|
+
actions = [make_published, refresh_cache] # each entry must use @action
|
|
176
|
+
|
|
177
|
+
@display(label='Upper title')
|
|
178
|
+
def title_upper(self, obj):
|
|
179
|
+
return obj.title.upper()
|
|
180
|
+
|
|
181
|
+
async def has_export_permission(self, request=None, obj=None):
|
|
182
|
+
return True
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
List actions require **`@action`** (Django `@admin.action` style). Use `row=True` for per-row buttons; `bulk=False` to hide from the bulk dropdown; `toolbar=True` for page-level tools above the list (always empty selection). Signature is always `(modeladmin, request, objects)`.
|
|
186
|
+
|
|
187
|
+
## Auth
|
|
188
|
+
|
|
189
|
+
- Default: **session cookie** login (`SessionAuthBackend` + `SessionAuth`).
|
|
190
|
+
- SSO (Keycloak gateway handoff): `GatewayWebAuthBackend` — see `examples/tortoise/app_gateway_web.py` and [examples/README.md](examples/README.md).
|
|
191
|
+
|
|
192
|
+
## CLI
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
litedmin createsuperuser --settings examples.tortoise.settings:orm_settings
|
|
196
|
+
# or
|
|
197
|
+
python -m litedmin createsuperuser --settings module.path:orm_settings
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`--username` / `--password` / `--email`, or `ADMIN_USERNAME` / `ADMIN_PASSWORD` / `ADMIN_EMAIL`.
|
|
201
|
+
|
|
202
|
+
## Config split
|
|
203
|
+
|
|
204
|
+
| Object | Role |
|
|
205
|
+
|--------|------|
|
|
206
|
+
| **`AdminConfig`** | Theme, UI, sidebar groups, optional `auth_user_model` |
|
|
207
|
+
| **`OrmSettings`** | `orm_type`, database URL, app modules for ORM init |
|
|
208
|
+
|
|
209
|
+
## Features
|
|
210
|
+
|
|
211
|
+
- `@register` / `AdminSite.register` ModelAdmin API
|
|
212
|
+
- SQLAlchemy field attributes / `Link(...)` in ModelAdmin settings (strings for Tortoise)
|
|
213
|
+
- `@display` computed list columns
|
|
214
|
+
- `@action` list actions (bulk dropdown + optional `row=True` buttons)
|
|
215
|
+
- CSV/JSON export (`has_export_permission`)
|
|
216
|
+
- Django-like permissions, groups, LogEntry audit trail
|
|
217
|
+
- `list_editable`, `date_hierarchy`, bulk actions, inlines
|
|
218
|
+
- Row-level scope via `get_list_scope_filters` + object-level `has_*`
|
|
219
|
+
- Double-submit CSRF cookie for session auth (`AdminConfig.csrf_protect`)
|
|
220
|
+
- `StorageBackend` protocol for file uploads
|
|
221
|
+
|
|
222
|
+
## Typing
|
|
223
|
+
|
|
224
|
+
Package is typed; project mypy config is in `pyproject.toml` (`[tool.mypy]`).
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
pip install -e ".[dev]"
|
|
228
|
+
python -m mypy litedmin
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Change the React UI
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pip install -e ".[all]"
|
|
235
|
+
# or: pip install -r requirements-dev.txt
|
|
236
|
+
bash scripts/build_frontend.sh
|
|
237
|
+
git add litedmin/static/
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
MIT
|