From ed2058c3daab5d43e088aca7536eb286c9d0cd1e Mon Sep 17 00:00:00 2001 From: <> Date: Sat, 26 Oct 2024 01:24:37 +0000 Subject: [PATCH] Deployed a5717c0 with MkDocs version: 1.6.1 --- .nojekyll | 0 404.html | 1338 +++ advanced/base-model-view/index.html | 1761 ++++ advanced/custom-field/index.html | 1684 ++++ alternatives/index.html | 1432 ++++ api/actions/index.html | 2620 ++++++ api/auth/index.html | 3456 ++++++++ api/base-admin/index.html | 3024 +++++++ api/contrib/sqlalchemy/modelview/index.html | 3284 ++++++++ api/fields/index.html | 7194 ++++++++++++++++ api/views/index.html | 7317 ++++++++++++++++ assets/_mkdocstrings.css | 143 + assets/favicon.ico | Bin 0 -> 5238 bytes assets/images/favicon.png | Bin 0 -> 1870 bytes assets/images/tutorials/basic/create.png | Bin 0 -> 23977 bytes assets/images/tutorials/basic/delete.png | Bin 0 -> 107938 bytes assets/images/tutorials/basic/edit.png | Bin 0 -> 28287 bytes assets/images/tutorials/basic/export.png | Bin 0 -> 30728 bytes .../tutorials/basic/fulltext_search.png | Bin 0 -> 34572 bytes assets/images/tutorials/basic/home_page.png | Bin 0 -> 8176 bytes assets/images/tutorials/basic/list_full.png | Bin 0 -> 49448 bytes assets/images/tutorials/basic/pagination.png | Bin 0 -> 25336 bytes .../images/tutorials/basic/search_builder.png | Bin 0 -> 38523 bytes .../tutorials/basic/show_hide_columns.png | Bin 0 -> 39156 bytes assets/javascripts/bundle.83f73b43.min.js | 16 + assets/javascripts/bundle.83f73b43.min.js.map | 7 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.el.min.js | 1 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.he.min.js | 1 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.hy.min.js | 1 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.kn.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + assets/javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.te.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++ .../workers/search.6ce7567c.min.js | 42 + .../workers/search.6ce7567c.min.js.map | 7 + assets/stylesheets/main.0253249f.min.css | 1 + assets/stylesheets/main.0253249f.min.css.map | 1 + assets/stylesheets/palette.06af60db.min.css | 1 + .../stylesheets/palette.06af60db.min.css.map | 1 + changelog/index.html | 3785 +++++++++ deployment/index.html | 1444 ++++ es/advanced/base-model-view/index.html | 1763 ++++ es/advanced/custom-field/index.html | 1686 ++++ es/alternatives/index.html | 1434 ++++ es/api/actions/index.html | 2622 ++++++ es/api/auth/index.html | 3458 ++++++++ es/api/base-admin/index.html | 3026 +++++++ .../contrib/sqlalchemy/modelview/index.html | 3286 ++++++++ es/api/fields/index.html | 7196 ++++++++++++++++ es/api/views/index.html | 7319 +++++++++++++++++ es/changelog/index.html | 3787 +++++++++ es/deployment/index.html | 1446 ++++ es/index.html | 1686 ++++ es/tutorials/basic/index.html | 2288 ++++++ es/tutorials/index.html | 1504 ++++ es/user-guide/actions/index.html | 1807 ++++ es/user-guide/authentication/index.html | 1860 +++++ es/user-guide/configurations/admin/index.html | 1522 ++++ .../configurations/modelview/index.html | 1925 +++++ es/user-guide/files/index.html | 1554 ++++ es/user-guide/getting-started/index.html | 1847 +++++ es/user-guide/multiple-admin/index.html | 1459 ++++ es/user-guide/validations/index.html | 1747 ++++ images/preview.jpg | Bin 0 -> 249852 bytes .../modelview/object_text_representation.png | Bin 0 -> 51659 bytes .../modelview/select2_customization.png | Bin 0 -> 85041 bytes images/validations/mongoengine.png | Bin 0 -> 95008 bytes images/validations/odmantic.png | Bin 0 -> 101537 bytes images/validations/sqla.png | Bin 0 -> 121123 bytes images/validations/sqlmodel.png | Bin 0 -> 91710 bytes index.html | 1748 ++++ objects.inv | Bin 0 -> 2389 bytes search/search_index.json | 1 + sitemap.xml | 311 + sitemap.xml.gz | Bin 0 -> 922 bytes stylesheets/extra.css | 4 + tutorials/basic/index.html | 2286 +++++ tutorials/index.html | 1502 ++++ user-guide/actions/index.html | 1805 ++++ user-guide/authentication/index.html | 1858 +++++ user-guide/configurations/admin/index.html | 1520 ++++ .../configurations/modelview/index.html | 1923 +++++ user-guide/files/index.html | 1552 ++++ user-guide/getting-started/index.html | 1845 +++++ user-guide/multiple-admin/index.html | 1457 ++++ user-guide/validations/index.html | 1745 ++++ 113 files changed, 121538 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 advanced/base-model-view/index.html create mode 100644 advanced/custom-field/index.html create mode 100644 alternatives/index.html create mode 100644 api/actions/index.html create mode 100644 api/auth/index.html create mode 100644 api/base-admin/index.html create mode 100644 api/contrib/sqlalchemy/modelview/index.html create mode 100644 api/fields/index.html create mode 100644 api/views/index.html create mode 100644 assets/_mkdocstrings.css create mode 100644 assets/favicon.ico create mode 100644 assets/images/favicon.png create mode 100644 assets/images/tutorials/basic/create.png create mode 100644 assets/images/tutorials/basic/delete.png create mode 100644 assets/images/tutorials/basic/edit.png create mode 100644 assets/images/tutorials/basic/export.png create mode 100644 assets/images/tutorials/basic/fulltext_search.png create mode 100644 assets/images/tutorials/basic/home_page.png create mode 100644 assets/images/tutorials/basic/list_full.png create mode 100644 assets/images/tutorials/basic/pagination.png create mode 100644 assets/images/tutorials/basic/search_builder.png create mode 100644 assets/images/tutorials/basic/show_hide_columns.png create mode 100644 assets/javascripts/bundle.83f73b43.min.js create mode 100644 assets/javascripts/bundle.83f73b43.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.el.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.he.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.6ce7567c.min.js create mode 100644 assets/javascripts/workers/search.6ce7567c.min.js.map create mode 100644 assets/stylesheets/main.0253249f.min.css create mode 100644 assets/stylesheets/main.0253249f.min.css.map create mode 100644 assets/stylesheets/palette.06af60db.min.css create mode 100644 assets/stylesheets/palette.06af60db.min.css.map create mode 100644 changelog/index.html create mode 100644 deployment/index.html create mode 100644 es/advanced/base-model-view/index.html create mode 100644 es/advanced/custom-field/index.html create mode 100644 es/alternatives/index.html create mode 100644 es/api/actions/index.html create mode 100644 es/api/auth/index.html create mode 100644 es/api/base-admin/index.html create mode 100644 es/api/contrib/sqlalchemy/modelview/index.html create mode 100644 es/api/fields/index.html create mode 100644 es/api/views/index.html create mode 100644 es/changelog/index.html create mode 100644 es/deployment/index.html create mode 100644 es/index.html create mode 100644 es/tutorials/basic/index.html create mode 100644 es/tutorials/index.html create mode 100644 es/user-guide/actions/index.html create mode 100644 es/user-guide/authentication/index.html create mode 100644 es/user-guide/configurations/admin/index.html create mode 100644 es/user-guide/configurations/modelview/index.html create mode 100644 es/user-guide/files/index.html create mode 100644 es/user-guide/getting-started/index.html create mode 100644 es/user-guide/multiple-admin/index.html create mode 100644 es/user-guide/validations/index.html create mode 100644 images/preview.jpg create mode 100644 images/tutorial/configurations/modelview/object_text_representation.png create mode 100644 images/tutorial/configurations/modelview/select2_customization.png create mode 100644 images/validations/mongoengine.png create mode 100644 images/validations/odmantic.png create mode 100644 images/validations/sqla.png create mode 100644 images/validations/sqlmodel.png create mode 100644 index.html create mode 100644 objects.inv create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz create mode 100644 stylesheets/extra.css create mode 100644 tutorials/basic/index.html create mode 100644 tutorials/index.html create mode 100644 user-guide/actions/index.html create mode 100644 user-guide/authentication/index.html create mode 100644 user-guide/configurations/admin/index.html create mode 100644 user-guide/configurations/modelview/index.html create mode 100644 user-guide/files/index.html create mode 100644 user-guide/getting-started/index.html create mode 100644 user-guide/multiple-admin/index.html create mode 100644 user-guide/validations/index.html diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..32898f57 --- /dev/null +++ b/404.html @@ -0,0 +1,1338 @@ + + + + + + + + + + + + + + + + + + + + + Starlette Admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/advanced/base-model-view/index.html b/advanced/base-model-view/index.html new file mode 100644 index 00000000..60b9b3e7 --- /dev/null +++ b/advanced/base-model-view/index.html @@ -0,0 +1,1761 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Extending BaseModelView - Starlette Admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + +

Extending BaseModelView

+

Starlette-Admin makes a few assumptions about the database models that it works with. If you want to implement your +own database backend, and still have Starlette-Admin’s model views work as expected, then you should take note of the +following:

+
    +
  1. Each model must have one field which acts as a primary key to uniquely identify instances of that model. However, + there are no restriction on the data type or the field name of the primary key field.
  2. +
  3. Models must make their data accessible as python properties.
  4. +
+

If that is the case, then you can implement your own database backend by extending the +BaseModelView class, and implementing the set of methods listed below.

+

Let's say you've defined your models like this:

+
from dataclasses import dataclass
+from typing import List
+
+
+@dataclass
+class Post:
+    id: int
+    title: str
+    content: str
+    tags: List[str]
+
+

First you need to define a new class, which derives from BaseModelView.

+
from starlette_admin import BaseModelView
+
+
+class PostView(BaseModelView):
+    pass
+
+

Metadata

+

Set the identity, name and label for the new class

+
from starlette_admin import BaseModelView
+
+
+class PostView(BaseModelView):
+    identity = "post"
+    name = "Post"
+    label = "Blog Posts"
+    icon = "fa fa-blog"
+
+
+

Important

+

identity is used to identify the model associated to this view and should be unique.

+
+

Primary key

+

Set the pk_attr value which is primary key attribute name

+
from starlette_admin import BaseModelView
+
+
+class PostView(BaseModelView):
+    pk_attr = "id"
+
+

Fields

+

Internally, Starlette-Admin uses custom fields all inherit from BaseField to +represent each attribute. So, you need to choose the right field for each attribute or create a new field if needed. +See API Reference for full list of default fields.

+
from starlette_admin import BaseModelView
+from starlette_admin import IntegerField, StringField, TagsField, TextAreaField
+
+class PostView(BaseModelView):
+    fields = [
+        IntegerField("id"),
+        StringField("title"),
+        TextAreaField("content"),
+        TagsField("tags"),
+    ]
+
+

CRUD methods

+

Finally, you need to implement these CRUD methods:

+ +

Full example

+
from dataclasses import dataclass
+from typing import Any, Dict, Iterable, List, Optional, Union
+
+from starlette.requests import Request
+from starlette_admin import IntegerField, StringField, TagsField, TextAreaField
+from starlette_admin.exceptions import FormValidationError
+from starlette_admin.views import BaseModelView
+
+
+@dataclass
+class Post:
+    id: int
+    title: str
+    content: str
+    tags: List[str]
+
+    def is_valid_for_term(self, term):
+        return (
+            str(term).lower() in self.title.lower()
+            or str(term).lower() in self.content.lower()
+            or any([str(term).lower() in tag.lower() for tag in self.tags])
+        )
+
+    def update(self, data: Dict):
+        for key, value in data.items():
+            if hasattr(self, key):
+                setattr(self, key, value)
+
+
+db: Dict[int, Post] = dict()
+next_id = 1
+
+
+def filter_values(values: Iterable[Post], term):
+    filtered_values = []
+    for value in values:
+        if value.is_valid_for_term(term):
+            filtered_values.append(value)
+    return filtered_values
+
+
+class PostView(BaseModelView):
+    identity = "post"
+    name = "Post"
+    label = "Blog Posts"
+    icon = "fa fa-blog"
+    pk_attr = "id"
+    fields = [
+        IntegerField("id"),
+        StringField("title"),
+        TextAreaField("content"),
+        TagsField("tags"),
+    ]
+    sortable_fields = ("id", "title", "content")
+    search_builder = False
+
+    async def count(
+        self,
+        request: Request,
+        where: Union[Dict[str, Any], str, None] = None,
+    ) -> int:
+        values = list(db.values())
+        if where is not None:
+            values = filter_values(values, where)
+        return len(values)
+
+    async def find_all(
+        self,
+        request: Request,
+        skip: int = 0,
+        limit: int = 100,
+        where: Union[Dict[str, Any], str, None] = None,
+        order_by: Optional[List[str]] = None,
+    ) -> List[Any]:
+        values = list(db.values())
+        if order_by is not None:
+            assert len(order_by) < 2, "Not supported"
+            if len(order_by) == 1:
+                key, dir = order_by[0].split(maxsplit=1)
+                values.sort(key=lambda v: getattr(v, key), reverse=(dir == "desc"))
+
+        if where is not None and isinstance(where, (str, int)):
+            values = filter_values(values, where)
+        if limit > 0:
+            return values[skip : skip + limit]
+        return values[skip:]
+
+    async def find_by_pk(self, request: Request, pk):
+        return db.get(int(pk), None)
+
+    async def find_by_pks(self, request: Request, pks):
+        return [db.get(int(pk)) for pk in pks]
+
+    async def validate_data(self, data: Dict):
+        errors = {}
+        if data["title"] is None or len(data["title"]) < 3:
+            errors["title"] = "Ensure title has at least 03 characters"
+        if data["tags"] is None or len(data["tags"]) < 1:
+            errors["tags"] = "You need at least one tag"
+        if len(errors) > 0:
+            raise FormValidationError(errors)
+
+    async def create(self, request: Request, data: Dict):
+        await self.validate_data(data)
+        global next_id
+        obj = Post(id=next_id, **data)
+        db[next_id] = obj
+        next_id += 1
+        return obj
+
+    async def edit(self, request: Request, pk, data: Dict):
+        await self.validate_data(data)
+        db[int(pk)].update(data)
+        return db[int(pk)]
+
+    async def delete(self, request: Request, pks: List[Any]) -> Optional[int]:
+        cnt = 0
+        for pk in pks:
+            value = await self.find_by_pk(request, pk)
+            if value is not None:
+                del db[int(pk)]
+                cnt += 1
+        return cnt
+
+ + + + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/advanced/custom-field/index.html b/advanced/custom-field/index.html new file mode 100644 index 00000000..f4600725 --- /dev/null +++ b/advanced/custom-field/index.html @@ -0,0 +1,1684 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Custom Field - Starlette Admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + +

Custom Field

+

Starlette-Admin has a lot of built-in fields available. But you can override or create your own field +according to your need.

+
+

Important

+

Before creating a new field, try first to extend the existing ones. They are flexible enough to fit most use cases.

+
+

The first step is to define a new class, which derives from BaseField or any others fields to customize it

+
from starlette_admin import BaseField
+from dataclasses import dataclass
+
+@dataclass
+class CustomField(BaseField):
+    pass
+
+

List Rendering

+

Starlette-Admin use Datatables to render list. By default all fields will be render as text field. +To customize this behavior you need to write a javascript function to +render your column inside datatable instance. For more information on how to write your function +read Datatables documentation.

+
    +
  • First, you need to provide a link to your custom javascript file in which you add additional render function, by overriding +the admin class
  • +
+
+

Example

+

This is simple example with SQLAlchemy backend

+
from starlette_admin.contrib.sqla import Admin as BaseAdmin
+
+class Admin(BaseAdmin):
+    def custom_render_js(self, request: Request) -> Optional[str]:
+        return request.url_for("statics", path="js/custom_render.js")
+
+admin = Admin(engine)
+admin.add_view(...)
+
+
statics/js/custom_render.js
Object.assign(render, {
+  mycustomkey: function render(data, type, full, meta, fieldOptions) {
+        ...
+  },
+});
+
+
+
+

Note

+

fieldOptions is your field as javascript object. Your field attributes is serialized into +javascript object by using dataclass asdict function.

+
+
    +
  • Then, set render_function_key value
  • +
+
from starlette_admin import BaseField
+from dataclasses import dataclass
+
+@dataclass
+class CustomField(BaseField):
+    render_function_key: str = "mycustomkey"
+
+

Form

+

For form rendering, you should create a new html file under the directory forms in your templates dir.

+

These jinja2 variables are available:

+
    +
  • field: Your field instance
  • +
  • error: Error message coming from FormValidationError
  • +
  • data: current value. Will be available if it is edit or when validation error occur
  • +
  • action: EDIT or CREATE
  • +
+
+

Example

+
forms/custom.html
<div class="{%if error%}is-invalid{%endif%}">
+    <input id="{{field.id}}" name="{{field.id}}" ... />
+    {% if field.help_text %}
+    <small class="form-hint">{{field.help_text}}</small>
+    {%endif%}
+</div>
+{%if error %}
+<div class="invalid-feedback">{{error}}</div>
+{%endif%}
+
+
+
from starlette_admin import BaseField
+from dataclasses import dataclass
+
+@dataclass
+class CustomField(BaseField):
+    render_function_key: str = "mycustomkey"
+    form_template: str = "forms/custom.html"
+
+

Detail Page

+

To render your field on detail page, you should create a new html file under the directory displays in your template dir.

+

These jinja2 variables are available:

+
    +
  • field: Your field instance
  • +
  • data: value to display
  • +
+
+

Example

+
displays/custom.html
<span>Hello {{data}}</span>
+
+
+
from starlette_admin import BaseField
+from dataclasses import dataclass
+
+@dataclass
+class CustomField(BaseField):
+    render_function_key: str = "mycustomkey"
+    form_template: str = "forms/custom.html"
+    display_template: str = "displays/custom.html"
+
+

Data processing

+

For data processing you will need to override two functions:

+
    +
  • process_form_data: Will be call when converting field value into python dict object
  • +
  • serialize_field_value: Will be call when serializing value to send through the API. This is the same data +you will get in your render function
  • +
+
from dataclasses import dataclass
+from typing import Any, Dict
+
+from requests import Request
+from starlette.datastructures import FormData
+from starlette_admin import BaseField
+
+
+@dataclass
+class CustomField(BaseField):
+    render_function_key: str = "mycustomkey"
+    form_template: str = "forms/custom.html"
+    display_template: str = "displays/custom.html"
+
+    async def parse_form_data(self, request: Request, form_data: FormData) -> Any:
+        return form_data.get(self.name)
+
+    async def serialize_value(self, request: Request, value: Any, action: RequestAction) -> Any:
+        return value
+
+    def dict(self) -> Dict[str, Any]:
+        return super().dict()
+
+
+

Important

+

Override dict function to get control of the options which is available in javascript.

+
+ + + + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/alternatives/index.html b/alternatives/index.html new file mode 100644 index 00000000..8cb2cc9b --- /dev/null +++ b/alternatives/index.html @@ -0,0 +1,1432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Alternatives, Inspiration and Comparisons - Starlette Admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + +

Alternatives, Inspiration and Comparisons

+
    +
  • Flask-Admin: Simple and extensible administrative interface framework for Flask. The main goal of this project is to provide similar tool for Starlette/FastApi.
  • +
  • FastApi-Admin: A fast admin dashboard based on FastAPI and TortoiseORM.
  • +
  • sqladmin: SQLAlchemy Admin for FastAPI and Starlette
  • +
+ + + + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/actions/index.html b/api/actions/index.html new file mode 100644 index 00000000..76f8a460 --- /dev/null +++ b/api/actions/index.html @@ -0,0 +1,2620 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Actions - Starlette Admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + +

Actions

+ +
+ + + +

+ starlette_admin.actions + + +

+ +
+ + + + + + + + +
+ + + + + + + + + +
+ + +

+ action(name, text, confirmation=None, submit_btn_class='btn-primary', submit_btn_text=_('Yes, Proceed'), icon_class=None, form=None, custom_response=False) + +

+ + +
+ +

Decorator to add custom batch actions to your ModelView

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ name + + str + +
+

unique action name for your ModelView

+
+
+ required +
+ text + + str + +
+

Action text

+
+
+ required +
+ confirmation + + Optional[str] + +
+

Confirmation text. If not provided, action will be executed + unconditionally.

+
+
+ None +
+ submit_btn_text + + Optional[str] + +
+

Submit button text

+
+
+ lazy_gettext('Yes, Proceed') +
+ submit_btn_class + + Optional[str] + +
+

Submit button variant (ex. btn-primary, btn-ghost-info, + btn-outline-danger, ...)

+
+
+ 'btn-primary' +
+ icon_class + + Optional[str] + +
+

Icon class (ex. fa-lite fa-folder, fa-duotone fa-circle-right, ...)

+
+
+ None +
+ form + + Optional[str] + +
+

Custom form to collect data from user

+
+
+ None +
+ custom_response + + Optional[bool] + +
+

Set to True when you want to return a custom Starlette response +from your action instead of a string.

+
+
+ False +
+
+

Usage

+
class ArticleView(ModelView):
+    actions = ['make_published', 'redirect']
+
+    @action(
+        name="make_published",
+        text="Mark selected articles as published",
+        confirmation="Are you sure you want to mark selected articles as published ?",
+        submit_btn_text="Yes, proceed",
+        submit_btn_class="btn-success",
+        form='''
+        <form>
+            <div class="mt-3">
+                <input type="text" class="form-control" name="example-text-input" placeholder="Enter value">
+            </div>
+        </form>
+        '''
+    )
+    async def make_published_action(self, request: Request, pks: List[Any]) -> str:
+        # Write your logic here
+
+        data: FormData =  await request.form()
+        user_input = data.get("example-text-input")
+
+        if ... :
+            # Display meaningfully error
+            raise ActionFailed("Sorry, We can't proceed this action now.")
+        # Display successfully message
+        return "{} articles were successfully marked as published".format(len(pks))
+
+    # For custom response
+    @action(
+        name="redirect",
+        text="Redirect",
+        custom_response=True,
+        confirmation="Fill the form",
+        form='''
+        <form>
+            <div class="mt-3">
+                <input type="text" class="form-control" name="value" placeholder="Enter value">
+            </div>
+        </form>
+        '''
+     )
+    async def redirect_action(self, request: Request, pks: List[Any]) -> Response:
+        data = await request.form()
+        return RedirectResponse(f"https://example.com/?value={data['value']}")
+
+
+ +
+ Source code in starlette_admin/actions.py +
 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
def action(
+    name: str,
+    text: str,
+    confirmation: Optional[str] = None,
+    submit_btn_class: Optional[str] = "btn-primary",
+    submit_btn_text: Optional[str] = _("Yes, Proceed"),
+    icon_class: Optional[str] = None,
+    form: Optional[str] = None,
+    custom_response: Optional[bool] = False,
+) -> Callable[[Callable[..., Awaitable[str]]], Any]:
+    """
+    Decorator to add custom batch actions to your [ModelView][starlette_admin.views.BaseModelView]
+
+    Args:
+        name: unique action name for your ModelView
+        text: Action text
+        confirmation: Confirmation text. If not provided, action will be executed
+                      unconditionally.
+        submit_btn_text: Submit button text
+        submit_btn_class: Submit button variant (ex. `btn-primary`, `btn-ghost-info`,
+                `btn-outline-danger`, ...)
+        icon_class: Icon class (ex. `fa-lite fa-folder`, `fa-duotone fa-circle-right`, ...)
+        form: Custom form to collect data from user
+        custom_response: Set to True when you want to return a custom Starlette response
+            from your action instead of a string.
+
+
+    !!! usage
+
+        ```python
+        class ArticleView(ModelView):
+            actions = ['make_published', 'redirect']
+
+            @action(
+                name="make_published",
+                text="Mark selected articles as published",
+                confirmation="Are you sure you want to mark selected articles as published ?",
+                submit_btn_text="Yes, proceed",
+                submit_btn_class="btn-success",
+                form='''
+                <form>
+                    <div class="mt-3">
+                        <input type="text" class="form-control" name="example-text-input" placeholder="Enter value">
+                    </div>
+                </form>
+                '''
+            )
+            async def make_published_action(self, request: Request, pks: List[Any]) -> str:
+                # Write your logic here
+
+                data: FormData =  await request.form()
+                user_input = data.get("example-text-input")
+
+                if ... :
+                    # Display meaningfully error
+                    raise ActionFailed("Sorry, We can't proceed this action now.")
+                # Display successfully message
+                return "{} articles were successfully marked as published".format(len(pks))
+
+            # For custom response
+            @action(
+                name="redirect",
+                text="Redirect",
+                custom_response=True,
+                confirmation="Fill the form",
+                form='''
+                <form>
+                    <div class="mt-3">
+                        <input type="text" class="form-control" name="value" placeholder="Enter value">
+                    </div>
+                </form>
+                '''
+             )
+            async def redirect_action(self, request: Request, pks: List[Any]) -> Response:
+                data = await request.form()
+                return RedirectResponse(f"https://example.com/?value={data['value']}")
+        ```
+    """
+
+    def wrap(f: Callable[..., Awaitable[str]]) -> Callable[..., Awaitable[str]]:
+        f._action = {  # type: ignore
+            "name": name,
+            "text": text,
+            "confirmation": confirmation,
+            "submit_btn_text": submit_btn_text,
+            "submit_btn_class": submit_btn_class,
+            "icon_class": icon_class,
+            "form": form if form is not None else "",
+            "custom_response": custom_response,
+        }
+        return f
+
+    return wrap
+
+
+
+ +
+ +
+ + +

+ row_action(name, text, confirmation=None, action_btn_class=None, submit_btn_class='btn-primary', submit_btn_text=_('Yes, Proceed'), icon_class=None, form=None, custom_response=False, exclude_from_list=False, exclude_from_detail=False) + +

+ + +
+ +

Decorator to add custom row actions to your ModelView

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ name + + str + +
+

Unique row action name for the ModelView.

+
+
+ required +
+ text + + str + +
+

Action text displayed to users.

+
+
+ required +
+ confirmation + + Optional[str] + +
+

Confirmation text; if provided, the action will require confirmation.

+
+
+ None +
+ action_btn_class + + Optional[str] + +
+

Action button variant for detail page (ex. btn-success, btn-outline, ...)

+
+
+ None +
+ submit_btn_class + + Optional[str] + +
+

Submit button variant (ex. btn-primary, btn-ghost-info, btn-outline-danger, ...)

+
+
+ 'btn-primary' +
+ submit_btn_text + + Optional[str] + +
+

Text for the submit button.

+
+
+ lazy_gettext('Yes, Proceed') +
+ icon_class + + Optional[str] + +
+

Icon class (ex. fa-lite fa-folder, fa-duotone fa-circle-right, ...)

+
+
+ None +
+ form + + Optional[str] + +
+

Custom HTML to collect data from the user.

+
+
+ None +
+ custom_response + + Optional[bool] + +
+

Set to True when you want to return a custom Starlette response +from your action instead of a string.

+
+
+ False +
+ exclude_from_list + + bool + +
+

Set to True to exclude the action from the list view.

+
+
+ False +
+ exclude_from_detail + + bool + +
+

Set to True to exclude the action from the detail view.

+
+
+ False +
+
+

Usage

