Giter Site home page Giter Site logo

jowilf / starlette-admin Goto Github PK

View Code? Open in Web Editor NEW
477.0 5.0 52.0 5.72 MB

Fast, beautiful and extensible administrative interface framework for Starlette & FastApi applications

Home Page: https://jowilf.github.io/starlette-admin/

License: MIT License

Python 82.27% CSS 0.10% JavaScript 5.88% HTML 11.76%
fastapi mongoengine sqlalchemy starlette admin admin-dashboard datatables

starlette-admin's Introduction

starlette-admin

Starlette-Admin is a fast, beautiful and extensible administrative interface framework for Starlette/FastApi applications.

Test suite Publish Codecov Package version Supported Python versions

Preview image

Getting started

Features

  • CRUD any data with ease
  • Automatic form validation
  • Advanced table widget with Datatables
  • Search and filtering
  • Search highlighting
  • Multi-column ordering
  • Export data to CSV/EXCEL/PDF and Browser Print
  • Authentication
  • Authorization
  • Manage Files
  • Custom views
  • Custom batch actions
  • Supported ORMs
  • Internationalization

Installation

PIP

$ pip install starlette-admin

Poetry

$ poetry add starlette-admin

Example

This is a simple example with SQLAlchemy model

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from starlette.applications import Starlette
from starlette_admin.contrib.sqla import Admin, ModelView

Base = declarative_base()
engine = create_engine("sqlite:///test.db", connect_args={"check_same_thread": False})


# Define your model
class Post(Base):
    __tablename__ = "posts"

    id = Column(Integer, primary_key=True)
    title = Column(String)


Base.metadata.create_all(engine)

app = Starlette()  # FastAPI()

# Create admin
admin = Admin(engine, title="Example: SQLAlchemy")

# Add view
admin.add_view(ModelView(Post))

# Mount admin to your app
admin.mount_to(app)

Access your admin interface in your browser at http://localhost:8000/admin

Third party

starlette-admin is built with other open source projects:

Contributing

Contributions are welcome and greatly appreciated! Before getting started, please read our contribution guidelines

starlette-admin's People

Contributors

dependabot[bot] avatar disrupted avatar dolamroth avatar giaptx avatar hasansezertasan avatar ihuro avatar jowilf avatar mixartemev avatar mrharpo avatar nessshon avatar omarmoo5 avatar pre-commit-ci[bot] avatar rafnixg avatar sinisaos avatar whchi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

starlette-admin's Issues

Bug:

AssertionError: Multiple PK columns not supported, A possible solution is to override BaseModelView class and put your own logic.
I have two tables and m2m relation between them, it works but not with starlette-admin:

class Profile(Base):
    ...
    categories = relationship(ProfilesCategories)

class Category(Base):
    ...

class ProfilesCategories(Base):
      __tablename__ = "categories_in_profile"
      category_id = Column(Integer, ForeignKey('categories.id'), primary_key=True)
      profile_id = Column(Integer, ForeignKey('profiles.id'), primary_key=True)

      category = relationship("Category")

Pydantic models:

class ProfilesCategoriesSchema(BaseModel):
    ....

class ProfileReadSchema(BaseModel):
    ...
    categories = lsit[ProfilesCategoriesSchema]

If I dont add view for m2m table and make changes to get this:

class Profile(Base):
    ...
    categories = relationship(Category, secondary="categories_in_profile")

Starlette-admin works, but my api responses fail

  • Starlette-Admin version: [e.g. 0.3.2]
  • ORM/ODMs: SQLAlchemy

Bug: Not found routes

Describe the bug
Hi
I am facing a weird issue that some routes are discovered while the others return 404

For example, the following route can be retrieved

image

while on the same page, there's an XHR request for another route which returns 404, as illustrated in the following screenshot

image

To Reproduce
Here's the sample code I am using

class Bots(Base):
    __tablename__ = "bots"

    bot_id = Column(Integer, primary_key=True, index=True, autoincrement=True)
    bot_name = Column(String, unique=True, index=False)
    bot_config = Column(MutableDict.as_mutable(JSONB), default="{}")
    
    bot_created_at = Column(DateTime(timezone=True), server_default=func.now())
    bot_updated_at = Column(DateTime(timezone=True), onupdate=func.now())

    intents = Column(String, unique=False, index=False)

app = FastAPI() # I tried Starlette() too

admin = Admin(engine, title="Example: SQLAlchemy", base_url="/root")

admin.add_view(ModelView(Bots))
admin.mount_to(app)

Environment (please complete the following information):

  • Starlette-Admin version: 0.5.1
  • ORM/ODMs: SQLAlchemy

Enhancement: not display sensitive data in relation form

Is there any opportunity to not display fields according to persmision level for the users?
image

For now there is some invonvenience in safety by displaying some sensitive information whoever the user is
Even if i try to restrict field as much as possible it displays information in relation field of other view:

@dataclass
class PasswordField(StringField):
    """A StringField, except renders an `<input type="password">`."""

    input_type: str = "password"
    class_: str = "field-password form-control"
    exclude_from_list: Optional[bool] = True
    exclude_from_detail: Optional[bool] = True
    exclude_from_create: Optional[bool] = True
    exclude_from_edit: Optional[bool] = True
    searchable: Optional[bool] = False
    orderable: Optional[bool] = False
class User(MyModelView):
    fields = [
        Employee.id,
        Employee.login,
        PasswordField("password"),
        Employee.role, #some reltaion
        Employee.notes, #another relation
    ]

