我正在使用Flask,Flask-SQLAlchemy,Flask-Marshmallow + marshmallow-sqlalchemy,试图实现REST api PUT方法。我没有找到任何使用SQLA和Marshmallow实现更新的教程。
以下是代码:
class NodeSchema(ma.Schema):
# ...
class NodeAPI(MethodView):
decorators = [login_required, ]
model = Node
def get_queryset(self):
if g.user.is_admin:
return self.model.query
return self.model.query.filter(self.model.owner == g.user)
def put(self, node_id):
json_data = request.get_json()
if not json_data:
return jsonify({'message': 'Invalid request'}), 400
# Here is part which I can't make it work for me
data, errors = node_schema.load(json_data)
if errors:
return jsonify(errors), 422
queryset = self.get_queryset()
node = queryset.filter(Node.id == node_id).first_or_404()
# Here I need some way to update this object
node.update(data) #=> raises AttributeError: 'Node' object has no attribute 'update'
# Also tried:
# node = queryset.filter(Node.id == node_id)
# node.update(data) <-- It doesn't if know there is any object
# Wrote testcase, when user1 tries to modify node of user2. Node doesn't change (OK), but user1 gets status code 200 (NOT OK).
db.session.commit()
return jsonify(), 200
答案 0 :(得分:20)
您必须将正在编辑的对象作为参数传递给schema.load(),如下所示:
node_schema.load(json_data, instance=Node().query.get(node_id))
如果您想加载没有Model的所有必填字段,可以添加“partial = True”,如下所示:
node_schema.load(json_data, instance=Node().query.get(node_id), partial=True)
答案 1 :(得分:3)
我在这个问题上争吵了一段时间,结果又一次又一次地回到了这个帖子。最终使我的情况变得困难的是,有一个涉及SQLAlchemy会话的混淆问题。我认为这对于Flask,Flask-SQLAlchemy,SQLAlchemy和Marshmallow来说是很常见的,可以进行讨论。我当然不会声称自己是这方面的专家,但我相信我在下面所说的内容基本上是正确的。
事实上,db.session与使用Marshmallow更新数据库的过程密切相关,因此决定提供详细信息,但首先是缺少它。以下是我使用Marshmallow更新数据库的答案。这是与Jair Perrut非常有用的帖子不同的方法。我确实看过Marshmallow API但却无法让他的解决方案在所提供的代码中运行,因为当时我正在尝试他的解决方案,我没有正确管理我的SQLAlchemy会话。更进一步,人们可能会说我根本没有管理它们。可以通过以下方式更新模型:
if user['id']:
user_model = user_schema.load(user)
db.session.add(user_model.data)
db.session.commit()
else:
user_model = user_schema.load(user)
db.session.add(user_model.data)
db.sessiom.commit()
为session.add()提供一个带主键的模型,它会假定更新,保留主键并创建新记录。这并不令人惊讶,因为MySQL有ON DUPLICATE KEY UPDATE
子句,如果密钥存在则会执行更新,如果不存在则会创建。
在对应用程序的请求期间,Flask-SQLAlchemy会处理SQLAlchemy会话。在请求开始时,会话被打开,当请求被关闭时,该会话也被关闭。 Flask提供了用于设置和拆除应用程序的钩子,其中可以找到用于管理会话和连接的代码。但最后,SQLAlchemy会话由开发人员管理,而Flask-SQLAlchemy只是帮助。以下是一个说明会话管理的特殊情况。
考虑一个将用户字典作为参数获取的函数,并将其与Marshmallow()一起使用以将字典加载到模型中。在这种情况下,所需要的不是创建新对象,而是更新现有对象。一开始要记住两件事:
db = SQLAlchemy()
以满足此要求。实际上,这会为模型创建一个会话。
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
db.session.add(user_model)
和db.session.commit().
这个会话的创建方式与上面的项目符号相同。创建了2个SQLAlchemy会话。模型位于一个(SignallingSession)中,模块使用自己的(scoped_session)。事实上,有3个。棉花糖UserSchema
有sqla_session = db.session
:会话附加到它。这是第三个,详情见下面的代码:
from marshmallow_sqlalchemy import ModelSchema
from donate_api.models.donation import UserModel
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class UserSchema(ModelSchema):
class Meta(object):
model = UserModel
strict = True
sqla_session = db.session
def some_function(user):
user_schema = UserSchema()
user['customer_id'] = '654321'
if user['id']:
user_model = user_schema.load(user)
# Debug code:
user_model_query = UserModel.query.filter_by(id=3255161).first()
print db.session.object_session(user_model_query)
print db.session.object_session(user_model.data)
print db.session
db.session.add(user_model.data)
db.session.commit()
else:
user_model = user_schema.load(user)
db.session.add(user_model.data)
db.sessiom.commit()
return
在此模块的开头,导入模型,创建其会话,然后模块将创建自己的会话。当然,正如指出的那样,还有棉花糖会议。这在某种程度上是完全可以接受的,因为SQLAlchemy允许开发人员管理会话。考虑调用some_function(user)
时会发生什么,其中user['id']
被赋予数据库中存在的某些值。
由于user
包含有效的主键,因此db.session.add(user_model.data)
知道它没有创建新行,而是更新现有行。这种行为应该不会令人感到意外,并且至少应该从MySQL文档中得到预期:
13.2.5.2 INSERT ... ON DUPLICATE KEY UPDATE语法
如果指定ON DUPLICATE KEY UPDATE子句并且要插入的行将导致UNIQUE索引或PRIMARY KEY中出现重复值,则会出现旧行的UPDATE。
然后,我们看到代码片段正在为主键32155161的用户更新字典上的customer_id
。新的customer_id
是&#39; 654321&#39;。字典加载了Marshmallow并对数据库进行了提交。检查数据库可以发现它确实已更新。您可以尝试两种方法来验证这一点:
db.session.query(UserModel).filter_by(id=325516).first()
select * from user
如果您考虑以下事项:
UserModel.query.filter_by(id=3255161).customer_id
你会发现查询带回了None。该模型未与数据库同步。我无法正确管理SQLAlchemy会话。为了明确这一点,在进行单独导入时考虑打印语句的输出:
<sqlalchemy.orm.session.SignallingSession object at 0x7f81b9107b90>
<sqlalchemy.orm.session.SignallingSession object at 0x7f81b90a6150>
<sqlalchemy.orm.scoping.scoped_session object at 0x7f81b95eac50>
在这种情况下,UserModel.query
会话与Marshmallow会话不同。 Marshmallow会话是加载和添加的。这意味着查询模型不会显示我们的更改。事实上,如果我们这样做:
模型查询现在将带回更新的customer_id!考虑第二种替代方案,即通过flask_essentials
进行导入:
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
db = SQLAlchemy()
ma = Marshmallow()
<sqlalchemy.orm.session.SignallingSession object at 0x7f00fe227910>
<sqlalchemy.orm.session.SignallingSession object at 0x7f00fe227910>
<sqlalchemy.orm.scoping.scoped_session object at 0x7f00fed38710>
UserModel.query
会话现在与user_model.data
(Marshmallow)会话相同。现在UserModel.query
确实反映了数据库中的变化:Marshmallow和UserModel.query
会话是相同的。
注意:信令会话是Flask-SQLAlchemy使用的默认会话。它通过绑定选择和修改跟踪扩展了默认会话系统。
答案 2 :(得分:2)
我推出了自己的解决方案。希望它可以帮助别人。解决方案在Node模型上实现更新方法。
解决方案:
class Node(db.Model):
# ...
def update(self, **kwargs):
# py2 & py3 compatibility do:
# from six import iteritems
# for key, value in six.iteritems(kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
class NodeAPI(MethodView):
decorators = [login_required, ]
model = Node
def get_queryset(self):
if g.user.is_admin:
return self.model.query
return self.model.query.filter(self.model.owner == g.user)
def put(self, node_id):
json_data = request.get_json()
if not json_data:
abort(400)
data, errors = node_schema.load(json_data) # validate with marshmallow
if errors:
return jsonify(errors), 422
queryset = self.get_queryset()
node = queryset.filter(self.model.id == node_id).first_or_404()
node.update(**data)
db.session.commit()
return jsonify(message='Successfuly updated'), 200
答案 3 :(得分:0)
最新更新[2020]:
您可能会面临将键映射到数据库模型的问题。您的请求正文只有更新的字段,因此,您只想更改那些而不影响其他字段。可以选择写多个if条件,但这不是一个好方法。
解决方案 您只能使用sqlalchemy库实现patch或put方法。
例如:
YourModelName.query.filter_by(
your_model_column_id = 12 #change 12: where condition to find particular row
).update(request_data)
request_data应该是dict对象。例如。
{
"your_model_column_name_1": "Hello",
"your_model_column_name_2": "World",
}
在上述情况下,将仅更新两列:your_model_column_name_1
和your_model_column_name_2
更新功能将request_data映射到数据库模型并为您创建更新查询。检出此内容:https://docs.sqlalchemy.org/en/13/core/dml.html#sqlalchemy.sql.expression.update
答案 4 :(得分:0)
先前的答案似乎已过时,因为ModelSchema
现在为deprecated。
您应该改为SQLAlchemyAutoSchema
和适当的options。
class NodeSchema(SQLAlchemyAutoSchema):
class Meta:
model = Node
load_instance = True
sqla_session = db.session
node_schema = NodeSchema()
# then when you need to update a Node orm instance :
node_schema.load(node_data, instance=node, partial=True)
db.session.update()