* 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:
hackish 2023-09-13 13:17:51 -07:00
parent ed16c81304
commit 7c45b422e6
3 changed files with 182 additions and 0 deletions

45
mxroute_relay/app.py Normal file
View 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
View 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
View 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")