+
@row_action(
+    name="make_published",
+    text="Mark as published",
+    confirmation="Are you sure you want to mark this article as published ?",
+    icon_class="fas fa-check-circle",
+    submit_btn_text="Yes, proceed",
+    submit_btn_class="btn-success",
+    action_btn_class="btn-info",
+)
+async def make_published_row_action(self, request: Request, pk: Any) -> str:
+    session: Session = request.state.session
+    article = await self.find_by_pk(request, pk)
+    if article.status == Status.Published:
+        raise ActionFailed("The article is already marked as published.")
+    article.status = Status.Published
+    session.add(article)
+    session.commit()
+    return f"The article was successfully marked as published."
+
+
+ +
+ Source code in starlette_admin/actions.py +
def row_action(
+    name: str,
+    text: str,
+    confirmation: Optional[str] = None,
+    action_btn_class: Optional[str] = None,
+    submit_btn_class: Optional[str] = "btn-primary",
+    submit_btn_text: Optional[str] = _("Yes, Proceed"),
+    icon_class: Optional[str] = None,
+    form: Optional[str] = None,
+    custom_response: Optional[bool] = False,
+    exclude_from_list: bool = False,
+    exclude_from_detail: bool = False,
+) -> Callable[[Callable[..., Awaitable[str]]], Any]:
+    """
+    Decorator to add custom row actions to your [ModelView][starlette_admin.views.BaseModelView]
+
+    Args:
+        name: Unique row action name for the ModelView.
+        text: Action text displayed to users.
+        confirmation: Confirmation text; if provided, the action will require confirmation.
+        action_btn_class: Action button variant for detail page (ex. `btn-success`, `btn-outline`, ...)
+        submit_btn_class: Submit button variant (ex. `btn-primary`, `btn-ghost-info`, `btn-outline-danger`, ...)
+        submit_btn_text: Text for the submit button.
+        icon_class: Icon class (ex. `fa-lite fa-folder`, `fa-duotone fa-circle-right`, ...)
+        form: Custom HTML to collect data from the user.
+        custom_response: Set to True when you want to return a custom Starlette response
+            from your action instead of a string.
+        exclude_from_list: Set to True to exclude the action from the list view.
+        exclude_from_detail: Set to True to exclude the action from the detail view.
+
+
+    !!! usage
+
+        ```python
+        @row_action(
+            name="make_published",
+            text="Mark as published",
+            confirmation="Are you sure you want to mark this article as published ?",
+            icon_class="fas fa-check-circle",
+            submit_btn_text="Yes, proceed",
+            submit_btn_class="btn-success",
+            action_btn_class="btn-info",
+        )
+        async def make_published_row_action(self, request: Request, pk: Any) -> str:
+            session: Session = request.state.session
+            article = await self.find_by_pk(request, pk)
+            if article.status == Status.Published:
+                raise ActionFailed("The article is already marked as published.")
+            article.status = Status.Published
+            session.add(article)
+            session.commit()
+            return f"The article was successfully marked as published."
+        ```
+    """
+
+    def wrap(f: Callable[..., Awaitable[str]]) -> Callable[..., Awaitable[str]]:
+        f._row_action = {  # type: ignore
+            "name": name,
+            "text": text,
+            "confirmation": confirmation,
+            "action_btn_class": action_btn_class,
+            "submit_btn_text": submit_btn_text,
+            "submit_btn_class": submit_btn_class,
+            "icon_class": icon_class,
+            "form": form if form is not None else "",
+            "custom_response": custom_response,
+            "exclude_from_list": exclude_from_list,
+            "exclude_from_detail": exclude_from_detail,
+        }
+        return f
+
+    return wrap
+
+
+
+ +
+ +
+ + + + + +
+ +

Decorator to add custom row link actions to a ModelView for URL redirection.

+
+

Note

+

This decorator is designed to create row actions that redirect to a URL, making it ideal for cases where a +row action should simply navigate users to a website or internal page.

+
+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ name + + str + +
+

Unique row action name for the ModelView.

+
+
+ required +
+ text + + str + +
+

Action text displayed to users.

+
+
+ required +
+ action_btn_class + + Optional[str] + +
+

Action button variant for detail page (ex. btn-success, btn-outline, ...)

+
+
+ None +
+ icon_class + + Optional[str] + +
+

Icon class (ex. fa-lite fa-folder, fa-duotone fa-circle-right, ...)

+
+
+ None +
+ exclude_from_list + + bool + +
+

Set to True to exclude the action from the list view.

+
+
+ False +
+ exclude_from_detail + + bool + +
+

Set to True to exclude the action from the detail view.

+
+
+ False +
+
+

Usage

+
@link_row_action(
+    name="go_to_example",
+    text="Go to example.com",
+    icon_class="fas fa-arrow-up-right-from-square",
+)
+def go_to_example_row_action(self, request: Request, pk: Any) -> str:
+    return f"https://example.com/?pk={pk}"
+
+
+ +
+ Source code in starlette_admin/actions.py +
def link_row_action(
+    name: str,
+    text: str,
+    action_btn_class: Optional[str] = None,
+    icon_class: Optional[str] = None,
+    exclude_from_list: bool = False,
+    exclude_from_detail: bool = False,
+) -> Callable[[Callable[..., str]], Any]:
+    """
+    Decorator to add custom row link actions to a ModelView for URL redirection.
+
+    !!! note
+
+        This decorator is designed to create row actions that redirect to a URL, making it ideal for cases where a
+        row action should simply navigate users to a website or internal page.
+
+    Args:
+        name: Unique row action name for the ModelView.
+        text: Action text displayed to users.
+        action_btn_class: Action button variant for detail page (ex. `btn-success`, `btn-outline`, ...)
+        icon_class: Icon class (ex. `fa-lite fa-folder`, `fa-duotone fa-circle-right`, ...)
+        exclude_from_list: Set to True to exclude the action from the list view.
+        exclude_from_detail: Set to True to exclude the action from the detail view.
+
+
+    !!! usage
+
+        ```python
+        @link_row_action(
+            name="go_to_example",
+            text="Go to example.com",
+            icon_class="fas fa-arrow-up-right-from-square",
+        )
+        def go_to_example_row_action(self, request: Request, pk: Any) -> str:
+            return f"https://example.com/?pk={pk}"
+        ```
+
+    """
+
+    def wrap(f: Callable[..., str]) -> Callable[..., str]:
+        f._row_action = {  # type: ignore
+            "name": name,
+            "text": text,
+            "action_btn_class": action_btn_class,
+            "icon_class": icon_class,
+            "is_link": True,
+            "exclude_from_list": exclude_from_list,
+            "exclude_from_detail": exclude_from_detail,
+        }
+        return f
+
+    return wrap
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/auth/index.html b/api/auth/index.html new file mode 100644 index 00000000..0717bac0 --- /dev/null +++ b/api/auth/index.html @@ -0,0 +1,3456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Auth - Starlette Admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+ +
+ + + +
+
+ + + + + + + + + + + + + + + +

Auth

+ +
+ + + +

+ starlette_admin.auth + + +

+ +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ BaseAuthProvider + + +

+ + +
+

+ Bases: ABC

+ + +

Base class for implementing the Authentication into your admin interface

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ login_path + + str + +
+

The path for the login page.

+
+
+ '/login' +
+ logout_path + + str + +
+

The path for the logout page.

+
+
+ '/logout' +
+ allow_paths + + Optional[Sequence[str]] + +
+

A list of paths that are allowed without authentication.

+
+
+ None +
+ allow_routes + + Optional[Sequence[str]] + +
+

A list of route names that are allowed without authentication.

+
+
+ None +
+ + +
+ Warning +
    +
  • The usage of allow_paths is deprecated. It is recommended to use allow_routes + that specifies the route names instead.
  • +
+
+ + + + + +
+ Source code in starlette_admin/auth.py +
class BaseAuthProvider(ABC):
+    """
+    Base class for implementing the Authentication into your admin interface
+
+    Args:
+        login_path: The path for the login page.
+        logout_path: The path for the logout page.
+        allow_paths: A list of paths that are allowed without authentication.
+        allow_routes: A list of route names that are allowed without authentication.
+
+    Warning:
+        - The usage of `allow_paths` is deprecated. It is recommended to use `allow_routes`
+          that specifies the route names instead.
+
+    """
+
+    def __init__(
+        self,
+        login_path: str = "/login",
+        logout_path: str = "/logout",
+        allow_paths: Optional[Sequence[str]] = None,
+        allow_routes: Optional[Sequence[str]] = None,
+    ) -> None:
+        self.login_path = login_path
+        self.logout_path = logout_path
+        self.allow_paths = allow_paths
+        self.allow_routes = allow_routes
+
+        if allow_paths:
+            warnings.warn(
+                "`allow_paths` is deprecated. Use `allow_routes` instead.",
+                DeprecationWarning,
+                stacklevel=2,
+            )
+
+    @abstractmethod
+    def setup_admin(self, admin: "BaseAdmin") -> None:
+        """
+        This method is an abstract method that must be implemented in subclasses.
+        It allows custom configuration and setup of the admin interface
+        related to authentication and authorization.
+        """
+        raise NotImplementedError()
+
+    def get_middleware(self, admin: "BaseAdmin") -> Middleware:
+        """
+        This method returns the authentication middleware required for the admin interface
+        to enable authentication
+        """
+        return Middleware(AuthMiddleware, provider=self)
+
+    async def is_authenticated(self, request: Request) -> bool:
+        """
+        This method will be called to validate each incoming request.
+        You can also save the connected user information into the
+        request state and use it later to restrict access to some part
+        of your admin interface
+
+        Returns:
+            True: to accept the request
+            False: to redirect to login page
+
+        Examples:
+            ```python
+            async def is_authenticated(self, request: Request) -> bool:
+                if request.session.get("username", None) in users:
+                    # Save user object in state
+                    request.state.user = my_users_db.get(request.session["username"])
+                    return True
+                return False
+            ```
+        """
+        return False
+
+    def get_admin_config(self, request: Request) -> Optional[AdminConfig]:
+        """
+        Override this method to display custom `logo_url` and/or `app_title`
+
+        Returns:
+            AdminConfig: The admin interface config
+
+        Examples:
+            ```python
+            def get_admin_config(self, request: Request) -> AdminConfig:
+                user = request.state.user  # Retrieve current user (previously saved in the request state)
+                return AdminConfig(
+                    logo_url=request.url_for("static", path=user["company_logo_url"]),
+                )
+            ```
+
+            ```python
+            def get_admin_config(self, request: Request) -> AdminConfig:
+                user = request.state.user  # Retrieve current user (previously saved in the request state)
+                return AdminConfig(
+                    app_title="Hello, " + user["name"] + "!",
+                )
+            ```
+        """
+        return None  # pragma: no cover
+
+    def get_admin_user(self, request: Request) -> Optional[AdminUser]:
+        """
+        Override this method to display connected user `name` and/or `profile`
+
+        Returns:
+            AdminUser: The connected user info
+
+        Examples:
+            ```python
+            def get_admin_user(self, request: Request) -> AdminUser:
+                user = request.state.user  # Retrieve current user (previously saved in the request state)
+                return AdminUser(username=user["name"], photo_url=user["photo_url"])
+            ```
+        """
+        return None  # pragma: no cover
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ get_admin_config(request) + +

+ + +
+ +

Override this method to display custom logo_url and/or app_title

+ + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
AdminConfig + Optional[AdminConfig] + +
+

The admin interface config

+
+
+ + +

Examples:

+
def get_admin_config(self, request: Request) -> AdminConfig:
+    user = request.state.user  # Retrieve current user (previously saved in the request state)
+    return AdminConfig(
+        logo_url=request.url_for("static", path=user["company_logo_url"]),
+    )
+
+
def get_admin_config(self, request: Request) -> AdminConfig:
+    user = request.state.user  # Retrieve current user (previously saved in the request state)
+    return AdminConfig(
+        app_title="Hello, " + user["name"] + "!",
+    )
+
+ +
+ Source code in starlette_admin/auth.py +
def get_admin_config(self, request: Request) -> Optional[AdminConfig]:
+    """
+    Override this method to display custom `logo_url` and/or `app_title`
+
+    Returns:
+        AdminConfig: The admin interface config
+
+    Examples:
+        ```python
+        def get_admin_config(self, request: Request) -> AdminConfig:
+            user = request.state.user  # Retrieve current user (previously saved in the request state)
+            return AdminConfig(
+                logo_url=request.url_for("static", path=user["company_logo_url"]),
+            )
+        ```
+
+        ```python
+        def get_admin_config(self, request: Request) -> AdminConfig:
+            user = request.state.user  # Retrieve current user (previously saved in the request state)
+            return AdminConfig(
+                app_title="Hello, " + user["name"] + "!",
+            )
+        ```
+    """
+    return None  # pragma: no cover
+
+
+
+ +
+ +
+ + +

+ get_admin_user(request) + +

+ + +
+ +

Override this method to display connected user name and/or profile

+ + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
AdminUser + Optional[AdminUser] + +
+

The connected user info

+
+
+ + +

Examples:

+
def get_admin_user(self, request: Request) -> AdminUser:
+    user = request.state.user  # Retrieve current user (previously saved in the request state)
+    return AdminUser(username=user["name"], photo_url=user["photo_url"])
+
+ +
+ Source code in starlette_admin/auth.py +
def get_admin_user(self, request: Request) -> Optional[AdminUser]:
+    """
+    Override this method to display connected user `name` and/or `profile`
+
+    Returns:
+        AdminUser: The connected user info
+
+    Examples:
+        ```python
+        def get_admin_user(self, request: Request) -> AdminUser:
+            user = request.state.user  # Retrieve current user (previously saved in the request state)
+            return AdminUser(username=user["name"], photo_url=user["photo_url"])
+        ```
+    """
+    return None  # pragma: no cover
+
+
+
+ +
+ +
+ + +

+ get_middleware(admin) + +

+ + +
+ +

This method returns the authentication middleware required for the admin interface +to enable authentication

+ +
+ Source code in starlette_admin/auth.py +
def get_middleware(self, admin: "BaseAdmin") -> Middleware:
+    """
+    This method returns the authentication middleware required for the admin interface
+    to enable authentication
+    """
+    return Middleware(AuthMiddleware, provider=self)
+
+
+
+ +
+ +
+ + +

+ is_authenticated(request) + + + async + + +

+ + +
+ +

This method will be called to validate each incoming request. +You can also save the connected user information into the +request state and use it later to restrict access to some part +of your admin interface

+ + +

Returns:

+ + + + + + + + + + + + + + + + + +
Name TypeDescription
True + bool + +
+

to accept the request

+
+
False + bool + +
+

to redirect to login page

+
+
+ + +

Examples:

+
async def is_authenticated(self, request: Request) -> bool:
+    if request.session.get("username", None) in users:
+        # Save user object in state
+        request.state.user = my_users_db.get(request.session["username"])
+        return True
+    return False
+
+ +
+ Source code in starlette_admin/auth.py +
async def is_authenticated(self, request: Request) -> bool:
+    """
+    This method will be called to validate each incoming request.
+    You can also save the connected user information into the
+    request state and use it later to restrict access to some part
+    of your admin interface
+
+    Returns:
+        True: to accept the request
+        False: to redirect to login page
+
+    Examples:
+        ```python
+        async def is_authenticated(self, request: Request) -> bool:
+            if request.session.get("username", None) in users:
+                # Save user object in state
+                request.state.user = my_users_db.get(request.session["username"])
+                return True
+            return False
+        ```
+    """
+    return False
+
+
+
+ +
+ +
+ + +

+ setup_admin(admin) + + + abstractmethod + + +

+ + +
+ +

This method is an abstract method that must be implemented in subclasses. +It allows custom configuration and setup of the admin interface +related to authentication and authorization.

+ +
+ Source code in starlette_admin/auth.py +
88
+89
+90
+91
+92
+93
+94
+95
@abstractmethod
+def setup_admin(self, admin: "BaseAdmin") -> None:
+    """
+    This method is an abstract method that must be implemented in subclasses.
+    It allows custom configuration and setup of the admin interface
+    related to authentication and authorization.
+    """
+    raise NotImplementedError()
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ AuthProvider + + +

+ + +
+

+ Bases: BaseAuthProvider

+ + + + + + + +
+ Source code in starlette_admin/auth.py +
class AuthProvider(BaseAuthProvider):
+    async def login(
+        self,
+        username: str,
+        password: str,
+        remember_me: bool,
+        request: Request,
+        response: Response,
+    ) -> Response:
+        """
+        This method will be called to validate user credentials
+
+        Returns:
+            response: return the response back
+
+        Raises:
+            FormValidationError: when form values is not valid
+            LoginFailed: to display general error
+
+        Examples:
+            ```python
+            async def login(
+                self,
+                username: str,
+                password: str,
+                remember_me: bool,
+                request: Request,
+                response: Response,
+            ) -> Response:
+                if len(username) < 3:
+                    # Form data validation
+                    raise FormValidationError(
+                        {"username": "Ensure username has at least 03 characters"}
+                    )
+
+                if username in my_users_db and password == "password":
+                    # Save username in session
+                    request.session.update({"username": username})
+                    return response
+
+                raise LoginFailed("Invalid username or password")
+            ```
+        """
+        raise LoginFailed("Not Implemented")
+
+    async def logout(self, request: Request, response: Response) -> Response:
+        """
+        Implement logout logic (clear sessions, cookies, ...) here
+        and return the response back
+
+        Returns:
+            response: return the response back
+
+        Examples:
+            ```python
+            async def logout(self, request: Request, response: Response) -> Response:
+                request.session.clear()
+                return response
+            ```
+        """
+        raise NotImplementedError()
+
+    async def render_login(self, request: Request, admin: "BaseAdmin") -> Response:
+        """Render the default login page for username & password authentication."""
+        if request.method == "GET":
+            return admin.templates.TemplateResponse(
+                request=request,
+                name="login.html",
+                context={"_is_login_path": True},
+            )
+        form = await request.form()
+        try:
+            return await self.login(
+                form.get("username"),  # type: ignore
+                form.get("password"),  # type: ignore
+                form.get("remember_me") == "on",
+                request,
+                RedirectResponse(
+                    request.query_params.get("next")
+                    or request.url_for(admin.route_name + ":index"),
+                    status_code=HTTP_303_SEE_OTHER,
+                ),
+            )
+        except FormValidationError as errors:
+            return admin.templates.TemplateResponse(
+                request=request,
+                name="login.html",
+                context={"form_errors": errors, "_is_login_path": True},
+                status_code=HTTP_422_UNPROCESSABLE_ENTITY,
+            )
+        except LoginFailed as error:
+            return admin.templates.TemplateResponse(
+                request=request,
+                name="login.html",
+                context={"error": error.msg, "_is_login_path": True},
+                status_code=HTTP_400_BAD_REQUEST,
+            )
+
+    async def render_logout(self, request: Request, admin: "BaseAdmin") -> Response:
+        """Render the default logout page."""
+        return await self.logout(
+            request,
+            RedirectResponse(
+                request.url_for(admin.route_name + ":index"),
+                status_code=HTTP_303_SEE_OTHER,
+            ),
+        )
+
+    def get_login_route(self, admin: "BaseAdmin") -> Route:
+        """
+        Get the login route for the admin interface.
+        """
+        return Route(
+            self.login_path,
+            wrap_endpoint_with_kwargs(self.render_login, admin=admin),
+            methods=["GET", "POST"],
+        )
+
+    def get_logout_route(self, admin: "BaseAdmin") -> Route:
+        """
+        Get the logout route for the admin interface.
+        """
+        return Route(
+            self.logout_path,
+            wrap_endpoint_with_kwargs(self.render_logout, admin=admin),
+            methods=["GET"],
+        )
+
+    def setup_admin(self, admin: "BaseAdmin") -> None:
+        """
+        Set up the admin interface by adding necessary middleware and routes.
+        """
+        admin.middlewares.append(self.get_middleware(admin=admin))
+        login_route = self.get_login_route(admin=admin)
+        logout_route = self.get_logout_route(admin=admin)
+        login_route.name = "login"
+        logout_route.name = "logout"
+        admin.routes.extend([login_route, logout_route])
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ get_login_route(admin) + +

+ + +
+ +

Get the login route for the admin interface.

+ +
+ Source code in starlette_admin/auth.py +
def get_login_route(self, admin: "BaseAdmin") -> Route:
+    """
+    Get the login route for the admin interface.
+    """
+    return Route(
+        self.login_path,
+        wrap_endpoint_with_kwargs(self.render_login, admin=admin),
+        methods=["GET", "POST"],
+    )
+
+
+
+ +
+ +
+ + +

+ get_logout_route(admin) + +

+ + +
+ +

Get the logout route for the admin interface.

+ +
+ Source code in starlette_admin/auth.py +
def get_logout_route(self, admin: "BaseAdmin") -> Route:
+    """
+    Get the logout route for the admin interface.
+    """
+    return Route(
+        self.logout_path,
+        wrap_endpoint_with_kwargs(self.render_logout, admin=admin),
+        methods=["GET"],
+    )
+
+
+
+ +
+ +
+ + +

+ login(username, password, remember_me, request, response) + + + async + + +

+ + +
+ +

This method will be called to validate user credentials

+ + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
response + Response + +
+

return the response back

+
+
+ + +

Raises:

+ + + + + + + + + + + + + + + + + +
TypeDescription
+ FormValidationError + +
+

when form values is not valid

+
+
+ LoginFailed + +
+

to display general error

+
+
+ + +

Examples:

+
async def login(
+    self,
+    username: str,
+    password: str,
+    remember_me: bool,
+    request: Request,
+    response: Response,
+) -> Response:
+    if len(username) < 3:
+        # Form data validation
+        raise FormValidationError(
+            {"username": "Ensure username has at least 03 characters"}
+        )
+
+    if username in my_users_db and password == "password":
+        # Save username in session
+        request.session.update({"username": username})
+        return response
+
+    raise LoginFailed("Invalid username or password")
+
+ +
+ Source code in starlette_admin/auth.py +
async def login(
+    self,
+    username: str,
+    password: str,
+    remember_me: bool,
+    request: Request,
+    response: Response,
+) -> Response:
+    """
+    This method will be called to validate user credentials
+
+    Returns:
+        response: return the response back
+
+    Raises:
+        FormValidationError: when form values is not valid
+        LoginFailed: to display general error
+
+    Examples:
+        ```python
+        async def login(
+            self,
+            username: str,
+            password: str,
+            remember_me: bool,
+            request: Request,
+            response: Response,
+        ) -> Response:
+            if len(username) < 3:
+                # Form data validation
+                raise FormValidationError(
+                    {"username": "Ensure username has at least 03 characters"}
+                )
+
+            if username in my_users_db and password == "password":
+                # Save username in session
+                request.session.update({"username": username})
+                return response
+
+            raise LoginFailed("Invalid username or password")
+        ```
+    """
+    raise LoginFailed("Not Implemented")
+
+
+
+ +
+ +
+ + +

+ logout(request, response) + + + async + + +

+ + +
+ +

Implement logout logic (clear sessions, cookies, ...) here +and return the response back

+ + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
response + Response + +
+

return the response back

+
+
+ + +

Examples:

+
async def logout(self, request: Request, response: Response) -> Response:
+    request.session.clear()
+    return response
+
+ +
+ Source code in starlette_admin/auth.py +
async def logout(self, request: Request, response: Response) -> Response:
+    """
+    Implement logout logic (clear sessions, cookies, ...) here
+    and return the response back
+
+    Returns:
+        response: return the response back
+
+    Examples:
+        ```python
+        async def logout(self, request: Request, response: Response) -> Response:
+            request.session.clear()
+            return response
+        ```
+    """
+    raise NotImplementedError()
+
+
+
+ +
+ +
+ + +

+ render_login(request, admin) + + + async + + +

+ + +
+ +

Render the default login page for username & password authentication.

+ +
+ Source code in starlette_admin/auth.py +
async def render_login(self, request: Request, admin: "BaseAdmin") -> Response:
+    """Render the default login page for username & password authentication."""
+    if request.method == "GET":
+        return admin.templates.TemplateResponse(
+            request=request,
+            name="login.html",
+            context={"_is_login_path": True},
+        )
+    form = await request.form()
+    try:
+        return await self.login(
+            form.get("username"),  # type: ignore
+            form.get("password"),  # type: ignore
+            form.get("remember_me") == "on",
+            request,
+            RedirectResponse(
+                request.query_params.get("next")
+                or request.url_for(admin.route_name + ":index"),
+                status_code=HTTP_303_SEE_OTHER,
+            ),
+        )
+    except FormValidationError as errors:
+        return admin.templates.TemplateResponse(
+            request=request,
+            name="login.html",
+            context={"form_errors": errors, "_is_login_path": True},
+            status_code=HTTP_422_UNPROCESSABLE_ENTITY,
+        )
+    except LoginFailed as error:
+        return admin.templates.TemplateResponse(
+            request=request,
+            name="login.html",
+            context={"error": error.msg, "_is_login_path": True},
+            status_code=HTTP_400_BAD_REQUEST,
+        )
+
+
+
+ +
+ +
+ + +

+ render_logout(request, admin) + + + async + + +

+ + +
+ +

Render the default logout page.

+ +
+ Source code in starlette_admin/auth.py +
async def render_logout(self, request: Request, admin: "BaseAdmin") -> Response:
+    """Render the default logout page."""
+    return await self.logout(
+        request,
+        RedirectResponse(
+            request.url_for(admin.route_name + ":index"),
+            status_code=HTTP_303_SEE_OTHER,
+        ),
+    )
+
+
+
+ +
+ +
+ + +

+ setup_admin(admin) + +

+ + +
+ +

Set up the admin interface by adding necessary middleware and routes.

+ +
+ Source code in starlette_admin/auth.py +
def setup_admin(self, admin: "BaseAdmin") -> None:
+    """
+    Set up the admin interface by adding necessary middleware and routes.
+    """
+    admin.middlewares.append(self.get_middleware(admin=admin))
+    login_route = self.get_login_route(admin=admin)
+    logout_route = self.get_logout_route(admin=admin)
+    login_route.name = "login"
+    logout_route.name = "logout"
+    admin.routes.extend([login_route, logout_route])
+
+
+
+ +
+ + + +
+ +
+ +
+ + +
+ + +

+ login_not_required(endpoint) + +

+ + +
+ +

Decorators for endpoints that do not require login.

+ +
+ Source code in starlette_admin/auth.py +
31
+32
+33
+34
+35
+36
+37
+38
def login_not_required(
+    endpoint: Callable[..., Any],
+) -> Callable[..., Any]:
+    """Decorators for endpoints that do not require login."""
+
+    endpoint._login_not_required = True  # type: ignore[attr-defined]
+
+    return endpoint
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/base-admin/index.html b/api/base-admin/index.html new file mode 100644 index 00000000..e24f58bb --- /dev/null +++ b/api/base-admin/index.html @@ -0,0 +1,3024 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + BaseAdmin - Starlette Admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + +

BaseAdmin

+ +
+ + + + +
+ + +

Base class for implementing Admin interface.

+ + + + + + +
+ Source code in starlette_admin/base.py +
 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
