add adminlog

This commit is contained in:
long2ice
2020-09-01 23:50:00 +08:00
parent 4e48822711
commit 2dd95e364a
9 changed files with 102 additions and 6 deletions

View File

@@ -8,6 +8,7 @@ ChangeLog
0.3.1
-----
- Auto register permission.
- Add admin log.
0.3.0
-----

View File

@@ -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

View File

@@ -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;

View File

@@ -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",),
],
),

View File

@@ -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)

View File

@@ -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")

View File

@@ -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:

View File

@@ -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

View File

@@ -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