Skip to content

Commit 8a2c566

Browse files
authored
FastAPI + PonyORM example (#59)
1 parent 5b04ebc commit 8a2c566

9 files changed

Lines changed: 1278 additions & 2 deletions

File tree

examples/fastapi_ponyorm/Makefile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
all: install run
2+
3+
.PHONY: fastapi
4+
run:
5+
poetry run fastapi dev --reload --port=8090 example.py
6+
7+
.PHONY: install
8+
install:
9+
poetry install

examples/fastapi_ponyorm/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## FastAPI + Pony ORM Example
2+
3+
- Uses in-memory SQLite 3 instance
4+
- Creates "admin/admin" superuser
5+
6+
```bash
7+
make install # Creates virtualenv with Poetry
8+
make run # Runs fastapi dev
9+
```
10+
11+
So open `http://127.0.0.1:8090/admin/` and have a fun!
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import os
2+
import sys
3+
from pathlib import Path
4+
5+
os.environ["ADMIN_USER_MODEL"] = "User"
6+
os.environ["ADMIN_USER_MODEL_USERNAME_FIELD"] = "username"
7+
os.environ["ADMIN_SECRET_KEY"] = "secret"
8+
9+
sys.path.append(str(Path(__file__).resolve().parent))
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from contextlib import asynccontextmanager
2+
3+
from fastapi import FastAPI
4+
from fastapi.middleware.cors import CORSMiddleware
5+
from models import db, BaseEvent, Event, Tournament, User
6+
from pony.orm import db_session
7+
8+
from fastadmin import PonyORMInlineModelAdmin, PonyORMModelAdmin, action, display
9+
from fastadmin import fastapi_app as admin_app
10+
from fastadmin import register
11+
12+
13+
@register(User)
14+
class UserModelAdmin(PonyORMModelAdmin):
15+
exclude = ("password",)
16+
list_display = ("id", "username", "is_superuser")
17+
list_display_links = ("id", "username")
18+
list_filter = ("id", "username", "is_superuser")
19+
search_fields = ("username",)
20+
21+
async def authenticate(self, username, password):
22+
with db_session:
23+
obj = next((f for f in User.select(username=username, password=password, is_superuser=True)), None) # fmt: skip
24+
if not obj:
25+
return None
26+
return obj.id
27+
28+
async def change_password(self, user_id, password):
29+
user = await self.model_cls.filter(id=user_id).first()
30+
if not user:
31+
return
32+
# direct saving password is only for tests - use hash
33+
user.password = password
34+
await user.save()
35+
36+
37+
class EventInlineModelAdmin(PonyORMInlineModelAdmin):
38+
model = Event
39+
40+
41+
@register(Tournament)
42+
class TournamentModelAdmin(PonyORMModelAdmin):
43+
list_display = ("id", "name")
44+
inlines = (EventInlineModelAdmin,)
45+
46+
47+
@register(BaseEvent)
48+
class BaseEventModelAdmin(PonyORMModelAdmin):
49+
pass
50+
51+
52+
@register(Event)
53+
class EventModelAdmin(PonyORMModelAdmin):
54+
actions = ("make_is_active", "make_is_not_active")
55+
list_display = (
56+
"id",
57+
"name_with_price",
58+
"rating",
59+
"event_type",
60+
"is_active",
61+
"started",
62+
)
63+
64+
@action(description="Make user active")
65+
async def make_is_active(self, ids):
66+
await self.model_cls.filter(id__in=ids).update(is_active=True)
67+
68+
@action
69+
async def make_is_not_active(self, ids):
70+
await self.model_cls.filter(id__in=ids).update(is_active=False)
71+
72+
@display
73+
async def started(self, obj):
74+
return bool(obj.start_time)
75+
76+
@display()
77+
async def name_with_price(self, obj):
78+
return f"{obj.name} - {obj.price}"
79+
80+
81+
def init_db():
82+
db.bind(provider="sqlite", filename=":sharedmemory:")
83+
db.generate_mapping(create_tables=True)
84+
pass
85+
86+
87+
@db_session
88+
def create_superuser():
89+
User(username="admin", password="admin", is_superuser=True)
90+
91+
92+
@asynccontextmanager
93+
async def lifespan(app: FastAPI):
94+
init_db()
95+
create_superuser()
96+
yield
97+
98+
99+
app = FastAPI(lifespan=lifespan)
100+
101+
app.mount("/admin", admin_app)
102+
103+
app.add_middleware(
104+
CORSMiddleware,
105+
allow_origins=["*"],
106+
allow_credentials=True,
107+
allow_methods=["*"],
108+
allow_headers=["*"],
109+
)

examples/fastapi_ponyorm/models.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from datetime import date, datetime, time
2+
from decimal import Decimal
3+
from enum import Enum
4+
5+
from pony.orm import *
6+
7+
8+
db = Database()
9+
10+
11+
class EventTypeEnum(str, Enum):
12+
PRIVATE = "PRIVATE"
13+
PUBLIC = "PUBLIC"
14+
15+
16+
class BaseModel:
17+
# id = PrimaryKey(int, auto=True)
18+
created_at = Required(datetime, default=datetime.utcnow)
19+
updated_at = Required(datetime, default=datetime.utcnow)
20+
21+
def before_update(self):
22+
self.updated_at = datetime.utcnow()
23+
24+
@classmethod
25+
def get_model_name(cls):
26+
return f"ponyorm.{cls.__name__}"
27+
28+
29+
class User(db.Entity, BaseModel):
30+
_table_ = "user"
31+
32+
username = Required(str, max_len=255)
33+
password = Required(str, max_len=255)
34+
is_superuser = Required(bool, default=False)
35+
36+
events = Set("Event", table="event_participants", column="event_id")
37+
38+
def __str__(self):
39+
return self.username
40+
41+
42+
class Tournament(db.Entity, BaseModel):
43+
_table_ = "tournament"
44+
45+
name = Required(str, max_len=255)
46+
47+
events = Set("Event")
48+
49+
def __str__(self):
50+
return self.name
51+
52+
53+
class BaseEvent(db.Entity, BaseModel):
54+
_table_ = "base_event"
55+
56+
id = PrimaryKey(int, auto=True)
57+
name = Required(str, max_len=255)
58+
59+
event = Optional("Event")
60+
61+
def __str__(self):
62+
return self.name
63+
64+
65+
class Event(db.Entity, BaseModel):
66+
_table_ = "event"
67+
68+
base = Optional(BaseEvent, column="base_id")
69+
name = Required(str)
70+
71+
tournament = Required(Tournament, column="tournament_id")
72+
participants = Set(User, table="event_participants", column="user_id")
73+
74+
rating = Required(int, default=0)
75+
description = Optional(LongStr)
76+
event_type = Required(EventTypeEnum, default=EventTypeEnum.PUBLIC)
77+
is_active = Required(bool, default=True)
78+
start_time = Optional(time)
79+
date = Optional(date)
80+
latitude = Optional(float)
81+
longitude = Optional(float)
82+
price = Optional(Decimal)
83+
84+
json = Optional(Json)
85+
86+
def __str__(self):
87+
return self.name

0 commit comments

Comments
 (0)