class BaseAdmin:
+    """Base class for implementing Admin interface."""
+
+    def __init__(
+        self,
+        title: str = _("Admin"),
+        base_url: str = "/admin",
+        route_name: str = "admin",
+        logo_url: Optional[str] = None,
+        login_logo_url: Optional[str] = None,
+        templates_dir: str = "templates",
+        statics_dir: Optional[str] = None,
+        index_view: Optional[CustomView] = None,
+        auth_provider: Optional[BaseAuthProvider] = None,
+        middlewares: Optional[Sequence[Middleware]] = None,
+        debug: bool = False,
+        i18n_config: Optional[I18nConfig] = None,
+        favicon_url: Optional[str] = None,
+    ):
+        """
+        Parameters:
+            title: Admin title.
+            base_url: Base URL for Admin interface.
+            route_name: Mounted Admin name
+            logo_url: URL of logo to be displayed instead of title.
+            login_logo_url: If set, it will be used for login interface instead of logo_url.
+            templates_dir: Templates dir for customisation
+            statics_dir: Statics dir for customisation
+            index_view: CustomView to use for index page.
+            auth_provider: Authentication Provider
+            middlewares: Starlette middlewares
+            i18n_config: i18n configuration
+            favicon_url: URL of favicon.
+        """
+        self.title = title
+        self.base_url = base_url
+        self.route_name = route_name
+        self.logo_url = logo_url
+        self.login_logo_url = login_logo_url
+        self.favicon_url = favicon_url
+        self.templates_dir = templates_dir
+        self.statics_dir = statics_dir
+        self.auth_provider = auth_provider
+        self.middlewares = list(middlewares) if middlewares is not None else []
+        self.index_view = (
+            index_view
+            if (index_view is not None)
+            else CustomView("", add_to_menu=False)
+        )
+        self._views: List[BaseView] = []
+        self._models: List[BaseModelView] = []
+        self.routes: List[Union[Route, Mount]] = []
+        self.debug = debug
+        self.i18n_config = i18n_config
+        self._setup_templates()
+        self.init_locale()
+        self.init_auth()
+        self.init_routes()
+
+    def add_view(self, view: Union[Type[BaseView], BaseView]) -> None:
+        """
+        Add View to the Admin interface.
+        """
+        view_instance = view if isinstance(view, BaseView) else view()
+        self._views.append(view_instance)
+        self.setup_view(view_instance)
+
+    def custom_render_js(self, request: Request) -> Optional[str]:
+        """
+        Override this function to provide a link to custom js to override the
+        global `render` object in javascript which is use to render fields in
+        list page.
+
+        Args:
+            request: Starlette Request
+        """
+        return None
+
+    def init_locale(self) -> None:
+        if self.i18n_config is not None:
+            try:
+                import babel  # noqa
+            except ImportError as err:
+                raise ImportError(
+                    "'babel' package is required to use i18n features."
+                    "Install it with `pip install starlette-admin[i18n]`"
+                ) from err
+            self.middlewares.insert(
+                0, Middleware(LocaleMiddleware, i18n_config=self.i18n_config)
+            )
+
+    def init_auth(self) -> None:
+        if self.auth_provider is not None:
+            self.auth_provider.setup_admin(self)
+
+    def init_routes(self) -> None:
+        statics = StaticFiles(directory=self.statics_dir, packages=["starlette_admin"])
+        self.routes.extend(
+            [
+                Mount("/statics", app=statics, name="statics"),
+                Route(
+                    self.index_view.path,
+                    self._render_custom_view(self.index_view),
+                    methods=self.index_view.methods,
+                    name="index",
+                ),
+                Route(
+                    "/api/{identity}",
+                    self._render_api,
+                    methods=["GET"],
+                    name="api",
+                ),
+                Route(
+                    "/api/{identity}/action",
+                    self.handle_action,
+                    methods=["GET", "POST"],
+                    name="action",
+                ),
+                Route(
+                    "/api/{identity}/row-action",
+                    self.handle_row_action,
+                    methods=["GET", "POST"],
+                    name="row-action",
+                ),
+                Route(
+                    "/{identity}/list",
+                    self._render_list,
+                    methods=["GET"],
+                    name="list",
+                ),
+                Route(
+                    "/{identity}/detail/{pk}",
+                    self._render_detail,
+                    methods=["GET"],
+                    name="detail",
+                ),
+                Route(
+                    "/{identity}/create",
+                    self._render_create,
+                    methods=["GET", "POST"],
+                    name="create",
+                ),
+                Route(
+                    "/{identity}/edit/{pk}",
+                    self._render_edit,
+                    methods=["GET", "POST"],
+                    name="edit",
+                ),
+            ]
+        )
+        if self.index_view.add_to_menu:
+            self._views.append(self.index_view)
+
+    def _setup_templates(self) -> None:
+        env = Environment(
+            loader=ChoiceLoader(
+                [
+                    FileSystemLoader(self.templates_dir),
+                    PackageLoader("starlette_admin", "templates"),
+                ]
+            ),
+            extensions=["jinja2.ext.i18n"],
+        )
+        templates = Jinja2Templates(env=env)
+
+        # globals
+        templates.env.globals["views"] = self._views
+        templates.env.globals["app_title"] = self.title
+        templates.env.globals["is_auth_enabled"] = self.auth_provider is not None
+        templates.env.globals["__name__"] = self.route_name
+        templates.env.globals["logo_url"] = self.logo_url
+        templates.env.globals["login_logo_url"] = self.login_logo_url
+        templates.env.globals["favicon_url"] = self.favicon_url
+        templates.env.globals["custom_render_js"] = lambda r: self.custom_render_js(r)
+        templates.env.globals["get_locale"] = get_locale
+        templates.env.globals["get_locale_display_name"] = get_locale_display_name
+        templates.env.globals["i18n_config"] = self.i18n_config or I18nConfig()
+        # filters
+        templates.env.filters["is_custom_view"] = lambda r: isinstance(r, CustomView)
+        templates.env.filters["is_link"] = lambda res: isinstance(res, Link)
+        templates.env.filters["is_model"] = lambda res: isinstance(res, BaseModelView)
+        templates.env.filters["is_dropdown"] = lambda res: isinstance(res, DropDown)
+        templates.env.filters["get_admin_user"] = (
+            self.auth_provider.get_admin_user if self.auth_provider else None
+        )
+        templates.env.filters["get_admin_config"] = (
+            self.auth_provider.get_admin_config if self.auth_provider else None
+        )
+        templates.env.filters["tojson"] = lambda data: json.dumps(data, default=str)
+        templates.env.filters["file_icon"] = get_file_icon
+        templates.env.filters["to_model"] = (
+            lambda identity: self._find_model_from_identity(identity)
+        )
+        templates.env.filters["is_iter"] = lambda v: isinstance(v, (list, tuple))
+        templates.env.filters["is_str"] = lambda v: isinstance(v, str)
+        templates.env.filters["is_dict"] = lambda v: isinstance(v, dict)
+        templates.env.filters["ra"] = lambda a: RequestAction(a)
+        # install i18n
+        templates.env.install_gettext_callables(gettext, ngettext, True)  # type: ignore
+        self.templates = templates
+
+    def setup_view(self, view: BaseView) -> None:
+        if isinstance(view, DropDown):
+            for sub_view in view.views:
+                self.setup_view(sub_view)
+        elif isinstance(view, CustomView):
+            self.routes.insert(
+                0,
+                Route(
+                    view.path,
+                    endpoint=self._render_custom_view(view),
+                    methods=view.methods,
+                    name=view.name,
+                ),
+            )
+        elif isinstance(view, BaseModelView):
+            view._find_foreign_model = self._find_model_from_identity
+            self._models.append(view)
+
+    def _find_model_from_identity(self, identity: Optional[str]) -> BaseModelView:
+        if identity is not None:
+            for model in self._models:
+                if model.identity == identity:
+                    return model
+        raise HTTPException(
+            HTTP_404_NOT_FOUND,
+            _("Model with identity %(identity)s not found") % {"identity": identity},
+        )
+
+    def _render_custom_view(
+        self, custom_view: CustomView
+    ) -> Callable[[Request], Awaitable[Response]]:
+        async def wrapper(request: Request) -> Response:
+            if not custom_view.is_accessible(request):
+                raise HTTPException(HTTP_403_FORBIDDEN)
+            return await custom_view.render(request, self.templates)
+
+        return wrapper
+
+    async def _render_api(self, request: Request) -> Response:
+        identity = request.path_params.get("identity")
+        model = self._find_model_from_identity(identity)
+        if not model.is_accessible(request):
+            return JSONResponse(None, status_code=HTTP_403_FORBIDDEN)
+        skip = int(request.query_params.get("skip") or "0")
+        limit = int(request.query_params.get("limit") or "100")
+        order_by = request.query_params.getlist("order_by")
+        where = request.query_params.get("where")
+        pks = request.query_params.getlist("pks")
+        select2 = "select2" in request.query_params
+        request.state.action = RequestAction.API if select2 else RequestAction.LIST
+        if len(pks) > 0:
+            items = await model.find_by_pks(request, pks)
+            total = len(items)
+        else:
+            if where is not None:
+                try:
+                    where = json.loads(where)
+                except JSONDecodeError:
+                    where = str(where)
+            items = await model.find_all(
+                request=request,
+                skip=skip,
+                limit=limit,
+                where=where,
+                order_by=order_by,
+            )
+            total = await model.count(request=request, where=where)
+        serialized_items = [
+            (
+                await model.serialize(
+                    item,
+                    request,
+                    RequestAction.API if select2 else RequestAction.LIST,
+                    include_relationships=not select2,
+                    include_select2=select2,
+                )
+            )
+            for item in items
+        ]
+
+        if not select2:
+            # Add row actions for datatables
+            row_actions = await model.get_all_row_actions(request)
+            assert model.pk_attr
+            for serialized_item in serialized_items:
+                serialized_item["_meta"]["rowActions"] = self.templates.get_template(
+                    "row-actions.html"
+                ).render(
+                    _actions=row_actions,
+                    display_type=model.row_actions_display_type,
+                    pk=serialized_item[model.pk_attr],
+                    request=request,
+                    model=model,
+                )
+
+        return JSONResponse(
+            {
+                "items": serialized_items,
+                "total": total,
+            }
+        )
+
+    async def handle_action(self, request: Request) -> Response:
+        request.state.action = RequestAction.ACTION
+        try:
+            identity = request.path_params.get("identity")
+            pks = request.query_params.getlist("pks")
+            name = not_none(request.query_params.get("name"))
+            model = self._find_model_from_identity(identity)
+            if not model.is_accessible(request):
+                raise ActionFailed("Forbidden")
+            handler_return = await model.handle_action(request, pks, name)
+            if isinstance(handler_return, Response):
+                return handler_return
+            return JSONResponse({"msg": handler_return})
+        except ActionFailed as exc:
+            return JSONResponse({"msg": exc.msg}, status_code=HTTP_400_BAD_REQUEST)
+
+    async def handle_row_action(self, request: Request) -> Response:
+        request.state.action = RequestAction.ROW_ACTION
+        try:
+            identity = request.path_params.get("identity")
+            pk = request.query_params.get("pk")
+            name = not_none(request.query_params.get("name"))
+            model = self._find_model_from_identity(identity)
+            if not model.is_accessible(request):
+                raise ActionFailed("Forbidden")
+            handler_return = await model.handle_row_action(request, pk, name)
+            if isinstance(handler_return, Response):
+                return handler_return
+            return JSONResponse({"msg": handler_return})
+        except ActionFailed as exc:
+            return JSONResponse({"msg": exc.msg}, status_code=HTTP_400_BAD_REQUEST)
+
+    async def _render_list(self, request: Request) -> Response:
+        request.state.action = RequestAction.LIST
+        identity = request.path_params.get("identity")
+        model = self._find_model_from_identity(identity)
+        if not model.is_accessible(request):
+            raise HTTPException(HTTP_403_FORBIDDEN)
+        return self.templates.TemplateResponse(
+            request=request,
+            name=model.list_template,
+            context={
+                "model": model,
+                "title": model.title(request),
+                "_actions": await model.get_all_actions(request),
+                "__js_model__": await model._configs(request),
+            },
+        )
+
+    async def _render_detail(self, request: Request) -> Response:
+        request.state.action = RequestAction.DETAIL
+        identity = request.path_params.get("identity")
+        model = self._find_model_from_identity(identity)
+        if not model.is_accessible(request) or not model.can_view_details(request):
+            raise HTTPException(HTTP_403_FORBIDDEN)
+        pk = request.path_params.get("pk")
+        obj = await model.find_by_pk(request, pk)
+        if obj is None:
+            raise HTTPException(HTTP_404_NOT_FOUND)
+        return self.templates.TemplateResponse(
+            request=request,
+            name=model.detail_template,
+            context={
+                "title": model.title(request),
+                "model": model,
+                "raw_obj": obj,
+                "_actions": await model.get_all_row_actions(request),
+                "obj": await model.serialize(obj, request, RequestAction.DETAIL),
+            },
+        )
+
+    async def _render_create(self, request: Request) -> Response:
+        request.state.action = RequestAction.CREATE
+        identity = request.path_params.get("identity")
+        model = self._find_model_from_identity(identity)
+        config = {"title": model.title(request), "model": model}
+        if not model.is_accessible(request) or not model.can_create(request):
+            raise HTTPException(HTTP_403_FORBIDDEN)
+        if request.method == "GET":
+            return self.templates.TemplateResponse(
+                request=request,
+                name=model.create_template,
+                context=config,
+            )
+        form = await request.form()
+        dict_obj = await self.form_to_dict(request, form, model, RequestAction.CREATE)
+        try:
+            obj = await model.create(request, dict_obj)
+        except FormValidationError as exc:
+            config.update(
+                {
+                    "errors": exc.errors,
+                    "obj": dict_obj,
+                }
+            )
+            return self.templates.TemplateResponse(
+                request=request,
+                name=model.create_template,
+                context=config,
+                status_code=HTTP_422_UNPROCESSABLE_ENTITY,
+            )
+        pk = await model.get_pk_value(request, obj)
+        url = request.url_for(self.route_name + ":list", identity=model.identity)
+        if form.get("_continue_editing", None) is not None:
+            url = request.url_for(
+                self.route_name + ":edit", identity=model.identity, pk=pk
+            )
+        elif form.get("_add_another", None) is not None:
+            url = request.url
+        return RedirectResponse(url, status_code=HTTP_303_SEE_OTHER)
+
+    async def _render_edit(self, request: Request) -> Response:
+        request.state.action = RequestAction.EDIT
+        identity = request.path_params.get("identity")
+        model = self._find_model_from_identity(identity)
+        if not model.is_accessible(request) or not model.can_edit(request):
+            raise HTTPException(HTTP_403_FORBIDDEN)
+        pk = request.path_params.get("pk")
+        obj = await model.find_by_pk(request, pk)
+        if obj is None:
+            raise HTTPException(HTTP_404_NOT_FOUND)
+        config = {
+            "title": model.title(request),
+            "model": model,
+            "raw_obj": obj,
+            "obj": await model.serialize(obj, request, RequestAction.EDIT),
+        }
+        if request.method == "GET":
+            return self.templates.TemplateResponse(
+                request=request,
+                name=model.edit_template,
+                context=config,
+            )
+        form = await request.form()
+        dict_obj = await self.form_to_dict(request, form, model, RequestAction.EDIT)
+        try:
+            obj = await model.edit(request, pk, dict_obj)
+        except FormValidationError as exc:
+            config.update(
+                {
+                    "errors": exc.errors,
+                    "obj": dict_obj,
+                }
+            )
+            return self.templates.TemplateResponse(
+                request=request,
+                name=model.edit_template,
+                context=config,
+                status_code=HTTP_422_UNPROCESSABLE_ENTITY,
+            )
+        pk = await model.get_pk_value(request, obj)
+        url = request.url_for(self.route_name + ":list", identity=model.identity)
+        if form.get("_continue_editing", None) is not None:
+            url = request.url_for(
+                self.route_name + ":edit", identity=model.identity, pk=pk
+            )
+        elif form.get("_add_another", None) is not None:
+            url = request.url_for(self.route_name + ":create", identity=model.identity)
+        return RedirectResponse(url, status_code=HTTP_303_SEE_OTHER)
+
+    async def _render_error(
+        self,
+        request: Request,
+        exc: Exception = HTTPException(status_code=HTTP_500_INTERNAL_SERVER_ERROR),
+    ) -> Response:
+        assert isinstance(exc, HTTPException)
+        return self.templates.TemplateResponse(
+            request=request,
+            name="error.html",
+            context={"exc": exc},
+            status_code=exc.status_code,
+        )
+
+    async def form_to_dict(
+        self,
+        request: Request,
+        form_data: FormData,
+        model: BaseModelView,
+        action: RequestAction,
+    ) -> Dict[str, Any]:
+        data = {}
+        for field in model.get_fields_list(request, action):
+            data[field.name] = await field.parse_form_data(request, form_data, action)
+        return data
+
+    def mount_to(self, app: Starlette) -> None:
+        admin_app = Starlette(
+            routes=self.routes,
+            middleware=self.middlewares,
+            debug=self.debug,
+            exception_handlers={HTTPException: self._render_error},
+        )
+        admin_app.state.ROUTE_NAME = self.route_name
+        app.mount(
+            self.base_url,
+            app=admin_app,
+            name=self.route_name,
+        )
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ __init__(title=_('Admin'), base_url='/admin', route_name='admin', logo_url=None, login_logo_url=None, templates_dir='templates', statics_dir=None, index_view=None, auth_provider=None, middlewares=None, debug=False, i18n_config=None, favicon_url=None) + +

+ + +
+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ title + + str + +
+

Admin title.

+
+
+ lazy_gettext('Admin') +
+ base_url + + str + +
+

Base URL for Admin interface.

+
+
+ '/admin' +
+ route_name + + str + +
+

Mounted Admin name

+
+
+ 'admin' +
+ logo_url + + Optional[str] + +
+

URL of logo to be displayed instead of title.

+
+
+ None +
+ login_logo_url + + Optional[str] + +
+

If set, it will be used for login interface instead of logo_url.

+
+
+ None +
+ templates_dir + + str + +
+

Templates dir for customisation

+
+
+ 'templates' +
+ statics_dir + + Optional[str] + +
+

Statics dir for customisation

+
+
+ None +
+ index_view + + Optional[CustomView] + +
+

CustomView to use for index page.

+
+
+ None +
+ auth_provider + + Optional[BaseAuthProvider] + +
+

Authentication Provider

+
+
+ None +
+ middlewares + + Optional[Sequence[Middleware]] + +
+

Starlette middlewares

+
+
+ None +
+ i18n_config + + Optional[I18nConfig] + +
+

i18n configuration

+
+
+ None +
+ favicon_url + + Optional[str] + +
+

URL of favicon.

+
+
+ None +
+ +
+ Source code in starlette_admin/base.py +
42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
def __init__(
+    self,
+    title: str = _("Admin"),
+    base_url: str = "/admin",
+    route_name: str = "admin",
+    logo_url: Optional[str] = None,
+    login_logo_url: Optional[str] = None,
+    templates_dir: str = "templates",
+    statics_dir: Optional[str] = None,
+    index_view: Optional[CustomView] = None,
+    auth_provider: Optional[BaseAuthProvider] = None,
+    middlewares: Optional[Sequence[Middleware]] = None,
+    debug: bool = False,
+    i18n_config: Optional[I18nConfig] = None,
+    favicon_url: Optional[str] = None,
+):
+    """
+    Parameters:
+        title: Admin title.
+        base_url: Base URL for Admin interface.
+        route_name: Mounted Admin name
+        logo_url: URL of logo to be displayed instead of title.
+        login_logo_url: If set, it will be used for login interface instead of logo_url.
+        templates_dir: Templates dir for customisation
+        statics_dir: Statics dir for customisation
+        index_view: CustomView to use for index page.
+        auth_provider: Authentication Provider
+        middlewares: Starlette middlewares
+        i18n_config: i18n configuration
+        favicon_url: URL of favicon.
+    """
+    self.title = title
+    self.base_url = base_url
+    self.route_name = route_name
+    self.logo_url = logo_url
+    self.login_logo_url = login_logo_url
+    self.favicon_url = favicon_url
+    self.templates_dir = templates_dir
+    self.statics_dir = statics_dir
+    self.auth_provider = auth_provider
+    self.middlewares = list(middlewares) if middlewares is not None else []
+    self.index_view = (
+        index_view
+        if (index_view is not None)
+        else CustomView("", add_to_menu=False)
+    )
+    self._views: List[BaseView] = []
+    self._models: List[BaseModelView] = []
+    self.routes: List[Union[Route, Mount]] = []
+    self.debug = debug
+    self.i18n_config = i18n_config
+    self._setup_templates()
+    self.init_locale()
+    self.init_auth()
+    self.init_routes()
+
+
+
+ +
+ +
+ + +

+ add_view(view) + +

+ + +
+ +

Add View to the Admin interface.

+ +
+ Source code in starlette_admin/base.py +
def add_view(self, view: Union[Type[BaseView], BaseView]) -> None:
+    """
+    Add View to the Admin interface.
+    """
+    view_instance = view if isinstance(view, BaseView) else view()
+    self._views.append(view_instance)
+    self.setup_view(view_instance)
+
+
+
+ +
+ +
+ + +

+ custom_render_js(request) + +

+ + +
+ +

Override this function to provide a link to custom js to override the +global render object in javascript which is use to render fields in +list page.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

Starlette Request

+
+
+ required +
+ +
+ Source code in starlette_admin/base.py +
def custom_render_js(self, request: Request) -> Optional[str]:
+    """
+    Override this function to provide a link to custom js to override the
+    global `render` object in javascript which is use to render fields in
+    list page.
+
+    Args:
+        request: Starlette Request
+    """
+    return None
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/contrib/sqlalchemy/modelview/index.html b/api/contrib/sqlalchemy/modelview/index.html new file mode 100644 index 00000000..63047396 --- /dev/null +++ b/api/contrib/sqlalchemy/modelview/index.html @@ -0,0 +1,3284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + ModelView - Starlette Admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + +

ModelView

+ +
+ + + + +
+

+ Bases: BaseModelView

+ + +

A view for managing SQLAlchemy models.

+ + + + + + +
+ Source code in starlette_admin/contrib/sqla/view.py +
 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
