mirror of
https://github.com/fastapi-admin/fastapi-admin.git
synced 2026-03-13 10:32:25 +08:00
add adminlog
This commit is contained in:
@@ -8,6 +8,7 @@ ChangeLog
|
||||
0.3.1
|
||||
-----
|
||||
- Auto register permission.
|
||||
- Add admin log.
|
||||
|
||||
0.3.0
|
||||
-----
|
||||
|
||||
@@ -290,6 +290,10 @@ class User(AbstractUser):
|
||||
computed = ("rowVariant", "cellVariants")
|
||||
```
|
||||
|
||||
### Admin log
|
||||
|
||||
You can log each admin action like `delete`,`create` and `update`,just set `admin_log=True` in `admin_app.init()` and inherit `fastapi_admin.models.AbstractAdminLog`.
|
||||
|
||||
## Deployment
|
||||
|
||||
Deploy fastapi app by gunicorn+uvicorn or reference
|
||||
|
||||
@@ -153,6 +153,14 @@ INSERT INTO `permission`
|
||||
VALUES (44, 'Update Config', 'Config', 3);
|
||||
INSERT INTO `permission`
|
||||
VALUES (45, 'Read Config', 'Config', 4);
|
||||
INSERT INTO `permission`
|
||||
VALUES (46, 'Create AdminLog', 'AdminLog', 1);
|
||||
INSERT INTO `permission`
|
||||
VALUES (47, 'Delete AdminLog', 'AdminLog', 2);
|
||||
INSERT INTO `permission`
|
||||
VALUES (48, 'Update AdminLog', 'AdminLog', 3);
|
||||
INSERT INTO `permission`
|
||||
VALUES (49, 'Read AdminLog', 'AdminLog', 4);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
@@ -296,6 +304,8 @@ INSERT INTO `role_permission`
|
||||
VALUES (1, 44);
|
||||
INSERT INTO `role_permission`
|
||||
VALUES (1, 45);
|
||||
INSERT INTO `role_permission`
|
||||
VALUES (1, 49);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
@@ -362,4 +372,22 @@ VALUES (3, 'test', '$2b$12$mrRdNt8n5V8Lsmdh8OGCEOh3.xkUzJRbTo0Ew8IcdyNHjRTfJ0ptG
|
||||
'test', '2020-04-14 16:54:40.510555', 0);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for adminlog
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `adminlog`;
|
||||
CREATE TABLE `adminlog`
|
||||
(
|
||||
`admin_log_id` int NOT NULL AUTO_INCREMENT,
|
||||
`action` varchar(20) NOT NULL,
|
||||
`model` varchar(50) NOT NULL,
|
||||
`content` text NOT NULL,
|
||||
`admin_id` int NOT NULL,
|
||||
PRIMARY KEY (`admin_log_id`),
|
||||
KEY `fk_adminlog_user_50bc034f` (`admin_id`),
|
||||
CONSTRAINT `fk_adminlog_user_50bc034f` FOREIGN KEY (`admin_id`) REFERENCES `user` (`id`) ON DELETE CASCADE
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
@@ -57,6 +57,7 @@ async def start_up():
|
||||
await admin_app.init( # nosec
|
||||
admin_secret="test",
|
||||
permission=True,
|
||||
admin_log=True,
|
||||
site=Site(
|
||||
name="FastAPI-Admin DEMO",
|
||||
login_footer="FASweTAPI ADMIN - FastAPI Admin Dashboard",
|
||||
@@ -112,6 +113,12 @@ async def start_up():
|
||||
),
|
||||
Menu(name="Role", url="/rest/Role", icon="fa fa-group",),
|
||||
Menu(name="Permission", url="/rest/Permission", icon="fa fa-user-plus",),
|
||||
Menu(
|
||||
name="AdminLog",
|
||||
url="/rest/AdminLog",
|
||||
icon="fa fa-align-left",
|
||||
search_fields=("action", "admin", "model"),
|
||||
),
|
||||
Menu(name="Logout", url="/logout", icon="fa fa-lock",),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -2,7 +2,7 @@ import datetime
|
||||
|
||||
from tortoise import Model, fields
|
||||
|
||||
from fastapi_admin.models import AbstractPermission, AbstractRole, AbstractUser
|
||||
from fastapi_admin.models import AbstractAdminLog, AbstractPermission, AbstractRole, AbstractUser
|
||||
|
||||
from .enums import ProductType, Status
|
||||
|
||||
@@ -44,6 +44,12 @@ class Role(AbstractRole):
|
||||
"""
|
||||
|
||||
|
||||
class AdminLog(AbstractAdminLog):
|
||||
"""
|
||||
must inheritance AbstractAdminLog
|
||||
"""
|
||||
|
||||
|
||||
class Category(Model):
|
||||
slug = fields.CharField(max_length=200)
|
||||
name = fields.CharField(max_length=200)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, Path, Query
|
||||
@@ -107,3 +108,27 @@ read_checker = PermissionsChecker(action=enums.PermissionAction.read)
|
||||
create_checker = PermissionsChecker(action=enums.PermissionAction.create)
|
||||
update_checker = PermissionsChecker(action=enums.PermissionAction.update)
|
||||
delete_checker = PermissionsChecker(action=enums.PermissionAction.delete)
|
||||
|
||||
|
||||
class AdminLog:
|
||||
def __init__(self, action: str):
|
||||
self.action = action
|
||||
|
||||
async def __call__(
|
||||
self, request: Request, resource: str = Path(...), admin_id=Depends(jwt_required),
|
||||
):
|
||||
if app.admin_log:
|
||||
content = None
|
||||
if request.method in ["POST", "PUT"]:
|
||||
content = await request.json()
|
||||
elif request.method == "DELETE":
|
||||
content = {"pk": request.path_params.get("id")}
|
||||
if content:
|
||||
await app.admin_log_model.create(
|
||||
admin_id=admin_id, action=self.action, model=resource, content=content
|
||||
)
|
||||
|
||||
|
||||
admin_log_create = AdminLog(action="create")
|
||||
admin_log_update = AdminLog(action="update")
|
||||
admin_log_delete = AdminLog(action="delete")
|
||||
|
||||
@@ -8,7 +8,7 @@ from tortoise import Model
|
||||
from . import enums
|
||||
from .common import get_all_models, import_obj, pwd_context
|
||||
from .exceptions import exception_handler
|
||||
from .models import AbstractPermission, AbstractRole, AbstractUser
|
||||
from .models import AbstractAdminLog, AbstractPermission, AbstractRole, AbstractUser
|
||||
from .schemas import LoginIn
|
||||
from .shortcuts import get_object_or_404
|
||||
from .site import Field, Menu, Resource, Site
|
||||
@@ -38,8 +38,10 @@ class AdminApp(FastAPI):
|
||||
user_model: Type[Model]
|
||||
permission_model: Type[Model]
|
||||
role_model: Type[Model]
|
||||
admin_log_model: Type[Model]
|
||||
site: Site
|
||||
permission: bool
|
||||
admin_log: bool
|
||||
_inited: bool = False
|
||||
_field_type_mapping = {
|
||||
"IntField": "number",
|
||||
@@ -138,10 +140,12 @@ class AdminApp(FastAPI):
|
||||
site: Site,
|
||||
admin_secret: str,
|
||||
permission: bool = False,
|
||||
admin_log: bool = False,
|
||||
login_view: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
init admin site
|
||||
:param admin_log:
|
||||
:param login_view:
|
||||
:param permission: active builtin permission
|
||||
:param site:
|
||||
@@ -151,9 +155,12 @@ class AdminApp(FastAPI):
|
||||
self.site = site
|
||||
self.permission = permission
|
||||
self.admin_secret = admin_secret
|
||||
self.admin_log = admin_log
|
||||
for model_name, model in get_all_models():
|
||||
if issubclass(model, AbstractUser):
|
||||
self.user_model = model
|
||||
elif issubclass(model, AbstractAdminLog):
|
||||
self.admin_log_model = model
|
||||
self.models[model_name] = model
|
||||
self._inited = True
|
||||
if not site.menus:
|
||||
|
||||
@@ -40,3 +40,14 @@ class AbstractRole(Model):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class AbstractAdminLog(Model):
|
||||
admin_log_id = fields.IntField(pk=True)
|
||||
admin = fields.ForeignKeyField("models.User")
|
||||
action = fields.CharField(max_length=20)
|
||||
model = fields.CharField(max_length=50)
|
||||
content = fields.JSONField()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@@ -13,6 +13,9 @@ from tortoise.fields import ManyToManyRelation
|
||||
from ..common import handle_m2m_fields_create_or_update
|
||||
from ..depends import (
|
||||
QueryItem,
|
||||
admin_log_create,
|
||||
admin_log_delete,
|
||||
admin_log_update,
|
||||
create_checker,
|
||||
delete_checker,
|
||||
get_model,
|
||||
@@ -107,19 +110,23 @@ async def view(resource: str,):
|
||||
return resource.dict(by_alias=True, exclude_unset=True)
|
||||
|
||||
|
||||
@router.post("/{resource}/bulk/delete", dependencies=[Depends(delete_checker)])
|
||||
@router.post(
|
||||
"/{resource}/bulk/delete", dependencies=[Depends(delete_checker), Depends(admin_log_delete)]
|
||||
)
|
||||
async def bulk_delete(bulk_in: BulkIn, model=Depends(get_model)):
|
||||
await model.filter(pk__in=bulk_in.pk_list).delete()
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.delete("/{resource}/{id}", dependencies=[Depends(delete_checker)])
|
||||
@router.delete(
|
||||
"/{resource}/{id}", dependencies=[Depends(delete_checker), Depends(admin_log_delete)]
|
||||
)
|
||||
async def delete_one(id: int, model=Depends(get_model)):
|
||||
await model.filter(pk=id).delete()
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.put("/{resource}/{id}", dependencies=[Depends(update_checker)])
|
||||
@router.put("/{resource}/{id}", dependencies=[Depends(update_checker), Depends(admin_log_update)])
|
||||
async def update_one(id: int, parsed=Depends(parse_body), model=Depends(get_model)):
|
||||
body, resource_fields = parsed
|
||||
m2m_fields = model._meta.m2m_fields
|
||||
@@ -135,7 +142,7 @@ async def update_one(id: int, parsed=Depends(parse_body), model=Depends(get_mode
|
||||
return creator.from_orm(obj).dict()
|
||||
|
||||
|
||||
@router.post("/{resource}", dependencies=[Depends(create_checker)])
|
||||
@router.post("/{resource}", dependencies=[Depends(create_checker), Depends(admin_log_create)])
|
||||
async def create_one(parsed=Depends(parse_body), model=Depends(get_model)):
|
||||
body, resource_fields = parsed
|
||||
m2m_fields = model._meta.m2m_fields
|
||||
|
||||
Reference in New Issue
Block a user