image

Bug: server error when trying to set some field as relation field

Set
field = [ "id", RelationField("some_relation") ]
=>

404 Not found when trying to acces site.
Same with "HasOne", "HasMany".

I know that it works automaticly, but when i want to modify this field and set to specific ModelView, it will not work through.
When I tried to make just plain new field (and set it to relation field) to display some information instead "id" it also lead to error (but after opening first both connected modelview; first time some one of them works perfect and displays correct information).

@dataclass
class NotesField(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/notes.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,
            }

        )

    async def serialize_value(
        self, request: Request, value: Any, action: RequestAction
    ) -> str:
        return value
        
class TestView(MyModelView):
    fields = [
        Test.id,
        NotesField("notes"),
        ]

=>

TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Note is not JSON serializable

Environment (please complete the following information):

  • Starlette-Admin version: 0.6.0
  • ORM/ODMs: sqlmodel==0.0.8(SQLAlchemy==1.4.41, sqlalchemy2-stubs==0.0.2a32)

Enhancement: Optional WYSIWYG editor for TextAreaField

First of all I want to thank you for a great project. What I liked most is both async and sync support and that everything works out of the box without single problem (including one-to-many and many-to-many relational fields, which is not the case in most other admin interfaces)

Describe the solution you'd like
Tabler default WYSIWYG editor is TinyMCE. The solution would be something like this

# in fields.py
@dataclass
class RichTextField(TextAreaField):
    """This field is an optional field used to represent a WYSIWYG editor
    for long text content.
    """

    form_template: str = "forms/richtext.html"

after that, we create richtext.html in forms directory and include support for TinyMCE in it.

Usage
We can use this new field like all other fields in ModelView fields property like this

class PostView(SQLModelView):
    fields = [
        "id",
        "title",
        RichTextField("content"),
    ]

If RichTextField is not provided we get default textarea in forms.

If you agree with this changes I can try to make PR. Thanks again. 

Bug: EnumField options displayed incorrectly in list/detail templates

When I use EnumField.from_choices(, [(, ), ]) to define a field on my ViewModel, enum members' labels are displayed appropriately on the edit template, but on the list and detail templates the values are displayed instead of the labels.

Cause: EnumField.display_template is the BaseField.display_template, which is "displays/text.html". This template blindly uses the value instead of the label.