class ModelView(BaseModelView):
+    """A view for managing SQLAlchemy models."""
+
+    sortable_field_mapping: ClassVar[Dict[str, InstrumentedAttribute]] = {}
+    """A dictionary for overriding the default model attribute used for sorting.
+
+    Example:
+        ```python
+        class Post(Base):
+            __tablename__ = "post"
+
+            id: Mapped[int] = mapped_column(primary_key=True)
+            title: Mapped[str] = mapped_column()
+            user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
+            user: Mapped[User] = relationship(back_populates="posts")
+
+
+        class PostView(ModelView):
+            sortable_field = ["id", "title", "user"]
+            sortable_field_mapping = {
+                "user": User.age,  # Sort by the age of the related user
+            }
+        ```
+    """
+
+    def __init__(
+        self,
+        model: Type[Any],
+        icon: Optional[str] = None,
+        name: Optional[str] = None,
+        label: Optional[str] = None,
+        identity: Optional[str] = None,
+        converter: Optional[BaseSQLAModelConverter] = None,
+    ):
+        try:
+            mapper: Mapper = inspect(model)  # type: ignore
+        except NoInspectionAvailable:
+            raise InvalidModelError(  # noqa B904
+                f"Class {model.__name__} is not a SQLAlchemy model."
+            )
+        self.model = model
+        self.identity = (
+            identity or self.identity or slugify_class_name(self.model.__name__)
+        )
+        self.label = (
+            label or self.label or prettify_class_name(self.model.__name__) + "s"
+        )
+        self.name = name or self.name or prettify_class_name(self.model.__name__)
+        self.icon = icon
+        if self.fields is None or len(self.fields) == 0:
+            self.fields = [
+                self.model.__dict__[f].key
+                for f in self.model.__dict__
+                if type(self.model.__dict__[f]) is InstrumentedAttribute
+            ]
+        self.fields = (converter or ModelConverter()).convert_fields_list(
+            fields=self.fields, model=self.model, mapper=mapper
+        )
+        self._setup_primary_key()
+        self.exclude_fields_from_list = normalize_list(self.exclude_fields_from_list)  # type: ignore
+        self.exclude_fields_from_detail = normalize_list(self.exclude_fields_from_detail)  # type: ignore
+        self.exclude_fields_from_create = normalize_list(self.exclude_fields_from_create)  # type: ignore
+        self.exclude_fields_from_edit = normalize_list(self.exclude_fields_from_edit)  # type: ignore
+        _default_list = [
+            field.name
+            for field in self.fields
+            if not isinstance(field, (RelationField, FileField))
+        ]
+        self.searchable_fields = normalize_list(
+            self.searchable_fields
+            if (self.searchable_fields is not None)
+            else _default_list
+        )
+        self.sortable_fields = normalize_list(
+            self.sortable_fields
+            if (self.sortable_fields is not None)
+            else _default_list
+        )
+        self.export_fields = normalize_list(self.export_fields)
+        self.fields_default_sort = normalize_list(
+            self.fields_default_sort, is_default_sort_list=True
+        )
+        super().__init__()
+
+    def _setup_primary_key(self) -> None:
+        # Detect the primary key attribute(s) of the model
+        _pk_attrs = []
+        self._pk_column: Union[
+            Tuple[InstrumentedAttribute, ...], InstrumentedAttribute
+        ] = ()
+        self._pk_coerce: Union[Tuple[type, ...], type] = ()
+        for key in self.model.__dict__:
+            attr = getattr(self.model, key)
+            if isinstance(attr, InstrumentedAttribute) and getattr(
+                attr, "primary_key", False
+            ):
+                _pk_attrs.append(key)
+        if len(_pk_attrs) > 1:
+            self._pk_column = tuple(getattr(self.model, attr) for attr in _pk_attrs)
+            self._pk_coerce = tuple(
+                extract_column_python_type(c) for c in self._pk_column
+            )
+            self.pk_field: BaseField = MultiplePKField(_pk_attrs)
+        else:
+            assert (
+                len(_pk_attrs) == 1
+            ), f"No primary key found in model {self.model.__name__}"
+            self._pk_column = getattr(self.model, _pk_attrs[0])
+            self._pk_coerce = extract_column_python_type(self._pk_column)  # type: ignore[arg-type]
+            try:
+                # Try to find the primary key field among the fields
+                self.pk_field = next(f for f in self.fields if f.name == _pk_attrs[0])
+            except StopIteration:
+                # If the primary key is not among the fields, treat its value as a string
+                self.pk_field = StringField(_pk_attrs[0])
+        self.pk_attr = self.pk_field.name
+
+    async def handle_action(
+        self, request: Request, pks: List[Any], name: str
+    ) -> Union[str, Response]:
+        try:
+            return await super().handle_action(request, pks, name)
+        except SQLAlchemyError as exc:
+            raise ActionFailed(str(exc)) from exc
+
+    async def handle_row_action(
+        self, request: Request, pk: Any, name: str
+    ) -> Union[str, Response]:
+        try:
+            return await super().handle_row_action(request, pk, name)
+        except SQLAlchemyError as exc:
+            raise ActionFailed(str(exc)) from exc
+
+    def get_list_query(self, request: Request) -> Select:
+        """
+        Return a Select expression which is used as base statement for
+        [find_all][starlette_admin.views.BaseModelView.find_all] method.
+
+        Examples:
+            ```python  hl_lines="3-4"
+            class PostView(ModelView):
+
+                    def get_list_query(self, request: Request):
+                        return super().get_list_query().where(Post.published == true())
+
+                    def get_count_query(self, request: Request):
+                        return super().get_count_query().where(Post.published == true())
+            ```
+
+        If you override this method, don't forget to also override
+        [get_count_query][starlette_admin.contrib.sqla.ModelView.get_count_query],
+        for displaying the correct item count in the list view.
+        """
+        return select(self.model)
+
+    def get_count_query(self, request: Request) -> Select:
+        """
+        Return a Select expression which is used as base statement for
+        [count][starlette_admin.views.BaseModelView.count] method.
+
+        Examples:
+            ```python hl_lines="6-7"
+            class PostView(ModelView):
+
+                    def get_list_query(self, request: Request):
+                        return super().get_list_query().where(Post.published == true())
+
+                    def get_count_query(self, request: Request):
+                        return super().get_count_query().where(Post.published == true())
+            ```
+        """
+        return select(func.count()).select_from(self.model)
+
+    def get_search_query(self, request: Request, term: str) -> Any:
+        """
+        Return SQLAlchemy whereclause to use for full text search
+
+        Args:
+           request: Starlette request
+           term: Filtering term
+
+        Examples:
+           ```python
+           class PostView(ModelView):
+
+                def get_search_query(self, request: Request, term: str):
+                    return Post.title.contains(term)
+           ```
+        """
+        clauses = []
+        for field in self.get_fields_list(request):
+            if field.searchable and type(field) in [
+                StringField,
+                TextAreaField,
+                EmailField,
+                URLField,
+                PhoneField,
+                ColorField,
+            ]:
+                attr = getattr(self.model, field.name)
+                clauses.append(cast(attr, String).ilike(f"%{term}%"))
+        return or_(*clauses)
+
+    async def count(
+        self,
+        request: Request,
+        where: Union[Dict[str, Any], str, None] = None,
+    ) -> int:
+        session: Union[Session, AsyncSession] = request.state.session
+        stmt = self.get_count_query(request)
+        if where is not None:
+            if isinstance(where, dict):
+                where = build_query(where, self.model)
+            else:
+                where = await self.build_full_text_search_query(
+                    request, where, self.model
+                )
+            stmt = stmt.where(where)  # type: ignore
+        if isinstance(session, AsyncSession):
+            return (await session.execute(stmt)).scalar_one()
+        return (await anyio.to_thread.run_sync(session.execute, stmt)).scalar_one()  # type: ignore[arg-type]
+
+    async def find_all(
+        self,
+        request: Request,
+        skip: int = 0,
+        limit: int = 100,
+        where: Union[Dict[str, Any], str, None] = None,
+        order_by: Optional[List[str]] = None,
+    ) -> Sequence[Any]:
+        session: Union[Session, AsyncSession] = request.state.session
+        stmt = self.get_list_query(request).offset(skip)
+        if limit > 0:
+            stmt = stmt.limit(limit)
+        if where is not None:
+            if isinstance(where, dict):
+                where = build_query(where, self.model)
+            else:
+                where = await self.build_full_text_search_query(
+                    request, where, self.model
+                )
+            stmt = stmt.where(where)  # type: ignore
+        stmt = self.build_order_clauses(request, order_by or [], stmt)
+        for field in self.get_fields_list(request, RequestAction.LIST):
+            if isinstance(field, RelationField):
+                stmt = stmt.options(joinedload(getattr(self.model, field.name)))
+        if isinstance(session, AsyncSession):
+            return (await session.execute(stmt)).scalars().unique().all()
+        return (
+            (await anyio.to_thread.run_sync(session.execute, stmt))  # type: ignore[arg-type]
+            .scalars()
+            .unique()
+            .all()
+        )
+
+    async def find_by_pk(self, request: Request, pk: Any) -> Any:
+        session: Union[Session, AsyncSession] = request.state.session
+        if isinstance(self._pk_column, tuple):
+            """
+            For composite primary keys, the pk parameter is a comma-separated string
+            representing the values of each primary key attribute.
+
+            For example, if the model has two primary keys (id1, id2):
+            - the `pk` will be: "val1,val2"
+            - the generated query: (id1 == val1 AND id2 == val2)
+            """
+            assert isinstance(self._pk_coerce, tuple)
+            clause = and_(
+                (
+                    _pk_col == _coerce(_pk)
+                    if _coerce is not bool
+                    else _pk_col
+                    == (_pk == "True")  # to avoid bool("False") which is True
+                )
+                for _pk_col, _coerce, _pk in zip(
+                    self._pk_column, self._pk_coerce, iterdecode(pk)  # type: ignore[type-var,arg-type]
+                )
+            )
+        else:
+            assert isinstance(self._pk_coerce, type)
+            clause = self._pk_column == self._pk_coerce(pk)
+        stmt = select(self.model).where(clause)
+        for field in self.get_fields_list(request, request.state.action):
+            if isinstance(field, RelationField):
+                stmt = stmt.options(joinedload(getattr(self.model, field.name)))
+        if isinstance(session, AsyncSession):
+            return (await session.execute(stmt)).scalars().unique().one_or_none()
+        return (
+            (await anyio.to_thread.run_sync(session.execute, stmt))  # type: ignore[arg-type]
+            .scalars()
+            .unique()
+            .one_or_none()
+        )
+
+    async def find_by_pks(self, request: Request, pks: List[Any]) -> Sequence[Any]:
+        has_multiple_pks = isinstance(self._pk_column, tuple)
+        try:
+            return await self._exec_find_by_pks(request, pks)
+        except DBAPIError:  # pragma: no cover
+            if has_multiple_pks:
+                # Retry for multiple primary keys in case of an error related to the composite IN construct
+                # This section is intentionally not covered by the test suite because SQLite, MySQL, and
+                # PostgreSQL support composite IN construct.
+                return await self._exec_find_by_pks(request, pks, False)
+            raise
+
+    async def _exec_find_by_pks(
+        self, request: Request, pks: List[Any], use_composite_in: bool = True
+    ) -> Sequence[Any]:
+        session: Union[Session, AsyncSession] = request.state.session
+        has_multiple_pks = isinstance(self._pk_column, tuple)
+
+        if has_multiple_pks:
+            """Handle composite primary keys"""
+            clause = await self._get_multiple_pks_in_clause(pks, use_composite_in)
+        else:
+            clause = self._pk_column.in_(map(self._pk_coerce, pks))  # type: ignore
+        stmt = select(self.model).where(clause)
+        for field in self.get_fields_list(request, request.state.action):
+            if isinstance(field, RelationField):
+                stmt = stmt.options(joinedload(getattr(self.model, field.name)))
+        if isinstance(session, AsyncSession):
+            return (await session.execute(stmt)).scalars().unique().all()
+        return (
+            (await anyio.to_thread.run_sync(session.execute, stmt))  # type: ignore[arg-type]
+            .scalars()
+            .unique()
+            .all()
+        )
+
+    async def _get_multiple_pks_in_clause(
+        self, pks: List[Any], use_composite_in: bool
+    ) -> Any:
+        """
+        Constructs the WHERE clause for models with multiple primary keys.
+
+        Args:
+            pks: A list of comma-separated values
+                Example: ["val1,val2", "val3,val4"]
+            use_composite_in: A flag indicating whether to use the composite IN construct.
+
+        The generated query depends on the value of `use_composite_in`:
+
+        - When `use_composite_in` is True:
+            WHERE (id1, id2) IN ((val1, val2), (val3, val4))
+
+            Note: The composite IN construct may not be supported by all database backends.
+                Read https://docs.sqlalchemy.org/en/latest/core/sqlelement.html#sqlalchemy.sql.expression.tuple_
+
+        - When `use_composite_in` is False:
+            WHERE (id1 == val1 AND id2 == val2) OR (id1 == val3 AND id2 == val4)
+        """
+        assert isinstance(self._pk_coerce, tuple)
+        decoded_pks = tuple(iterdecode(pk) for pk in pks)
+        if use_composite_in:
+            return tuple_(*self._pk_column).in_(
+                tuple(
+                    (_coerce(_pk) if _coerce is not bool else _pk == "True")
+                    for _coerce, _pk in zip(
+                        self._pk_coerce, decoded_pk  # type: ignore[type-var,arg-type]
+                    )
+                )
+                for decoded_pk in decoded_pks
+            )
+        else:  # noqa: RET505, pragma: no cover
+            clauses = []
+            for decoded_pk in decoded_pks:
+                clauses.append(
+                    and_(
+                        (
+                            _pk_col == _coerce(_pk)
+                            if _coerce is not bool
+                            else (_pk_col == (_pk == "True"))
+                        )  # to avoid bool("False") which is True
+                        for _pk_col, _coerce, _pk in zip(
+                            self._pk_column, self._pk_coerce, decoded_pk  # type: ignore[type-var,arg-type]
+                        )
+                    )
+                )
+            return or_(*clauses)
+
+    async def validate(self, request: Request, data: Dict[str, Any]) -> None:
+        """
+        Inherit this method to validate your data.
+
+        Args:
+            request: Starlette request
+            data: Submitted data
+
+        Raises:
+            FormValidationError: to display errors to users
+
+        Examples:
+            ```python
+            from starlette_admin.contrib.sqla import ModelView
+            from starlette_admin.exceptions import FormValidationError
+
+
+            class Post(Base):
+                __tablename__ = "post"
+
+                id = Column(Integer, primary_key=True)
+                title = Column(String(100), nullable=False)
+                text = Column(Text, nullable=False)
+                date = Column(Date)
+
+
+            class PostView(ModelView):
+
+                async def validate(self, request: Request, data: Dict[str, Any]) -> None:
+                    errors: Dict[str, str] = dict()
+                    _2day_from_today = date.today() + timedelta(days=2)
+                    if data["title"] is None or len(data["title"]) < 3:
+                        errors["title"] = "Ensure this value has at least 03 characters"
+                    if data["text"] is None or len(data["text"]) < 10:
+                        errors["text"] = "Ensure this value has at least 10 characters"
+                    if data["date"] is None or data["date"] < _2day_from_today:
+                        errors["date"] = "We need at least one day to verify your post"
+                    if len(errors) > 0:
+                        raise FormValidationError(errors)
+                    return await super().validate(request, data)
+            ```
+
+        """
+
+    async def create(self, request: Request, data: Dict[str, Any]) -> Any:
+        try:
+            data = await self._arrange_data(request, data)
+            await self.validate(request, data)
+            session: Union[Session, AsyncSession] = request.state.session
+            obj = await self._populate_obj(request, self.model(), data)
+            session.add(obj)
+            await self.before_create(request, data, obj)
+            if isinstance(session, AsyncSession):
+                await session.commit()
+                await session.refresh(obj)
+            else:
+                await anyio.to_thread.run_sync(session.commit)  # type: ignore[arg-type]
+                await anyio.to_thread.run_sync(session.refresh, obj)  # type: ignore[arg-type]
+            await self.after_create(request, obj)
+            return obj
+        except Exception as e:
+            return self.handle_exception(e)
+
+    async def edit(self, request: Request, pk: Any, data: Dict[str, Any]) -> Any:
+        try:
+            data = await self._arrange_data(request, data, True)
+            await self.validate(request, data)
+            session: Union[Session, AsyncSession] = request.state.session
+            obj = await self.find_by_pk(request, pk)
+            await self._populate_obj(request, obj, data, True)
+            session.add(obj)
+            await self.before_edit(request, data, obj)
+            if isinstance(session, AsyncSession):
+                await session.commit()
+                await session.refresh(obj)
+            else:
+                await anyio.to_thread.run_sync(session.commit)  # type: ignore[arg-type]
+                await anyio.to_thread.run_sync(session.refresh, obj)  # type: ignore[arg-type]
+            await self.after_edit(request, obj)
+            return obj
+        except Exception as e:
+            self.handle_exception(e)
+
+    async def _arrange_data(
+        self,
+        request: Request,
+        data: Dict[str, Any],
+        is_edit: bool = False,
+    ) -> Dict[str, Any]:
+        """
+        This function will return a new dict with relationships loaded from
+        database.
+        """
+        arranged_data: Dict[str, Any] = {}
+        for field in self.get_fields_list(request, request.state.action):
+            if isinstance(field, RelationField) and data[field.name] is not None:
+                foreign_model = self._find_foreign_model(field.identity)  # type: ignore
+                if not field.multiple:
+                    arranged_data[field.name] = await foreign_model.find_by_pk(
+                        request, data[field.name]
+                    )
+                else:
+                    arranged_data[field.name] = await foreign_model.find_by_pks(
+                        request, data[field.name]
+                    )
+            else:
+                arranged_data[field.name] = data[field.name]
+        return arranged_data
+
+    async def _populate_obj(
+        self,
+        request: Request,
+        obj: Any,
+        data: Dict[str, Any],
+        is_edit: bool = False,
+    ) -> Any:
+        for field in self.get_fields_list(request, request.state.action):
+            name, value = field.name, data.get(field.name, None)
+            if isinstance(field, FileField):
+                value, should_be_deleted = value
+                if should_be_deleted:
+                    setattr(obj, name, None)
+                elif (not field.multiple and value is not None) or (
+                    field.multiple and isinstance(value, list) and len(value) > 0
+                ):
+                    setattr(obj, name, value)
+            else:
+                setattr(obj, name, value)
+        return obj
+
+    async def delete(self, request: Request, pks: List[Any]) -> Optional[int]:
+        session: Union[Session, AsyncSession] = request.state.session
+        objs = await self.find_by_pks(request, pks)
+        if isinstance(session, AsyncSession):
+            for obj in objs:
+                await self.before_delete(request, obj)
+                await session.delete(obj)
+            await session.commit()
+        else:
+            for obj in objs:
+                await self.before_delete(request, obj)
+                await anyio.to_thread.run_sync(session.delete, obj)  # type: ignore[arg-type]
+            await anyio.to_thread.run_sync(session.commit)  # type: ignore[arg-type]
+        for obj in objs:
+            await self.after_delete(request, obj)
+        return len(objs)
+
+    async def build_full_text_search_query(
+        self, request: Request, term: str, model: Any
+    ) -> Any:
+        return self.get_search_query(request, term)
+
+    def build_order_clauses(
+        self, request: Request, order_list: List[str], stmt: Select
+    ) -> Select:
+        for value in order_list:
+            attr_key, order = value.strip().split(maxsplit=1)
+            model_attr = getattr(self.model, attr_key, None)
+            if model_attr is not None and isinstance(
+                model_attr.property, RelationshipProperty
+            ):
+                stmt = stmt.outerjoin(model_attr)
+            sorting_attr = self.sortable_field_mapping.get(attr_key, model_attr)
+            stmt = stmt.order_by(
+                not_none(sorting_attr).desc()
+                if order.lower() == "desc"
+                else sorting_attr
+            )
+        return stmt
+
+    async def get_pk_value(self, request: Request, obj: Any) -> Any:
+        return await self.pk_field.parse_obj(request, obj)
+
+    async def get_serialized_pk_value(self, request: Request, obj: Any) -> Any:
+        value = await self.get_pk_value(request, obj)
+        return await self.pk_field.serialize_value(request, value, request.state.action)
+
+    def handle_exception(self, exc: Exception) -> None:
+        try:
+            """Automatically handle sqlalchemy_file error"""
+            from sqlalchemy_file.exceptions import ValidationError
+
+            if isinstance(exc, ValidationError):
+                raise FormValidationError({exc.key: exc.msg})
+        except ImportError:  # pragma: no cover
+            pass
+        raise exc  # pragma: no cover
+
+
+ + + +
+ + + + + + + +
+ + + +

+ sortable_field_mapping: Dict[str, InstrumentedAttribute] = {} + + + class-attribute + + +

+ + +
+ +

A dictionary for overriding the default model attribute used for sorting.

+ + +
+ Example +
class Post(Base):
+    __tablename__ = "post"
+
+    id: Mapped[int] = mapped_column(primary_key=True)
+    title: Mapped[str] = mapped_column()
+    user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
+    user: Mapped[User] = relationship(back_populates="posts")
+
+
+class PostView(ModelView):
+    sortable_field = ["id", "title", "user"]
+    sortable_field_mapping = {
+        "user": User.age,  # Sort by the age of the related user
+    }
+
+
+ +
+ + + +
+ + +

+ get_count_query(request) + +

+ + +
+ +

Return a Select expression which is used as base statement for +count method.

+ + +

Examples:

+
class PostView(ModelView):
+
+        def get_list_query(self, request: Request):
+            return super().get_list_query().where(Post.published == true())
+
+        def get_count_query(self, request: Request):
+            return super().get_count_query().where(Post.published == true())
+
+ +
+ Source code in starlette_admin/contrib/sqla/view.py +
def get_count_query(self, request: Request) -> Select:
+    """
+    Return a Select expression which is used as base statement for
+    [count][starlette_admin.views.BaseModelView.count] method.
+
+    Examples:
+        ```python hl_lines="6-7"
+        class PostView(ModelView):
+
+                def get_list_query(self, request: Request):
+                    return super().get_list_query().where(Post.published == true())
+
+                def get_count_query(self, request: Request):
+                    return super().get_count_query().where(Post.published == true())
+        ```
+    """
+    return select(func.count()).select_from(self.model)
+
+
+
+ +
+ +
+ + +

+ get_list_query(request) + +

+ + +
+ +

Return a Select expression which is used as base statement for +find_all method.

+ + +

Examples:

+
class PostView(ModelView):
+
+        def get_list_query(self, request: Request):
+            return super().get_list_query().where(Post.published == true())
+
+        def get_count_query(self, request: Request):
+            return super().get_count_query().where(Post.published == true())
+
+

If you override this method, don't forget to also override +get_count_query, +for displaying the correct item count in the list view.

+ +
+ Source code in starlette_admin/contrib/sqla/view.py +
def get_list_query(self, request: Request) -> Select:
+    """
+    Return a Select expression which is used as base statement for
+    [find_all][starlette_admin.views.BaseModelView.find_all] method.
+
+    Examples:
+        ```python  hl_lines="3-4"
+        class PostView(ModelView):
+
+                def get_list_query(self, request: Request):
+                    return super().get_list_query().where(Post.published == true())
+
+                def get_count_query(self, request: Request):
+                    return super().get_count_query().where(Post.published == true())
+        ```
+
+    If you override this method, don't forget to also override
+    [get_count_query][starlette_admin.contrib.sqla.ModelView.get_count_query],
+    for displaying the correct item count in the list view.
+    """
+    return select(self.model)
+
+
+
+ +
+ +
+ + +

+ get_search_query(request, term) + +

+ + +
+ +

Return SQLAlchemy whereclause to use for full text search

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

Starlette request

+
+
+ required +
+ term + + str + +
+

Filtering term

+
+
+ required +
+ + +

Examples:

+
class PostView(ModelView):
+
+     def get_search_query(self, request: Request, term: str):
+         return Post.title.contains(term)
+
+ +
+ Source code in starlette_admin/contrib/sqla/view.py +
def get_search_query(self, request: Request, term: str) -> Any:
+    """
+    Return SQLAlchemy whereclause to use for full text search
+
+    Args:
+       request: Starlette request
+       term: Filtering term
+
+    Examples:
+       ```python
+       class PostView(ModelView):
+
+            def get_search_query(self, request: Request, term: str):
+                return Post.title.contains(term)
+       ```
+    """
+    clauses = []
+    for field in self.get_fields_list(request):
+        if field.searchable and type(field) in [
+            StringField,
+            TextAreaField,
+            EmailField,
+            URLField,
+            PhoneField,
+            ColorField,
+        ]:
+            attr = getattr(self.model, field.name)
+            clauses.append(cast(attr, String).ilike(f"%{term}%"))
+    return or_(*clauses)
+
+
+
+ +
+ +
+ + +

+ validate(request, data) + + + async + + +

+ + +
+ +

Inherit this method to validate your data.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

Starlette request

+
+
+ required +
+ data + + Dict[str, Any] + +
+

Submitted data

+
+
+ required +
+ + +

Raises:

+ + + + + + + + + + + + + +
TypeDescription
+ FormValidationError + +
+

to display errors to users

+
+
+ + +

Examples:

+
from starlette_admin.contrib.sqla import ModelView
+from starlette_admin.exceptions import FormValidationError
+
+
+class Post(Base):
+    __tablename__ = "post"
+
+    id = Column(Integer, primary_key=True)
+    title = Column(String(100), nullable=False)
+    text = Column(Text, nullable=False)
+    date = Column(Date)
+
+
+class PostView(ModelView):
+
+    async def validate(self, request: Request, data: Dict[str, Any]) -> None:
+        errors: Dict[str, str] = dict()
+        _2day_from_today = date.today() + timedelta(days=2)
+        if data["title"] is None or len(data["title"]) < 3:
+            errors["title"] = "Ensure this value has at least 03 characters"
+        if data["text"] is None or len(data["text"]) < 10:
+            errors["text"] = "Ensure this value has at least 10 characters"
+        if data["date"] is None or data["date"] < _2day_from_today:
+            errors["date"] = "We need at least one day to verify your post"
+        if len(errors) > 0:
+            raise FormValidationError(errors)
+        return await super().validate(request, data)
+
+ +
+ Source code in starlette_admin/contrib/sqla/view.py +
async def validate(self, request: Request, data: Dict[str, Any]) -> None:
+    """
+    Inherit this method to validate your data.
+
+    Args:
+        request: Starlette request
+        data: Submitted data
+
+    Raises:
+        FormValidationError: to display errors to users
+
+    Examples:
+        ```python
+        from starlette_admin.contrib.sqla import ModelView
+        from starlette_admin.exceptions import FormValidationError
+
+
+        class Post(Base):
+            __tablename__ = "post"
+
+            id = Column(Integer, primary_key=True)
+            title = Column(String(100), nullable=False)
+            text = Column(Text, nullable=False)
+            date = Column(Date)
+
+
+        class PostView(ModelView):
+
+            async def validate(self, request: Request, data: Dict[str, Any]) -> None:
+                errors: Dict[str, str] = dict()
+                _2day_from_today = date.today() + timedelta(days=2)
+                if data["title"] is None or len(data["title"]) < 3:
+                    errors["title"] = "Ensure this value has at least 03 characters"
+                if data["text"] is None or len(data["text"]) < 10:
+                    errors["text"] = "Ensure this value has at least 10 characters"
+                if data["date"] is None or data["date"] < _2day_from_today:
+                    errors["date"] = "We need at least one day to verify your post"
+                if len(errors) > 0:
+                    raise FormValidationError(errors)
+                return await super().validate(request, data)
+        ```
+
+    """
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/fields/index.html b/api/fields/index.html new file mode 100644 index 00000000..5cd7091c --- /dev/null +++ b/api/fields/index.html @@ -0,0 +1,7194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Fields - Starlette Admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + +

Fields

+ +
+ + + +

+ starlette_admin.fields + + +

+ +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ BaseField + + + + dataclass + + +

+ + +
+ + +

Base class for fields

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ name + + str + +
+

Field name, same as attribute name in your model

+
+
+ required +
+ label + + Optional[str] + +
+

Field label

+
+
+ None +
+ help_text + + Optional[str] + +
+

Hint message to display in forms

+
+
+ None +
+ type + + Optional[str] + +
+

Field type, unique key used to define the field

+
+
+ None +
+ disabled + + Optional[bool] + +
+

Disabled in forms

+
+
+ False +
+ read_only + + Optional[bool] + +
+

Read only in forms

+
+
+ False +
+ id + + str + +
+

Unique id, used to represent field instance

+
+
+ '' +
+ search_builder_type + + Optional[str] + +
+

datatable columns.searchBuilderType, For more information +click here

+
+
+ 'default' +
+ required + + Optional[bool] + +
+

Indicate if the fields is required

+
+
+ False +
+ exclude_from_list + + Optional[bool] + +
+

Control field visibility in list page

+
+
+ False +
+ exclude_from_detail + + Optional[bool] + +
+

Control field visibility in detail page

+
+
+ False +
+ exclude_from_create + + Optional[bool] + +
+

Control field visibility in create page

+
+
+ False +
+ exclude_from_edit + + Optional[bool] + +
+

Control field visibility in edit page

+
+
+ False +
+ searchable + + Optional[bool] + +
+

Indicate if the fields is searchable

+
+
+ True +
+ orderable + + Optional[bool] + +
+

Indicate if the fields is orderable

+
+
+ True +
+ render_function_key + + str + +
+

Render function key inside the global render variable in javascript

+
+
+ 'text' +
+ form_template + + str + +
+

template for rendering this field in creation and edit page

+
+
+ 'forms/input.html' +
+ display_template + + str + +
+

template for displaying this field in detail page

+
+
+ 'displays/text.html' +
+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class BaseField:
+    """
+    Base class for fields
+
+    Parameters:
+        name: Field name, same as attribute name in your model
+        label: Field label
+        help_text: Hint message to display in forms
+        type: Field type, unique key used to define the field
+        disabled: Disabled in forms
+        read_only: Read only in forms
+        id: Unique id, used to represent field instance
+        search_builder_type: datatable columns.searchBuilderType, For more information
+            [click here](https://datatables.net/reference/option/columns.searchBuilderType)
+        required: Indicate if the fields is required
+        exclude_from_list: Control field visibility in list page
+        exclude_from_detail: Control field visibility in detail page
+        exclude_from_create: Control field visibility in create page
+        exclude_from_edit: Control field visibility in edit page
+        searchable: Indicate if the fields is searchable
+        orderable: Indicate if the fields is orderable
+        render_function_key: Render function key inside the global `render` variable in javascript
+        form_template: template for rendering this field in creation and edit page
+        display_template: template for displaying this field in detail page
+    """
+
+    name: str
+    label: Optional[str] = None
+    type: Optional[str] = None
+    help_text: Optional[str] = None
+    disabled: Optional[bool] = False
+    read_only: Optional[bool] = False
+    id: str = ""
+    search_builder_type: Optional[str] = "default"
+    required: Optional[bool] = False
+    exclude_from_list: Optional[bool] = False
+    exclude_from_detail: Optional[bool] = False
+    exclude_from_create: Optional[bool] = False
+    exclude_from_edit: Optional[bool] = False
+    searchable: Optional[bool] = True
+    orderable: Optional[bool] = True
+    render_function_key: str = "text"
+    form_template: str = "forms/input.html"
+    label_template: str = "forms/_label.html"
+    display_template: str = "displays/text.html"
+    error_class = "is-invalid"
+
+    def __post_init__(self) -> None:
+        if self.label is None:
+            self.label = self.name.replace("_", " ").capitalize()
+        if self.type is None:
+            self.type = type(self).__name__
+        self.id = self.name
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Any:
+        """
+        Extracts the value of this field from submitted form data.
+        """
+        return form_data.get(self.id)
+
+    async def parse_obj(self, request: Request, obj: Any) -> Any:
+        """Extracts the value of this field from a model instance.
+
+        By default, this function returns the value of the attribute with the name `self.name` from `obj`.
+        However, this function can be overridden to provide custom logic for computing the value of a field.
+
+        ??? Example
+
+            ```py
+            # Suppose we have a `User` model with `id`, `first_name`, and `last_name` fields.
+            # We define a custom field called `MyCustomField` to compute the full name of the user:
+
+            class MyCustomField(StringField):
+                async def parse_obj(self, request: Request, obj: Any) -> Any:
+                    return f"{obj.first_name} {obj.last_name}"  # Returns the full name of the user
+
+
+            # Then, We can define our view as follows
+
+            class UserView(ModelView):
+                fields = ["id", MyCustomField("full_name")]
+            ```
+        """
+        return getattr(obj, self.name, None)
+
+    async def serialize_none_value(
+        self, request: Request, action: RequestAction
+    ) -> Any:
+        """Formats a None value for sending to the frontend.
+
+        Args:
+            request: The current request object.
+            action: The current request action.
+
+        Returns:
+            Any: The formatted None value.
+        """
+        return None
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> Any:
+        """Formats a value for sending to the frontend based on the current request action.
+
+        !!! important
+
+            Make sure this value is JSON Serializable for RequestAction.LIST and RequestAction.API
+
+        Args:
+            request: The current request object.
+            value: The value to format.
+            action: The current request action.
+
+        Returns:
+            Any: The formatted value.
+        """
+        return value
+
+    def additional_css_links(
+        self, request: Request, action: RequestAction
+    ) -> List[str]:
+        """Returns a list of CSS file URLs to include for the current request action."""
+        return []
+
+    def additional_js_links(self, request: Request, action: RequestAction) -> List[str]:
+        """Returns a list of JavaScript file URLs to include for the current request action."""
+        return []
+
+    def dict(self) -> Dict[str, Any]:
+        """Return the dataclass instance as a dictionary."""
+        return asdict(self)
+
+    def input_params(self) -> str:
+        """Return HTML input parameters as a string."""
+        return html_params(
+            {
+                "disabled": self.disabled,
+                "readonly": self.read_only,
+            }
+        )
+
+
+ + + +
+ + + + + + + + + +
+ + + + + +
+ +

Returns a list of CSS file URLs to include for the current request action.

