Skip to content

Commit 7815fd6

Browse files
committed
refactor:user pydantic validator
1 parent 0e65553 commit 7815fd6

8 files changed

Lines changed: 149 additions & 101 deletions

File tree

app/api/cms/admin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@
2323
from sqlalchemy import func
2424

2525
from app.api import AuthorizationBearerSecurity, api
26+
from app.api.cms.schema import ResetPasswordSchema
2627
from app.api.cms.schema.admin import (
2728
AdminGroupListSchema,
2829
AdminGroupPermissionSchema,
2930
AdminUserPageSchema,
30-
GroupQuerySearchSchema,
31-
ResetPasswordSchema,
32-
UpdateUserInfoSchema,
3331
CreateGroupSchema,
3432
GroupBaseSchema,
3533
GroupIdWithPermissionIdListSchema,
34+
GroupQuerySearchSchema,
35+
UpdateUserInfoSchema,
3636
)
3737
from app.util.page import get_page_from_query
3838

app/api/cms/schema/__init__.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from typing import List, Optional
2+
3+
from lin import BaseModel, ParameterError
4+
from pydantic import EmailStr, Field, validator
5+
6+
7+
class EmailSchema(BaseModel):
8+
email: Optional[str] = Field(description="用户邮箱")
9+
10+
@validator("email")
11+
def check_email(cls, v, values, **kwargs):
12+
return EmailStr.validate(v) if v else ""
13+
14+
15+
class ResetPasswordSchema(BaseModel):
16+
new_password: str = Field(description="新密码", min_length=6, max_length=22)
17+
confirm_password: str = Field(description="确认密码", min_length=6, max_length=22)
18+
19+
@validator("confirm_password")
20+
def passwords_match(cls, v, values, **kwargs):
21+
if v != values["new_password"]:
22+
raise ParameterError("两次输入的密码不一致,请输入相同的密码")
23+
return v
24+
25+
26+
class GroupIdListSchema(BaseModel):
27+
group_ids: List[int] = Field(description="用户组ID列表")
28+
29+
@validator("group_ids", each_item=True)
30+
def check_group_id(cls, v, values, **kwargs):
31+
if v <= 0:
32+
raise ParameterError("用户组ID必须大于0")
33+
return v

app/api/cms/schema/admin.py

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,11 @@
1-
import re
21
from typing import List, Optional
32

43
from lin import BaseModel, ParameterError
5-
from pydantic import EmailStr, Field, validator
4+
from pydantic import Field, validator
65

76
from app.schema import BasePageSchema, QueryPageSchema
87

9-
10-
class ResetPasswordSchema(BaseModel):
11-
new_password: str = Field(description="新密码", min_length=6, max_length=22)
12-
confirm_password: str = Field(description="确认密码", min_length=6, max_length=22)
13-
14-
@validator("confirm_password", allow_reuse=True)
15-
def passwords_match(cls, v, values, **kwargs):
16-
if v != values["new_password"]:
17-
raise ParameterError("两次输入的密码不一致,请输入相同的密码")
18-
return v
19-
20-
21-
class GroupIdListSchema(BaseModel):
22-
group_ids: List[int] = Field(description="用户组ID列表")
23-
24-
@validator("group_ids", each_item=True)
25-
def check_group_id(cls, v, values, **kwargs):
26-
if v <= 0:
27-
raise ParameterError("用户组ID必须大于0")
28-
return v
29-
30-
31-
class RegisterSchema(ResetPasswordSchema, GroupIdListSchema):
32-
username: str = Field(description="用户名", min_length=2, max_length=10)
33-
34-
@validator("username")
35-
def check_username(cls, v, values, **kwargs):
36-
if not re.match(r"^[a-zA-Z0-9_]{2,10}$", v):
37-
raise ParameterError("用户名只能由字母、数字、下划线组成,且长度为2-10位")
38-
return v
8+
from . import EmailSchema, GroupIdListSchema
399

4010

