add custom login_view

This commit is contained in:
long2ice
2020-06-05 18:58:57 +08:00
parent 213f976ae3
commit 461335c421
9 changed files with 122 additions and 91 deletions

View File

@ -4,6 +4,10 @@ ChangeLog
0.2
===
0.2.7
-----
- Add custom login_view.
0.2.6
-----
- Fix createsuperuser error.

View File

@ -1,15 +1,11 @@
import os
import uvicorn
from fastapi import Depends, FastAPI
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from starlette.templating import Jinja2Templates
from tortoise.contrib.fastapi import register_tortoise
from tortoise.contrib.pydantic import pydantic_queryset_creator
from fastapi_admin.depends import get_model
from fastapi_admin.factory import app as admin_app
from fastapi_admin.schemas import BulkIn
from fastapi_admin.site import Site
TORTOISE_ORM = {
@ -22,21 +18,6 @@ TORTOISE_ORM = {
},
}
templates = Jinja2Templates(directory="examples/templates")
@admin_app.post("/rest/{resource}/bulk/test_bulk")
async def test_bulk(bulk_in: BulkIn, model=Depends(get_model)):
qs = model.filter(pk__in=bulk_in.pk_list)
pydantic = pydantic_queryset_creator(model)
ret = await pydantic.from_queryset(qs)
return ret.dict()
@admin_app.get("/home",)
async def home():
return {"html": templates.get_template("home.html").render()}
def create_app():
fast_app = FastAPI(debug=False)
@ -73,6 +54,7 @@ async def start_up():
locale_switcher=True,
theme_switcher=True,
),
login_view="examples.routes.login",
)

37
examples/routes.py Normal file
View File

@ -0,0 +1,37 @@
from fastapi import APIRouter, Depends
from starlette.templating import Jinja2Templates
from tortoise.contrib.pydantic import pydantic_queryset_creator
from fastapi_admin.depends import get_model
from fastapi_admin.factory import app
from fastapi_admin.schemas import BulkIn
templates = Jinja2Templates(directory="templates")
router = APIRouter()
@router.post("/rest/{resource}/bulk/test_bulk")
async def test_bulk(bulk_in: BulkIn, model=Depends(get_model)):
qs = model.filter(pk__in=bulk_in.pk_list)
pydantic = pydantic_queryset_creator(model)
ret = await pydantic.from_queryset(qs)
return ret.dict()
@router.get("/home",)
async def home():
return {"html": templates.get_template("home.html").render()}
async def login():
return {
"user": {
"username": "admin",
"is_superuser": False,
"avatar": "https://avatars2.githubusercontent.com/u/13377178?s=460&u=d150d522579f41a52a0b3dd8ea997e0161313b6e&v=4",
},
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.HSlcYkOEQewxyPuaqcVwCcw_wkbLB50Ws1-ZxfPoLAQ",
}
app.include_router(router)

View File

@ -1,5 +1,4 @@
import argparse
import importlib
import sys
from colorama import Fore, init
@ -7,7 +6,7 @@ from prompt_toolkit import PromptSession
from tortoise import Tortoise, run_async
from fastapi_admin import enums
from fastapi_admin.common import pwd_context
from fastapi_admin.common import import_obj, pwd_context
from fastapi_admin.models import Permission
init(autoreset=True)
@ -27,13 +26,6 @@ class Logger:
print(Fore.RED + text)
def import_obj(path):
splits = path.split(".")
module = ".".join(splits[:-1])
class_name = splits[-1]
return getattr(importlib.import_module(module), class_name)
async def init_tortoise(args):
await Tortoise.init(config=import_obj(args.config))

View File

