fastapi-lite-admin 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_lite_admin-0.1.0/PKG-INFO +41 -0
- fastapi_lite_admin-0.1.0/README.md +24 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/__init__.py +3 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/core/config.py +14 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/core/crud.py +115 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/core/registry.py +39 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/core/schema.py +27 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/dependencies/db.py +7 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/integrations/sqlalchemy.py +22 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/main.py +103 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/routers/admin.py +75 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/ui/templates/dashboard.html +236 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/ui/templates/layout.html +329 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/ui/templates/model_detail.html +170 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/ui/templates/model_form.html +359 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/ui/templates/model_list.html +440 -0
- fastapi_lite_admin-0.1.0/fastapi_admin_lite/ui/views.py +154 -0
- fastapi_lite_admin-0.1.0/fastapi_lite_admin.egg-info/PKG-INFO +41 -0
- fastapi_lite_admin-0.1.0/fastapi_lite_admin.egg-info/SOURCES.txt +22 -0
- fastapi_lite_admin-0.1.0/fastapi_lite_admin.egg-info/dependency_links.txt +1 -0
- fastapi_lite_admin-0.1.0/fastapi_lite_admin.egg-info/requires.txt +10 -0
- fastapi_lite_admin-0.1.0/fastapi_lite_admin.egg-info/top_level.txt +1 -0
- fastapi_lite_admin-0.1.0/pyproject.toml +32 -0
- fastapi_lite_admin-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-lite-admin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight, pluggable admin panel for FastAPI
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: fastapi>=0.100.0
|
|
9
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0.0
|
|
11
|
+
Requires-Dist: jinja2>=3.1.0
|
|
12
|
+
Requires-Dist: python-multipart>=0.0.6
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest; extra == "dev"
|
|
15
|
+
Requires-Dist: httpx; extra == "dev"
|
|
16
|
+
Requires-Dist: uvicorn; extra == "dev"
|
|
17
|
+
|
|
18
|
+
# FastAPI Lite Admin
|
|
19
|
+
|
|
20
|
+
A premium, lightweight, pluggable admin panel for FastAPI and SQLAlchemy.
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Zero-config CRUD**: Automatically generate admin interfaces for your models.
|
|
25
|
+
- **ORM Agnostic**: Initial support for SQLAlchemy, designed to support others.
|
|
26
|
+
- **API First**: All admin actions are available via a REST API.
|
|
27
|
+
- **Lightweight UI**: Simple Jinja2 templates for the dashboard and forms.
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
(Documentation coming soon)
|
|
32
|
+
|
|
33
|
+
## Development
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Install dependencies
|
|
37
|
+
pip install -e ".[dev]"
|
|
38
|
+
|
|
39
|
+
# Run example
|
|
40
|
+
python -m example.main
|
|
41
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# FastAPI Lite Admin
|
|
2
|
+
|
|
3
|
+
A premium, lightweight, pluggable admin panel for FastAPI and SQLAlchemy.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero-config CRUD**: Automatically generate admin interfaces for your models.
|
|
8
|
+
- **ORM Agnostic**: Initial support for SQLAlchemy, designed to support others.
|
|
9
|
+
- **API First**: All admin actions are available via a REST API.
|
|
10
|
+
- **Lightweight UI**: Simple Jinja2 templates for the dashboard and forms.
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
(Documentation coming soon)
|
|
15
|
+
|
|
16
|
+
## Development
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Install dependencies
|
|
20
|
+
pip install -e ".[dev]"
|
|
21
|
+
|
|
22
|
+
# Run example
|
|
23
|
+
python -m example.main
|
|
24
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
class ModelConfig(BaseModel):
|
|
5
|
+
name: str
|
|
6
|
+
display_name: Optional[str] = None
|
|
7
|
+
fields: List[str] = Field(default_factory=list)
|
|
8
|
+
readonly_fields: List[str] = Field(default_factory=list)
|
|
9
|
+
searchable_fields: List[str] = Field(default_factory=list)
|
|
10
|
+
hidden_fields: List[str] = Field(default_factory=list)
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def label(self) -> str:
|
|
14
|
+
return self.display_name or self.name.capitalize()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from typing import Any, List, Optional, Type
|
|
2
|
+
from sqlalchemy.orm import Session
|
|
3
|
+
from sqlalchemy import Boolean, Integer, Float
|
|
4
|
+
|
|
5
|
+
class CRUDEngine:
|
|
6
|
+
def __init__(self, model: Type[Any]):
|
|
7
|
+
self.model = model
|
|
8
|
+
|
|
9
|
+
def _prepare_data(self, data: dict) -> dict:
|
|
10
|
+
"""
|
|
11
|
+
Cast string values from form data to correct types based on SQLAlchemy model.
|
|
12
|
+
"""
|
|
13
|
+
prepared = {}
|
|
14
|
+
columns = self.model.__table__.columns
|
|
15
|
+
|
|
16
|
+
for key, value in data.items():
|
|
17
|
+
if key not in columns:
|
|
18
|
+
continue
|
|
19
|
+
|
|
20
|
+
col = columns[key]
|
|
21
|
+
|
|
22
|
+
if isinstance(col.type, Boolean):
|
|
23
|
+
if isinstance(value, str):
|
|
24
|
+
prepared[key] = value.lower() in ('true', '1', 'yes', 'on')
|
|
25
|
+
else:
|
|
26
|
+
prepared[key] = bool(value)
|
|
27
|
+
elif isinstance(col.type, Integer):
|
|
28
|
+
try:
|
|
29
|
+
prepared[key] = int(value)
|
|
30
|
+
except (ValueError, TypeError):
|
|
31
|
+
prepared[key] = value
|
|
32
|
+
elif isinstance(col.type, Float):
|
|
33
|
+
try:
|
|
34
|
+
prepared[key] = float(value)
|
|
35
|
+
except (ValueError, TypeError):
|
|
36
|
+
prepared[key] = value
|
|
37
|
+
else:
|
|
38
|
+
prepared[key] = value
|
|
39
|
+
|
|
40
|
+
return prepared
|
|
41
|
+
|
|
42
|
+
def count(self, db: Session, search: Optional[str] = None) -> int:
|
|
43
|
+
query = db.query(self.model)
|
|
44
|
+
# TODO: Implement search filtering in count
|
|
45
|
+
return query.count()
|
|
46
|
+
|
|
47
|
+
def list(
|
|
48
|
+
self,
|
|
49
|
+
db: Session,
|
|
50
|
+
skip: int = 0,
|
|
51
|
+
limit: int = 100,
|
|
52
|
+
search: Optional[str] = None,
|
|
53
|
+
order_by: Optional[str] = None,
|
|
54
|
+
order_dir: str = "asc"
|
|
55
|
+
) -> List[Any]:
|
|
56
|
+
query = db.query(self.model)
|
|
57
|
+
|
|
58
|
+
# Apply Search
|
|
59
|
+
if search:
|
|
60
|
+
# TODO: Implement generic search logic
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
# Apply Sorting
|
|
64
|
+
if order_by and hasattr(self.model, order_by):
|
|
65
|
+
column = getattr(self.model, order_by)
|
|
66
|
+
if order_dir.lower() == "desc":
|
|
67
|
+
query = query.order_by(column.desc())
|
|
68
|
+
else:
|
|
69
|
+
query = query.order_by(column.asc())
|
|
70
|
+
|
|
71
|
+
return query.offset(skip).limit(limit).all()
|
|
72
|
+
|
|
73
|
+
def _cast_id(self, id: Any) -> Any:
|
|
74
|
+
"""
|
|
75
|
+
Cast ID to the correct type based on the model's primary key.
|
|
76
|
+
"""
|
|
77
|
+
col = self.model.__table__.primary_key.columns[0]
|
|
78
|
+
if isinstance(col.type, Integer):
|
|
79
|
+
try:
|
|
80
|
+
return int(id)
|
|
81
|
+
except (ValueError, TypeError):
|
|
82
|
+
return id
|
|
83
|
+
return id
|
|
84
|
+
|
|
85
|
+
def get(self, db: Session, id: Any) -> Optional[Any]:
|
|
86
|
+
casted_id = self._cast_id(id)
|
|
87
|
+
return db.query(self.model).filter(self.model.id == casted_id).first()
|
|
88
|
+
|
|
89
|
+
def create(self, db: Session, data: dict) -> Any:
|
|
90
|
+
prepared_data = self._prepare_data(data)
|
|
91
|
+
obj = self.model(**prepared_data)
|
|
92
|
+
db.add(obj)
|
|
93
|
+
db.commit()
|
|
94
|
+
db.refresh(obj)
|
|
95
|
+
return obj
|
|
96
|
+
|
|
97
|
+
def update(self, db: Session, id: Any, data: dict) -> Optional[Any]:
|
|
98
|
+
obj = self.get(db, id)
|
|
99
|
+
if not obj:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
prepared_data = self._prepare_data(data)
|
|
103
|
+
for key, value in prepared_data.items():
|
|
104
|
+
setattr(obj, key, value)
|
|
105
|
+
db.commit()
|
|
106
|
+
db.refresh(obj)
|
|
107
|
+
return obj
|
|
108
|
+
|
|
109
|
+
def delete(self, db: Session, id: Any) -> bool:
|
|
110
|
+
obj = self.get(db, id)
|
|
111
|
+
if not obj:
|
|
112
|
+
return False
|
|
113
|
+
db.delete(obj)
|
|
114
|
+
db.commit()
|
|
115
|
+
return True
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Any, Dict, Type, Callable, Optional
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class ModelRegistration:
|
|
6
|
+
model: Type[Any]
|
|
7
|
+
get_db: Callable
|
|
8
|
+
config: Dict[str, Any] = field(default_factory=dict)
|
|
9
|
+
name: str = ""
|
|
10
|
+
|
|
11
|
+
class Registry:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self._models: Dict[str, ModelRegistration] = {}
|
|
14
|
+
|
|
15
|
+
def register(
|
|
16
|
+
self,
|
|
17
|
+
model: Type[Any],
|
|
18
|
+
get_db: Callable,
|
|
19
|
+
config: Optional[Dict[str, Any]] = None
|
|
20
|
+
):
|
|
21
|
+
config = config or {}
|
|
22
|
+
name = config.get("name") or model.__name__.lower()
|
|
23
|
+
|
|
24
|
+
if name in self._models:
|
|
25
|
+
raise ValueError(f"Model with name '{name}' already registered.")
|
|
26
|
+
|
|
27
|
+
registration = ModelRegistration(
|
|
28
|
+
model=model,
|
|
29
|
+
get_db=get_db,
|
|
30
|
+
config=config,
|
|
31
|
+
name=name
|
|
32
|
+
)
|
|
33
|
+
self._models[name] = registration
|
|
34
|
+
|
|
35
|
+
def get_models(self) -> Dict[str, ModelRegistration]:
|
|
36
|
+
return self._models
|
|
37
|
+
|
|
38
|
+
def get_model(self, name: str) -> Optional[ModelRegistration]:
|
|
39
|
+
return self._models.get(name)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
from .registry import Registry
|
|
3
|
+
from ..integrations.sqlalchemy import introspect_sqlalchemy_model
|
|
4
|
+
|
|
5
|
+
def generate_admin_schema(registry: Registry) -> Dict[str, Any]:
|
|
6
|
+
"""
|
|
7
|
+
Generate a JSON schema of all registered models for the UI.
|
|
8
|
+
"""
|
|
9
|
+
models_info = []
|
|
10
|
+
for name, reg in registry.get_models().items():
|
|
11
|
+
introspection = introspect_sqlalchemy_model(reg.model)
|
|
12
|
+
|
|
13
|
+
clean_config = reg.config.copy()
|
|
14
|
+
if "attention_filter" in clean_config:
|
|
15
|
+
del clean_config["attention_filter"]
|
|
16
|
+
|
|
17
|
+
models_info.append({
|
|
18
|
+
"name": name,
|
|
19
|
+
"display_name": reg.config.get("display_name") or name.capitalize(),
|
|
20
|
+
"fields": introspection["fields"],
|
|
21
|
+
"list_display": reg.config.get("list_display"),
|
|
22
|
+
"config": clean_config
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
"models": models_info
|
|
27
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Common database dependencies
|
|
2
|
+
from typing import Generator
|
|
3
|
+
from sqlalchemy.orm import Session
|
|
4
|
+
|
|
5
|
+
# This can be used as a default if none is provided, but usually the user provides their own get_db
|
|
6
|
+
def get_db_placeholder() -> Generator[Session, None, None]:
|
|
7
|
+
yield None
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Type
|
|
2
|
+
from sqlalchemy import inspect
|
|
3
|
+
from sqlalchemy.orm import DeclarativeMeta
|
|
4
|
+
|
|
5
|
+
def introspect_sqlalchemy_model(model: Type[Any]) -> Dict[str, Any]:
|
|
6
|
+
"""
|
|
7
|
+
Introspect a SQLAlchemy model to extract field information.
|
|
8
|
+
"""
|
|
9
|
+
mapper = inspect(model)
|
|
10
|
+
fields = []
|
|
11
|
+
for column in mapper.columns:
|
|
12
|
+
fields.append({
|
|
13
|
+
"name": column.key,
|
|
14
|
+
"type": str(column.type),
|
|
15
|
+
"primary_key": column.primary_key,
|
|
16
|
+
"nullable": column.nullable,
|
|
17
|
+
"default": str(column.default) if column.default else None,
|
|
18
|
+
"required": not column.nullable and column.default is None and not column.primary_key
|
|
19
|
+
})
|
|
20
|
+
return {
|
|
21
|
+
"fields": fields
|
|
22
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Type, Callable
|
|
2
|
+
from fastapi import FastAPI, APIRouter, Depends
|
|
3
|
+
from .core.registry import Registry
|
|
4
|
+
from .routers.admin import create_admin_router
|
|
5
|
+
from .ui.views import create_ui_router
|
|
6
|
+
|
|
7
|
+
class Admin:
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
title: str = "FastAPI Admin Lite",
|
|
11
|
+
base_url: str = "/admin",
|
|
12
|
+
enable_ui: bool = True,
|
|
13
|
+
dependencies: Optional[List[Any]] = None,
|
|
14
|
+
auth_dependency: Optional[Callable] = None,
|
|
15
|
+
permission_checker: Optional[Callable] = None,
|
|
16
|
+
dashboard_template: Optional[str] = None,
|
|
17
|
+
dashboard_models: Optional[List[str]] = None,
|
|
18
|
+
get_logs: Optional[Callable] = None,
|
|
19
|
+
logs_config: Optional[Dict[str, Any]] = None
|
|
20
|
+
):
|
|
21
|
+
self.title = title
|
|
22
|
+
self.base_url = base_url
|
|
23
|
+
self.enable_ui = enable_ui
|
|
24
|
+
self.dependencies = dependencies or []
|
|
25
|
+
self.registry = Registry()
|
|
26
|
+
self.dashboard_template = dashboard_template
|
|
27
|
+
self.dashboard_models = dashboard_models
|
|
28
|
+
self.get_logs = get_logs
|
|
29
|
+
self.logs_config = logs_config or {
|
|
30
|
+
"columns": ["level", "timestamp", "message"],
|
|
31
|
+
"title": "System Activity"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Auth & Permissions
|
|
35
|
+
self.auth_dependency = auth_dependency
|
|
36
|
+
self.permission_checker = permission_checker or self.default_permission
|
|
37
|
+
|
|
38
|
+
# Print warnings if not configured
|
|
39
|
+
if not auth_dependency:
|
|
40
|
+
print("\033[93m[WARNING] FastAPI Admin Lite: No auth_dependency provided. Admin panel is publicly accessible!\033[0m")
|
|
41
|
+
if not permission_checker:
|
|
42
|
+
print("\033[93m[WARNING] FastAPI Admin Lite: No permission_checker provided. Using default (allow all).\033[0m")
|
|
43
|
+
|
|
44
|
+
async def default_permission(self, user: Any = None) -> bool:
|
|
45
|
+
"""Default permission checker that allows everything."""
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
def register(
|
|
49
|
+
self,
|
|
50
|
+
model: Type[Any],
|
|
51
|
+
get_db: Callable,
|
|
52
|
+
list_display: Optional[List[str]] = None,
|
|
53
|
+
date_field: Optional[str] = None,
|
|
54
|
+
attention_filter: Optional[Any] = None,
|
|
55
|
+
readonly_fields: Optional[List[str]] = None,
|
|
56
|
+
config: Optional[Dict[str, Any]] = None
|
|
57
|
+
):
|
|
58
|
+
"""
|
|
59
|
+
Register a model with the admin panel.
|
|
60
|
+
"""
|
|
61
|
+
config = config or {}
|
|
62
|
+
if list_display:
|
|
63
|
+
# Only include fields that exist in the model
|
|
64
|
+
valid_columns = {c.key for c in model.__table__.columns}
|
|
65
|
+
sanitized_display = [f for f in list_display if f in valid_columns]
|
|
66
|
+
config["list_display"] = sanitized_display
|
|
67
|
+
|
|
68
|
+
if date_field:
|
|
69
|
+
config["date_field"] = date_field
|
|
70
|
+
|
|
71
|
+
if attention_filter is not None:
|
|
72
|
+
config["attention_filter"] = attention_filter
|
|
73
|
+
|
|
74
|
+
if readonly_fields:
|
|
75
|
+
config["readonly_fields"] = readonly_fields
|
|
76
|
+
|
|
77
|
+
self.registry.register(model, get_db, config)
|
|
78
|
+
|
|
79
|
+
def mount(self, app: FastAPI):
|
|
80
|
+
"""
|
|
81
|
+
Mount the admin router to the FastAPI application.
|
|
82
|
+
"""
|
|
83
|
+
# Collect all dependencies
|
|
84
|
+
all_deps = list(self.dependencies)
|
|
85
|
+
if self.auth_dependency:
|
|
86
|
+
all_deps.append(Depends(self.auth_dependency))
|
|
87
|
+
|
|
88
|
+
router = create_admin_router(self)
|
|
89
|
+
app.include_router(
|
|
90
|
+
router,
|
|
91
|
+
prefix=self.base_url + "/api",
|
|
92
|
+
tags=["Admin"],
|
|
93
|
+
dependencies=all_deps
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if self.enable_ui:
|
|
97
|
+
ui_router = create_ui_router(self)
|
|
98
|
+
app.include_router(
|
|
99
|
+
ui_router,
|
|
100
|
+
prefix=self.base_url,
|
|
101
|
+
include_in_schema=False,
|
|
102
|
+
dependencies=all_deps
|
|
103
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
3
|
+
from sqlalchemy.orm import Session
|
|
4
|
+
from ..core.schema import generate_admin_schema
|
|
5
|
+
from ..core.crud import CRUDEngine
|
|
6
|
+
|
|
7
|
+
def create_admin_router(admin: Any) -> APIRouter:
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
|
|
10
|
+
@router.get("/schema")
|
|
11
|
+
async def get_schema():
|
|
12
|
+
return generate_admin_schema(admin.registry)
|
|
13
|
+
|
|
14
|
+
@router.get("/models")
|
|
15
|
+
async def list_models():
|
|
16
|
+
return list(admin.registry.get_models().keys())
|
|
17
|
+
|
|
18
|
+
# Dynamically generate routes for each registered model
|
|
19
|
+
for name, registration in admin.registry.get_models().items():
|
|
20
|
+
crud = CRUDEngine(registration.model)
|
|
21
|
+
get_db_dep = registration.get_db
|
|
22
|
+
|
|
23
|
+
def create_routes(model_name: str, crud_engine: CRUDEngine, db_dep: Any, reg: Any):
|
|
24
|
+
@router.get(f"/{model_name}", name=f"admin_list_{model_name}")
|
|
25
|
+
async def list_records(
|
|
26
|
+
skip: int = 0,
|
|
27
|
+
limit: int = 100,
|
|
28
|
+
search: str = None,
|
|
29
|
+
order_by: str = None,
|
|
30
|
+
order_dir: str = "asc",
|
|
31
|
+
db: Session = Depends(db_dep)
|
|
32
|
+
):
|
|
33
|
+
records = crud_engine.list(
|
|
34
|
+
db, skip=skip, limit=limit, search=search,
|
|
35
|
+
order_by=order_by, order_dir=order_dir
|
|
36
|
+
)
|
|
37
|
+
total = crud_engine.count(db, search=search)
|
|
38
|
+
return {"data": records, "total": total}
|
|
39
|
+
|
|
40
|
+
@router.get(f"/{model_name}/{{id}}", name=f"admin_get_{model_name}")
|
|
41
|
+
async def get_record(id: Any, db: Session = Depends(db_dep)):
|
|
42
|
+
record = crud_engine.get(db, id)
|
|
43
|
+
if not record:
|
|
44
|
+
raise HTTPException(status_code=404, detail="Record not found")
|
|
45
|
+
return record
|
|
46
|
+
|
|
47
|
+
@router.post(f"/{model_name}", name=f"admin_create_{model_name}")
|
|
48
|
+
async def create_record(data: Dict[str, Any], db: Session = Depends(db_dep)):
|
|
49
|
+
readonly = reg.config.get("readonly_fields", [])
|
|
50
|
+
for field in readonly:
|
|
51
|
+
if field in data:
|
|
52
|
+
del data[field]
|
|
53
|
+
return crud_engine.create(db, data)
|
|
54
|
+
|
|
55
|
+
@router.put(f"/{model_name}/{{id}}", name=f"admin_update_{model_name}")
|
|
56
|
+
async def update_record(id: Any, data: Dict[str, Any], db: Session = Depends(db_dep)):
|
|
57
|
+
readonly = reg.config.get("readonly_fields", [])
|
|
58
|
+
for field in readonly:
|
|
59
|
+
if field in data:
|
|
60
|
+
del data[field]
|
|
61
|
+
record = crud_engine.update(db, id, data)
|
|
62
|
+
if not record:
|
|
63
|
+
raise HTTPException(status_code=404, detail="Record not found")
|
|
64
|
+
return record
|
|
65
|
+
|
|
66
|
+
@router.delete(f"/{model_name}/{{id}}", name=f"admin_delete_{model_name}")
|
|
67
|
+
async def delete_record(id: Any, db: Session = Depends(db_dep)):
|
|
68
|
+
success = crud_engine.delete(db, id)
|
|
69
|
+
if not success:
|
|
70
|
+
raise HTTPException(status_code=404, detail="Record not found")
|
|
71
|
+
return {"success": True}
|
|
72
|
+
|
|
73
|
+
create_routes(name, crud, get_db_dep, registration)
|
|
74
|
+
|
|
75
|
+
return router
|