day100:MoFang:使用者模型類的建立&Marshmallow模組&使用基本構造器Schema完成資料的序列化轉換和反序列化轉換

Poke發表於2020-12-01

目錄

1.使用者模型的建立

2.Marshmallow模組

3.MarshMallow基本構造器:Schema

  1.基於Schema完成資料序列化轉換

  2.基於Schema完成資料反序列化轉換

  3.反序列化階段對資料進行校驗

1.使用者模型的建立

我們當前開發的專案屬於社交型別專案,所以關於使用者的資訊和功能直接貫穿了整個專案。所以此處實現使用者模組功能,我們先把使用者基本資訊構建起來,並通過基本資訊實現使用者註冊登入相關功能,後面遇到業務再繼續擴充套件。

1.建立使用者藍圖/註冊使用者藍圖/新增總路由

cd application/apps
python ../../manage.py blue -n users

application/settings/dev.py

# 註冊藍圖
INSTALLED_APPS = [
        "application.apps.home",
        "application.apps.users",
    ]

application/urls.py

from application.utils import include
urlpatterns = [
    include("","home.urls"),
    include("/users","users.urls"), # ***
]

2.使用者相關模型

1.公共模型:BaseModel

application/model/utils.py

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)

2.使用者模型:User/UserProfile

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

3.註冊功能的實現:手機號碼唯一性驗證介面

在開發中,針對客戶端提交的資料進行驗證或提供模型資料轉換格式成字典給客戶端。可以使用Marshmallow模組來進行。

下面我們來了解一下Marshmallow模組.

2.Marshmallow模組

1.Marshmallow介紹

官方文件:https://marshmallow.readthedocs.io/en/latest/

Marshmallow,中文譯作:棉花糖。是一個輕量級的資料格式轉換的模組,也叫序列化和反序列化模組,常用於將複雜的orm模型物件與python原生資料型別之間相互轉換。marshmallow提供了豐富的api功能。如下:

  1. Serializing

    序列化[可以把資料物件轉化為可儲存或可傳輸的資料型別,例如:objects/object->list/dict,dict/list->string]

  2. Deserializing

    反序列化器[把可儲存或可傳輸的資料型別轉換成資料物件,例如:list/dict->objects/object,string->dict/list]

  3. Validation

    資料校驗,可以在反序列化階段,針對要轉換資料的內容進行型別驗證或自定義驗證。

Marshmallow本身是一個單獨的庫,基於我們當前專案使用框架是flask並且資料庫ORM框架使用SQLAlchemy,所以我們可以通過安裝flask-sqlalchemy和marshmallow-sqlalchemy整合到專案就可以了。

2.基本安裝和配置

1.模組安裝

pip install -U marshmallow-sqlalchemy
pip install -U flask-sqlalchemy
pip install -U flask-marshmallow

2.模組初始化

import os

from flask_marshmallow import Marshmallow
...
# 資料轉換器的物件建立
ma = Marshmallow()

def init_app(config_path):
    ...
    # 資料轉換器的初始化
    ma.init_app(app)

  

3.建立一個marsh藍圖模組

為了方便學習和使用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"),
]

3.MarshMallow基本構造器:Schema

marshmallow轉換資料格式主要通過構造器類來完成,而Schema類提供了資料轉換的基本功能:序列化,驗證和反序列化。所以在使用marshmallow的過程中所有的構造器類必須直接或間接繼承於Schema基類

1.基於Schema完成資料序列化轉換

1.序列化單個資料物件

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"

2.序列化多個資料物件

在前面進行的序列化操作屬於序列化單個資料物件, 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"

3.構造器巢狀使用

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"

2.基於Schema完成資料反序列化轉換

1.反序列化時設定欄位的預設值:missing

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

2.反序列化轉換/忽略部分資料:required/partial

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"

3.設定欄位只在序列化或反序列化階段才啟用:load_only/dump_only

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"
'''

4.反序列化階段的鉤子方法

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"

Tip:schema常用屬性資料型別

型別描述
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[[], …) 外來鍵型別

 

Tip:schema資料型別的常用屬性

屬性名描述
default 序列化階段中設定欄位的預設值
missing 反序列化階段中設定欄位的預設值
validate 反序列化階段呼叫的內建資料驗證器或者內建驗證集合
required 設定當前欄位的必填欄位
allow_none 是否允許為空
load_only 是否在反序列化階段才使用到當前欄位
dump_omly 是否在序列化階段才使用到當前欄位
error_messages 字典型別,可以用來替代預設的欄位異常提示語,格式:<br>error_messages={“required”: “使用者名稱為必填項。”}

 

3.反序列化階段對資料進行校驗

1.基於內建器對資料進行校驗

內建驗證器描述
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

 

 

Tip:Schema常用屬性資料型別

 

 

 

 

 

 

 

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"

2.自定義驗證方法

區域性鉤子和全域性鉤子,比如區域性鉤子對單個欄位(使用者名稱)的判斷,以及全域性鉤子對密碼,確認密碼多個欄位的判斷

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[[], …)
外來鍵型別

 

相關文章