Reason to fix: this bug noticebly harms human readability, since Enums are commonly used as process statuses.
It also damages the internationalization attepmpts (however, that's a whole other issue).

How to fix: add a display template

Bug: `CustomField` doesn't inherit class attributes for templates

Describe the bug
Following the examples in the docs, class CustomField does not use templates that are defined as class attributes.

To Reproduce
Adapting from examples/auth/view.py

from starlette_admin import BaseField
from starlette_admin.contrib.sqla import ModelView
from dataclasses import dataclass

@dataclass
class CustomField(BaseField):
    render_function_key: str = "mycustomkey"
    form_template: str = "forms/custom.html"
    display_template = "displays/custom.html"

class ArticleView(ModelView):
    fields = [
        "id",
        "title",
        "body",
        "status",
        CustomField(
            "title",
            label="Custom Title"
        )
    ]
templates/displays/custom.html
<span>Hello {{data}}</span>

Expected results

The detail page should show Hello {{data}}

Actual results

The detail page shows {{data}}

Environment (please complete the following information):

  • Starlette-Admin version: 0.8.0
  • ORM/ODMs: SQLModel, SQLAlchemy

Additional context

Passing variables

The templates are correctly rendered when passing them into CustomField:

class ArticleView(ModelView):
    fields = [
        "id",
        "title",
        "body",
        "status",
        CustomField(
            "title",
            label="Custom Title",
            display_template="displays/custom.html",
            form_template="forms/custom.html",
        ),
    ]

render_function_key works as expected when defined as a class attribute, or passed into the instance.

How to display custom js

I need to update the starlette admin data in real time, I want to use js to set an interval, which X seconds refreshes the page

But how do I do it?

Bug: Blank Edit Form Displayed for IntegerField with Value 0

Describe the bug
Mongoengine: when edit a int field, if vaule is 0, it not load in edit form.

To Reproduce
define an int field. If value is not 0, i work correctly when edit. But if value is 0, it not load in edit form (blank value)

Environment (please complete the following information):

  • Starlette-Admin version: 0.8.0
  • ORM/ODMs: MongoEngine

Additional context
Add any other context about the problem here.

Bug: NotSupportedColumn UUID

Describe the bug
Startletter-admin does not support uuid column

To Reproduce

  • import from sqlalchemy.dialects.postgresql import UUID
  • add uuid = Column(UUID(as_uuid=True), default=uuid.uuid4, unique=True) to table

Environment:

  • Starlette-Admin version: 0.5.0
  • ORM/ODMs: SQLAlchemy
  • Framework: FastAPI
  • Database: PostgreSQL

Additional context
When I take out the UUID column from the database table, the admin interface works. And when I add it back, it gives me this error:
raise NotSupportedColumn( # pragma: no cover starlette_admin.contrib.sqla.exceptions.NotSupportedColumn: Column UUID is not supported

However, when I accessed this file starlette_admin/contrib/sqla/helpers.py, line 149, in the convert_to_field function, I see that it supports "dialects.postgresql.base.UUID": StringField

Bug: EnumField options displayed incorrectly in list/detail templates [2]

The issue is the same as reported in #95. I'm terribly sorry about leaving it hanging for 3 weeks, but that problem was in my backlog all this time. Opening a new issue so you don't miss a comment on the old one.
However, this once I bring you reproducable example.

I cloned and started up starlette-admin-demo, set up the sqla admin as per readme.md and applied the following changes:

app/sqla/models.py:
updated Gender enum content

class Gender(str, enum.Enum):
    MALE = "Mâle"
    FEMALE = "Femme"
    UNKNOWN = 'ignoré'
    STORED = 'LaBeL'

app/sqla/views.py:
added sex field to the model View via clean EnumField, not inferred from a model; added a choice list, copied from the Gender enum. added EnumFields based on both enum and choices_list (one of these is always commented out, of course)

sex_choices = [('MALE', 'Mâle'), ('FEMALE', 'Femme'), ('UNKNOWN', 'ignoré'), ('STORED', 'LaBeL')]

class UserView(ModelView):
    page_size_options = [5, 10, 25, -1]
    fields = [
        "id",
        "full_name",
        EmailField("username"),
        "avatar",
        "posts",
        EnumField('sex', choices=sex_choices),
        # EnumField('sex', enum=Gender),
        CommentCounterField("comments_counter", label="Number of Comments"),
        "comments",
    ]
    ...

If Enum-based EnumField is used, then all the views (list, detail, create, edit) always display names of the enumeration members, and never their values.
image
image

if choices-list-based EnumField is used, then create/edit views display values (per Django terminology) and list/detail views display labels.
image
image
image

It's hard to notice when values and names of members a similar to each other and frontend styles format the received value. However, it's rather clear when values are written in another language.

I believe, the template shenenigans I mentioned in #95 are still the key to the core of the problem. The choices-list-based field is displayed correctly in create/edit forms, because options' values and labels are explicitly set in parts of generated html in starlette_admin/templates/forms/enum.html.
EnumField.serialize_value doesn't seem to be actually used during rendering (though I might be wrong, as I don't know this codebase this thorougly).

Sidenote: when choices-list-based EnumField is used, current value is erased on edit. It's not a case when the Enum-based field is used.
image
image

Bug: error page is not rendered, if exception occurs in delete methods

Describe the bug
If I try to delete object via admin panel, template "error.html" is not displayed. Instead, it arrives as response to AJAX query (visible in browser console).

To Reproduce
I am using starlette_admin with sqlalchemy contrib module. Simply override method delete in starlette_admin.contrib.sqla.view.ModelView to raise starlette.exceptions.HTTPException(500).

Environment (please complete the following information):

  • browser: Mozilla Firefox 110.0
  • starlette==0.25.0
  • starlette-admin==0.5.2
  • SQLAlchemy==1.4.46

Additional context
Add any other context about the problem here.

Bug: `add_to_menu=False` in CustomView results in the view to be in the menu

Describe the bug
If I set add_to_menu=False in a CustomView the view is added to the menu.

To Reproduce

The minimum working version that shows the bug is

from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import Response
from starlette.templating import Jinja2Templates
from starlette_admin import CustomView

from sqlalchemy import create_engine
from starlette_admin.contrib.sqla import Admin


engine = create_engine("sqlite:///test.db", connect_args={"check_same_thread": False})

app = FastAPI()

admin = Admin(engine)


class HomeView(CustomView):
    async def render(self, request: Request, templates: Jinja2Templates) -> Response:
        return templates.TemplateResponse(
            "home.html",
        )


admin.add_view(
    HomeView(
        label="This should not be visible",
        icon="fa fa-home",
        path="/home",
        add_to_menu=False,
    ),
)

admin.mount_to(app)

Then if I run the app and go on <host>/admin I see

Screenshot 2023-06-06 at 14 44 28

Environment (please complete the following information):

  • Starlette-Admin version: 0.9
  • ORM/ODMs: SQLAlchemy

Additional context

This happens even if the view is added to a DropDown. Not sure if it helps but maybe

Bug: `TextAreaField` output is identical to `StringField`

Describe the bug
I have a long text formatted with newlines kinda like

Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
Pellentesque fringilla non justo in laoreet.
Morbi et tincidunt nunc. 
Phasellus vulputate non nisi vel molestie.

displaying it in a StringField shows it on one single line, which is fine. The problem is that TextAreaField has the same behavior. My understanding is that TextAreaField should respect the new lines right?

To Reproduce
Create a TextAreaField and visualize it's value

Environment (please complete the following information):

  • Starlette-Admin version: 0.10
  • ORM/ODMs: SQLAlchemy

Additional context
Add any other context about the problem here.

Bug: Custom sqla.ModelView does not respect name/label, defined as class attribute

Describe the bug
starlette_admin.views.BaseModelView defines a name/label as class attributes.
Child model starlette_admin.contrib.sqla.ModelView only accepts these as instance attributes and forcibly overrides.

To Reproduce
This does allow to change the label:

admin.add_view(ModelView(NomenclatureStatus, label="Nomenclature statuses"))

whereas this DOES NOT

class NomenclatureStatusView(ModelView):
    label = "Nomenclature statuses"

admin.add_view(NomenclatureStatusView(NomenclatureStatus))

Environment (please complete the following information):

  • Starlette-Admin version: 0.8.0
  • ORM/ODMs: SQLAlchemy 2.0.9

Additional context
Probably, easy to fix with changing this line

self.label = label or prettify_class_name(self.model.__name__) + "s"

to

self.label = label or self.__class__.label or prettify_class_name(self.model.__name__) + "s"

(probably, same to other class attributes)

Bug and Propose: build_full_text_search_query not use request, extend get_search_query

  1. you can safely remove request in build_full_text_search_query and nested function
  2. require a way to easily customize search by fields
    my case:
def get_search_query(term: str, model) -> Any:
    """Return SQLAlchemy whereclause to use for full text search"""
    clauses = []
    for field_name, field in model.__fields__.items():
        if field.type_ in [
            str,
            int,
            float,
            uuid_pkg.UUID,

        ] or hasattr(field.type_, 'numerator'):  # Pydantic fields like price: int = Field(...
            attr = getattr(model, field.name)
            clauses.append(cast(attr, String).ilike(f"%{term}%"))
    return or_(*clauses)

I adapt the code to work with SQLModel, works fine!
Th bro!

Bug: BaseModelView cannot load queryset with relational field, whose model is not linked to admin

Describe the bug
I have multiple tables, say Nomenclature and Segment, which are linked by m2m table NomenclatureToSegment.
NomenclatureToSegment has a composite primary key, so I cannot load it to admin (a known limitation).

Now, if I just query the list page of NomenclatureAdmin, it will return an empty list. The query returns 404.

http://127.0.0.1/admin/api/nomenclature?skip=0&limit=10&order_by=uuid%20asc

The exact place, where it falls, is here:

await model.serialize(

foreign_model = self._find_foreign_model(field.identity) # type: ignore

def _find_model_from_identity(self, identity: Optional[str]) -> BaseModelView:

The error is (404, 'Model with identity nomenclature-to-segment not found')
It cannot resolve the model for a relational field, because the respective model is not linked to admin panel.
I don't understand how to easily fix this, since there are a lot of components under question.

Environment (please complete the following information):

  • Starlette-Admin version: [e.g. 0.8.0]
  • ORM/ODMs: SQLAlchemy

Additional context
The query returns a queryset, if I add select2 GET-parameter, but I don't understand how do I add it automatically.

http://127.0.0.1/admin/api/nomenclature?skip=0&limit=10&order_by=uuid%20asc&select2

DataTables issue

Hi, I stumbled upon your app and it's very impressive.
I've been testing it migrating from another admin and is almost direct.
I have one issue so far, and it's with the fields parameter

class UserAdmin(ModelView):
    fields = [User.login]

app.admin_site.add_view(UserAdmin(User))

I get a DataTables warning (it's pretty anoying)

DataTables warning: table id=dt - Requested unknown parameter 'DT_RowId' for row 0, column 0. For more information about this error, please see http://datatables.net/tn/4

if I remove the fields parameter in UserAdmin, it works like a charm.
Is there a way to disable the ajax load function for datatables?

Good job

starlette-admin is better than sqladmin when you use sqlalchemy!

Enhancement: Add option to authenticate via oauth2/OIDC instead of username/password

Is your feature request related to a problem? Please describe.
As far as I understood username & password is currently the only option to implement admin authentication. E.g. the default login screen is always rendered even if you change the login_path manually when instantiating the admin-backend

Describe the solution you'd like
It would be nice to use an oauth2 flow to authenticate users.
So far I used an authlib-oauthclient to handle the oauth2flow.

With my oauthclient I need to call 2 methods to complete the sign-in:

  • authlib_client.authorize_redirect(<redirect_uri>) -> This sends the user to the OAuth-provider for authentication, the user is then sent back to our application to the specified redirect_uri
  • authlib_client.authorize_access_token() -> coming back from the oauthprovider the OAuth client validates the token/userdata. If successful some data is stored in the current session (e.g. "isAdmin"=True if the user has admin role in his userdata)

After the login steps the standard "is_authenticated" method is used to validate if and what the user is allowed to see

What is needed to facilitate the OAuth flow:

  • if OAuth authentication is used, you should be able to run a custom login function (which sends the user to the external login_url of your OAuth-provider without showing the login screen/template)
  • [optional] You need to be able to handle the response and store data in the session -> optional because this could be done with a separate fastapi route

Describe alternatives you've considered
You could implement the whole login flow with 2 separate fastapi-endpoints completely outside of the starlette-admin context (login & response validation) -> then you just need the option to disable the login screen and

Additional context

FEAT: Add description to Field and display in create/edit forms

Thank you so much for this library! I've been watching sqladmin for a while to see if it has support for everything I need but this is already ready to go.

Flask-admin allows you to specify field descriptions for use in the form modals. It would be great to see that here too

Enhancement: UUID field

I have a strong feeling that there should be a standard implementation to display a UUID Field as a part of the ModelView.

UUID is a de facto standard for unique indexation in distributed systems and quite commonly used as a primary key for entities. Even early on I already have a use case where i would like to display and use it as a part of my admin panel.

Bug: default behavior of BaseModelView.is_action_allowed is not as expected

Describe the bug
By default BaseModelView.is_action_allowed returns True for delete_action, even if can_delete returns False.

Environment (please complete the following information):

  • starlette==0.25.0
  • starlette-admin==0.5.2
  • SQLAlchemy==1.4.46

Additional context
To fix the bug, coroutine self.can_delete(request) should be awaited.

async def is_action_allowed(self, request: Request, name: str) -> bool:
    if name == "delete":
        return await self.can_delete(request)
    return True

Enhancement: input data in custom action

Thank you very much for the great framework!

I want to ask is there any way to send some input data for the custom action. For example, when you need to do some bulk edits: replace some specified field for all selected rows with some text/int/etc. And other operations that might require data for the execution.
Without this feature, you have to edit all the entries manually, which can spend a very long time.

Bug: sqlalchemy.exc.ArgumentError: Strings are not accepted for attribute names in loader options; please use class-bound attributes directly.

Summary

An error is thrown when there is a relationship in an sqlalchemy based model. The following is the stack trace:

  File "/home/user/.cache/pypoetry/virtualenvs/project-bghs2piA-py3.10/lib/python3.10/site-packages/starlette/routing.py", line 66, in app
    response = await func(request)
  File "/home/user/.cache/pypoetry/virtualenvs/project-bghs2piA-py3.10/lib/python3.10/site-packages/starlette_admin/base.py", line 366, in _render_detail
    obj = await model.find_by_pk(request, pk)
  File "/home/user/.cache/pypoetry/virtualenvs/project-bghs2piA-py3.10/lib/python3.10/site-packages/starlette_admin/contrib/sqla/view.py", line 213, in find_by_pk
    stmt = stmt.options(joinedload(field.name))
  File "/home/user/.cache/pypoetry/virtualenvs/project-bghs2piA-py3.10/lib/python3.10/site-packages/sqlalchemy/orm/strategy_options.py", line 2359, in joinedload
    return _generate_from_keys(Load.joinedload, keys, False, kw)
  File "/home/user/.cache/pypoetry/virtualenvs/project-bghs2piA-py3.10/lib/python3.10/site-packages/sqlalchemy/orm/strategy_options.py", line 2261, in _generate_from_keys
    raise sa_exc.ArgumentError(
sqlalchemy.exc.ArgumentError: Strings are not accepted for attribute names in loader options; please use class-bound attributes directly.

Reproduction steps

Sample sqlalchemy models:

from sqlalchemy import ForeignKey, Integer, String
from sqlalchemy.ext.declarative import as_declarative, declared_attr
from sqlalchemy.orm import Mapped as T
from sqlalchemy.orm import mapped_column as column
from sqlalchemy.orm import relationship

@as_declarative()
class Base:
    id: Any
    __name__: str

    # Generate __tablename__ automatically
    @declared_attr
    def __tablename__(cls) -> str:
        return cls.__name__.lower()

class Parent(Base):
    id: T[int] = column(primary_key=True, autoincrement=True)
    name: T[str] = column(String(100))
    children: T[list["Child"]] = relationship(back_populates="parent")

class Child(Base):
    id: T[int] = column(primary_key=True, autoincrement=True)
    name: T[str] = column(String(100))
    parent_id: T[int] = column(ForeignKey("parent.id", ondelete="CASCADE"))
    parent: T[Parent] = relationship(back_populates="children")

Register models:

from starlette_admin.contrib.sqla import ModelView

admin.add_view(ModelView(Parent))
admin.add_view(ModelView(Child))

Open the admin and try to do some crud operations. This will result in an error.

Environment

  • Starlette-Admin 0.5.5
  • ORM/ODMs: SQLAlchemy 2.0.5.post1

Additional context

Following is what I have found from PDB:

(Pdb) field.name
'children'
(Pdb) l
206  	       )
207  	
208  	   async def find_by_pk(self, request: Request, pk: Any) -> Any:
209  	       session: Union[Session, AsyncSession] = request.state.session
210  	       stmt = select(self.model).where(self._pk_column == self._pk_coerce(pk))
211  ->	       for field in self.fields:
212  	           if isinstance(field, RelationField):
213  	               try:
214  	                   stmt = stmt.options(joinedload(field.name))
215  	               except:
216  	                   breakpoint()
(Pdb) stmt.options(joinedload(field.name))  # Gives error
*** sqlalchemy.exc.ArgumentError: Strings are not accepted for attribute names in loader options; please use class-bound attributes directly.
(Pdb) stmt.options(joinedload(getattr(self.model, field.name)))  # Works
<sqlalchemy.sql.selectable.Select object at 0x7f10ab5fcb50>

I also get the same error at:

  File "/home/user/.cache/pypoetry/virtualenvs/project-bghs2piA-py3.10/lib/python3.10/site-packages/starlette_admin/contrib/sqla/view.py", line 198, in find_all
    stmt = stmt.options(joinedload(field.name))

I think there are many places where this needs to be fixed.

Bug: IntegrityError in ModelView.delete method causes PendingRollbackError in some cases

Describe the bug
I am using starlette_admin with sqlalchemy contrib. DBSessionMiddleware, as first middleware, creates session and then properly closes it at the end. However, if error occurs during ModelView.delete method, the IntegrityError occurs, which should be handled.

I have inherited BaseAdmin and re-defined exception handlers to handle StarletteAdminException and SQLAlchemy's IntegrityError with the same function _render_error, which was used for HttpException. However, this function renders template "error.html" by using AuthProvider.get_admin_user, which (in my case, at least) internally asks session for user fields. And since session is in invalid state, but not rollbacked yet by DBSessionMiddleware, there occurs a sqlalchemy.exc.PendingRollbackError exception.

The traceback is as follows:

Traceback (most recent call last):
  File "D:\Projects\starlette-web\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 403, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "D:\Projects\starlette-web\venv\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 74, in __call__
    return await self.app(scope, receive, send)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\errors.py", line 184, in __call__
    raise exc
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "D:\Projects\starlette-web\venv\lib\site-packages\sentry_sdk\integrations\asgi.py", line 139, in _run_asgi3
    return await self._run_app(scope, lambda: self.app(scope, receive, send))
  File "D:\Projects\starlette-web\venv\lib\site-packages\sentry_sdk\integrations\asgi.py", line 186, in _run_app
    raise exc from None
  File "D:\Projects\starlette-web\venv\lib\site-packages\sentry_sdk\integrations\asgi.py", line 183, in _run_app
    return await callback()
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\exceptions.py", line 75, in __call__
    raise exc
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\exceptions.py", line 64, in __call__
    await self.app(scope, receive, sender)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\routing.py", line 687, in __call__
    await route.handle(scope, receive, send)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\routing.py", line 436, in handle
    await self.app(scope, receive, send)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\errors.py", line 184, in __call__
    raise exc
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\base.py", line 104, in __call__
    response = await self.dispatch_func(request, call_next)
  File "D:\Projects\starlette-web\starlette_web\contrib\admin\middleware.py", line 28, in dispatch
    response = await call_next(request)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\base.py", line 80, in call_next
    raise app_exc
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\base.py", line 66, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File "D:\Projects\starlette-web\starlette_web\contrib\admin\middleware.py", line 75, in __call__
    await self.app(scope, receive, send_wrapper)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\base.py", line 104, in __call__
    response = await self.dispatch_func(request, call_next)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette_admin\auth.py", line 161, in dispatch
    return await call_next(request)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\base.py", line 80, in call_next
    raise app_exc
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\base.py", line 66, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\middleware\exceptions.py", line 84, in __call__
    response = await handler(request, exc)
  File "D:\Projects\starlette-web\starlette_web\contrib\admin\admin.py", line 101, in _render_error
    return self.templates.TemplateResponse(
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\templating.py", line 112, in TemplateResponse
    return _TemplateResponse(
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette\templating.py", line 38, in __init__
    content = template.render(context)
  File "D:\Projects\starlette-web\venv\lib\site-packages\jinja2\environment.py", line 1285, in render
    self.environment.handle_exception()
  File "D:\Projects\starlette-web\venv\lib\site-packages\jinja2\environment.py", line 926, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "D:\Projects\starlette-web\venv\lib\site-packages\starlette_admin\templates\error.html", line 1, in top-level template code
    {% extends "layout.html" %}
File "D:\Projects\starlette-web\venv\lib\site-packages\starlette_admin\templates\layout.html", line 3, in top-level template code
    {% set current_user = (request | get_admin_user) %}
  File "D:\Projects\starlette-web\starlette_web\contrib\admin\auth_provider.py", line 65, in get_admin_user
    username = request.scope["user"].email
  File "D:\Projects\starlette-web\venv\lib\site-packages\sqlalchemy\orm\attributes.py", line 478, in __get__
    return self.impl.get(state, dict_)
  File "D:\Projects\starlette-web\venv\lib\site-packages\sqlalchemy\orm\attributes.py", line 919, in get
    value = self._fire_loader_callables(state, key, passive)
  File "D:\Projects\starlette-web\venv\lib\site-packages\sqlalchemy\orm\attributes.py", line 946, in _fire_loader_callables
    return state._load_expired(state, passive)
  File "D:\Projects\starlette-web\venv\lib\site-packages\sqlalchemy\orm\state.py", line 695, in _load_expired
    self.manager.expired_attribute_loader(self, toload, passive)
  File "D:\Projects\starlette-web\venv\lib\site-packages\sqlalchemy\orm\loading.py", line 1383, in load_scalar_attributes
    result = load_on_ident(
  File "D:\Projects\starlette-web\venv\lib\site-packages\sqlalchemy\orm\loading.py", line 385, in load_on_ident
    return load_on_pk_identity(
  File "D:\Projects\starlette-web\venv\lib\site-packages\sqlalchemy\orm\loading.py", line 499, in load_on_pk_identity
    session.execute(
  File "D:\Projects\starlette-web\venv\lib\site-packages\sqlalchemy\orm\session.py", line 1676, in execute
    conn = self._connection_for_bind(bind)
  File "D:\Projects\starlette-web\venv\lib\site-packages\sqlalchemy\orm\session.py", line 1529, in _connection_for_bind
    return self._transaction._connection_for_bind(engine, execution_options)
  File "D:\Projects\starlette-web\venv\lib\site-packages\sqlalchemy\orm\session.py", line 714, in _connection_for_bind
    self._assert_active()
  File "D:\Projects\starlette-web\venv\lib\site-packages\sqlalchemy\orm\session.py", line 598, in _assert_active
    raise sa_exc.PendingRollbackError(
sqlalchemy.exc.PendingRollbackError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original exception was:

(sqlalchemy.dialects.postgresql.asyncpg.IntegrityError) <class 'asyncpg.exceptions.ForeignKeyViolationError'>: UPDATE или DELETE в таблице "auth_users" нарушает ограничение внешнего ключа "auth_sessions_user_id_fkey" таблицы "auth_sessions"
DETAIL:  На ключ (id)=(1) всё ещё есть ссылки в таблице "auth_sessions".
[SQL: DELETE FROM auth_users WHERE auth_users.id = %s]
[parameters: ((1,), (2,))]
(Background on this error at: https://sqlalche.me/e/14/gkpj) (Background on this error at: https://sqlalche.me/e/14/7s2a)

(Sorry that the last lines are in Russian, but idea is understandable, I hope)

Environment (please complete the following information):

  • starlette==0.25.0
  • starlette-admin==0.5.2
  • SQLAlchemy==1.4.46

Additional context
This seems to be a part of more general problem, that points out, that whenever exception occurs in methods, session should be rolled back immediately.

Luckly, the bug seems to be solvable by simply wrapping this 2 lines in async with session.begin_nested() https://github.com/jowilf/starlette-admin/blob/main/starlette_admin/contrib/sqla/view.py#L378-L379 (in the same way, with sync-session)

I haven't studied this behavior on methods, other than delete.

Enhancement:

Hi~ ,would you consider adding the schedule function?
It seems that this function already exists on other projects, feel good for example

Bug: 303 redirects at login screen

Describe the bug
Unable to see logo (from statics dir) at login screen, and also 1 of scripts (js.cookie.min.js) is not in allowed paths in AuthMiddleware.
Screenshot 2566-05-04 at 16 48 33

To Reproduce
Set statics_dir and put login_logo in this dir.
Screenshot 2566-05-04 at 16 56 05

Environment (please complete the following information):

  • Starlette-Admin version: 0.8.1
  • ORM/ODMs: SQLAlchemy

Enhancement: Support custom response for batch actions

Enhance the @action decorator to support custom responses, such as redirects or rendering HTML pages, instead of just returning strings (related to #205)

Todo:

  • Add a custom_response flag to the @action decorator function (default: False).
  • Update submitAction() in list.js to handle custom responses.
  • Modify handle_action in the base admin class to support custom responses.
  • Ensure backward compatibility.
  • Include an example in custom_actions example
  • Add tests.
  • Update the documentation

Enhancement: beanie support

did you start integrating beanie? do you have a possible date? I use benie because it allows me to create class dynamically with create_model (pydantic). thank you in advanced

Suggestion for URLs

Hello, I'm trying your app and I noticed it creates a few routes /statics, /api/{identity}, they get added at the root.

INFO: 127.0.0.1:41598 - "GET /user/list HTTP/1.1" 200 OK
INFO: 127.0.0.1:41598 - "GET /api/user?skip=0&limit=10 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56438 - "GET /api/user?skip=0&limit=10 HTTP/1.1" 200 OK

I believe they should be added after base_url=/admin so the URL always has /admin prepended.
I was about to make the fork and then the PR but I noticed you're about lo launch a new release, are you considering making this modification? I can make it if you didn't consider it for the new release.

Bug: Column CHAR(36) is not supported

Describe the bug
ERROR: Traceback (most recent call last):
INFO: Started reloader process [47636] using WatchFiles
INFO: Started server process [47640]
INFO: Waiting for application startup.
ERROR: Traceback (most recent call last):
File "/Users/billy/Library/Caches/pypoetry/virtualenvs/fl-api-sf841ExJ-py3.11/lib/python3.11/site-packages/starlette/routing.py", line 671, in lifespan
async with self.lifespan_context(app):
File "/Users/billy/Library/Caches/pypoetry/virtualenvs/fl-api-sf841ExJ-py3.11/lib/python3.11/site-packages/starlette/routing.py", line 566, in aenter
await self._router.startup()
File "/Users/billy/Library/Caches/pypoetry/virtualenvs/fl-api-sf841ExJ-py3.11/lib/python3.11/site-packages/starlette/routing.py", line 648, in startup
await handler()
File "/Users/billy/Desktop/goli/goli/goli_api/goli_api/web/lifetime.py", line 59, in _startup
_setup_db(app)
File "/Users/billy/Desktop/goli/goli/goli_api/goli_api/web/lifetime.py", line 39, in _setup_db
admin.add_view(ModelView(UserModel))
^^^^^^^^^^^^^^^^^^^^
File "/Users/billy/Library/Caches/pypoetry/virtualenvs/fl-api-sf841ExJ-py3.11/lib/python3.11/site-packages/starlette_admin/contrib/sqla/view.py", line 66, in init
self.fields = normalize_fields(self.fields, mapper)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/billy/Library/Caches/pypoetry/virtualenvs/fl-api-sf841ExJ-py3.11/lib/python3.11/site-packages/starlette_admin/contrib/sqla/helpers.py", line 207, in normalize_fields
field = convert_to_field(column)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/billy/Library/Caches/pypoetry/virtualenvs/fl-api-sf841ExJ-py3.11/lib/python3.11/site-packages/starlette_admin/contrib/sqla/helpers.py", line 160, in convert_to_field
raise NotSupportedColumn( # pragma: no cover
starlette_admin.contrib.sqla.exceptions.NotSupportedColumn: Column CHAR(36) is not supported

To Reproduce
I use Fastapi-users as packages for my authentification and I have this error for my user model that use UUID for primary key

from fastapi import Depends
from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql.sqltypes import String

from golibro_flash_api.db.base import Base
from golibro_flash_api.db.dependencies import get_db_session


class UserModel(SQLAlchemyBaseUserTableUUID, Base):
    """Model for demo purpose."""

    __tablename__ = "user_model"

    fullName: Mapped[str] = mapped_column(String(length=200), nullable=True, default="")
    username: Mapped[str] = mapped_column(String(length=200), nullable=True, default="")
    role: Mapped[str] = mapped_column(String(length=200), nullable=True, default="")


async def get_user_db(session: AsyncSession = Depends(get_db_session)):
    yield SQLAlchemyUserDatabase(session, UserModel)

Environment (please complete the following information):

  • Starlette-Admin version: "^0.5.2"
  • FASTAPI : "^0.89.1"
  • ORM/ODMs: [SQLAlchemy : 2.0]

Enhancement: Support Swagger

Is your feature request related to a problem? Please describe.
FastAPI supports OpenAPI as builtin.
Is it possible to support OpenAPI to describe detail spec for another client which want to create, read, update, delete?

Describe the solution you'd like
support API endpoint with OpenAPI description such as FastAPI does.

Describe alternatives you've considered

Additional context

Enhancement: logging and/or more explicit form validation exceptions

Is your feature request related to a problem? Please describe.
I've spent this night in pitiful attempts to understand why the {identity}/edit/{pk} view's form submission didn't trigger the update command in my mongodb - create, list and detail worked fine, in the example the aforementioned view worked fine, but it silently refused to act properly in my case.
The reason for it is the fact that the on-write validation on my UUID field for that model fails but the error message about it can't be displayed on the frontend since that field is not a part of the ModelView. I found it out after overwriting the edit async method for my ModelView and adding logging there.

The case I encountered goes like this:

  1. Update the included fields
  2. Click "Submit"
  3. Be redirected to the very same page with no explicit warnings on the result
  4. Check the logs - 200 on POST
  5. Check the DB and item list - see the unchanged item

6. Curse elaborately and spend the night researching the guts of the library to understand what exactly is going on.

Describe the solution you'd like

  • First of all, it's a dreadful idea to return 200 OK for every type of result, especially an internal ValidationError. There really should be certain differentiation, that's what http result codes are for.
  • Secondly, it would be good to see the exception details in the reason of the response in debug mode, so no similar confusion will arise when the error is related to a non-visible field.
  • Thirdly, some form of architectural decision must be made regarding the model errors that currently can't be shown through the ModelView (as goes the one described).
  • Finally, there clearly should be some controllable logging wired through the library to facilitate the investigations (and help the library improve and grow long-term).

Bug: No data loading

Describe the bug
No data loads in the list view of any of my tables.
Screen Shot 2023-01-15 at 1 46 29 PM

However, creating new items works - but then they continue not to show up in the list view. I can see they do indeed exist in the DB, which indicates that I am setting up the engine correctly.

To Reproduce
I'm not sure if this is a library issue or something I am doing wrong.

Environment (please complete the following information):

  • Starlette-Admin version: 0.5.2
  • ORM/ODMs: SQLModel

**Notes
Here is how I am setting up the views
Screen Shot 2023-01-15 at 1 49 33 PM

Bug: Unable to download files on mongoengine

Describe the bug
When uploading files or images using mongoengine, it's unable to view due to the incorrect type being passed to StreamingResponse

To Reproduce

  • Create an object with a FileField/ImageField
  • Attempt to view the file you just uploaded. The thumbnail and download functionality don't work

Environment (please complete the following information):

  • starlette-admin==0.10.0
  • mongoengine==0.27.0
  • Python 3.7.16

Additional context

Traceback (most recent call last):
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/uvicorn/protocols/http/httptools_impl.py", line 405, in run_asgi
    self.scope, self.receive, self.send
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
    return await self.app(scope, receive, send)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/fastapi/applications.py", line 270, in __call__
    await super().__call__(scope, receive, send)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/applications.py", line 124, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/middleware/exceptions.py", line 75, in __call__
    raise exc
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/middleware/exceptions.py", line 64, in __call__
    await self.app(scope, receive, sender)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
    raise e
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/routing.py", line 680, in __call__
    await route.handle(scope, receive, send)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/routing.py", line 427, in handle
    await self.app(scope, receive, send)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/applications.py", line 124, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/middleware/exceptions.py", line 75, in __call__
    raise exc
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/middleware/exceptions.py", line 64, in __call__
    await self.app(scope, receive, sender)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/routing.py", line 680, in __call__
    await route.handle(scope, receive, send)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/routing.py", line 275, in handle
    await self.app(scope, receive, send)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/routing.py", line 68, in app
    await response(scope, receive, send)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/responses.py", line 273, in __call__
    await wrap(partial(self.listen_for_disconnect, receive))
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/anyio/_backends/_asyncio.py", line 662, in __aexit__
    raise exceptions[0]
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/anyio/_backends/_asyncio.py", line 702, in _run_wrapped_task
    await coro
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/responses.py", line 269, in wrap
    await func()
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/responses.py", line 258, in stream_response
    async for chunk in self.body_iterator:
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/concurrency.py", line 63, in iterate_in_threadpool
    yield await anyio.to_thread.run_sync(_next, iterator)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/anyio/to_thread.py", line 32, in run_sync
    func, *args, cancellable=cancellable, limiter=limiter
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/anyio/_backends/_asyncio.py", line 937, in run_sync_in_worker_thread
    return await future
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/anyio/_backends/_asyncio.py", line 867, in run
    result = context.run(func, *args)
  File "/Users/enea/lean_mail/backend/venv/lib/python3.7/site-packages/starlette/concurrency.py", line 53, in _next
    return next(iterator)
TypeError: 'GridOut' object is not an iterator

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.