+ +
+ Source code in starlette_admin/fields.py +
def additional_css_links(
+    self, request: Request, action: RequestAction
+) -> List[str]:
+    """Returns a list of CSS file URLs to include for the current request action."""
+    return []
+
+
+
+ +
+ +
+ + + + + +
+ +

Returns a list of JavaScript file URLs to include for the current request action.

+ +
+ Source code in starlette_admin/fields.py +
def additional_js_links(self, request: Request, action: RequestAction) -> List[str]:
+    """Returns a list of JavaScript file URLs to include for the current request action."""
+    return []
+
+
+
+ +
+ +
+ + +

+ dict() + +

+ + +
+ +

Return the dataclass instance as a dictionary.

+ +
+ Source code in starlette_admin/fields.py +
def dict(self) -> Dict[str, Any]:
+    """Return the dataclass instance as a dictionary."""
+    return asdict(self)
+
+
+
+ +
+ +
+ + +

+ input_params() + +

+ + +
+ +

Return HTML input parameters as a string.

+ +
+ Source code in starlette_admin/fields.py +
def input_params(self) -> str:
+    """Return HTML input parameters as a string."""
+    return html_params(
+        {
+            "disabled": self.disabled,
+            "readonly": self.read_only,
+        }
+    )
+
+
+
+ +
+ +
+ + +

+ parse_form_data(request, form_data, action) + + + async + + +

+ + +
+ +

Extracts the value of this field from submitted form data.

+ +
+ Source code in starlette_admin/fields.py +
async def parse_form_data(
+    self, request: Request, form_data: FormData, action: RequestAction
+) -> Any:
+    """
+    Extracts the value of this field from submitted form data.
+    """
+    return form_data.get(self.id)
+
+
+
+ +
+ +
+ + +

+ parse_obj(request, obj) + + + async + + +

+ + +
+ +

Extracts the value of this field from a model instance.

+

By default, this function returns the value of the attribute with the name self.name from obj. +However, this function can be overridden to provide custom logic for computing the value of a field.

+
+Example +
# Suppose we have a `User` model with `id`, `first_name`, and `last_name` fields.
+# We define a custom field called `MyCustomField` to compute the full name of the user:
+
+class MyCustomField(StringField):
+    async def parse_obj(self, request: Request, obj: Any) -> Any:
+        return f"{obj.first_name} {obj.last_name}"  # Returns the full name of the user
+
+
+# Then, We can define our view as follows
+
+class UserView(ModelView):
+    fields = ["id", MyCustomField("full_name")]
+
+
+ +
+ Source code in starlette_admin/fields.py +
async def parse_obj(self, request: Request, obj: Any) -> Any:
+    """Extracts the value of this field from a model instance.
+
+    By default, this function returns the value of the attribute with the name `self.name` from `obj`.
+    However, this function can be overridden to provide custom logic for computing the value of a field.
+
+    ??? Example
+
+        ```py
+        # Suppose we have a `User` model with `id`, `first_name`, and `last_name` fields.
+        # We define a custom field called `MyCustomField` to compute the full name of the user:
+
+        class MyCustomField(StringField):
+            async def parse_obj(self, request: Request, obj: Any) -> Any:
+                return f"{obj.first_name} {obj.last_name}"  # Returns the full name of the user
+
+
+        # Then, We can define our view as follows
+
+        class UserView(ModelView):
+            fields = ["id", MyCustomField("full_name")]
+        ```
+    """
+    return getattr(obj, self.name, None)
+
+
+
+ +
+ +
+ + +

+ serialize_none_value(request, action) + + + async + + +

+ + +
+ +

Formats a None value for sending to the frontend.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

The current request object.

+
+
+ required +
+ action + + RequestAction + +
+

The current request action.

+
+
+ required +
+ + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Any + Any + +
+

The formatted None value.

+
+
+ +
+ Source code in starlette_admin/fields.py +
async def serialize_none_value(
+    self, request: Request, action: RequestAction
+) -> Any:
+    """Formats a None value for sending to the frontend.
+
+    Args:
+        request: The current request object.
+        action: The current request action.
+
+    Returns:
+        Any: The formatted None value.
+    """
+    return None
+
+
+
+ +
+ +
+ + +

+ serialize_value(request, value, action) + + + async + + +

+ + +
+ +

Formats a value for sending to the frontend based on the current request action.

+
+

Important

+

Make sure this value is JSON Serializable for RequestAction.LIST and RequestAction.API

+
+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

The current request object.

+
+
+ required +
+ value + + Any + +
+

The value to format.

+
+
+ required +
+ action + + RequestAction + +
+

The current request action.

+
+
+ required +
+ + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Any + Any + +
+

The formatted value.

+
+
+ +
+ Source code in starlette_admin/fields.py +
async def serialize_value(
+    self, request: Request, value: Any, action: RequestAction
+) -> Any:
+    """Formats a value for sending to the frontend based on the current request action.
+
+    !!! important
+
+        Make sure this value is JSON Serializable for RequestAction.LIST and RequestAction.API
+
+    Args:
+        request: The current request object.
+        value: The value to format.
+        action: The current request action.
+
+    Returns:
+        Any: The formatted value.
+    """
+    return value
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ BooleanField + + + + dataclass + + +

+ + +
+

+ Bases: BaseField

+ + +

This field displays the true/false value of a boolean property.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class BooleanField(BaseField):
+    """This field displays the `true/false` value of a boolean property."""
+
+    search_builder_type: Optional[str] = "bool"
+    render_function_key: str = "boolean"
+    form_template: str = "forms/boolean.html"
+    display_template: str = "displays/boolean.html"
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> bool:
+        return form_data.get(self.id) == "on"
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> bool:
+        return bool(value)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ IntegerField + + + + dataclass + + +

+ + +
+

+ Bases: NumberField

+ + +

This field is used to represent the value of properties that store integer numbers. +Erroneous input is ignored and will not be accepted as a value.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class IntegerField(NumberField):
+    """
+    This field is used to represent the value of properties that store integer numbers.
+    Erroneous input is ignored and will not be accepted as a value."""
+
+    class_: str = "field-integer form-control"
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Optional[int]:
+        try:
+            return int(form_data.get(self.id))  # type: ignore
+        except (ValueError, TypeError):
+            return None
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> Any:
+        return int(value)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ DecimalField + + + + dataclass + + +

+ + +
+

+ Bases: NumberField

+ + +

This field is used to represent the value of properties that store decimal numbers. +Erroneous input is ignored and will not be accepted as a value.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class DecimalField(NumberField):
+    """
+    This field is used to represent the value of properties that store decimal numbers.
+    Erroneous input is ignored and will not be accepted as a value.
+    """
+
+    step: str = "any"
+    class_: str = "field-decimal form-control"
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Optional[decimal.Decimal]:
+        try:
+            return decimal.Decimal(form_data.get(self.id))  # type: ignore
+        except (decimal.InvalidOperation, ValueError):
+            return None
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> str:
+        return str(value)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ FloatField + + + + dataclass + + +

+ + +
+

+ Bases: StringField

+ + +

A text field, except all input is coerced to an float. + Erroneous input is ignored and will not be accepted as a value.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class FloatField(StringField):
+    """
+    A text field, except all input is coerced to an float.
+     Erroneous input is ignored and will not be accepted as a value.
+    """
+
+    class_: str = "field-float form-control"
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Optional[float]:
+        try:
+            return float(form_data.get(self.id))  # type: ignore
+        except ValueError:
+            return None
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> float:
+        return float(value)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ StringField + + + + dataclass + + +

+ + +
+

+ Bases: BaseField

+ + +

This field is used to represent any kind of short text content.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class StringField(BaseField):
+    """This field is used to represent any kind of short text content."""
+
+    maxlength: Optional[int] = None
+    minlength: Optional[int] = None
+    search_builder_type: Optional[str] = "string"
+    input_type: str = "text"
+    class_: str = "field-string form-control"
+    placeholder: Optional[str] = None
+
+    def input_params(self) -> str:
+        return html_params(
+            {
+                "type": self.input_type,
+                "minlength": self.minlength,
+                "maxlength": self.maxlength,
+                "placeholder": self.placeholder,
+                "required": self.required,
+                "disabled": self.disabled,
+                "readonly": self.read_only,
+            }
+        )
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> Any:
+        return str(value)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ TextAreaField + + + + dataclass + + +

+ + +
+

+ Bases: StringField

+ + +

This field is used to represent any kind of long text content. +For short text contents, use StringField

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class TextAreaField(StringField):
+    """This field is used to represent any kind of long text content.
+    For short text contents, use [StringField][starlette_admin.fields.StringField]"""
+
+    rows: int = 6
+    class_: str = "field-textarea form-control"
+    form_template: str = "forms/textarea.html"
+    display_template: str = "displays/textarea.html"
+
+    def input_params(self) -> str:
+        return html_params(
+            {
+                "rows": self.rows,
+                "minlength": self.minlength,
+                "maxlength": self.maxlength,
+                "placeholder": self.placeholder,
+                "required": self.required,
+                "disabled": self.disabled,
+                "readonly": self.read_only,
+            }
+        )
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ TinyMCEEditorField + + + + dataclass + + +

+ + +
+

+ Bases: TextAreaField

+ + +

A field that provides a WYSIWYG editor for long text content using the + TinyMCE library.

+

This field can be used as an alternative to the TextAreaField +to provide a more sophisticated editor for user input.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ version_tinymce + + str + +
+

TinyMCE version

+
+
+ '6.4' +
+ version_tinymce_jquery + + str + +
+

TinyMCE jQuery version

+
+
+ '2.0' +
+ height + + int + +
+

Height of the editor

+
+
+ 300 +
+ menubar + + Union[bool, str] + +
+

Show/hide the menubar in the editor

+
+
+ False +
+ statusbar + + bool + +
+

Show/hide the statusbar in the editor

+
+
+ False +
+ toolbar + + str + +
+

Toolbar options to show in the editor

+
+
+ 'undo redo | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat' +
+ content_style + + str + +
+

CSS style to apply to the editor content

+
+
+ 'body { font-family: -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif; font-size: 14px; -webkit-font-smoothing: antialiased; }' +
+ extra_options + + Dict[str, Any] + +
+

Other options to pass to TinyMCE

+
+
+ dict() +
+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class TinyMCEEditorField(TextAreaField):
+    """A field that provides a WYSIWYG editor for long text content using the
+     [TinyMCE](https://www.tiny.cloud/) library.
+
+    This field can be used as an alternative to the [TextAreaField][starlette_admin.fields.TextAreaField]
+    to provide a more sophisticated editor for user input.
+
+    Parameters:
+        version_tinymce: TinyMCE version
+        version_tinymce_jquery: TinyMCE jQuery version
+        height: Height of the editor
+        menubar: Show/hide the menubar in the editor
+        statusbar: Show/hide the statusbar in the editor
+        toolbar: Toolbar options to show in the editor
+        content_style: CSS style to apply to the editor content
+        extra_options: Other options to pass to TinyMCE
+    """
+
+    class_: str = "field-tinymce-editor form-control"
+    display_template: str = "displays/tinymce.html"
+    version_tinymce: str = "6.4"
+    version_tinymce_jquery: str = "2.0"
+    height: int = 300
+    menubar: Union[bool, str] = False
+    statusbar: bool = False
+    toolbar: str = (
+        "undo redo | formatselect | bold italic backcolor | alignleft aligncenter"
+        " alignright alignjustify | bullist numlist outdent indent | removeformat"
+    )
+    content_style: str = (
+        "body { font-family: -apple-system, BlinkMacSystemFont, San Francisco, Segoe"
+        " UI, Roboto, Helvetica Neue, sans-serif; font-size: 14px;"
+        " -webkit-font-smoothing: antialiased; }"
+    )
+    extra_options: Dict[str, Any] = dc_field(default_factory=dict)
+    """For more options, see the [TinyMCE | Documentation](https://www.tiny.cloud/docs/tinymce/6/)"""
+
+    def additional_js_links(self, request: Request, action: RequestAction) -> List[str]:
+        if action.is_form():
+            return [
+                f"https://cdn.jsdelivr.net/npm/tinymce@{self.version_tinymce}/tinymce.min.js",
+                f"https://cdn.jsdelivr.net/npm/@tinymce/tinymce-jquery@{self.version_tinymce_jquery}/dist/tinymce-jquery.min.js",
+            ]
+        return []
+
+    def input_params(self) -> str:
+        _options = {
+            "height": self.height,
+            "menubar": self.menubar,
+            "statusbar": self.statusbar,
+            "toolbar": self.toolbar,
+            "content_style": self.content_style,
+            **self.extra_options,
+        }
+
+        return (
+            super().input_params()
+            + " "
+            + html_params({"data-options": json.dumps(_options)})
+        )
+
+
+ + + +
+ + + + + + + +
+ + + +

+ extra_options: Dict[str, Any] = dc_field(default_factory=dict) + + + class-attribute + instance-attribute + + +

+ + +
+ +

For more options, see the TinyMCE | Documentation

+
+ +
+ + + + + +
+ +
+ +
+ +
+ + + +

+ TagsField + + + + dataclass + + +

+ + +
+

+ Bases: BaseField

+ + +

This field is used to represent the value of properties that store a list of +string values. Render as select2 tags input.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class TagsField(BaseField):
+    """
+    This field is used to represent the value of properties that store a list of
+    string values. Render as `select2` tags input.
+    """
+
+    form_template: str = "forms/tags.html"
+    form_js: str = "js/field/forms/tags.js"
+    class_: str = "field-tags form-control form-select"
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> List[str]:
+        return form_data.getlist(self.id)  # type: ignore
+
+    def additional_css_links(
+        self, request: Request, action: RequestAction
+    ) -> List[str]:
+        if action.is_form():
+            return [
+                str(
+                    request.url_for(
+                        f"{request.app.state.ROUTE_NAME}:statics",
+                        path="css/select2.min.css",
+                    )
+                )
+            ]
+        return []
+
+    def additional_js_links(self, request: Request, action: RequestAction) -> List[str]:
+        if action.is_form():
+            return [
+                str(
+                    request.url_for(
+                        f"{request.app.state.ROUTE_NAME}:statics",
+                        path="js/vendor/select2.min.js",
+                    )
+                )
+            ]
+        return []
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ EmailField + + + + dataclass + + +

+ + +
+

+ Bases: StringField

+ + +

This field is used to represent a text content +that stores a single email address.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class EmailField(StringField):
+    """This field is used to represent a text content
+    that stores a single email address."""
+
+    input_type: str = "email"
+    render_function_key: str = "email"
+    class_: str = "field-email form-control"
+    display_template: str = "displays/email.html"
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ URLField + + + + dataclass + + +

+ + +
+

+ Bases: StringField

+ + +

This field is used to represent a text content that stores a single URL.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class URLField(StringField):
+    """This field is used to represent a text content that stores a single URL."""
+
+    input_type: str = "url"
+    render_function_key: str = "url"
+    class_: str = "field-url form-control"
+    display_template: str = "displays/url.html"
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ PhoneField + + + + dataclass + + +

+ + +
+

+ Bases: StringField

+ + +

A StringField, except renders an <input type="phone">.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class PhoneField(StringField):
+    """A StringField, except renders an `<input type="phone">`."""
+
+    input_type: str = "phone"
+    class_: str = "field-phone form-control"
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ ColorField + + + + dataclass + + +

+ + +
+

+ Bases: StringField

+ + +

A StringField, except renders an <input type="color">.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class ColorField(StringField):
+    """A StringField, except renders an `<input type="color">`."""
+
+    input_type: str = "color"
+    class_: str = "field-color form-control form-control-color"
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ PasswordField + + + + dataclass + + +

+ + +
+

+ Bases: StringField

+ + +

A StringField, except renders an <input type="password">.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class PasswordField(StringField):
+    """A StringField, except renders an `<input type="password">`."""
+
+    input_type: str = "password"
+    class_: str = "field-password form-control"
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ EnumField + + + + dataclass + + +

+ + +
+

+ Bases: StringField

+ + +

Enumeration Field. +It takes a python enum.Enum class or a list of (value, label) pairs. +It can also be a list of only values, in which case the value is used as the label. +Example: +

class Status(str, enum.Enum):
+    NEW = "new"
+    ONGOING = "ongoing"
+    DONE = "done"
+
+class MyModel:
+    status: Optional[Status] = None
+
+class MyModelView(ModelView):
+    fields = [EnumField("status", enum=Status)]
+

+
```python
+class MyModel:
+    language: str
+
+class MyModelView(ModelView):
+    fields = [
+        EnumField(
+            "language",
+            choices=[("cpp", "C++"), ("py", "Python"), ("text", "Plain Text")],
+        )
+    ]
+```
+
+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class EnumField(StringField):
+    """
+    Enumeration Field.
+    It takes a python `enum.Enum` class or a list of *(value, label)* pairs.
+    It can also be a list of only values, in which case the value is used as the label.
+    Example:
+        ```python
+        class Status(str, enum.Enum):
+            NEW = "new"
+            ONGOING = "ongoing"
+            DONE = "done"
+
+        class MyModel:
+            status: Optional[Status] = None
+
+        class MyModelView(ModelView):
+            fields = [EnumField("status", enum=Status)]
+        ```
+
+        ```python
+        class MyModel:
+            language: str
+
+        class MyModelView(ModelView):
+            fields = [
+                EnumField(
+                    "language",
+                    choices=[("cpp", "C++"), ("py", "Python"), ("text", "Plain Text")],
+                )
+            ]
+        ```
+    """
+
+    multiple: bool = False
+    enum: Optional[Type[Enum]] = None
+    choices: Union[Sequence[str], Sequence[Tuple[Any, str]], None] = None
+    choices_loader: Optional[
+        Callable[[Request], Union[Sequence[str], Sequence[Tuple[Any, str]]]]
+    ] = dc_field(default=None, compare=False)
+    form_template: str = "forms/enum.html"
+    class_: str = "field-enum form-control form-select"
+    coerce: Callable[[Any], Any] = str
+    select2: bool = True
+
+    def __post_init__(self) -> None:
+        if self.choices and not isinstance(self.choices[0], (list, tuple)):
+            self.choices = list(zip(self.choices, self.choices))  # type: ignore
+        elif self.enum:
+            self.choices = [(e.value, e.name.replace("_", " ")) for e in self.enum]
+            self.coerce = int if issubclass(self.enum, IntEnum) else str
+        elif not self.choices and self.choices_loader is None:
+            raise ValueError(
+                "EnumField required a list of choices, enum class or a choices_loader for dynamic choices"
+            )
+        super().__post_init__()
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Any:
+        return (
+            list(map(self.coerce, form_data.getlist(self.id)))
+            if self.multiple
+            else (
+                self.coerce(form_data.get(self.id)) if form_data.get(self.id) else None
+            )
+        )
+
+    def _get_choices(self, request: Request) -> Any:
+        return (
+            self.choices
+            if self.choices_loader is None
+            else self.choices_loader(request)
+        )
+
+    def _get_label(self, value: Any, request: Request) -> Any:
+        for v, label in self._get_choices(request):
+            if value == v:
+                return label
+        raise ValueError(f"Invalid choice value: {value}")
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> Any:
+        if isinstance(value, Enum):
+            value = value.value
+        labels = [
+            (self._get_label(v, request) if action != RequestAction.EDIT else v)
+            for v in (value if self.multiple else [value])
+        ]
+        return labels if self.multiple else labels[0]
+
+    def additional_css_links(
+        self, request: Request, action: RequestAction
+    ) -> List[str]:
+        if self.select2 and action.is_form():
+            return [
+                str(
+                    request.url_for(
+                        f"{request.app.state.ROUTE_NAME}:statics",
+                        path="css/select2.min.css",
+                    )
+                )
+            ]
+        return []
+
+    def additional_js_links(self, request: Request, action: RequestAction) -> List[str]:
+        if self.select2 and action.is_form():
+            return [
+                str(
+                    request.url_for(
+                        f"{request.app.state.ROUTE_NAME}:statics",
+                        path="js/vendor/select2.min.js",
+                    )
+                )
+            ]
+        return []
+
+    @classmethod
+    def from_enum(
+        cls,
+        name: str,
+        enum_type: Type[Enum],
+        multiple: bool = False,
+        **kwargs: Dict[str, Any],
+    ) -> "EnumField":
+        warnings.warn(
+            f'This method is deprecated. Use EnumField("name", enum={enum_type.__name__}) instead.',
+            DeprecationWarning,
+            stacklevel=1,
+        )
+        return cls(name, enum=enum_type, multiple=multiple, **kwargs)  # type: ignore
+
+    @classmethod
+    def from_choices(
+        cls,
+        name: str,
+        choices: Union[Sequence[str], Sequence[Tuple[str, str]], None],
+        multiple: bool = False,
+        **kwargs: Dict[str, Any],
+    ) -> "EnumField":
+        warnings.warn(
+            f'This method is deprecated. Use EnumField("name", choices={choices}) instead.',
+            DeprecationWarning,
+            stacklevel=1,
+        )
+        return cls(name, choices=choices, multiple=multiple, **kwargs)  # type: ignore
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ TimeZoneField + + + + dataclass + + +

+ + +
+

+ Bases: EnumField

+ + +

This field is used to represent the name of a timezone (eg. Africa/Porto-Novo)

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class TimeZoneField(EnumField):
+    """This field is used to represent the name of a timezone (eg. Africa/Porto-Novo)"""
+
+    def __post_init__(self) -> None:
+        if self.choices is None:
+            self.choices = [
+                (self.coerce(x), x.replace("_", " ")) for x in common_timezones
+            ]
+        super().__post_init__()
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ CountryField + + + + dataclass + + +

+ + +
+

+ Bases: EnumField

+ + +

This field is used to represent the name that corresponds to the country code stored in your database

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class CountryField(EnumField):
+    """This field is used to represent the name that corresponds to the country code stored in your database"""
+
+    def __post_init__(self) -> None:
+        try:
+            import babel  # noqa
+        except ImportError as err:
+            raise ImportError(
+                "'babel' package is required to use 'CountryField'. Install it with `pip install starlette-admin[i18n]`"
+            ) from err
+        self.choices_loader = lambda request: get_countries_list()
+        super().__post_init__()
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ CurrencyField + + + + dataclass + + +

+ + +
+

+ Bases: EnumField

+ + +

This field is used to represent a value that stores the +3-letter ISO 4217 code of currency

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class CurrencyField(EnumField):
+    """
+    This field is used to represent a value that stores the
+    [3-letter ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) code of currency
+    """
+
+    def __post_init__(self) -> None:
+        try:
+            import babel  # noqa
+        except ImportError as err:
+            raise ImportError(
+                "'babel' package is required to use 'CurrencyField'. Install it with `pip install starlette-admin[i18n]`"
+            ) from err
+        self.choices_loader = lambda request: get_currencies_list()
+        super().__post_init__()
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ DateTimeField + + + + dataclass + + +

+ + +
+

+ Bases: NumberField

+ + +

This field is used to represent a value that stores a python datetime.datetime object +Parameters: + search_format: moment.js format to send for searching. Use None for iso Format + output_format: display output format

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class DateTimeField(NumberField):
+    """
+    This field is used to represent a value that stores a python datetime.datetime object
+    Parameters:
+        search_format: moment.js format to send for searching. Use None for iso Format
+        output_format: display output format
+    """
+
+    input_type: str = "datetime-local"
+    class_: str = "field-datetime form-control"
+    search_builder_type: str = "moment-LL LT"
+    output_format: Optional[str] = None
+    search_format: Optional[str] = None
+    form_alt_format: Optional[str] = "F j, Y  H:i:S"
+
+    def input_params(self) -> str:
+        return html_params(
+            {
+                "type": self.input_type,
+                "min": self.min,
+                "max": self.max,
+                "step": self.step,
+                "data_alt_format": self.form_alt_format,
+                "data_locale": get_locale(),
+                "placeholder": self.placeholder,
+                "required": self.required,
+                "disabled": self.disabled,
+                "readonly": self.read_only,
+            }
+        )
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Any:
+        try:
+            return datetime.fromisoformat(form_data.get(self.id))  # type: ignore
+        except (TypeError, ValueError):
+            return None
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> str:
+        assert isinstance(
+            value, (datetime, date, time)
+        ), f"Expect datetime | date | time, got  {type(value)}"
+        if action != RequestAction.EDIT:
+            return format_datetime(value, self.output_format)
+        return value.isoformat()
+
+    def additional_css_links(
+        self, request: Request, action: RequestAction
+    ) -> List[str]:
+        if action.is_form():
+            return [
+                str(
+                    request.url_for(
+                        f"{request.app.state.ROUTE_NAME}:statics",
+                        path="css/flatpickr.min.css",
+                    )
+                )
+            ]
+        return []
+
+    def additional_js_links(self, request: Request, action: RequestAction) -> List[str]:
+        _links = [
+            str(
+                request.url_for(
+                    f"{request.app.state.ROUTE_NAME}:statics",
+                    path="js/vendor/flatpickr.min.js",
+                )
+            )
+        ]
+        if get_locale() != "en":
+            _links.append(
+                str(
+                    request.url_for(
+                        f"{request.app.state.ROUTE_NAME}:statics",
+                        path=f"i18n/flatpickr/{get_locale()}.js",
+                    )
+                )
+            )
+        if action.is_form():
+            return _links
+        return []
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ DateField + + + + dataclass + + +

+ + +
+

+ Bases: DateTimeField

+ + +

This field is used to represent a value that stores a python datetime.date object +Parameters: + search_format: moment.js format to send for searching. Use None for iso Format + output_format: Set display output format

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class DateField(DateTimeField):
+    """
+    This field is used to represent a value that stores a python datetime.date object
+    Parameters:
+        search_format: moment.js format to send for searching. Use None for iso Format
+        output_format: Set display output format
+    """
+
+    input_type: str = "date"
+    class_: str = "field-date form-control"
+    output_format: Optional[str] = None
+    search_format: str = "YYYY-MM-DD"
+    search_builder_type: str = "moment-LL"
+    form_alt_format: Optional[str] = "F j, Y"
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Any:
+        try:
+            return date.fromisoformat(form_data.get(self.id))  # type: ignore
+        except (TypeError, ValueError):
+            return None
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> str:
+        assert isinstance(value, date), f"Expect date, got  {type(value)}"
+        if action != RequestAction.EDIT:
+            return format_date(value, self.output_format)
+        return value.isoformat()
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ TimeField + + + + dataclass + + +

+ + +
+

+ Bases: DateTimeField

+ + +

This field is used to represent a value that stores a python datetime.time object +Parameters: + search_format: Format to send for search. Use None for iso Format + output_format: Set display output format

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class TimeField(DateTimeField):
+    """
+    This field is used to represent a value that stores a python datetime.time object
+    Parameters:
+        search_format: Format to send for search. Use None for iso Format
+        output_format: Set display output format
+    """
+
+    input_type: str = "time"
+    class_: str = "field-time form-control"
+    search_builder_type: str = "moment-LTS"
+    output_format: Optional[str] = None
+    search_format: str = "HH:mm:ss"
+    form_alt_format: Optional[str] = "H:i:S"
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Any:
+        try:
+            return time.fromisoformat(form_data.get(self.id))  # type: ignore
+        except (TypeError, ValueError):
+            return None
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> str:
+        assert isinstance(value, time), f"Expect time, got  {type(value)}"
+        if action != RequestAction.EDIT:
+            return format_time(value, self.output_format)
+        return value.isoformat()
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ ArrowField + + + + dataclass + + +

+ + +
+

+ Bases: DateTimeField

+ + +