@ -1,3 +1,4 @@
import importlib
from copy import deepcopy
from passlib.context import CryptContext
@ -33,3 +34,15 @@ async def handle_m2m_fields_create_or_update(body, m2m_fields, model, create=Tru
m2m_objs = await m2m_model.filter(pk__in=v)
await m2m_related.add(*m2m_objs)
return obj
def import_obj(path: str):
"""
import obj from module path
:param path:
:return:
"""
splits = path.split(".")
module = ".".join(splits[:-1])
class_name = splits[-1]
return getattr(importlib.import_module(module), class_name)

View File

@ -1,13 +1,36 @@
from copy import deepcopy
from typing import Any, Dict, List, Optional, Type
import jwt
from fastapi import FastAPI, HTTPException
from starlette.status import HTTP_403_FORBIDDEN
from tortoise import Model, Tortoise
from .common import import_obj, pwd_context
from .exceptions import exception_handler
from .schemas import LoginIn
from .shortcuts import get_object_or_404
from .site import Field, Menu, Resource, Site
async def login(login_in: LoginIn):
user_model = app.user_model
user = await get_object_or_404(user_model, username=login_in.username)
if not user.is_active:
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="User is not Active!")
if not pwd_context.verify(login_in.password, user.password):
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Incorrect Password!")
ret = {
"user": {
"username": user.username,
"is_superuser": user.is_superuser,
"avatar": user.avatar if hasattr(user, "avatar") else None,
},
"token": jwt.encode({"user_id": user.pk}, app.admin_secret, algorithm="HS256"),
}
return ret
class AdminApp(FastAPI):
models: Any
admin_secret: str
@ -115,9 +138,11 @@ class AdminApp(FastAPI):
tortoise_app: str,
admin_secret: str,
permission: bool = False,
login_view: Optional[str] = None,
):
"""
init admin site
:param login_view:
:param tortoise_app:
:param permission: active builtin permission
:param site:
@ -134,6 +159,10 @@ class AdminApp(FastAPI):
if not site.menus:
site.menus = self._build_default_menus(permission)
self._get_model_menu_mapping(site.menus)
if login_view:
self.add_api_route("/login", import_obj(login_view), methods=["POST"])
else:
self.add_api_route("/login", login, methods=["POST"])
def _exclude_field(self, resource: str, field: str):
"""

View File

@ -2,8 +2,7 @@ from fastapi import Depends
from ..depends import jwt_required
from ..factory import app
from . import login, rest, site
from . import rest, site
app.include_router(login.router)
app.include_router(site.router)
app.include_router(rest.router, dependencies=[Depends(jwt_required)], prefix="/rest")

View File

@ -1,29 +0,0 @@
import jwt
from fastapi import APIRouter, HTTPException
from starlette.status import HTTP_403_FORBIDDEN
from ..common import pwd_context
from ..factory import app
from ..schemas import LoginIn
from ..shortcuts import get_object_or_404
router = APIRouter()
@router.post("/login",)
async def login(login_in: LoginIn):
user_model = app.user_model
user = await get_object_or_404(user_model, username=login_in.username)
if not user.is_active:
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="User is not Active!")
if not pwd_context.verify(login_in.password, user.password):
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Incorrect Password!")
ret = {
"user": {
"username": user.username,
"is_superuser": user.is_superuser,
"avatar": user.avatar if hasattr(user, "avatar") else None,
},
"token": jwt.encode({"user_id": user.pk}, app.admin_secret, algorithm="HS256"),
}
return ret

66
poetry.lock generated
View File

