1.用戶模型的建立html
2.Marshmallow模塊python
3.反序列化階段對數據進行校驗json
咱們當前開發的項目屬於社交類型項目,因此關於用戶的信息和功能直接貫穿了整個項目。因此此處實現用戶模塊功能,咱們先把用戶基本信息構建起來,並經過基本信息實現用戶註冊登陸相關功能,後面遇到業務再繼續擴展。flask
cd application/apps
python ../../manage.py blue -n users
application/settings/dev.pyapi
# 註冊藍圖 INSTALLED_APPS = [ "application.apps.home", "application.apps.users", ]
application/urls.py數組
from application.utils import include urlpatterns = [ include("","home.urls"), include("/users","users.urls"), # *** ]
application/model/utils.pyapp
from application import db from datetime import datetime class BaseModel(db.Model): """公共模型""" __abstract__ = True # 抽象模型 id = db.Column(db.Integer, primary_key=True, comment="主鍵ID") name = db.Column(db.String(255), default="", comment="名稱/標題") is_deleted = db.Column(db.Boolean, default=False, comment="邏輯刪除") orders = db.Column(db.Integer, default=0, comment="排序") status = db.Column(db.Boolean, default=True, comment="狀態(是否顯示,是否激活)") created_time = db.Column(db.DateTime, default=datetime.now, comment="建立時間") updated_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment="更新時間") def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self.name)
application/apps/users/models.py
from application.utils.models import BaseModel,db from werkzeug.security import generate_password_hash, check_password_hash class User(BaseModel): """用戶基本信息表""" __tablename__ = "mf_user" name = db.Column(db.String(255), index=True, comment="用戶帳戶") nickname = db.Column(db.String(255), comment="用戶暱稱") _password = db.Column(db.String(255), comment="登陸密碼") age = db.Column(db.SmallInteger, comment="年齡") money = db.Column(db.Numeric(7,2), comment="帳戶餘額") ip_address = db.Column(db.String(255), default="", index=True, comment="登陸IP") intro = db.Column(db.String(500), default="", comment="個性簽名") avatar = db.Column(db.String(255), default="", comment="頭像url地址") sex = db.Column(db.SmallInteger, default=0, comment="性別" ) # 0表示未設置,保密, 1表示男,2表示女 email = db.Column(db.String(32), index=True, default="", nullable=False, comment="郵箱地址") mobile = db.Column(db.String(32), index=True, nullable=False, comment="手機號碼") unique_id = db.Column(db.String(255), index=True, default="", comment="客戶端惟一標記符") province = db.Column(db.String(255), default="", comment="省份") city = db.Column(db.String(255), default="", comment="城市") area = db.Column(db.String(255), default="", comment="地區") info = db.relationship('UserProfile', backref='user', uselist=False) @property def password(self): return self._password @password.setter def password(self, rawpwd): """密碼加密""" self._password = generate_password_hash(rawpwd) def check_password(self, rawpwd): """驗證密碼""" return check_password_hash(self.password, rawpwd) class UserProfile(BaseModel): """用戶詳情信息表""" __tablename__ = "mf_user_profile" user_id = db.Column(db.Integer,db.ForeignKey('mf_user.id'), comment="用戶ID") education = db.Column(db.Integer, comment="學歷教育") middle_school = db.Column(db.String(255), default="", comment="初中/中專") high_school = db.Column(db.String(255), default="", comment="高中/高職") college_school = db.Column(db.String(255), default="", comment="大學/大專") profession_cate = db.Column(db.String(255), default="", comment="職業類型") profession_info = db.Column(db.String(255), default="", comment="職業名稱") position = db.Column(db.SmallInteger, default=0, comment="職位/職稱") emotion_status = db.Column(db.SmallInteger, default=0, comment="情感狀態") birthday =db.Column(db.DateTime, default="", comment="生日") hometown_province = db.Column(db.String(255), default="", comment="家鄉省份") hometown_city = db.Column(db.String(255), default="", comment="家鄉城市") hometown_area = db.Column(db.String(255), default="", comment="家鄉地區") hometown_address = db.Column(db.String(255), default="", comment="家鄉地址") living_province = db.Column(db.String(255), default="", comment="現居住省份") living_city = db.Column(db.String(255), default="", comment="現居住城市") living_area = db.Column(db.String(255), default="", comment="現居住地區") living_address = db.Column(db.String(255), default="", comment="現居住地址")
執行數據庫遷移命令
cd ../.. # 切換工做目錄會到項目根目錄,manage.py所在目錄下 python manage.py db init python manage.py db migrate -m "users table" python manage.py db upgrade
在開發中,針對客戶端提交的數據進行驗證或提供模型數據轉換格式成字典給客戶端。可使用Marshmallow模塊來進行。
下面咱們來了解一下Marshmallow模塊.
Marshmallow,中文譯做:棉花糖。是一個輕量級的數據格式轉換的模塊,也叫序列化和反序列化模塊,經常使用於將複雜的orm模型對象與python原生數據類型之間相互轉換。marshmallow提供了豐富的api功能。以下:
Serializing
序列化[能夠把數據對象轉化爲可存儲或可傳輸的數據類型,例如:objects/object->list/dict,dict/list->string]
Deserializing
反序列化器[把可存儲或可傳輸的數據類型轉換成數據對象,例如:list/dict->objects/object,string->dict/list]
Validation
數據校驗,能夠在反序列化階段,針對要轉換數據的內容進行類型驗證或自定義驗證。
Marshmallow自己是一個單獨的庫,基於咱們當前項目使用框架是flask而且數據庫ORM框架使用SQLAlchemy,因此咱們能夠經過安裝flask-sqlalchemy和marshmallow-sqlalchemy集成到項目就能夠了。
pip install -U marshmallow-sqlalchemy pip install -U flask-sqlalchemy pip install -U flask-marshmallow
import os from flask_marshmallow import Marshmallow ... # 數據轉換器的對象建立 ma = Marshmallow() def init_app(config_path): ... # 數據轉換器的初始化 ma.init_app(app)
爲了方便學習和使用Marshllow, 咱們單首創建一個藍圖來驗證這個模塊的基本使用.
cd application/apps
python ../../manage.py blue -n marsh
INSTALLED_APPS = [ "application.apps.home", "application.apps.users", "application.apps.marsh", ]
from application.utils import include urlpatterns = [ include("","home.urls"), include("/users","users.urls"), include("/marsh","marsh.urls"), ]
marshmallow轉換數據格式主要經過構造器類來完成,而Schema類提供了數據轉換的基本功能:序列化,驗證和反序列化。因此在使用marshmallow的過程當中全部的構造器類必須直接或間接繼承於Schema基類
application/apps/marsh/urls.py
from . import views from application.utils import path urlpatterns = [ path("", views.index), ]
application/apps/marsh/views.py
from marshmallow import Schema,fields from application.apps.users.models import User,UserProfile class UserSchema(Schema): name = fields.String() age = fields.Integer() email = fields.Email() money = fields.Number() class Meta: fields = ["name","age","money","email","info"] ordered = True # 轉換成有序字典 def index(): """序列化""" """單個模型數據的序列化處理""" user1 = User(name="xiaoming", password="123456", age=16, email="333@qq.com", money=31.50) data1 = UserSchema().dump(user1) # 將模型類對象序列化爲字典dict格式 data2 = UserSchema().dumps(user1) # 把模型對象轉換成json字符串格式 return "ok"
在前面進行的序列化操做屬於序列化單個數據對象, MarshMallow中也能夠進行多個數據對象的序列化.
application/apps/marsh/views.py
from marshmallow import Schema,fields from application.apps.users.models import User,UserProfile class UserSchema(Schema): name = fields.String() age = fields.Integer() email = fields.Email() money = fields.Number() class Meta: fields = ["name","age","money","email","info"] ordered = True # 轉換成有序字典 def index(): """序列化""" """多個模型數據的序列化""" user1 = User(name="xiaoming", password="123456", age=15, email="333@qq.com", money=31.50) user2 = User(name="xiaohong", password="123456", age=16, email="333@qq.com", money=31.50) user3 = User(name="xiaopang", password="123456", age=17, email="333@qq.com", money=31.50) data_list = [user1,user2,user3] data1 = UserSchema(many=True).dumps(data_list) # 注意:序列化多個數據對象要加many=True return "ok"
application/apps/marsh/views.py
from marshmallow import Schema,fields from application.apps.users.models import User,UserProfile class UserProfileSchema(Schema): education = fields.Integer() middle_school = fields.String() class UserSchema(Schema): name = fields.String() age = fields.Integer() email = fields.Email() money = fields.Number() # only的含義是外層序列化器要內層序列化器的哪些字段 info = fields.Nested(UserProfileSchema,only=["middle_school"]) class Meta: fields = ["name","age","money","email","info"] ordered = True # 轉換成有序字典 def index(): """序列化""" """序列化嵌套使用""" user1 = User(name="xiaoming", password="123456", age=15, email="333@qq.com", money=31.50) user1.info = UserProfile( education=3, middle_school="北京師範學院附屬中學白沙路分校" ) data = UserSchema().dump(user1) data = UserSchema().dumps(user1) print(data) return "ok"
from marshmallow import Schema, fields, validate, ValidationError,post_load class UserSchema2(Schema): name = fields.String() sex = fields.String() age = fields.Integer(missing=18) # 反序列化數據時,若是數據沒有給age字段賦值,則age默認值爲18 email = fields.Email() mobile = fields.String() @post_load def post_load(self, data, **kwargs): return User(**data) def index(): user_data = {"mobile":"1331345635","name": "xiaoming", "email": "xiaoming@qq.com","sex":"abc"} us2 = UserSchema2() result = us2.load(user_data) # 將字典轉化爲模型類對象 print(type(result),result) # <class 'application.apps.users.models.User'> <User: xiaoming> return "ok"
將user_data數據反序列化後再序列化,能夠看到結果多了age:18這一項,這就是由於在schema中的age字段中設置了missing=18
from marshmallow import Schema, fields, validate, ValidationError,post_load class UserSchema2(Schema): name = fields.String() sex = fields.String() age = fields.Integer(missing=18) email = fields.Email() # 設置反序列化時必需要有mobile字段 mobile = fields.String(required=True) @post_load def post_load(self, data, **kwargs): return User(**data) def index(): user_data = {"name": "xiaoming","sex":"abc"} us2 = UserSchema2() # 設置反序列化時能夠忽略部分數據 result = us2.load(user_data,partial=True) print(result) # ==> <User xiaoming> return "ok"
from marshmallow import Schema, fields, validate, ValidationError,post_load class UserSchema2(Schema): name = fields.String() sex = fields.Integer() age = fields.Integer(missing=18) email = fields.Email() mobile = fields.String() password = fields.String(load_only=True) # 設置當前字段爲只寫字段,只會在反序列化階段啓用 @post_load def post_load(self, data, **kwargs): return User(**data) def index(): user_data = {"name": "xiaoming","password":"123456","sex":1} us2 = UserSchema2() # 反序列化 result = us2.load(user_data) print(result) # ==> <User xiaoming> # 序列化 us3 = UserSchema2(only=["sex","name","age"]) # 限制處理的字段,也就是序列化出來只有這三個字段 result2 = us3.dump(result) print(result2) return "ok" ''' class UserSchema(Schema): name = fields.Str() # password is password = fields.Str(load_only=True) # 至關於只寫字段 "write-only" created_time = fields.DateTime(dump_only=True) # 至關於只讀字段 "read-only" '''
post_dump([fn,pass_many,pass_original]) 註冊序列化對象後調用的方法,它會在對象序列化後被調用。
post_load([fn,pass_many,pass_original]) 註冊反序列化對象後要調用的方法,它會在驗證數據以後被調用。
pre_dump([fn,pass_many]) 註冊序列化對象以前調用的方法,它會在序列化對象以前被調用。
pre_load([fn,pass_many]) 在反序列化對象以前,註冊要調用的方法,它會在驗證數據以前調用。
from marshmallow import Schema, fields, validate, ValidationError,post_load,post_dump class UserSchema2(Schema): name = fields.String() sex = fields.Integer(validate=validate.OneOf([0,1,2])) age = fields.Integer(missing=18) email = fields.Email() mobile = fields.String() password = fields.String(load_only=True) # 設置當前字段爲只寫字段,只會在反序列化階段啓用 @post_load def post_load(self, data, **kwargs): return User(**data) @post_dump def post_dump(self,data, **kwargs): data["mobile"] = data["mobile"][:3] +"*****"+ data["mobile"][-3:] return data def index(): user_data = {"name": "xiaoming","password":"123456","sex":1,"mobile":"133123454656"} us2 = UserSchema2()
# 反序列化 result = us2.load(user_data) print(result) # ==> <User xiaoming>
# 序列化 us3 = UserSchema2(only=["sex","name","age","mobile"]) # 限制處理的字段 result2 = us3.dump(result) print(result2) return "ok"
描述 | |
---|---|
fields.Dict (keys, type]] = None, values, …) |
字典類型,經常使用於接收json類型數據 |
fields.List (cls_or_instance, type], **kwargs) |
列表類型,經常使用於接收數組數據 |
fields.Tuple (tuple_fields, *args, **kwargs) |
元組類型 |
fields.String (*, default, missing, data_key, …) |
字符串類型 |
fields.UUID (*, default, missing, data_key, …) |
UUID格式類型的字符串 |
fields.Number (*, as_string, **kwargs) |
數值基本類型 |
fields.Integer (*, strict, **kwargs) |
整型 |
fields.Decimal (places, rounding, *, allow_nan, …) |
數值型 |
fields.Boolean (*, truthy, falsy, **kwargs) |
布爾型 |
fields.Float (*, allow_nan, as_string, **kwargs) |
浮點數類型 |
fields.DateTime (format, **kwargs) |
日期時間類型 |
fields.Time (format, **kwargs) |
時間類型 |
fields.Date (format, **kwargs) |
日期類型 |
fields.Url (*, relative, schemes, Set[str]]] = None, …) |
url網址字符串類型 |
fields.Email (*args, **kwargs) |
郵箱字符串類型 |
fields.IP (*args[, exploded]) |
IP地址字符串類型 |
fields.IPv4 (*args[, exploded]) |
IPv4地址字符串類型 |
fields.IPv6 (*args[, exploded]) |
IPv6地址字符串類型 |
fields.Method (serialize, deserialize, **kwargs) |
基於Schema類方法返回值的字段 |
fields.Function (serialize, Any], Callable[[Any, …) |
基於函數返回值得字段 |
fields.Nested (nested, type, str, Callable[[], …) |
外鍵類型 |
描述 | |
---|---|
default | 序列化階段中設置字段的默認值 |
missing | 反序列化階段中設置字段的默認值 |
validate | 反序列化階段調用的內置數據驗證器或者內置驗證集合 |
required | 設置當前字段的必填字段 |
allow_none | 是否容許爲空 |
load_only | 是否在反序列化階段才使用到當前字段 |
dump_omly | 是否在序列化階段才使用到當前字段 |
error_messages | 字典類型,能夠用來替代默認的字段異常提示語,格式:<br>error_messages={「required」: 「用戶名爲必填項。」} |
描述 | |
---|---|
validate.Email (*, error) |
郵箱驗證 |
validate.Equal (comparable, *, error) |
判斷值是否相等 |
validate.Length (min, max, *, equal, error) |
值長度/大小驗證 |
validate.OneOf (choices, labels, *, error) |
選項驗證 |
validate.Range ([min, max]) |
範圍驗證 |
validate.Regexp (regex, bytes, Pattern][, flags]) |
正則驗證 |
validate.URL (*, relative, schemes, Set[str]]] = None, …) |
驗證是否爲URL |
from marshmallow import Schema, fields, validate, ValidationError,post_load class UserSchema3(Schema): name = fields.String(required=True) sex = fields.String(required=True,error_messages={"required":"對不起,permission必須填寫"}) age = fields.Integer(missing=18,validate=validate.Range(min=18,max=40,error="年齡必須在18-40之間!")) # 限制數值範圍 email = fields.Email(error_messages={"invalid":"對不起,必須填寫郵箱格式!"}) mobile = fields.String(required=True, validate=validate.Regexp("^1[3-9]\d{9}$",error="手機號碼格式不正確"),error_messages={"Regexp":"手機格式不正確"}) @post_load def make_user_obj(self, data, **kwargs): return User(**data) def index3(): user_data = {"mobile":"1331345635","name": "xiaoming","age":40, "email": "xiaoming@qq.com","sex":"abc"} us2 = UserSchema3() result = us2.load(user_data) result2 = us2.dumps(result) print(result) print(result2) return "ok"
局部鉤子和全局鉤子,好比局部鉤子對單個字段(用戶名)的判斷,以及全局鉤子對密碼,確認密碼多個字段的判斷
from marshmallow import Schema, fields, validate,validates, ValidationError,post_load,validates_schema class UserSchema4(Schema): name = fields.String(required=True) sex = fields.String(required=True,error_messages={"required":"對不起,permission必須填寫"}) age = fields.Integer(missing=18,validate=validate.Range(min=18,max=40,error="年齡必須在18-40之間!")) # 限制數值範圍 email = fields.Email(error_messages={"invalid":"對不起,必須填寫郵箱格式!"}) mobile = fields.String(required=True, validate=validate.Regexp("^1[3-9]\d{9}$",error="手機號碼格式不正確"),error_messages={"Regexp":"手機格式不正確"}) password = fields.String(required=True, load_only=True) password2 = fields.String(required=True, allow_none=True) @post_load def make_user_obj(self, data, **kwargs): return User(**data) # 局部鉤子 *** @validates("name") def validate_name(self,data,**kwargs): print("name=%s" % data) if data == "root": raise ValidationError({"對不起,root用戶是超級用戶!您沒有權限註冊!"}) # 必須有返回值 return data # 全局鉤子 *** @validates_schema def validate(self,data,**kwargs): print(data) if data["password"] != data["password2"]: raise ValidationError("密碼和確認密碼必須同樣!") data.pop("password2") return data def index(): user_data = {"password":"12345","password2":"123456","mobile":"13313345635","name": "root1","age":40, "email": "xiaoming@qq.com","sex":"abc"} us2 = UserSchema4() result = us2.load(user_data) print(result) return "ok"
類型描述fields.Dict(keys, type]] = None, values, …)字典類型,經常使用於接收json類型數據fields.List(cls_or_instance, type], **kwargs)列表類型,經常使用於接收數組數據fields.Tuple(tuple_fields, *args, **kwargs)元組類型fields.String(*, default, missing, data_key, …)字符串類型fields.UUID(*, default, missing, data_key, …)UUID格式類型的字符串fields.Number(*, as_string, **kwargs)數值基本類型fields.Integer(*, strict, **kwargs)整型fields.Decimal(places, rounding, *, allow_nan, …)數值型fields.Boolean(*, truthy, falsy, **kwargs)布爾型fields.Float(*, allow_nan, as_string, **kwargs)浮點數類型fields.DateTime(format, **kwargs)日期時間類型fields.Time(format, **kwargs)時間類型fields.Date(format, **kwargs)日期類型fields.Url(*, relative, schemes, Set[str]]] = None, …)url網址字符串類型fields.Email(*args, **kwargs)郵箱字符串類型fields.IP(*args[, exploded])IP地址字符串類型fields.IPv4(*args[, exploded])IPv4地址字符串類型fields.IPv6(*args[, exploded])IPv6地址字符串類型fields.Method(serialize, deserialize, **kwargs)基於Schema類方法返回值的字段fields.Function(serialize, Any], Callable[[Any, …)基於函數返回值得字段fields.Nested(nested, type, str, Callable[[], …)外鍵類型