This field is used to represent sqlalchemy_utils.types.arrow.ArrowType

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class ArrowField(DateTimeField):
+    """
+    This field is used to represent sqlalchemy_utils.types.arrow.ArrowType
+    """
+
+    def __post_init__(self) -> None:
+        if not arrow:  # pragma: no cover
+            raise ImportError("'arrow' package is required to use 'ArrowField'")
+        super().__post_init__()
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Any:
+        try:
+            return arrow.get(form_data.get(self.id))  # type: ignore
+        except (TypeError, arrow.parser.ParserError):  # pragma: no cover
+            return None
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> str:
+        assert isinstance(value, arrow.Arrow), f"Expected Arrow, got  {type(value)}"
+        if action != RequestAction.EDIT:
+            return value.humanize(locale=get_locale())
+        return value.isoformat()
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ JSONField + + + + dataclass + + +

+ + +
+

+ Bases: BaseField

+ + +

This field render jsoneditor and represent a value that stores python dict object. +Erroneous input is ignored and will not be accepted as a value.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class JSONField(BaseField):
+    """
+    This field render jsoneditor and represent a value that stores python dict object.
+    Erroneous input is ignored and will not be accepted as a value."""
+
+    height: str = "20em"
+    modes: Optional[Sequence[str]] = None
+    render_function_key: str = "json"
+    form_template: str = "forms/json.html"
+    display_template: str = "displays/json.html"
+
+    def __post_init__(self) -> None:
+        if self.modes is None:
+            self.modes = ["view"] if self.read_only else ["tree", "code"]
+        super().__post_init__()
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Optional[Dict[str, Any]]:
+        try:
+            value = form_data.get(self.id)
+            return json.loads(value) if value is not None else None  # type: ignore
+        except JSONDecodeError:
+            return None
+
+    def additional_css_links(
+        self, request: Request, action: RequestAction
+    ) -> List[str]:
+        if action.is_form():
+            return [
+                str(
+                    request.url_for(
+                        f"{request.app.state.ROUTE_NAME}:statics",
+                        path="css/jsoneditor.min.css",
+                    )
+                )
+            ]
+        return []
+
+    def additional_js_links(self, request: Request, action: RequestAction) -> List[str]:
+        if action.is_form():
+            return [
+                str(
+                    request.url_for(
+                        f"{request.app.state.ROUTE_NAME}:statics",
+                        path="js/vendor/jsoneditor.min.js",
+                    )
+                )
+            ]
+        return []
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ FileField + + + + dataclass + + +

+ + +
+

+ Bases: BaseField

+ + +

Renders a file upload field. +This field is used to represent a value that stores starlette UploadFile object. +For displaying value, this field wait for three properties which is filename, +content-type and url. Use multiple=True for multiple file upload +When user ask for delete on editing page, the second part of the returned tuple is True.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class FileField(BaseField):
+    """
+    Renders a file upload field.
+    This field is used to represent a value that stores starlette UploadFile object.
+    For displaying value, this field wait for three properties which is `filename`,
+    `content-type` and `url`. Use `multiple=True` for multiple file upload
+    When user ask for delete on editing page, the second part of the returned tuple is True.
+    """
+
+    accept: Optional[str] = None
+    multiple: bool = False
+    render_function_key: str = "file"
+    form_template: str = "forms/file.html"
+    display_template: str = "displays/file.html"
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Tuple[Union[UploadFile, List[UploadFile], None], bool]:
+        should_be_deleted = form_data.get(f"_{self.id}-delete") == "on"
+        if self.multiple:
+            files = form_data.getlist(self.id)
+            return [f for f in files if not is_empty_file(f.file)], should_be_deleted  # type: ignore
+        file = form_data.get(self.id)
+        return (None if (file and is_empty_file(file.file)) else file), should_be_deleted  # type: ignore
+
+    def _isvalid_value(self, value: Any) -> bool:
+        return value is not None and all(
+            (
+                hasattr(v, "url")
+                or (isinstance(v, dict) and v.get("url", None) is not None)
+            )
+            for v in (value if self.multiple else [value])
+        )
+
+    def input_params(self) -> str:
+        return html_params(
+            {
+                "accept": self.accept,
+                "disabled": self.disabled,
+                "readonly": self.read_only,
+                "multiple": self.multiple,
+            }
+        )
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ ImageField + + + + dataclass + + +

+ + +
+

+ Bases: FileField

+ + +

FileField with accept="image/*".

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class ImageField(FileField):
+    """
+    FileField with `accept="image/*"`.
+    """
+
+    accept: Optional[str] = "image/*"
+    render_function_key: str = "image"
+    form_template: str = "forms/image.html"
+    display_template: str = "displays/image.html"
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ RelationField + + + + dataclass + + +

+ + +
+

+ Bases: BaseField

+ + +

A field representing a relation between two data models.

+

This field should not be used directly; instead, use either the HasOne +or HasMany fields to specify a relation +between your models.

+
+

Important

+

It is important to add both models in your admin interface.

+
+ + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ identity + + Optional[str] + +
+

Foreign ModelView identity

+
+
+ None +
+
+Example +
class Author:
+    id: Optional[int]
+    name: str
+    books: List["Book"]
+
+class Book:
+    id: Optional[int]
+    title: str
+    author: Optional["Author"]
+
+class AuthorView(ModelView):
+    fields = [
+        IntegerField("id"),
+        StringField("name"),
+        HasMany("books", identity="book"),
+    ]
+
+class BookView(ModelView):
+    fields = [
+        IntegerField("id"),
+        StringField("title"),
+        HasOne("author", identity="author"),
+    ]
+...
+admin.add_view(AuthorView(Author, identity="author"))
+admin.add_view(BookView(Book, identity="book"))
+...
+
+
+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class RelationField(BaseField):
+    """
+    A field representing a relation between two data models.
+
+    This field should not be used directly; instead, use either the [HasOne][starlette_admin.fields.HasOne]
+    or [HasMany][starlette_admin.fields.HasMany] fields to specify a relation
+    between your models.
+
+    !!! important
+
+        It is important to add both models in your admin interface.
+
+    Parameters:
+        identity: Foreign ModelView identity
+
+
+    ??? Example
+
+        ```py
+        class Author:
+            id: Optional[int]
+            name: str
+            books: List["Book"]
+
+        class Book:
+            id: Optional[int]
+            title: str
+            author: Optional["Author"]
+
+        class AuthorView(ModelView):
+            fields = [
+                IntegerField("id"),
+                StringField("name"),
+                HasMany("books", identity="book"),
+            ]
+
+        class BookView(ModelView):
+            fields = [
+                IntegerField("id"),
+                StringField("title"),
+                HasOne("author", identity="author"),
+            ]
+        ...
+        admin.add_view(AuthorView(Author, identity="author"))
+        admin.add_view(BookView(Book, identity="book"))
+        ...
+        ```
+    """
+
+    identity: Optional[str] = None
+    multiple: bool = False
+    render_function_key: str = "relation"
+    form_template: str = "forms/relation.html"
+    display_template: str = "displays/relation.html"
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Any:
+        if self.multiple:
+            return form_data.getlist(self.id)
+        return form_data.get(self.id)
+
+    def additional_css_links(
+        self, request: Request, action: RequestAction
+    ) -> List[str]:
+        if action.is_form():
+            return [
+                str(
+                    request.url_for(
+                        f"{request.app.state.ROUTE_NAME}:statics",
+                        path="css/select2.min.css",
+                    )
+                )
+            ]
+        return []
+
+    def additional_js_links(self, request: Request, action: RequestAction) -> List[str]:
+        if action.is_form():
+            return [
+                str(
+                    request.url_for(
+                        f"{request.app.state.ROUTE_NAME}:statics",
+                        path="js/vendor/select2.min.js",
+                    )
+                )
+            ]
+        return []
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ HasOne + + + + dataclass + + +

+ + +
+

+ Bases: RelationField

+ + +

A field representing a "has-one" relation between two models.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class HasOne(RelationField):
+    """
+    A field representing a "has-one" relation between two models.
+    """
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ HasMany + + + + dataclass + + +

+ + +
+

+ Bases: RelationField

+ + +

A field representing a "has-many" relationship between two models.

+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass
+class HasMany(RelationField):
+    """A field representing a "has-many" relationship between two models."""
+
+    multiple: bool = True
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ ListField + + + + dataclass + + +

+ + +
+

+ Bases: BaseField

+ + +

Encapsulate an ordered list of multiple instances of the same field type, +keeping data as a list.

+
+

Usage

