* feat(mxroute_relay): add mxroute_relay app with initial files and models
* feat(mxroute_relay): add server routes and database initialization * feat(mxroute_relay): add admin views for User, Post, Comment, and Dump models * feat(mxroute_relay): add custom ModelView class with pagination and export options * feat(mxroute_relay): add PostView with fields and exclusions * feat(mxroute_relay): add CommentView with exclusions and search/sort options * feat(mxroute_relay): add DumpView with fields and exclusions
This commit is contained in:
parent
ed16c81304
commit
7c45b422e6
3 changed files with 182 additions and 0 deletions
45
mxroute_relay/app.py
Normal file
45
mxroute_relay/app.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlmodel import SQLModel
|
||||||
|
from starlette.applications import Starlette
|
||||||
|
from starlette.responses import HTMLResponse
|
||||||
|
from starlette.routing import Route
|
||||||
|
from starlette_admin.contrib.sqlmodel import Admin
|
||||||
|
from starlette_admin.contrib.sqlmodel import ModelView
|
||||||
|
|
||||||
|
from .config import ENGINE_URI
|
||||||
|
from .models import Comment
|
||||||
|
from .models import Dump
|
||||||
|
from .models import Post
|
||||||
|
from .models import User
|
||||||
|
from .views import CommentView
|
||||||
|
from .views import DumpView
|
||||||
|
from .views import PostView
|
||||||
|
|
||||||
|
engine = create_engine(ENGINE_URI, connect_args={"check_same_thread": False}, echo=True)
|
||||||
|
|
||||||
|
|
||||||
|
def init_database() -> None:
|
||||||
|
SQLModel.metadata.create_all(engine)
|
||||||
|
|
||||||
|
|
||||||
|
app = Starlette(
|
||||||
|
routes=[
|
||||||
|
Route(
|
||||||
|
"/",
|
||||||
|
lambda r: HTMLResponse('<a href="/admin/">Click me to get to Admin!</a>'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
on_startup=[init_database],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create admin
|
||||||
|
admin = Admin(engine, title="Example: SQLModel")
|
||||||
|
|
||||||
|
# Add views
|
||||||
|
admin.add_view(ModelView(User, icon="fa fa-users"))
|
||||||
|
admin.add_view(PostView(Post, label="Blog Posts", icon="fa fa-blog"))
|
||||||
|
admin.add_view(CommentView(Comment, icon="fa fa-comments"))
|
||||||
|
admin.add_view(DumpView(Dump, icon="fa fa-dumpster"))
|
||||||
|
|
||||||
|
# Mount to admin to app
|
||||||
|
admin.mount_to(app)
|
75
mxroute_relay/models.py
Normal file
75
mxroute_relay/models.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import enum
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import AnyHttpUrl
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from pydantic import EmailStr
|
||||||
|
from pydantic import Field as PydField
|
||||||
|
from pydantic.color import Color
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlalchemy import DateTime
|
||||||
|
from sqlalchemy import Enum
|
||||||
|
from sqlalchemy import JSON
|
||||||
|
from sqlalchemy import String
|
||||||
|
from sqlalchemy import Text
|
||||||
|
from sqlmodel import Field
|
||||||
|
from sqlmodel import Relationship
|
||||||
|
from sqlmodel import SQLModel
|
||||||
|
|
||||||
|
|
||||||
|
class Gender(str, enum.Enum):
|
||||||
|
MALE = "male"
|
||||||
|
FEMALE = "female"
|
||||||
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
class User(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(primary_key=True)
|
||||||
|
full_name: str = Field(min_length=3, index=True)
|
||||||
|
sex: Optional[str] = Field(sa_column=Column(Enum(Gender)), default=Gender.UNKNOWN, index=True)
|
||||||
|
|
||||||
|
posts: List["Post"] = Relationship(back_populates="publisher")
|
||||||
|
comments: List["Comment"] = Relationship(back_populates="user")
|
||||||
|
|
||||||
|
|
||||||
|
class Post(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(primary_key=True)
|
||||||
|
title: str = Field(min_length=3)
|
||||||
|
content: str = Field(sa_column=Column(Text))
|
||||||
|
tags: List[str] = Field(sa_column=Column(JSON), min_items=1)
|
||||||
|
published_at: Optional[datetime] = Field(sa_column=Column(DateTime(timezone=True), default=datetime.utcnow))
|
||||||
|
|
||||||
|
publisher_id: Optional[int] = Field(foreign_key="user.id")
|
||||||
|
publisher: User = Relationship(back_populates="posts")
|
||||||
|
|
||||||
|
comments: List["Comment"] = Relationship(back_populates="post")
|
||||||
|
|
||||||
|
|
||||||
|
class Comment(SQLModel, table=True):
|
||||||
|
pk: Optional[int] = Field(primary_key=True)
|
||||||
|
content: str = Field(sa_column=Column(Text), min_length=5)
|
||||||
|
created_at: Optional[datetime] = Field(sa_column=Column(DateTime(timezone=True), default=datetime.utcnow))
|
||||||
|
|
||||||
|
post_id: Optional[int] = Field(foreign_key="post.id")
|
||||||
|
post: Post = Relationship(back_populates="comments")
|
||||||
|
|
||||||
|
user_id: Optional[int] = Field(foreign_key="user.id")
|
||||||
|
user: User = Relationship(back_populates="comments")
|
||||||
|
|
||||||
|
|
||||||
|
class Config(BaseModel):
|
||||||
|
key: str = PydField(min_length=3)
|
||||||
|
value: int = PydField(multiple_of=5)
|
||||||
|
|
||||||
|
|
||||||
|
class Dump(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(primary_key=True)
|
||||||
|
email: EmailStr = Field(index=True)
|
||||||
|
color: Color = Field(sa_column=Column(String(10)))
|
||||||
|
url: AnyHttpUrl
|
||||||
|
json_field: Dict[str, Any] = Field(sa_column=Column(JSON))
|
||||||
|
configs: List[Config] = Field(sa_column=Column(JSON))
|
62
mxroute_relay/views.py
Normal file
62
mxroute_relay/views.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
from starlette_admin import CollectionField
|
||||||
|
from starlette_admin import ColorField
|
||||||
|
from starlette_admin import EmailField
|
||||||
|
from starlette_admin import ExportType
|
||||||
|
from starlette_admin import IntegerField
|
||||||
|
from starlette_admin import JSONField
|
||||||
|
from starlette_admin import ListField
|
||||||
|
from starlette_admin import StringField
|
||||||
|
from starlette_admin import URLField
|
||||||
|
from starlette_admin.contrib.sqlmodel import ModelView
|
||||||
|
|
||||||
|
from .models import Comment
|
||||||
|
from .models import Post
|
||||||
|
|
||||||
|
|
||||||
|
class MyModelView(ModelView):
|
||||||
|
page_size = 5
|
||||||
|
page_size_options = [5, 10, 25 - 1]
|
||||||
|
export_types = [ExportType.EXCEL, ExportType.CSV]
|
||||||
|
|
||||||
|
|
||||||
|
class PostView(MyModelView):
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"title",
|
||||||
|
"content",
|
||||||
|
ListField(StringField("tags")),
|
||||||
|
"published_at",
|
||||||
|
"publisher",
|
||||||
|
"comments",
|
||||||
|
]
|
||||||
|
exclude_fields_from_list = [Post.content]
|
||||||
|
exclude_fields_from_create = [Post.published_at]
|
||||||
|
exclude_fields_from_edit = ["published_at"]
|
||||||
|
|
||||||
|
|
||||||
|
class CommentView(MyModelView):
|
||||||
|
exclude_fields_from_create = ["created_at"]
|
||||||
|
exclude_fields_from_edit = ["created_at"]
|
||||||
|
searchable_fields = [Comment.content, Comment.created_at]
|
||||||
|
sortable_fields = [Comment.pk, Comment.content, Comment.created_at]
|
||||||
|
|
||||||
|
|
||||||
|
class DumpView(MyModelView):
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
EmailField("email"),
|
||||||
|
URLField("url", required=True),
|
||||||
|
ColorField("color"),
|
||||||
|
JSONField("json_field"),
|
||||||
|
ListField(
|
||||||
|
CollectionField(
|
||||||
|
"configs",
|
||||||
|
fields=[
|
||||||
|
StringField("key"),
|
||||||
|
IntegerField("value", help_text="multiple of 5"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
exclude_fields_from_list = ("configs",)
|
||||||
|
searchable_fields = ("email", "url")
|
Loading…
Reference in a new issue