4111
class AdminGroupSchema(BaseModel):
@@ -48,10 +18,9 @@ class AdminGroupListSchema(BaseModel):
4818
__root__: List[AdminGroupSchema]
4919

5020

51-
class AdminUserSchema(BaseModel):
21+
class AdminUserSchema(EmailSchema):
5222
id: int = Field(description="用户ID")
5323
username: str = Field(description="用户名")
54-
email: Optional[EmailStr] = Field(description="邮箱")
5524
groups: List[AdminGroupSchema] = Field(description="用户组列表")
5625

5726

@@ -63,8 +32,8 @@ class GroupQuerySearchSchema(QueryPageSchema):
6332
group_id: Optional[int] = Field(gt=0, description="用户组ID")
6433

6534

66-
class UpdateUserInfoSchema(GroupIdListSchema):
67-
email: EmailStr = Field(description="电子邮件")
35+
class UpdateUserInfoSchema(GroupIdListSchema, EmailSchema):
36+
pass
6837

6938

7039
class PermissionSchema(BaseModel):

app/api/cms/schema/user.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
from typing import Optional
1+
import re
2+
from typing import List, Optional
23

3-
from lin import BaseModel
4-
from pydantic import Field
4+
from lin import BaseModel, ParameterError
5+
from pydantic import AnyHttpUrl, Field, validator
6+
7+
from . import EmailSchema, GroupIdListSchema, ResetPasswordSchema
58

69

710
class LoginSchema(BaseModel):
@@ -18,3 +21,48 @@ class LoginTokenSchema(BaseModel):
1821
class CaptchaSchema(BaseModel):
1922
image: str = Field("", description="验证码图片base64编码")
2023
tag: str = Field("", description="验证码标记码")
24+
25+
26+
class PermissionNameSchema(BaseModel):
27+
name: str = Field(description="权限名称")
28+
29+
30+
class PermissionModuleSchema(BaseModel):
31+
module: List[PermissionNameSchema] = Field(description="权限模块")
32+
33+
34+
class UserBaseInfoSchema(EmailSchema):
35+
nickname: Optional[str] = Field(description="用户昵称", min_length=2, max_length=10)
36+
avatar: Optional[AnyHttpUrl] = Field(description="头像url")
37+
38+
39+
class UserSchema(UserBaseInfoSchema):
40+
id: int = Field(description="用户id")
41+
username: str = Field(description="用户名")
42+
43+
44+
class UserPermissionSchema(UserSchema):
45+
admin: bool = Field(description="是否是管理员")
46+
permissions: List[PermissionModuleSchema] = Field(description="用户权限")
47+
48+
49+
class ChangePasswordSchema(ResetPasswordSchema):
50+
old_password: str = Field(description="旧密码")
51+
52+
53+
class UserRegisterSchema(GroupIdListSchema, EmailSchema):
54+
username: str = Field(description="用户名", min_length=2, max_length=10)
55+
password: str = Field(description="密码", min_length=6, max_length=22)
56+
confirm_password: str = Field(description="确认密码", min_length=6, max_length=22)
57+
58+
@validator("confirm_password")
59+
def passwords_match(cls, v, values, **kwargs):
60+
if v != values["password"]:
61+
raise ParameterError("两次输入的密码不一致,请输入相同的密码")
62+
return v
63+
64+
@validator("username")
65+
def check_username(cls, v, values, **kwargs):
66+
if not re.match(r"^[a-zA-Z0-9_]{2,10}$", v):
67+
raise ParameterError("用户名只能由字母、数字、下划线组成,且长度为2-10位")
68+
return v

app/api/cms/user.py