+
class MyModel:
+    id: Optional[int]
+    values: List[str]
+
+class ModelView(BaseModelView):
+    fields = [IntegerField("id"), ListField(StringField("values")]
+
+
+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass(init=False)
+class ListField(BaseField):
+    """
+    Encapsulate an ordered list of multiple instances of the same field type,
+    keeping data as a list.
+
+    !!! usage
+
+        ```python
+        class MyModel:
+            id: Optional[int]
+            values: List[str]
+
+        class ModelView(BaseModelView):
+            fields = [IntegerField("id"), ListField(StringField("values")]
+        ```
+    """
+
+    form_template: str = "forms/list.html"
+    display_template: str = "displays/list.html"
+    search_builder_type: str = "array"
+    field: BaseField = dc_field(default_factory=lambda: BaseField(""))
+
+    def __init__(self, field: BaseField, required: bool = False) -> None:
+        self.field = field
+        self.name = field.name
+        self.required = required
+        self.__post_init__()
+
+    def __post_init__(self) -> None:
+        super().__post_init__()
+        self.field.id = ""
+        if isinstance(self.field, CollectionField):
+            self.field._propagate_id()
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Any:
+        indices = self._extra_indices(form_data)
+        value = []
+        for index in indices:
+            self.field.id = f"{self.id}.{index}"
+            if isinstance(self.field, CollectionField):
+                self.field._propagate_id()
+            value.append(await self.field.parse_form_data(request, form_data, action))
+        return value
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> Any:
+        serialized_value = []
+        for item in value:
+            serialized_item_value = None
+            if item is not None:
+                serialized_item_value = await self.field.serialize_value(
+                    request, item, action
+                )
+            serialized_value.append(serialized_item_value)
+        return serialized_value
+
+    def _extra_indices(self, form_data: FormData) -> List[int]:
+        """
+        Return list of all indices.  For example, if field id is `foo` and
+        form_data contains following keys ['foo.0.bar', 'foo.1.baz'], then the indices are [0,1].
+        Note that some numbers can be skipped. For example, you may have [0,1,3,8]
+        as indices.
+        """
+        indices = set()
+        for name in form_data:
+            if name.startswith(self.id):
+                idx = name[len(self.id) + 1 :].split(".", maxsplit=1)[0]
+                if idx.isdigit():
+                    indices.add(int(idx))
+        return sorted(indices)
+
+    def _field_at(self, idx: Optional[int] = None) -> BaseField:
+        if idx is not None:
+            self.field.id = self.id + "." + str(idx)
+        else:
+            """To generate template string to be used in javascript"""
+            self.field.id = ""
+        if isinstance(self.field, CollectionField):
+            self.field._propagate_id()
+        return self.field
+
+    def additional_css_links(
+        self, request: Request, action: RequestAction
+    ) -> List[str]:
+        return self.field.additional_css_links(request, action)
+
+    def additional_js_links(self, request: Request, action: RequestAction) -> List[str]:
+        return self.field.additional_js_links(request, action)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ CollectionField + + + + dataclass + + +

+ + +
+

+ Bases: BaseField

+ + +

This field represents a collection of others fields. Can be used to represent embedded mongodb document.

+
+

Usage

+
+
 CollectionField("config", fields=[StringField("key"), IntegerField("value", help_text="multiple of 5")]),
+
+ + + + + + +
+ Source code in starlette_admin/fields.py +
@dataclass(init=False)
+class CollectionField(BaseField):
+    """
+    This field represents a collection of others fields. Can be used to represent embedded mongodb document.
+    !!! usage
+
+    ```python
+     CollectionField("config", fields=[StringField("key"), IntegerField("value", help_text="multiple of 5")]),
+    ```
+    """
+
+    fields: Sequence[BaseField] = dc_field(default_factory=list)
+    render_function_key: str = "json"
+    form_template: str = "forms/collection.html"
+    display_template: str = "displays/collection.html"
+
+    def __init__(
+        self, name: str, fields: Sequence[BaseField], required: bool = False
+    ) -> None:
+        self.name = name
+        self.fields = fields
+        self.required = required
+        super().__post_init__()
+        self._propagate_id()
+
+    def get_fields_list(
+        self,
+        request: Request,
+        action: RequestAction = RequestAction.LIST,
+    ) -> Sequence[BaseField]:
+        return extract_fields(self.fields, action)
+
+    def _propagate_id(self) -> None:
+        """Will update fields id by adding his id as prefix (ex: category.name)"""
+        for field in self.fields:
+            field.id = self.id + ("." if self.id else "") + field.name
+            if isinstance(field, type(self)):
+                field._propagate_id()
+
+    async def parse_form_data(
+        self, request: Request, form_data: FormData, action: RequestAction
+    ) -> Any:
+        value = {}
+        for field in self.get_fields_list(request, action):
+            value[field.name] = await field.parse_form_data(request, form_data, action)
+        return value
+
+    async def serialize_value(
+        self, request: Request, value: Any, action: RequestAction
+    ) -> Any:
+        serialized_value: Dict[str, Any] = {}
+        for field in self.get_fields_list(request, action):
+            name = field.name
+            serialized_value[name] = None
+            if hasattr(value, name) or (isinstance(value, dict) and name in value):
+                field_value = (
+                    getattr(value, name) if hasattr(value, name) else value[name]
+                )
+                if field_value is not None:
+                    serialized_value[name] = await field.serialize_value(
+                        request, field_value, action
+                    )
+        return serialized_value
+
+    def additional_css_links(
+        self, request: Request, action: RequestAction
+    ) -> List[str]:
+        _links = []
+        for f in self.get_fields_list(request, action):
+            _links.extend(f.additional_css_links(request, action))
+        return _links
+
+    def additional_js_links(self, request: Request, action: RequestAction) -> List[str]:
+        _links = []
+        for f in self.get_fields_list(request, action):
+            _links.extend(f.additional_js_links(request, action))
+        return _links
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/views/index.html b/api/views/index.html new file mode 100644 index 00000000..670154e3 --- /dev/null +++ b/api/views/index.html @@ -0,0 +1,7317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Views - Starlette Admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + +

Views

+ +
+ + + +

+ starlette_admin.views + + +

+ +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ BaseView + + +

+ + +
+ + +

Base class for all views

+ + +

Attributes:

+ + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
label + str + +
+

Label of the view to be displayed.

+
+
icon + Optional[str] + +
+

Icon to be displayed for this model in the admin. Only FontAwesome names are supported.

+
+
+ + + + + + +
+ Source code in starlette_admin/views.py +
37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
class BaseView:
+    """
+    Base class for all views
+
+    Attributes:
+        label: Label of the view to be displayed.
+        icon: Icon to be displayed for this model in the admin. Only FontAwesome names are supported.
+    """
+
+    label: str = ""
+    icon: Optional[str] = None
+
+    def title(self, request: Request) -> str:
+        """Return the title of the view to be displayed in the browser tab"""
+        return self.label
+
+    def is_active(self, request: Request) -> bool:
+        """Return true if the current view is active"""
+        return False
+
+    def is_accessible(self, request: Request) -> bool:
+        """
+        Override this method to add permission checks.
+        Return True if current user can access this view
+        """
+        return True
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ is_accessible(request) + +

+ + +
+ +

Override this method to add permission checks. +Return True if current user can access this view

+ +
+ Source code in starlette_admin/views.py +
57
+58
+59
+60
+61
+62
def is_accessible(self, request: Request) -> bool:
+    """
+    Override this method to add permission checks.
+    Return True if current user can access this view
+    """
+    return True
+
+
+
+ +
+ +
+ + +

+ is_active(request) + +

+ + +
+ +

Return true if the current view is active

+ +
+ Source code in starlette_admin/views.py +
53
+54
+55
def is_active(self, request: Request) -> bool:
+    """Return true if the current view is active"""
+    return False
+
+
+
+ +
+ +
+ + +

+ title(request) + +

+ + +
+ +

Return the title of the view to be displayed in the browser tab

+ +
+ Source code in starlette_admin/views.py +
49
+50
+51
def title(self, request: Request) -> str:
+    """Return the title of the view to be displayed in the browser tab"""
+    return self.label
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ DropDown + + +

+ + +
+

+ Bases: BaseView

+ + +

Group views inside a dropdown

+ + +
+ Example +
admin.add_view(
+    DropDown(
+        "Resources",
+        icon="fa fa-list",
+        views=[
+            ModelView(User),
+            Link(label="Home Page", url="/"),
+            CustomView(label="Dashboard", path="/dashboard", template_path="dashboard.html"),
+        ],
+    )
+)
+
+
+ + + + + +
+ Source code in starlette_admin/views.py +
class DropDown(BaseView):
+    """
+    Group views inside a dropdown
+
+    Example:
+        ```python
+        admin.add_view(
+            DropDown(
+                "Resources",
+                icon="fa fa-list",
+                views=[
+                    ModelView(User),
+                    Link(label="Home Page", url="/"),
+                    CustomView(label="Dashboard", path="/dashboard", template_path="dashboard.html"),
+                ],
+            )
+        )
+        ```
+    """
+
+    def __init__(
+        self,
+        label: str,
+        views: List[Union[Type[BaseView], BaseView]],
+        icon: Optional[str] = None,
+        always_open: bool = True,
+    ) -> None:
+        self.label = label
+        self.icon = icon
+        self.always_open = always_open
+        self.views: List[BaseView] = [
+            (v if isinstance(v, BaseView) else v()) for v in views
+        ]
+
+    def is_active(self, request: Request) -> bool:
+        return any(v.is_active(request) for v in self.views)
+
+    def is_accessible(self, request: Request) -> bool:
+        return any(v.is_accessible(request) for v in self.views)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + + + + +
+

+ Bases: BaseView

+ + +

Add arbitrary hyperlinks to the menu

+ + +
+ Example +
admin.add_view(Link(label="Home Page", icon="fa fa-link", url="/"))
+
+
+ + + + + +
+ Source code in starlette_admin/views.py +
class Link(BaseView):
+    """
+    Add arbitrary hyperlinks to the menu
+
+    Example:
+        ```python
+        admin.add_view(Link(label="Home Page", icon="fa fa-link", url="/"))
+        ```
+    """
+
+    def __init__(
+        self,
+        label: str = "",
+        icon: Optional[str] = None,
+        url: str = "/",
+        target: Optional[str] = "_self",
+    ):
+        self.label = label
+        self.icon = icon
+        self.url = url
+        self.target = target
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ CustomView + + +

+ + +
+

+ Bases: BaseView

+ + +

Add your own views (not tied to any particular model). For example, +a custom home page that displays some analytics data.

+ + +

Attributes:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
path + +
+

Route path

+
+
template_path + +
+

Path to template file

+
+
methods + +
+

HTTP methods

+
+
name + +
+

Route name

+
+
add_to_menu + +
+

Display to menu or not

+
+
+ + +
+ Example +
admin.add_view(CustomView(label="Home", icon="fa fa-home", path="/home", template_path="home.html"))
+
+
+ + + + + +
+ Source code in starlette_admin/views.py +
class CustomView(BaseView):
+    """
+    Add your own views (not tied to any particular model). For example,
+    a custom home page that displays some analytics data.
+
+    Attributes:
+        path: Route path
+        template_path: Path to template file
+        methods: HTTP methods
+        name: Route name
+        add_to_menu: Display to menu or not
+
+    Example:
+        ```python
+        admin.add_view(CustomView(label="Home", icon="fa fa-home", path="/home", template_path="home.html"))
+        ```
+    """
+
+    def __init__(
+        self,
+        label: str,
+        icon: Optional[str] = None,
+        path: str = "/",
+        template_path: str = "index.html",
+        name: Optional[str] = None,
+        methods: Optional[List[str]] = None,
+        add_to_menu: bool = True,
+    ):
+        self.label = label
+        self.icon = icon
+        self.path = path
+        self.template_path = template_path
+        self.name = name
+        self.methods = methods
+        self.add_to_menu = add_to_menu
+
+    async def render(self, request: Request, templates: Jinja2Templates) -> Response:
+        """Default methods to render view. Override this methods to add your custom logic."""
+        return templates.TemplateResponse(
+            request=request,
+            name=self.template_path,
+            context={"title": self.title(request)},
+        )
+
+    def is_active(self, request: Request) -> bool:
+        return request.scope["path"] == self.path
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ render(request, templates) + + + async + + +

+ + +
+ +

Default methods to render view. Override this methods to add your custom logic.

+ +
+ Source code in starlette_admin/views.py +
async def render(self, request: Request, templates: Jinja2Templates) -> Response:
+    """Default methods to render view. Override this methods to add your custom logic."""
+    return templates.TemplateResponse(
+        request=request,
+        name=self.template_path,
+        context={"title": self.title(request)},
+    )
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ BaseModelView + + +

+ + +
+

+ Bases: BaseView

+ + +

Base administrative view. +Derive from this class to implement your administrative interface piece.

+ + +

Attributes:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
identity + Optional[str] + +
+

Unique identity to identify the model associated to this view. +Will be used for URL of the endpoints.

+
+
name + Optional[str] + +
+

Name of the view to be displayed

+
+
fields + Sequence[BaseField] + +
+

List of fields

+
+
pk_attr + Optional[str] + +
+

Primary key field name

+
+
form_include_pk + bool + +
+

Indicates whether the primary key should be +included in create and edit forms. Default to False.

+
+
exclude_fields_from_list + Sequence[str] + +
+

List of fields to exclude in List page.

+
+
exclude_fields_from_detail + Sequence[str] + +
+

List of fields to exclude in Detail page.

+
+
exclude_fields_from_create + Sequence[str] + +
+

List of fields to exclude from creation page.

+
+
exclude_fields_from_edit + Sequence[str] + +
+

List of fields to exclude from editing page.

+
+
searchable_fields + Optional[Sequence[str]] + +
+

List of searchable fields.

+
+
sortable_fields + Optional[Sequence[str]] + +
+

List of sortable fields.

+
+
export_fields + Optional[Sequence[str]] + +
+

List of fields to include in exports.

+
+
fields_default_sort + Optional[Sequence[Union[Tuple[str, bool], str]]] + +
+

Initial order (sort) to apply to the table. +Should be a sequence of field names or a tuple of +(field name, True/False to indicate the sort direction). +For example: +["title", ("created_at", False), ("price", True)] will sort + by title ascending, created_at ascending and price descending.

+
+
export_types + Sequence[ExportType] + +
+

A list of available export filetypes. Available +exports are ['csv', 'excel', 'pdf', 'print']. Only pdf is +disabled by default.

+
+
column_visibility + bool + +
+

Enable/Disable +column visibility +extension

+
+
search_builder + bool + +
+

Enable/Disable search builder +extension

+
+
page_size + int + +
+

Default number of items to display in List page pagination. +Default value is set to 10.

+
+
page_size_options + Sequence[int] + +
+

Pagination choices displayed in List page. +Default value is set to [10, 25, 50, 100]. Use -1to display All

+
+
responsive_table + bool + +
+

Enable/Disable responsive +extension

+
+
save_state + bool + +
+

Enable/Disable state saving

+
+
datatables_options + Dict[str, Any] + +
+

Dict of Datatables options. +These will overwrite any default options set for the datatable.

+
+
list_template + str + +
+

List view template. Default is list.html.

+
+
detail_template + str + +
+

Details view template. Default is detail.html.

+
+
create_template + str + +
+

Edit view template. Default is create.html.

+
+
edit_template + str + +
+

Edit view template. Default is edit.html.

+
+
actions + Optional[Sequence[str]] + +
+

List of actions

+
+
additional_js_links + Optional[List[str]] + +
+

A list of additional JavaScript files to include.

+
+
additional_css_links + Optional[List[str]] + +
+

A list of additional CSS files to include.

+
+
+ + + + + + +
+ Source code in starlette_admin/views.py +
177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
class BaseModelView(BaseView):
+    """
+    Base administrative view.
+    Derive from this class to implement your administrative interface piece.
+
+    Attributes:
+        identity: Unique identity to identify the model associated to this view.
+            Will be used for URL of the endpoints.
+        name: Name of the view to be displayed
+        fields: List of fields
+        pk_attr: Primary key field name
+        form_include_pk (bool): Indicates whether the primary key should be
+            included in create and edit forms. Default to False.
+        exclude_fields_from_list: List of fields to exclude in List page.
+        exclude_fields_from_detail: List of fields to exclude in Detail page.
+        exclude_fields_from_create: List of fields to exclude from creation page.
+        exclude_fields_from_edit: List of fields to exclude from editing page.
+        searchable_fields: List of searchable fields.
+        sortable_fields: List of sortable fields.
+        export_fields: List of fields to include in exports.
+        fields_default_sort: Initial order (sort) to apply to the table.
+            Should be a sequence of field names or a tuple of
+            (field name, True/False to indicate the sort direction).
+            For example:
+            `["title",  ("created_at", False), ("price", True)]` will sort
+             by `title` ascending, `created_at` ascending and `price` descending.
+        export_types: A list of available export filetypes. Available
+            exports are `['csv', 'excel', 'pdf', 'print']`. Only `pdf` is
+            disabled by default.
+        column_visibility: Enable/Disable
+            [column visibility](https://datatables.net/extensions/buttons/built-in#Column-visibility)
+            extension
+        search_builder: Enable/Disable [search builder](https://datatables.net/extensions/searchbuilder/)
+            extension
+        page_size: Default number of items to display in List page pagination.
+            Default value is set to `10`.
+        page_size_options: Pagination choices displayed in List page.
+            Default value is set to `[10, 25, 50, 100]`. Use `-1`to display All
+        responsive_table: Enable/Disable [responsive](https://datatables.net/extensions/responsive/)
+            extension
+        save_state: Enable/Disable [state saving](https://datatables.net/examples/basic_init/state_save.html)
+        datatables_options: Dict of [Datatables options](https://datatables.net/reference/option/).
+            These will overwrite any default options set for the datatable.
+        list_template: List view template. Default is `list.html`.
+        detail_template: Details view template. Default is `detail.html`.
+        create_template: Edit view template. Default is `create.html`.
+        edit_template: Edit view template. Default is `edit.html`.
+        actions: List of actions
+        additional_js_links: A list of additional JavaScript files to include.
+        additional_css_links: A list of additional CSS files to include.
+
+
+    """
+
+    identity: Optional[str] = None
+    name: Optional[str] = None
+    fields: Sequence[BaseField] = []
+    pk_attr: Optional[str] = None
+    form_include_pk: bool = False
+    exclude_fields_from_list: Sequence[str] = []
+    exclude_fields_from_detail: Sequence[str] = []
+    exclude_fields_from_create: Sequence[str] = []
+    exclude_fields_from_edit: Sequence[str] = []
+    searchable_fields: Optional[Sequence[str]] = None
+    sortable_fields: Optional[Sequence[str]] = None
+    fields_default_sort: Optional[Sequence[Union[Tuple[str, bool], str]]] = None
+    export_types: Sequence[ExportType] = [
+        ExportType.CSV,
+        ExportType.EXCEL,
+        ExportType.PRINT,
+    ]
+    export_fields: Optional[Sequence[str]] = None
+    column_visibility: bool = True
+    search_builder: bool = True
+    page_size: int = 10
+    page_size_options: Sequence[int] = [10, 25, 50, 100]
+    responsive_table: bool = False
+    save_state: bool = True
+    datatables_options: ClassVar[Dict[str, Any]] = {}
+    list_template: str = "list.html"
+    detail_template: str = "detail.html"
+    create_template: str = "create.html"
+    edit_template: str = "edit.html"
+    actions: Optional[Sequence[str]] = None
+    row_actions: Optional[Sequence[str]] = None
+    additional_js_links: Optional[List[str]] = None
+    additional_css_links: Optional[List[str]] = None
+    row_actions_display_type: RowActionsDisplayType = RowActionsDisplayType.ICON_LIST
+
+    _find_foreign_model: Callable[[str], "BaseModelView"]
+
+    def __init__(self) -> None:  # noqa: C901
+        fringe = list(self.fields)
+        all_field_names = []
+        while len(fringe) > 0:
+            field = fringe.pop(0)
+            if not hasattr(field, "_name"):
+                field._name = field.name  # type: ignore
+            if isinstance(field, CollectionField):
+                for f in field.fields:
+                    f._name = f"{field._name}.{f.name}"  # type: ignore
+                fringe.extend(field.fields)
+            name = field._name  # type: ignore
+            if name == self.pk_attr and not self.form_include_pk:
+                field.exclude_from_create = True
+                field.exclude_from_edit = True
+            if name in self.exclude_fields_from_list:
+                field.exclude_from_list = True
+            if name in self.exclude_fields_from_detail:
+                field.exclude_from_detail = True
+            if name in self.exclude_fields_from_create:
+                field.exclude_from_create = True
+            if name in self.exclude_fields_from_edit:
+                field.exclude_from_edit = True
+            if not isinstance(field, CollectionField):
+                all_field_names.append(name)
+                field.searchable = (self.searchable_fields is None) or (
+                    name in self.searchable_fields
+                )
+                field.orderable = (self.sortable_fields is None) or (
+                    name in self.sortable_fields
+                )
+        if self.searchable_fields is None:
+            self.searchable_fields = all_field_names[:]
+        if self.sortable_fields is None:
+            self.sortable_fields = all_field_names[:]
+        if self.export_fields is None:
+            self.export_fields = all_field_names[:]
+        if self.fields_default_sort is None:
+            self.fields_default_sort = [self.pk_attr]  # type: ignore[list-item]
+
+        # Actions
+        self._actions: Dict[str, Dict[str, str]] = OrderedDict()
+        self._row_actions: Dict[str, Dict[str, str]] = OrderedDict()
+        self._actions_handlers: Dict[
+            str, Callable[[Request, Sequence[Any]], Awaitable]
+        ] = OrderedDict()
+        self._row_actions_handlers: Dict[str, Callable[[Request, Any], Awaitable]] = (
+            OrderedDict()
+        )
+        self._init_actions()
+
+    def is_active(self, request: Request) -> bool:
+        return request.path_params.get("identity", None) == self.identity
+
+    def _init_actions(self) -> None:
+        self._init_batch_actions()
+        self._init_row_actions()
+        self._validate_actions()
+
+    def _init_batch_actions(self) -> None:
+        """
+        This method initializes batch and row actions, collects their handlers,
+        and validates that all specified actions exist.
+        """
+        for _method_name, method in inspect.getmembers(
+            self, predicate=inspect.ismethod
+        ):
+            if hasattr(method, "_action"):
+                name = method._action.get("name")
+                self._actions[name] = method._action
+                self._actions_handlers[name] = method
+
+        if self.actions is None:
+            self.actions = list(self._actions_handlers.keys())
+
+    def _init_row_actions(self) -> None:
+        for _method_name, method in inspect.getmembers(
+            self, predicate=inspect.ismethod
+        ):
+            if hasattr(method, "_row_action"):
+                name = method._row_action.get("name")
+                self._row_actions[name] = method._row_action
+                self._row_actions_handlers[name] = method
+
+        if self.row_actions is None:
+            self.row_actions = list(self._row_actions_handlers.keys())
+
+    def _validate_actions(self) -> None:
+        for action_name in not_none(self.actions):
+            if action_name not in self._actions:
+                raise ValueError(f"Unknown action with name `{action_name}`")
+        for action_name in not_none(self.row_actions):
+            if action_name not in self._row_actions:
+                raise ValueError(f"Unknown row action with name `{action_name}`")
+
+    async def is_action_allowed(self, request: Request, name: str) -> bool:
+        """
+        Verify if action with `name` is allowed.
+        Override this method to allow or disallow actions based
+        on some condition.
+
+        Args:
+            name: Action name
+            request: Starlette request
+        """
+        if name == "delete":
+            return self.can_delete(request)
+        return True
+
+    async def is_row_action_allowed(self, request: Request, name: str) -> bool:
+        """
+        Verify if the row action with `name` is allowed.
+        Override this method to allow or disallow row actions based
+        on some condition.
+
+        Args:
+            name: Row action name
+            request: Starlette request
+        """
+        if name == "delete":
+            return self.can_delete(request)
+        if name == "edit":
+            return self.can_edit(request)
+        if name == "view":
+            return self.can_view_details(request)
+        return True
+
+    async def get_all_actions(self, request: Request) -> List[Dict[str, Any]]:
+        """Return a list of allowed batch actions"""
+        actions = []
+        for action_name in not_none(self.actions):
+            if await self.is_action_allowed(request, action_name):
+                actions.append(self._actions.get(action_name, {}))
+        return actions
+
+    async def get_all_row_actions(self, request: Request) -> List[Dict[str, Any]]:
+        """Return a list of allowed row actions"""
+        row_actions = []
+        for row_action_name in not_none(self.row_actions):
+            if await self.is_row_action_allowed(request, row_action_name):
+                _row_action = self._row_actions.get(row_action_name, {})
+                if (
+                    request.state.action == RequestAction.LIST
+                    and not _row_action.get("exclude_from_list")
+                ) or (
+                    request.state.action == RequestAction.DETAIL
+                    and not _row_action.get("exclude_from_detail")
+                ):
+                    row_actions.append(_row_action)
+        return row_actions
+
+    async def handle_action(
+        self, request: Request, pks: List[Any], name: str
+    ) -> Union[str, Response]:
+        """
+        Handle action with `name`.
+        Raises:
+            ActionFailed: to display meaningfully error
+        """
+        handler = self._actions_handlers.get(name, None)
+        if handler is None:
+            raise ActionFailed("Invalid action")
+        if not await self.is_action_allowed(request, name):
+            raise ActionFailed("Forbidden")
+        handler_return = await handler(request, pks)
+        custom_response = self._actions[name]["custom_response"]
+        if isinstance(handler_return, Response) and not custom_response:
+            raise ActionFailed(
+                "Set custom_response to true, to be able to return custom response"
+            )
+        return handler_return
+
+    async def handle_row_action(
+        self, request: Request, pk: Any, name: str
+    ) -> Union[str, Response]:
+        """
+        Handle row action with `name`.
+        Raises:
+            ActionFailed: to display meaningfully error
+        """
+        handler = self._row_actions_handlers.get(name, None)
+        if handler is None:
+            raise ActionFailed("Invalid row action")
+        if not await self.is_row_action_allowed(request, name):
+            raise ActionFailed("Forbidden")
+        handler_return = await handler(request, pk)
+        custom_response = self._row_actions[name]["custom_response"]
+        if isinstance(handler_return, Response) and not custom_response:
+            raise ActionFailed(
+                "Set custom_response to true, to be able to return custom response"
+            )
+        return handler_return
+
+    @action(
+        name="delete",
+        text=_("Delete"),
+        confirmation=_("Are you sure you want to delete selected items?"),
+        submit_btn_text=_("Yes, delete all"),
+        submit_btn_class="btn-danger",
+    )
+    async def delete_action(self, request: Request, pks: List[Any]) -> str:
+        affected_rows = await self.delete(request, pks)
+        return ngettext(
+            "Item was successfully deleted",
+            "%(count)d items were successfully deleted",
+            affected_rows or 0,
+        ) % {"count": affected_rows}
+
+    @link_row_action(
+        name="view",
+        text=_("View"),
+        icon_class="fa-solid fa-eye",
+        exclude_from_detail=True,
+    )
+    def row_action_1_view(self, request: Request, pk: Any) -> str:
+        route_name = request.app.state.ROUTE_NAME
+        return str(
+            request.url_for(route_name + ":detail", identity=self.identity, pk=pk)
+        )
+
+    @link_row_action(
+        name="edit",
+        text=_("Edit"),
+        icon_class="fa-solid fa-edit",
+        action_btn_class="btn-primary",
+    )
+    def row_action_2_edit(self, request: Request, pk: Any) -> str:
+        route_name = request.app.state.ROUTE_NAME
+        return str(request.url_for(route_name + ":edit", identity=self.identity, pk=pk))
+
+    @row_action(
+        name="delete",
+        text=_("Delete"),
+        confirmation=_("Are you sure you want to delete this item?"),
+        icon_class="fa-solid fa-trash",
+        submit_btn_text="Yes, delete",
+        submit_btn_class="btn-danger",
+        action_btn_class="btn-danger",
+    )
+    async def row_action_3_delete(self, request: Request, pk: Any) -> str:
+        await self.delete(request, [pk])
+        return gettext("Item was successfully deleted")
+
+    @abstractmethod
+    async def find_all(
+        self,
+        request: Request,
+        skip: int = 0,
+        limit: int = 100,
+        where: Union[Dict[str, Any], str, None] = None,
+        order_by: Optional[List[str]] = None,
+    ) -> Sequence[Any]:
+        """
+        Find all items
+        Parameters:
+            request: The request being processed
+            where: Can be dict for complex query
+                ```json
+                 {"and":[{"id": {"gt": 5}},{"name": {"startsWith": "ban"}}]}
+                ```
+                or plain text for full search
+            skip: should return values start from position skip+1
+            limit: number of maximum items to return
+            order_by: order data clauses in form `["id asc", "name desc"]`
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    async def count(
+        self,
+        request: Request,
+        where: Union[Dict[str, Any], str, None] = None,
+    ) -> int:
+        """
+        Count items
+        Parameters:
+            request: The request being processed
+            where: Can be dict for complex query
+                ```json
+                 {"and":[{"id": {"gt": 5}},{"name": {"startsWith": "ban"}}]}
+                ```
+                or plain text for full search
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    async def find_by_pk(self, request: Request, pk: Any) -> Any:
+        """
+        Find one item
+        Parameters:
+            request: The request being processed
+            pk: Primary key
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    async def find_by_pks(self, request: Request, pks: List[Any]) -> Sequence[Any]:
+        """
+        Find many items
+        Parameters:
+            request: The request being processed
+            pks: List of Primary key
+        """
+        raise NotImplementedError()
+
+    async def before_create(
+        self, request: Request, data: Dict[str, Any], obj: Any
+    ) -> None:
+        """
+        This hook is called before a new item is created.
+
+        Args:
+            request: The request being processed.
+            data: Dict values contained converted form data.
+            obj: The object about to be created.
+        """
+
+    @abstractmethod
+    async def create(self, request: Request, data: Dict) -> Any:
+        """
+        Create item
+        Parameters:
+            request: The request being processed
+            data: Dict values contained converted form data
+        Returns:
+            Any: Created Item
+        """
+        raise NotImplementedError()
+
+    async def after_create(self, request: Request, obj: Any) -> None:
+        """
+        This hook is called after a new item is successfully created.
+
+        Args:
+            request: The request being processed.
+            obj: The newly created object.
+        """
+
+    async def before_edit(
+        self, request: Request, data: Dict[str, Any], obj: Any
+    ) -> None:
+        """
+        This hook is called before an item is edited.
+
+        Args:
+            request: The request being processed.
+            data: Dict values contained converted form data
+            obj: The object about to be edited.
+        """
+
+    @abstractmethod
+    async def edit(self, request: Request, pk: Any, data: Dict[str, Any]) -> Any:
+        """
+        Edit item
+        Parameters:
+            request: The request being processed
+            pk: Primary key
+            data: Dict values contained converted form data
+        Returns:
+            Any: Edited Item
+        """
+        raise NotImplementedError()
+
+    async def after_edit(self, request: Request, obj: Any) -> None:
+        """
+        This hook is called after an item is successfully edited.
+
+        Args:
+            request: The request being processed.
+            obj: The edited object.
+        """
+
+    async def before_delete(self, request: Request, obj: Any) -> None:
+        """
+        This hook is called before an item is deleted.
+
+        Args:
+            request: The request being processed.
+            obj: The object about to be deleted.
+        """
+
+    @abstractmethod
+    async def delete(self, request: Request, pks: List[Any]) -> Optional[int]:
+        """
+        Bulk delete items
+        Parameters:
+            request: The request being processed
+            pks: List of primary keys
+        """
+        raise NotImplementedError()
+
+    async def after_delete(self, request: Request, obj: Any) -> None:
+        """
+        This hook is called after an item is successfully deleted.
+
+        Args:
+            request: The request being processed.
+            obj: The deleted object.
+        """
+
+    def can_view_details(self, request: Request) -> bool:
+        """Permission for viewing full details of Item. Return True by default"""
+        return True
+
+    def can_create(self, request: Request) -> bool:
+        """Permission for creating new Items. Return True by default"""
+        return True
+
+    def can_edit(self, request: Request) -> bool:
+        """Permission for editing Items. Return True by default"""
+        return True
+
+    def can_delete(self, request: Request) -> bool:
+        """Permission for deleting Items. Return True by default"""
+        return True
+
+    async def serialize_field_value(
+        self, value: Any, field: BaseField, action: RequestAction, request: Request
+    ) -> Any:
+        """
+        Format output value for each field.
+
+        !!! important
+
+            The returned value should be json serializable
+
+        Parameters:
+            value: attribute of item returned by `find_all` or `find_by_pk`
+            field: Starlette Admin field for this attribute
+            action: Specify where the data will be used. Possible values are
+                `VIEW` for detail page, `EDIT` for editing page and `API`
+                for listing page and select2 data.
+            request: The request being processed
+        """
+        if value is None:
+            return await field.serialize_none_value(request, action)
+        return await field.serialize_value(request, value, action)
+
+    async def serialize(
+        self,
+        obj: Any,
+        request: Request,
+        action: RequestAction,
+        include_relationships: bool = True,
+        include_select2: bool = False,
+    ) -> Dict[str, Any]:
+        obj_serialized: Dict[str, Any] = {}
+        obj_meta: Dict[str, Any] = {}
+        for field in self.get_fields_list(request, action):
+            if isinstance(field, RelationField) and include_relationships:
+                value = getattr(obj, field.name, None)
+                foreign_model = self._find_foreign_model(field.identity)  # type: ignore
+                if value is None:
+                    obj_serialized[field.name] = None
+                elif isinstance(field, HasOne):
+                    if action == RequestAction.EDIT:
+                        obj_serialized[field.name] = (
+                            await foreign_model.get_serialized_pk_value(request, value)
+                        )
+                    else:
+                        obj_serialized[field.name] = await foreign_model.serialize(
+                            value, request, action, include_relationships=False
+                        )
+                else:
+                    if action == RequestAction.EDIT:
+                        obj_serialized[field.name] = [
+                            (await foreign_model.get_serialized_pk_value(request, obj))
+                            for obj in value
+                        ]
+                    else:
+                        obj_serialized[field.name] = [
+                            await foreign_model.serialize(
+                                v, request, action, include_relationships=False
+                            )
+                            for v in value
+                        ]
+            elif not isinstance(field, RelationField):
+                value = await field.parse_obj(request, obj)
+                obj_serialized[field.name] = await self.serialize_field_value(
+                    value, field, action, request
+                )
+        if include_select2:
+            obj_meta["select2"] = {
+                "selection": await self.select2_selection(obj, request),
+                "result": await self.select2_result(obj, request),
+            }
+        obj_meta["repr"] = await self.repr(obj, request)
+
+        # Make sure the primary key is always available
+        pk_attr = not_none(self.pk_attr)
+        if pk_attr not in obj_serialized:
+            pk_value = await self.get_serialized_pk_value(request, obj)
+            obj_serialized[pk_attr] = pk_value
+
+        pk = await self.get_pk_value(request, obj)
+        route_name = request.app.state.ROUTE_NAME
+        obj_meta["detailUrl"] = str(
+            request.url_for(route_name + ":detail", identity=self.identity, pk=pk)
+        )
+        obj_serialized["_meta"] = obj_meta
+        return obj_serialized
+
+    async def repr(self, obj: Any, request: Request) -> str:
+        """Return a string representation of the given object that can be displayed in the admin interface.
+
+        If the object has a custom representation method `__admin_repr__`, it is used to generate the string. Otherwise,
+        the value of the object's primary key attribute is used.
+
+        Args:
+            obj: The object to represent.
+            request: The request being processed
+
+        Example:
+            For example, the following implementation for a `User` model will display
+            the user's full name instead of their primary key in the admin interface:
+
+            ```python
+            class User:
+                id: int
+                first_name: str
+                last_name: str
+
+                def __admin_repr__(self, request: Request):
+                    return f"{self.last_name} {self.first_name}"
+            ```
+        """
+        repr_method = getattr(obj, "__admin_repr__", None)
+        if repr_method is None:
+            return str(await self.get_pk_value(request, obj))
+        if inspect.iscoroutinefunction(repr_method):
+            return await repr_method(request)
+        return repr_method(request)
+
+    async def select2_result(self, obj: Any, request: Request) -> str:
+        """Returns an HTML-formatted string that represents the search results for a Select2 search box.
+
+        By default, this method returns a string that contains all the object's attributes in a list except
+        relation and file attributes.
+
+        If the object has a custom representation method `__admin_select2_repr__`, it is used to generate the
+        HTML-formatted string.
+
+        !!! note
+
+            The returned value should be valid HTML.
+
+        !!! danger
+
+            Escape your database value to avoid Cross-Site Scripting (XSS) attack.
+            You can use Jinja2 Template render with `autoescape=True`.
+            For more information [click here](https://owasp.org/www-community/attacks/xss/)
+
+        Parameters:
+            obj: The object returned by the `find_all` or `find_by_pk` method.
+            request: The request being processed
+
+        Example:
+            Here is an example implementation for a `User` model
+            that includes the user's name and photo:
+
+            ```python
+            class User:
+                id: int
+                name: str
+                photo_url: str
+
+                def __admin_select2_repr__(self, request: Request) -> str:
+                    return f'<div><img src="{escape(photo_url)}"><span>{escape(self.name)}</span></div>'
+            ```
+
+        """
+        template_str = (
+            "<span>{%for col in fields %}{%if obj[col]%}<strong>{{col}}:"
+            " </strong>{{obj[col]}} {%endif%}{%endfor%}</span>"
+        )
+        fields = [
+            field.name
+            for field in self.get_fields_list(request)
+            if (
+                not isinstance(field, (RelationField, FileField))
+                and not field.exclude_from_detail
+            )
+        ]
+        html_repr_method = getattr(
+            obj,
+            "__admin_select2_repr__",
+            lambda request: Template(template_str, autoescape=True).render(
+                obj=obj, fields=fields
+            ),
+        )
+        if inspect.iscoroutinefunction(html_repr_method):
+            return await html_repr_method(request)
+        return html_repr_method(request)
+
+    async def select2_selection(self, obj: Any, request: Request) -> str:
+        """
+        Returns the HTML representation of an item selected by a user in a Select2 component.
+        By default, it simply calls `select2_result()`.
+
+        !!! note
+
+            The returned value should be valid HTML.
+
+        !!! danger
+
+            Escape your database value to avoid Cross-Site Scripting (XSS) attack.
+            You can use Jinja2 Template render with `autoescape=True`.
+            For more information [click here](https://owasp.org/www-community/attacks/xss/)
+
+        Parameters:
+            obj: item returned by `find_all` or `find_by_pk`
+            request: The request being processed
+
+        """
+        return await self.select2_result(obj, request)
+
+    async def get_pk_value(self, request: Request, obj: Any) -> Any:
+        return getattr(obj, not_none(self.pk_attr))
+
+    async def get_serialized_pk_value(self, request: Request, obj: Any) -> Any:
+        """
+        Return serialized value of the primary key.
+
+        !!! note
+
+            The returned value should be JSON-serializable.
+
+        Parameters:
+            request: The request being processed
+            obj: object to get primary key of
+
+        Returns:
+            Any: Serialized value of a PK.
+        """
+        return await self.get_pk_value(request, obj)
+
+    def _length_menu(self) -> Any:
+        return [
+            self.page_size_options,
+            [(_("All") if i < 0 else i) for i in self.page_size_options],
+        ]
+
+    def _search_columns_selector(self) -> List[str]:
+        return [f"{name}:name" for name in self.searchable_fields]  # type: ignore
+
+    def _export_columns_selector(self) -> List[str]:
+        return [f"{name}:name" for name in self.export_fields]  # type: ignore
+
+    def get_fields_list(
+        self,
+        request: Request,
+        action: RequestAction = RequestAction.LIST,
+    ) -> Sequence[BaseField]:
+        """Return a list of field instances to display in the specified view action.
+        This function excludes fields with corresponding exclude flags, which are
+        determined by the `exclude_fields_from_*` attributes.
+
+        Parameters:
+             request: The request being processed.
+             action: The type of action being performed on the view.
+        """
+        return extract_fields(self.fields, action)
+
+    def _additional_css_links(
+        self, request: Request, action: RequestAction
+    ) -> Sequence[str]:
+        links = self.additional_css_links or []
+        for field in self.get_fields_list(request, action):
+            for link in field.additional_css_links(request, action) or []:
+                if link not in links:
+                    links.append(link)
+        return links
+
+    def _additional_js_links(
+        self, request: Request, action: RequestAction
+    ) -> Sequence[str]:
+        links = self.additional_js_links or []
+        for field in self.get_fields_list(request, action):
+            for link in field.additional_js_links(request, action) or []:
+                if link not in links:
+                    links.append(link)
+        return links
+
+    async def _configs(self, request: Request) -> Dict[str, Any]:
+        locale = get_locale()
+        return {
+            "label": self.label,
+            "pageSize": self.page_size,
+            "lengthMenu": self._length_menu(),
+            "searchColumns": self._search_columns_selector(),
+            "exportColumns": self._export_columns_selector(),
+            "fieldsDefaultSort": dict(
+                (it, False) if isinstance(it, str) else it
+                for it in self.fields_default_sort  # type: ignore[union-attr]
+            ),
+            "exportTypes": self.export_types,
+            "columnVisibility": self.column_visibility,
+            "searchBuilder": self.search_builder,
+            "responsiveTable": self.responsive_table,
+            "stateSave": self.save_state,
+            "fields": [f.dict() for f in self.get_fields_list(request)],
+            "pk": self.pk_attr,
+            "locale": locale,
+            "apiUrl": request.url_for(
+                f"{request.app.state.ROUTE_NAME}:api", identity=self.identity
+            ),
+            "actionUrl": request.url_for(
+                f"{request.app.state.ROUTE_NAME}:action", identity=self.identity
+            ),
+            "rowActionUrl": request.url_for(
+                f"{request.app.state.ROUTE_NAME}:row-action", identity=self.identity
+            ),
+            "dt_i18n_url": request.url_for(
+                f"{request.app.state.ROUTE_NAME}:statics", path=f"i18n/dt/{locale}.json"
+            ),
+            "datatablesOptions": self.datatables_options,
+        }
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ after_create(request, obj) + + + async + + +

+ + +
+ +

This hook is called after a new item is successfully created.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

The request being processed.

+
+
+ required +
+ obj + + Any + +
+

The newly created object.

+
+
+ required +
+ +
+ Source code in starlette_admin/views.py +
async def after_create(self, request: Request, obj: Any) -> None:
+    """
+    This hook is called after a new item is successfully created.
+
+    Args:
+        request: The request being processed.
+        obj: The newly created object.
+    """
+
+
+
+ +
+ +
+ + +

+ after_delete(request, obj) + + + async + + +

+ + +
+ +

This hook is called after an item is successfully deleted.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

The request being processed.

+
+
+ required +
+ obj + + Any + +
+

The deleted object.

+
+
+ required +
+ +
+ Source code in starlette_admin/views.py +
async def after_delete(self, request: Request, obj: Any) -> None:
+    """
+    This hook is called after an item is successfully deleted.
+
+    Args:
+        request: The request being processed.
+        obj: The deleted object.
+    """
+
+
+
+ +
+ +
+ + +

+ after_edit(request, obj) + + + async + + +

+ + +
+ +

This hook is called after an item is successfully edited.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

The request being processed.

+
+
+ required +
+ obj + + Any + +
+

The edited object.

+
+
+ required +
+ +
+ Source code in starlette_admin/views.py +
async def after_edit(self, request: Request, obj: Any) -> None:
+    """
+    This hook is called after an item is successfully edited.
+
+    Args:
+        request: The request being processed.
+        obj: The edited object.
+    """
+
+
+
+ +
+ +
+ + +

+ before_create(request, data, obj) + + + async + + +

+ + +
+ +

This hook is called before a new item is created.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

The request being processed.

+
+
+ required +
+ data + + Dict[str, Any] + +
+

Dict values contained converted form data.

+
+
+ required +
+ obj + + Any + +
+

The object about to be created.

+
+
+ required +
+ +
+ Source code in starlette_admin/views.py +
async def before_create(
+    self, request: Request, data: Dict[str, Any], obj: Any
+) -> None:
+    """
+    This hook is called before a new item is created.
+
+    Args:
+        request: The request being processed.
+        data: Dict values contained converted form data.
+        obj: The object about to be created.
+    """
+
+
+
+ +
+ +
+ + +

+ before_delete(request, obj) + + + async + + +

+ + +
+ +

This hook is called before an item is deleted.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

The request being processed.

+
+
+ required +
+ obj + + Any + +
+

The object about to be deleted.

+
+
+ required +
+ +
+ Source code in starlette_admin/views.py +
async def before_delete(self, request: Request, obj: Any) -> None:
+    """
+    This hook is called before an item is deleted.
+
+    Args:
+        request: The request being processed.
+        obj: The object about to be deleted.
+    """
+
+
+
+ +
+ +
+ + +

+ before_edit(request, data, obj) + + + async + + +

+ + +
+ +

This hook is called before an item is edited.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

The request being processed.

+
+
+ required +
+ data + + Dict[str, Any] + +
+

Dict values contained converted form data

+
+
+ required +
+ obj + + Any + +
+

The object about to be edited.

+
+
+ required +
+ +
+ Source code in starlette_admin/views.py +
async def before_edit(
+    self, request: Request, data: Dict[str, Any], obj: Any
+) -> None:
+    """
+    This hook is called before an item is edited.
+
+    Args:
+        request: The request being processed.
+        data: Dict values contained converted form data
+        obj: The object about to be edited.
+    """
+
+
+
+ +
+ +
+ + +

+ can_create(request) + +

+ + +
+ +

Permission for creating new Items. Return True by default

+ +
+ Source code in starlette_admin/views.py +
def can_create(self, request: Request) -> bool:
+    """Permission for creating new Items. Return True by default"""
+    return True
+
+
+
+ +
+ +
+ + +

+ can_delete(request) + +

+ + +
+ +

Permission for deleting Items. Return True by default

+ +
+ Source code in starlette_admin/views.py +
def can_delete(self, request: Request) -> bool:
+    """Permission for deleting Items. Return True by default"""
+    return True
+
+
+
+ +
+ +
+ + +

+ can_edit(request) + +

+ + +
+ +

Permission for editing Items. Return True by default

+ +
+ Source code in starlette_admin/views.py +
def can_edit(self, request: Request) -> bool:
+    """Permission for editing Items. Return True by default"""
+    return True
+
+
+
+ +
+ +
+ + +

+ can_view_details(request) + +

+ + +
+ +

Permission for viewing full details of Item. Return True by default

+ +
+ Source code in starlette_admin/views.py +
def can_view_details(self, request: Request) -> bool:
+    """Permission for viewing full details of Item. Return True by default"""
+    return True
+
+
+
+ +
+ +
+ + +

+ count(request, where=None) + + + abstractmethod + async + + +

+ + +
+ +

Count items +Parameters: + request: The request being processed + where: Can be dict for complex query +

 {"and":[{"id": {"gt": 5}},{"name": {"startsWith": "ban"}}]}
+
+ or plain text for full search

+ +
+ Source code in starlette_admin/views.py +
@abstractmethod
+async def count(
+    self,
+    request: Request,
+    where: Union[Dict[str, Any], str, None] = None,
+) -> int:
+    """
+    Count items
+    Parameters:
+        request: The request being processed
+        where: Can be dict for complex query
+            ```json
+             {"and":[{"id": {"gt": 5}},{"name": {"startsWith": "ban"}}]}
+            ```
+            or plain text for full search
+    """
+    raise NotImplementedError()
+
+
+
+ +
+ +
+ + +

+ create(request, data) + + + abstractmethod + async + + +

+ + +
+ +

Create item +Parameters: + request: The request being processed + data: Dict values contained converted form data +Returns: + Any: Created Item

+ +
+ Source code in starlette_admin/views.py +
@abstractmethod
+async def create(self, request: Request, data: Dict) -> Any:
+    """
+    Create item
+    Parameters:
+        request: The request being processed
+        data: Dict values contained converted form data
+    Returns:
+        Any: Created Item
+    """
+    raise NotImplementedError()
+
+
+
+ +
+ +
+ + +

+ delete(request, pks) + + + abstractmethod + async + + +

+ + +
+ +

Bulk delete items +Parameters: + request: The request being processed + pks: List of primary keys

+ +
+ Source code in starlette_admin/views.py +
@abstractmethod
+async def delete(self, request: Request, pks: List[Any]) -> Optional[int]:
+    """
+    Bulk delete items
+    Parameters:
+        request: The request being processed
+        pks: List of primary keys
+    """
+    raise NotImplementedError()
+
+
+
+ +
+ +
+ + +

+ edit(request, pk, data) + + + abstractmethod + async + + +

+ + +
+ +

Edit item +Parameters: + request: The request being processed + pk: Primary key + data: Dict values contained converted form data +Returns: + Any: Edited Item

+ +
+ Source code in starlette_admin/views.py +
@abstractmethod
+async def edit(self, request: Request, pk: Any, data: Dict[str, Any]) -> Any:
+    """
+    Edit item
+    Parameters:
+        request: The request being processed
+        pk: Primary key
+        data: Dict values contained converted form data
+    Returns:
+        Any: Edited Item
+    """
+    raise NotImplementedError()
+
+
+
+ +
+ +
+ + +

+ find_all(request, skip=0, limit=100, where=None, order_by=None) + + + abstractmethod + async + + +

+ + +
+ +

Find all items +Parameters: + request: The request being processed + where: Can be dict for complex query +

 {"and":[{"id": {"gt": 5}},{"name": {"startsWith": "ban"}}]}
+
+ or plain text for full search + skip: should return values start from position skip+1 + limit: number of maximum items to return + order_by: order data clauses in form ["id asc", "name desc"]

+ +
+ Source code in starlette_admin/views.py +
@abstractmethod
+async def find_all(
+    self,
+    request: Request,
+    skip: int = 0,
+    limit: int = 100,
+    where: Union[Dict[str, Any], str, None] = None,
+    order_by: Optional[List[str]] = None,
+) -> Sequence[Any]:
+    """
+    Find all items
+    Parameters:
+        request: The request being processed
+        where: Can be dict for complex query
+            ```json
+             {"and":[{"id": {"gt": 5}},{"name": {"startsWith": "ban"}}]}
+            ```
+            or plain text for full search
+        skip: should return values start from position skip+1
+        limit: number of maximum items to return
+        order_by: order data clauses in form `["id asc", "name desc"]`
+    """
+    raise NotImplementedError()
+
+
+
+ +
+ +
+ + +

+ find_by_pk(request, pk) + + + abstractmethod + async + + +

+ + +
+ +

Find one item +Parameters: + request: The request being processed + pk: Primary key

+ +
+ Source code in starlette_admin/views.py +
@abstractmethod
+async def find_by_pk(self, request: Request, pk: Any) -> Any:
+    """
+    Find one item
+    Parameters:
+        request: The request being processed
+        pk: Primary key
+    """
+    raise NotImplementedError()
+
+
+
+ +
+ +
+ + +

+ find_by_pks(request, pks) + + + abstractmethod + async + + +

+ + +
+ +

Find many items +Parameters: + request: The request being processed + pks: List of Primary key

+ +
+ Source code in starlette_admin/views.py +
@abstractmethod
+async def find_by_pks(self, request: Request, pks: List[Any]) -> Sequence[Any]:
+    """
+    Find many items
+    Parameters:
+        request: The request being processed
+        pks: List of Primary key
+    """
+    raise NotImplementedError()
+
+
+
+ +
+ +
+ + +

+ get_all_actions(request) + + + async + + +

+ + +
+ +

Return a list of allowed batch actions

+ +
+ Source code in starlette_admin/views.py +
async def get_all_actions(self, request: Request) -> List[Dict[str, Any]]:
+    """Return a list of allowed batch actions"""
+    actions = []
+    for action_name in not_none(self.actions):
+        if await self.is_action_allowed(request, action_name):
+            actions.append(self._actions.get(action_name, {}))
+    return actions
+
+
+
+ +
+ +
+ + +

+ get_all_row_actions(request) + + + async + + +

+ + +
+ +

Return a list of allowed row actions

+ +
+ Source code in starlette_admin/views.py +
async def get_all_row_actions(self, request: Request) -> List[Dict[str, Any]]:
+    """Return a list of allowed row actions"""
+    row_actions = []
+    for row_action_name in not_none(self.row_actions):
+        if await self.is_row_action_allowed(request, row_action_name):
+            _row_action = self._row_actions.get(row_action_name, {})
+            if (
+                request.state.action == RequestAction.LIST
+                and not _row_action.get("exclude_from_list")
+            ) or (
+                request.state.action == RequestAction.DETAIL
+                and not _row_action.get("exclude_from_detail")
+            ):
+                row_actions.append(_row_action)
+    return row_actions
+
+
+
+ +
+ +
+ + +

+ get_fields_list(request, action=RequestAction.LIST) + +

+ + +
+ +

Return a list of field instances to display in the specified view action. +This function excludes fields with corresponding exclude flags, which are +determined by the exclude_fields_from_* attributes.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

The request being processed.

+
+
+ required +
+ action + + RequestAction + +
+

The type of action being performed on the view.

+
+
+ LIST +
+ +
+ Source code in starlette_admin/views.py +
def get_fields_list(
+    self,
+    request: Request,
+    action: RequestAction = RequestAction.LIST,
+) -> Sequence[BaseField]:
+    """Return a list of field instances to display in the specified view action.
+    This function excludes fields with corresponding exclude flags, which are
+    determined by the `exclude_fields_from_*` attributes.
+
+    Parameters:
+         request: The request being processed.
+         action: The type of action being performed on the view.
+    """
+    return extract_fields(self.fields, action)
+
+
+
+ +
+ +
+ + +

+ get_serialized_pk_value(request, obj) + + + async + + +

+ + +
+ +

Return serialized value of the primary key.

+
+

Note

+

The returned value should be JSON-serializable.

+
+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ request + + Request + +
+

The request being processed

+
+
+ required +
+ obj + + Any + +
+

object to get primary key of

+
+
+ required +
+ + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Any + Any + +
+

Serialized value of a PK.

+
+
+ +
+ Source code in starlette_admin/views.py +
async def get_serialized_pk_value(self, request: Request, obj: Any) -> Any:
+    """
+    Return serialized value of the primary key.
+
+    !!! note
+
+        The returned value should be JSON-serializable.
+
+    Parameters:
+        request: The request being processed
+        obj: object to get primary key of
+
+    Returns:
+        Any: Serialized value of a PK.
+    """
+    return await self.get_pk_value(request, obj)
+
+
+
+ +
+ +
+ + +

+ handle_action(request, pks, name) + + + async + + +

+ + +
+ +

Handle action with name. +Raises: + ActionFailed: to display meaningfully error

+ +
+ Source code in starlette_admin/views.py +
async def handle_action(
+    self, request: Request, pks: List[Any], name: str
+) -> Union[str, Response]:
+    """
+    Handle action with `name`.
+    Raises:
+        ActionFailed: to display meaningfully error
+    """
+    handler = self._actions_handlers.get(name, None)
+    if handler is None:
+        raise ActionFailed("Invalid action")
+    if not await self.is_action_allowed(request, name):
+        raise ActionFailed("Forbidden")
+    handler_return = await handler(request, pks)
+    custom_response = self._actions[name]["custom_response"]
+    if isinstance(handler_return, Response) and not custom_response:
+        raise ActionFailed(
+            "Set custom_response to true, to be able to return custom response"
+        )
+    return handler_return
+
+
+
+ +
+ +
+ + +

+ handle_row_action(request, pk, name) + + + async + + +

+ + +
+ +

Handle row action with name. +Raises: + ActionFailed: to display meaningfully error

+ +
+ Source code in starlette_admin/views.py +
async def handle_row_action(
+    self, request: Request, pk: Any, name: str
+) -> Union[str, Response]:
+    """
+    Handle row action with `name`.
+    Raises:
+        ActionFailed: to display meaningfully error
+    """
+    handler = self._row_actions_handlers.get(name, None)
+    if handler is None:
+        raise ActionFailed("Invalid row action")
+    if not await self.is_row_action_allowed(request, name):
+        raise ActionFailed("Forbidden")
+    handler_return = await handler(request, pk)
+    custom_response = self._row_actions[name]["custom_response"]
+    if isinstance(handler_return, Response) and not custom_response:
+        raise ActionFailed(
+            "Set custom_response to true, to be able to return custom response"
+        )
+    return handler_return
+
+
+
+ +
+ +
+ + +

+ is_action_allowed(request, name) + + + async + + +

+ + +
+ +

Verify if action with name is allowed. +Override this method to allow or disallow actions based +on some condition.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ name + + str + +
+

Action name

+
+
+ required +
+ request + + Request + +
+

Starlette request

+
+
+ required +
+ +
+ Source code in starlette_admin/views.py +
async def is_action_allowed(self, request: Request, name: str) -> bool:
+    """
+    Verify if action with `name` is allowed.
+    Override this method to allow or disallow actions based
+    on some condition.
+
+    Args:
+        name: Action name
+        request: Starlette request
+    """
+    if name == "delete":
+        return self.can_delete(request)
+    return True
+
+
+
+ +
+ +
+ + +

+ is_row_action_allowed(request, name) + + + async + + +

+ + +
+ +

Verify if the row action with name is allowed. +Override this method to allow or disallow row actions based +on some condition.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ name + + str + +
+

Row action name

+
+
+ required +
+ request + + Request + +
+

Starlette request

+
+
+ required +
+ +
+ Source code in starlette_admin/views.py +
async def is_row_action_allowed(self, request: Request, name: str) -> bool:
+    """
+    Verify if the row action with `name` is allowed.
+    Override this method to allow or disallow row actions based
+    on some condition.
+
+    Args:
+        name: Row action name
+        request: Starlette request
+    """
+    if name == "delete":
+        return self.can_delete(request)
+    if name == "edit":
+        return self.can_edit(request)
+    if name == "view":
+        return self.can_view_details(request)
+    return True
+
+
+
+ +
+ +
+ + +

+ repr(obj, request) + + + async + + +

+ + +
+ +

Return a string representation of the given object that can be displayed in the admin interface.

+

If the object has a custom representation method __admin_repr__, it is used to generate the string. Otherwise, +the value of the object's primary key attribute is used.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ obj + + Any + +
+

The object to represent.

+
+
+ required +
+ request + + Request + +
+

The request being processed

+
+
+ required +
+ + +
+ Example +

For example, the following implementation for a User model will display +the user's full name instead of their primary key in the admin interface:

+
class User:
+    id: int
+    first_name: str
+    last_name: str
+
+    def __admin_repr__(self, request: Request):
+        return f"{self.last_name} {self.first_name}"
+
+
+
+ Source code in starlette_admin/views.py +
async def repr(self, obj: Any, request: Request) -> str:
+    """Return a string representation of the given object that can be displayed in the admin interface.
+
+    If the object has a custom representation method `__admin_repr__`, it is used to generate the string. Otherwise,
+    the value of the object's primary key attribute is used.
+
+    Args:
+        obj: The object to represent.
+        request: The request being processed
+
+    Example:
+        For example, the following implementation for a `User` model will display
+        the user's full name instead of their primary key in the admin interface:
+
+        ```python
+        class User:
+            id: int
+            first_name: str
+            last_name: str
+
+            def __admin_repr__(self, request: Request):
+                return f"{self.last_name} {self.first_name}"
+        ```
+    """
+    repr_method = getattr(obj, "__admin_repr__", None)
+    if repr_method is None:
+        return str(await self.get_pk_value(request, obj))
+    if inspect.iscoroutinefunction(repr_method):
+        return await repr_method(request)
+    return repr_method(request)
+
+
+
+ +
+ +
+ + +

+ select2_result(obj, request) + + + async + + +

+ + +
+ +

Returns an HTML-formatted string that represents the search results for a Select2 search box.

+

By default, this method returns a string that contains all the object's attributes in a list except +relation and file attributes.

+

If the object has a custom representation method __admin_select2_repr__, it is used to generate the +HTML-formatted string.

+
+

Note

+

The returned value should be valid HTML.

+
+
+

Danger

+

Escape your database value to avoid Cross-Site Scripting (XSS) attack. +You can use Jinja2 Template render with autoescape=True. +For more information click here

+
+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ obj + + Any + +
+

The object returned by the find_all or find_by_pk method.

+
+
+ required +
+ request + + Request + +
+

The request being processed

+
+
+ required +
+ + +
+ Example +

Here is an example implementation for a User model +that includes the user's name and photo:

+
class User:
+    id: int
+    name: str
+    photo_url: str
+
+    def __admin_select2_repr__(self, request: Request) -> str:
+        return f'<div><img src="{escape(photo_url)}"><span>{escape(self.name)}</span></div>'
+
+
+
+ Source code in starlette_admin/views.py +
async def select2_result(self, obj: Any, request: Request) -> str:
+    """Returns an HTML-formatted string that represents the search results for a Select2 search box.
+
+    By default, this method returns a string that contains all the object's attributes in a list except
+    relation and file attributes.
+
+    If the object has a custom representation method `__admin_select2_repr__`, it is used to generate the
+    HTML-formatted string.
+
+    !!! note
+
+        The returned value should be valid HTML.
+
+    !!! danger
+
+        Escape your database value to avoid Cross-Site Scripting (XSS) attack.
+        You can use Jinja2 Template render with `autoescape=True`.
+        For more information [click here](https://owasp.org/www-community/attacks/xss/)
+
+    Parameters:
+        obj: The object returned by the `find_all` or `find_by_pk` method.
+        request: The request being processed
+
+    Example:
+        Here is an example implementation for a `User` model
+        that includes the user's name and photo:
+
+        ```python
+        class User:
+            id: int
+            name: str
+            photo_url: str
+
+            def __admin_select2_repr__(self, request: Request) -> str:
+                return f'<div><img src="{escape(photo_url)}"><span>{escape(self.name)}</span></div>'
+        ```
+
+    """
+    template_str = (
+        "<span>{%for col in fields %}{%if obj[col]%}<strong>{{col}}:"
+        " </strong>{{obj[col]}} {%endif%}{%endfor%}</span>"
+    )
+    fields = [
+        field.name
+        for field in self.get_fields_list(request)
+        if (
+            not isinstance(field, (RelationField, FileField))
+            and not field.exclude_from_detail
+        )
+    ]
+    html_repr_method = getattr(
+        obj,
+        "__admin_select2_repr__",
+        lambda request: Template(template_str, autoescape=True).render(
+            obj=obj, fields=fields
+        ),
+    )
+    if inspect.iscoroutinefunction(html_repr_method):
+        return await html_repr_method(request)
+    return html_repr_method(request)
+
+
+
+ +
+ +
+ + +

+ select2_selection(obj, request) + + + async + + +

+ + +
+ +

Returns the HTML representation of an item selected by a user in a Select2 component. +By default, it simply calls select2_result().

+
+

Note

+

The returned value should be valid HTML.

+
+
+

Danger

+

Escape your database value to avoid Cross-Site Scripting (XSS) attack. +You can use Jinja2 Template render with autoescape=True. +For more information click here

+
+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ obj + + Any + +
+

item returned by find_all or find_by_pk

+
+
+ required +
+ request + + Request + +
+

The request being processed

+
+
+ required +
+ +
+ Source code in starlette_admin/views.py +
async def select2_selection(self, obj: Any, request: Request) -> str:
+    """
+    Returns the HTML representation of an item selected by a user in a Select2 component.
+    By default, it simply calls `select2_result()`.
+
+    !!! note
+
+        The returned value should be valid HTML.
+
+    !!! danger
+
+        Escape your database value to avoid Cross-Site Scripting (XSS) attack.
+        You can use Jinja2 Template render with `autoescape=True`.
+        For more information [click here](https://owasp.org/www-community/attacks/xss/)
+
+    Parameters:
+        obj: item returned by `find_all` or `find_by_pk`
+        request: The request being processed
+
+    """
+    return await self.select2_result(obj, request)
+
+
+
+ +
+ +
+ + +

+ serialize_field_value(value, field, action, request) + + + async + + +

+ + +
+ +

Format output value for each field.

+
+

Important

+

The returned value should be json serializable

+
+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ value + + Any + +
+

attribute of item returned by find_all or find_by_pk

+
+
+ required +
+ field + + BaseField + +
+

Starlette Admin field for this attribute

+
+
+ required +
+ action + + RequestAction + +
+

Specify where the data will be used. Possible values are +VIEW for detail page, EDIT for editing page and API +for listing page and select2 data.

+
+
+ required +
+ request + + Request + +
+

The request being processed

+
+
+ required +
+ +
+ Source code in starlette_admin/views.py +
async def serialize_field_value(
+    self, value: Any, field: BaseField, action: RequestAction, request: Request
+) -> Any:
+    """
+    Format output value for each field.
+
+    !!! important
+
+        The returned value should be json serializable
+
+    Parameters:
+        value: attribute of item returned by `find_all` or `find_by_pk`
+        field: Starlette Admin field for this attribute
+        action: Specify where the data will be used. Possible values are
+            `VIEW` for detail page, `EDIT` for editing page and `API`
+            for listing page and select2 data.
+        request: The request being processed
+    """
+    if value is None:
+        return await field.serialize_none_value(request, action)
+    return await field.serialize_value(request, value, action)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/assets/_mkdocstrings.css b/assets/_mkdocstrings.css new file mode 100644 index 00000000..b500381b --- /dev/null +++ b/assets/_mkdocstrings.css @@ -0,0 +1,143 @@ + +/* Avoid breaking parameter names, etc. in table cells. */ +.doc-contents td code { + word-break: normal !important; +} + +/* No line break before first paragraph of descriptions. */ +.doc-md-description, +.doc-md-description>p:first-child { + display: inline; +} + +/* Max width for docstring sections tables. */ +.doc .md-typeset__table, +.doc .md-typeset__table table { + display: table !important; + width: 100%; +} + +.doc .md-typeset__table tr { + display: table-row; +} + +/* Defaults in Spacy table style. */ +.doc-param-default { + float: right; +} + +/* Parameter headings must be inline, not blocks. */ +.doc-heading-parameter { + display: inline; +} + +/* Prefer space on the right, not the left of parameter permalinks. */ +.doc-heading-parameter .headerlink { + margin-left: 0 !important; + margin-right: 0.2rem; +} + +/* Backward-compatibility: docstring section titles in bold. */ +.doc-section-title { + font-weight: bold; +} + +/* Symbols in Navigation and ToC. */ +:root, :host, +[data-md-color-scheme="default"] { + --doc-symbol-parameter-fg-color: #df50af; + --doc-symbol-attribute-fg-color: #953800; + --doc-symbol-function-fg-color: #8250df; + --doc-symbol-method-fg-color: #8250df; + --doc-symbol-class-fg-color: #0550ae; + --doc-symbol-module-fg-color: #5cad0f; + + --doc-symbol-parameter-bg-color: #df50af1a; + --doc-symbol-attribute-bg-color: #9538001a; + --doc-symbol-function-bg-color: #8250df1a; + --doc-symbol-method-bg-color: #8250df1a; + --doc-symbol-class-bg-color: #0550ae1a; + --doc-symbol-module-bg-color: #5cad0f1a; +} + +[data-md-color-scheme="slate"] { + --doc-symbol-parameter-fg-color: #ffa8cc; + --doc-symbol-attribute-fg-color: #ffa657; + --doc-symbol-function-fg-color: #d2a8ff; + --doc-symbol-method-fg-color: #d2a8ff; + --doc-symbol-class-fg-color: #79c0ff; + --doc-symbol-module-fg-color: #baff79; + + --doc-symbol-parameter-bg-color: #ffa8cc1a; + --doc-symbol-attribute-bg-color: #ffa6571a; + --doc-symbol-function-bg-color: #d2a8ff1a; + --doc-symbol-method-bg-color: #d2a8ff1a; + --doc-symbol-class-bg-color: #79c0ff1a; + --doc-symbol-module-bg-color: #baff791a; +} + +code.doc-symbol { + border-radius: .1rem; + font-size: .85em; + padding: 0 .3em; + font-weight: bold; +} + +code.doc-symbol-parameter { + color: var(--doc-symbol-parameter-fg-color); + background-color: var(--doc-symbol-parameter-bg-color); +} + +code.doc-symbol-parameter::after { + content: "param"; +} + +code.doc-symbol-attribute { + color: var(--doc-symbol-attribute-fg-color); + background-color: var(--doc-symbol-attribute-bg-color); +} + +code.doc-symbol-attribute::after { + content: "attr"; +} + +code.doc-symbol-function { + color: var(--doc-symbol-function-fg-color); + background-color: var(--doc-symbol-function-bg-color); +} + +code.doc-symbol-function::after { + content: "func"; +} + +code.doc-symbol-method { + color: var(--doc-symbol-method-fg-color); + background-color: var(--doc-symbol-method-bg-color); +} + +code.doc-symbol-method::after { + content: "meth"; +} + +code.doc-symbol-class { + color: var(--doc-symbol-class-fg-color); + background-color: var(--doc-symbol-class-bg-color); +} + +code.doc-symbol-class::after { + content: "class"; +} + +code.doc-symbol-module { + color: var(--doc-symbol-module-fg-color); + background-color: var(--doc-symbol-module-bg-color); +} + +code.doc-symbol-module::after { + content: "mod"; +} + +.doc-signature .autorefs { + color: inherit; + border-bottom: 1px dotted currentcolor; +} diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..947467be13987bce18a9e1f9bcf6fcc7b187f242 GIT binary patch literal 5238 zcma)9Yitx%6rP0=qftZj2hkWUi6ThESJddQHSvcrMvX>HFvR%yf$<--q8LmeAXtim zBs{|-B9<71M1P3eZCCnkr4(9P@`Of*y zxp(e(Ud)@|O`hy=>v_|QJZ~De#l`xW`(Ne0>D#Mv6vU*TOsO(A7#!0nNO_Gx-m*Dh(Vuk)xx&2A^-MI>8d&P zD;}b*-S1Q9_j9Oo*Bq8VpoT9W6q~S>D2bH`JZr-X_Nz?#fx)=Rk|Jtc^$4|m^OW3; z|f7lP`vOjU-8+7ZJ7gK-ZChBk99=OxZTd29>3GIXOlSyEGg$?{{a`6mxRnBD}Cun_BSKv?OMfhWmlm`DRK{YJ*H*HdR)O=Mj ze<%R)N5|A$@i@o5g@y-uD0_W`?@_wc_a~k2{*CIFO;zz5eXeJzALPMsB0LsR&7wPL z|KhvpkHvRU>a*LZ`qNvruI3E&17$eCKh$-QPrVL+C3UA-@s;sJ73$7q*ALHE9CNLU zzP1G)-Swl5py3rMYmzyMCrYIzQ^KpVCF>VZX5EO)f7mCA`Hu4)6Nv?E90e~8ig6=` zT3L_6ht#L|q2pm0eKGhGw+4*9#WWqJH3QDGBg!a0gB!hHk-{@PnpX2@@{g9+ud$xj zYK$<&`1G-*USK=VmvHPse5L25Y;4vE zD;RKTk-s3vz7Gv6AC`R=>pA(!B;Jc>$_*KP*q1R!>1)4ye|2pzOc+{wfPNdd<=T&L z@Kl}b9ay{j%cjuPOgCN092I##_u(4ssSDxI4Wu-?DxmE4C`%Vr#N^I{3vH@IjcV|Uh3$IjU_c6C7VTj&q36nU-i z-`JG_`g`9Okv>?zoc%%gD!?^j@vj`$TQ{BS+)JmD%c=Xv59Nm3TT?D$x$L-|Zg|M& zRq?CyuGgF4JlKNMTyC7s9^6H(YoD=okt~@YYf#o6%O)B6I-T6Nn9n1t>^TZD`VcS9Sv8Apr@!`mEV{-ogAr(5 ze3ySmKFTpbr=8DQW7jTFc5FoPE6KK0i;x|=M&l!wsi%4c`)~)fe*2u|CFqBZ6S3>~ zZC{?wg_9`@WMH$`hiUY3hDL`nd=?qD<5Gn@slRobaFK`W#P)x%v7bs|vrU|PdJ;>i zb=~vS{PmN(_s*cE@);td59iR!={D=jp>3}P&b7#|?8hO;&UQ>DgW8zz@H2Jh86sc zz`o!NeY$oHoj>+F4gJ$WLuWf^l+T}#aTb=d{OVa$yX0PKS@U#6{TchV1$G3!9j7Bd z%oY4AnJ&xg%>>MC;BTGg)sKnaDn1ocXpPDF4dbDH)>HFqm)sM`=?$?>aq=Y80d%8n zBR<&Y^P2irk$K{0s2;j@zjs5(=t1QObb*r}o8}j1MfJ|t_%26E&+flYY+=k?-k9pZ zUZlA9`E`9U@1V%{5A$H=cI6wR`tJ$Zufp6Ld*YM+#<4zd4H#cltGF;W z5KEfB{qX*|K0L_xCCZJD%RUcz??P8OLp% zK+F*rcSiXT;Ga1NJTra)zVV$boJV3=`0yxmq0R@Pe5jZTVrd?%Z)_uMSwZUFz@U2Y xyOdPek8mEGXM+n9=C-lkOz=H$XYv{~wSoF@Yb3>IHT5pRzGALuGiFrU{{Rk%GIjs} literal 0 HcmV?d00001 diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf13b9f9d978896599290a74f77d5dbe7d1655c GIT binary patch literal 1870 zcmV-U2eJ5xP)Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ literal 0 HcmV?d00001 diff --git a/assets/images/tutorials/basic/create.png b/assets/images/tutorials/basic/create.png new file mode 100644 index 0000000000000000000000000000000000000000..2d34b293d085ae40fb6bf8698ae0efd12c31a7a6 GIT binary patch literal 23977 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 literal 0 HcmV?d00001 diff --git a/assets/images/tutorials/basic/delete.png b/assets/images/tutorials/basic/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..e26163bf30b0b25efe9bacb46460f8f3e945bf23 GIT binary patch literal 107938 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

2JZn>jK{5!V71 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

OPcX~4>L)q&Lt%rWjEMHw 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

7Hxh>qyvj!w#m399l8~a2)Ifsd59=X7KO-|^ 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 R+y#MD6g1?&Jbe}P{{fd3MHK)5 literal 0 HcmV?d00001 diff --git a/assets/images/tutorials/basic/home_page.png b/assets/images/tutorials/basic/home_page.png new file mode 100644 index 0000000000000000000000000000000000000000..2fda00bb59d3bc85bca414527135c21a3ab2031c GIT binary patch literal 8176 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 literal 0 HcmV?d00001 diff --git a/assets/images/tutorials/basic/list_full.png b/assets/images/tutorials/basic/list_full.png new file mode 100644 index 0000000000000000000000000000000000000000..88e834d3b5d86a5d6855fca211e8714b3aa3e2d2 GIT binary patch literal 49448 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