@ -161,15 +161,6 @@ optional = false
python-versions = "*"
version = "3.0.4"
[[package]]
category = "main"
description = "Fast ISO8601 date time parser for Python written in C"
marker = "sys_platform != \"win32\" and implementation_name == \"cpython\""
name = "ciso8601"
optional = false
python-versions = "*"
version = "2.1.3"
[[package]]
category = "main"
description = "Composable command line interface toolkit"
@ -406,7 +397,6 @@ version = "2.9"
[[package]]
category = "main"
description = "Simple module to parse ISO 8601 dates"
marker = "sys_platform == \"win32\" or implementation_name != \"cpython\""
name = "iso8601"
optional = false
python-versions = "*"
@ -641,7 +631,7 @@ description = "pytest: simple powerful testing with Python"
name = "pytest"
optional = false
python-versions = ">=3.5"
version = "5.4.2"
version = "5.4.3"
[package.dependencies]
atomicwrites = ">=1.0"
@ -801,15 +791,17 @@ description = "Easy async ORM for python, built with relations in mind"
name = "tortoise-orm"
optional = false
python-versions = "*"
version = "0.16.12"
version = "0.16.13"
[package.dependencies]
aiosqlite = ">=0.11.0"
ciso8601 = ">=2.1.2"
iso8601 = ">=0.1.12"
pypika = ">=0.36.5"
typing-extensions = ">=3.7"
[package.extras]
accel = ["python-rapidjson", "ciso8601 (>=2.1.2)", "uvloop (>=0.12.0)"]
[[package]]
category = "dev"
description = "a fork of Python 2 and 3 ast modules with type comment support"
@ -831,8 +823,8 @@ category = "main"
description = "Ultra fast JSON encoder and decoder for Python"
name = "ujson"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.0.3"
python-versions = ">=3.5"
version = "3.0.0"
[[package]]
category = "main"
@ -879,7 +871,7 @@ description = "Measures the displayed width of unicode strings in a terminal"
name = "wcwidth"
optional = false
python-versions = "*"
version = "0.2.2"
version = "0.2.3"
[[package]]
category = "main"
@ -1008,9 +1000,6 @@ chardet = [
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
]
ciso8601 = [
{file = "ciso8601-2.1.3.tar.gz", hash = "sha256:bdbb5b366058b1c87735603b23060962c439ac9be66f1ae91e8c7dbd7d59e262"},
]
click = [
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
@ -1139,6 +1128,11 @@ markupsafe = [
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
{file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
]
mccabe = [
@ -1240,8 +1234,8 @@ pypika = [
{file = "PyPika-0.37.7.tar.gz", hash = "sha256:20bebc05983cd401d428e3beb62d037e5f0271daab2bb5aba82f4e092d4a3694"},
]
pytest = [
{file = "pytest-5.4.2-py3-none-any.whl", hash = "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3"},
{file = "pytest-5.4.2.tar.gz", hash = "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"},
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
]
pytest-forked = [
{file = "pytest-forked-1.1.3.tar.gz", hash = "sha256:1805699ed9c9e60cb7a8179b8d4fa2b8898098e82d229b0825d8095f0f261100"},
@ -1350,7 +1344,7 @@ toml = [
{file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
]
tortoise-orm = [
{file = "tortoise-orm-0.16.12.tar.gz", hash = "sha256:170e4bbfe1c98223ad1fba33d7fded7923e4bb49c9d74c78bd173a0ebc861658"},
{file = "tortoise-orm-0.16.13.tar.gz", hash = "sha256:5f6fa4430a570172cb49517a97d45338dbfb1a690ed707030467efd154e67855"},
]
typed-ast = [
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
@ -1381,13 +1375,23 @@ typing-extensions = [
{file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"},
]
ujson = [
{file = "ujson-2.0.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7ae13733d9467d16ccac2f38212cdee841b49ae927085c533425be9076b0bc9d"},
{file = "ujson-2.0.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:6217c63a36e9b26e9271e686d212397ce7fb04c07d85509dd4e2ed73493320f8"},
{file = "ujson-2.0.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c8369ef49169804944e920c427e350182e33756422b69989c55608fc28bebf98"},
{file = "ujson-2.0.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0c23f21e8d2b60efab57bc6ce9d1fb7c4e96f4bfefbf5a6043a3f3309e2a738a"},
{file = "ujson-2.0.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3d1f4705a4ec1e48ff383a4d92299d8ec25e9a8158bcea619912440948117634"},
{file = "ujson-2.0.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2ab88e330405315512afe9276f29a60e9b3439187b273665630a57ed7fe1d936"},
{file = "ujson-2.0.3.tar.gz", hash = "sha256:bd2deffc983827510e5145fb66e4cc0f577480c62fe0b4882139f8f7d27ae9a3"},
{file = "ujson-3.0.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:0959a5b569e192459b492b007e3fd63d8f4b4bcb4f69dcddca850a9b9dfe3e7a"},
{file = "ujson-3.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:154f778f0b028390067aaedce8399730d4f528a16a1c214fe4eeb9c4e4f51810"},
{file = "ujson-3.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:019a17e7162f26e264f1645bb41630f7103d178c092ea4bb8f3b16126c3ea210"},
{file = "ujson-3.0.0-cp35-cp35m-win_amd64.whl", hash = "sha256:670018d4ab4b0755a7234a9f4791723abcd0506c0eed33b2ed50579c4aff31f2"},
{file = "ujson-3.0.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:634c206f4fb3be7e4523768c636d2dd41cb9c7130e2d219ef8305b8fb6f4838e"},
{file = "ujson-3.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3bd791d17a175c1c6566aeaec1755b58e3f021fe9bb62f10f02b656b299199f5"},
{file = "ujson-3.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0379ffc7484b862a292e924c15ad5f1c5306d4271e2efd162144812afb08ff97"},
{file = "ujson-3.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f40bb0d0cb534aad3e24884cf864bda7a71eb5984bd1da61d1711bbfb3be2c38"},
{file = "ujson-3.0.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:0f33359908df32033195bfdd59ba2bfb90a23cb280ef9a0ba11e5013a53d7fd9"},
{file = "ujson-3.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bea2958c7b5bf4f191f0def751b6f7c8b208edb5f7277e21776329f2ca042385"},
{file = "ujson-3.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f854702a9aff3a445f4a0b715d240f2a3d84014d8ae8aad05a982c7ffab12525"},
{file = "ujson-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c04d253fec814657fd9f150ef2333dbd0bc6f46208355aa753a29e0696b7fa7e"},
{file = "ujson-3.0.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a32f2def62b10e8a19084d17d40363c4da1ac5f52d300a9e99d7efb49fe5f34a"},
{file = "ujson-3.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9c68557da3e3ad57e0105aceba0cce5f8f7cd07d207c3860e59c0b3044532830"},
{file = "ujson-3.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0e2352b60c4ac4fc75b723435faf36ef5e7f3bfb988adb4d589b5e0e6e1d90aa"},
{file = "ujson-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:c841a6450d64c24c64cbcca429bab22cdb6daef5eaddfdfebe798a5e9e5aff4c"},
{file = "ujson-3.0.0.tar.gz", hash = "sha256:e0199849d61cc6418f94d52a314c6a27524d65e82174d2a043fb718f73d1520d"},
]
urllib3 = [
{file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"},
@ -1409,8 +1413,8 @@ uvloop = [
{file = "uvloop-0.14.0.tar.gz", hash = "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e"},
]
wcwidth = [
{file = "wcwidth-0.2.2-py2.py3-none-any.whl", hash = "sha256:b651b6b081476420e4e9ae61239ac4c1b49d0c5ace42b2e81dc2ff49ed50c566"},
{file = "wcwidth-0.2.2.tar.gz", hash = "sha256:3de2e41158cb650b91f9654cbf9a3e053cee0719c9df4ddc11e4b568669e9829"},
{file = "wcwidth-0.2.3-py2.py3-none-any.whl", hash = "sha256:980fbf4f3c196c0f329cdcd1e84c554d6a211f18e252e525a0cf4223154a41d6"},
{file = "wcwidth-0.2.3.tar.gz", hash = "sha256:edbc2b718b4db6cdf393eefe3a420183947d6aa312505ce6754516f458ff8830"},
]
websockets = [
{file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"},