Lines changed: 54 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
:copyright: © 2020 by the Lin team.
55
:license: MIT, see LICENSE for more details.
66
"""
7-
from flask import Blueprint, current_app, request
7+
from flask import Blueprint, current_app, g, request
88
from flask_jwt_extended import (
99
create_access_token,
1010
create_refresh_token,
@@ -32,12 +32,15 @@
3232

3333
from app.api import AuthorizationBearerSecurity, api
3434
from app.api.cms.exception import RefreshFailed
35-
from app.api.cms.schema.user import CaptchaSchema, LoginSchema, LoginTokenSchema
36-
from app.api.cms.validator import (
37-
ChangePasswordForm,
38-
LoginForm,
39-
RegisterForm,
40-
UpdateInfoForm,
35+
from app.api.cms.schema.user import (
36+
CaptchaSchema,
37+
ChangePasswordSchema,
38+
LoginSchema,
39+
LoginTokenSchema,
40+
UserBaseInfoSchema,
41+
UserPermissionSchema,
42+
UserRegisterSchema,
43+
UserSchema,
4144
)
4245
from app.util.captcha import CaptchaTool
4346
from app.util.common import split_group
@@ -52,19 +55,38 @@
5255
@api.validate(
5356
tags=["用户"],
5457
security=[AuthorizationBearerSecurity],
55-
resp=DocResponse(Success("用户创建成功")),
58+
resp=DocResponse(Success("用户创建成功"), Duplicated("字段重复,请重新输入")),
5659
)
57-
def register():
60+
def register(json: UserRegisterSchema):
5861
"""
5962
注册新用户
6063
"""
61-
form = RegisterForm().validate_for_api()
62-
if manager.user_model.count_by_username(form.username.data) > 0:
64+
if manager.user_model.count_by_username(g.username) > 0:
6365
raise Duplicated("用户名重复,请重新输入") # type: ignore
64-
if form.email.data and form.email.data.strip() != "":
65-
if manager.user_model.count_by_email(form.email.data) > 0:
66+
if g.email and g.email.strip() != "":
67+
if manager.user_model.count_by_email(g.email) > 0:
6668
raise Duplicated("注册邮箱重复,请重新输入") # type: ignore
67-
_register_user(form)
69+
# create a user
70+
with db.auto_commit():
71+
user = manager.user_model()
72+
user.username = g.username
73+
if g.email and g.email.strip() != "":
74+
user.email = g.email
75+
db.session.add(user)
76+
db.session.flush()
77+
user.password = g.password
78+
group_ids = g.group_ids
79+
# 如果没传分组数据,则将其设定为 guest 分组
80+
if len(group_ids) == 0:
81+
from lin import GroupLevelEnum
82+
83+
group_ids = [GroupLevelEnum.GUEST.value]
84+
for group_id in group_ids:
85+
user_group = manager.user_group_model()
86+
user_group.user_id = user.id
87+
user_group.group_id = group_id
88+
db.session.add(user_group)
89+
6890
return Success("用户创建成功") # type: ignore
6991

7092

@@ -74,16 +96,15 @@ def login(json: LoginSchema):
7496
"""
7597
用户登录
7698
"""
77-
form = LoginForm().validate_for_api()
7899
# 校对验证码
79100
if current_app.config.get("LOGIN_CAPTCHA"):
80101
tag = request.headers.get("tag")
81102
secret_key = current_app.config.get("SECRET_KEY")
82103
serializer = JWSSerializer(secret_key)
83-
if form.captcha.data != serializer.loads(tag):
104+
if g.captcha != serializer.loads(tag):
84105
raise Failed("验证码校验失败") # type: ignore
85106

86-
user = manager.user_model.verify(form.username.data, form.password.data)
107+
user = manager.user_model.verify(g.username, g.password)
87108
# 用户未登录,此处不能用装饰器记录日志
88109
Log.create_log(
89110
message=f"{user.username}登录成功获取了令牌",
@@ -105,29 +126,26 @@ def login(json: LoginSchema):
105126
@api.validate(
106127
tags=["用户"],
107128
security=[AuthorizationBearerSecurity],
129+
resp=DocResponse(Success("用户信息更新成功"), ParameterError("邮箱已被注册,请重新输入邮箱")),
108130
)
109-
def update():
131+
def update(json: UserBaseInfoSchema):
110132
"""
111133
更新用户信息
112134
"""
113-
form = UpdateInfoForm().validate_for_api()
114135
user = get_current_user()
115-
email = form.email.data
116-
nickname = form.nickname.data
117-
avatar = form.avatar.data
118136

119-
if email and user.email != email:
120-
exists = manager.user_model.get(email=form.email.data)
137+
if g.email and user.email != g.email:
138+
exists = manager.user_model.get(email=g.email)
121139
if exists:
122140
raise ParameterError("邮箱已被注册,请重新输入邮箱")
123141
with db.auto_commit():
124-
if email:
125-
user.email = form.email.data
126-
if nickname:
127-
user.nickname = form.nickname.data
128-
if avatar:
129-
user._avatar = form.avatar.data
130-
return Success("操作成功")
142+
if g.email:
143+
user.email = g.email
144+
if g.nickname:
145+
user.nickname = g.nickname
146+
if g.avatar:
147+
user._avatar = g.avatar
148+
return Success("用户信息更新成功")
131149

132150

133151
@user_api.route("/change_password", methods=["PUT"])
@@ -137,14 +155,14 @@ def update():
137155
@api.validate(
138156
tags=["用户"],
139157
security=[AuthorizationBearerSecurity],
158+
resp=DocResponse(Success("密码修改成功"), Failed("密码修改失败")),
140159
)
141-
def change_password():
160+
def change_password(json: ChangePasswordSchema):
142161
"""
143162
修改密码
144163
"""
145-
form = ChangePasswordForm().validate_for_api()
146164
user = get_current_user()
147-
ok = user.change_password(form.old_password.data, form.new_password.data)
165+
ok = user.change_password(g.old_password, g.new_password)
148166
if ok:
149167
db.session.commit()
150168
return Success("密码修改成功")
@@ -158,6 +176,7 @@ def change_password():
158176
@api.validate(
159177
tags=["用户"],
160178
security=[AuthorizationBearerSecurity],
179+
resp=DocResponse(r=UserSchema),
161180
)
162181
def get_information():
163182
"""
@@ -197,6 +216,7 @@ def refresh():
197216
@api.validate(
198217
tags=["用户"],
199218
security=[AuthorizationBearerSecurity],
219+
resp=DocResponse(r=UserPermissionSchema),
200220
)
201221
def get_allowed_apis():
202222
"""
@@ -218,28 +238,6 @@ def get_allowed_apis():
218238
return user
219239

220240

221-
def _register_user(form: RegisterForm):
222-
with db.auto_commit():
223-
user = manager.user_model()
224-
user.username = form.username.data
225-
if form.email.data and form.email.data.strip() != "":
226-
user.email = form.email.data
227-
db.session.add(user)
228-
db.session.flush()
229-
user.password = form.password.data
230-
group_ids = form.group_ids.data
231-
# 如果没传分组数据,则将其设定为 guest 分组
232-
if len(group_ids) == 0:
233-
from lin import GroupLevelEnum
234-
235-
group_ids = [GroupLevelEnum.GUEST.value]
236-
for group_id in group_ids:
237-
user_group = manager.user_group_model()
238-
user_group.user_id = user.id
239-
user_group.group_id = group_id
240-
db.session.add(user_group)
241-
242-
243241
@user_api.route("/captcha", methods=["GET", "POST"])
244242
@api.validate(
245243
resp=DocResponse(r=CaptchaSchema),

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ default = true
1111

1212
[tool.poetry.dependencies]
1313
python = "^3.8"
14-
Lin-CMS = "^0.4.4"
14+
Lin-CMS = "^0.4.5"
1515
pillow = "^9.0.0"
1616
flask-cors = "^3.0.10"
1717
gunicorn = "^20.1.0"

requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
python==3.8
2-
Lin-CMS==0.4.4
2+
Lin-CMS==0.4.5
33
pillow==9.0.0
44
flask-cors==3.0.10
55
gunicorn==20.1.0

0 commit comments

Comments
 (0)