Chr*_* B. 26 python django django-models
我在Django中有一个模型对象.对象上的一个方法使用行级锁定来确保值是准确的,如下所示:
class Foo(model.Model):
counter = models.IntegerField()
@transaction.commit_on_success
def increment(self):
x = Foo.objects.raw("SELECT * from fooapp_foo WHERE id = %s FOR UPDATE", [self.id])[0]
x.counter += 1
x.save()
Run Code Online (Sandbox Code Playgroud)
问题是如果调用incrementfoo对象,则对象的值不再反映数据库中的值.我需要一种方法来刷新对象中的值,或者至少将它们标记为陈旧,以便在必要时重新获取它们.显然,这是Django开发人员拒绝添加的功能.
我尝试使用以下代码:
for field in self.__class__._meta.get_all_field_names():
setattr(self, field, getattr(offer, field))
Run Code Online (Sandbox Code Playgroud)
不幸的是,我有第二个模型,其定义如下:
class Bar(model.Model):
foo = models.ForeignKey(Foo)
Run Code Online (Sandbox Code Playgroud)
这会导致错误,因为它显示在字段列表中但您不能getattr或setattr它.
我有两个问题:
如何刷新对象的值?
我是否需要担心刷新任何引用我的对象的对象,比如外键?
bli*_*OSX 42
最后,在Django 1.8中,我们有一个特定的方法来执行此操作.它叫做refresh_from_db,它是类的一个新方法django.db.models.Model.
用法示例:
def update_result(self):
obj = MyModel.objects.create(val=1)
MyModel.objects.filter(pk=obj.pk).update(val=F('val') + 1)
# At this point obj.val is still 1, but the value in the database
# was updated to 2. The object's updated value needs to be reloaded
# from the database.
obj.refresh_from_db()
Run Code Online (Sandbox Code Playgroud)
如果您的Django版本小于1.8但您想拥有此功能,请修改您的模型以继承RefreshableModel:
from django.db import models
from django.db.models.constants import LOOKUP_SEP
from django.db.models.query_utils import DeferredAttribute
class RefreshableModel(models.Model):
class Meta:
abstract = True
def get_deferred_fields(self):
"""
Returns a set containing names of deferred fields for this instance.
"""
return {
f.attname for f in self._meta.concrete_fields
if isinstance(self.__class__.__dict__.get(f.attname), DeferredAttribute)
}
def refresh_from_db(self, using=None, fields=None, **kwargs):
"""
Reloads field values from the database.
By default, the reloading happens from the database this instance was
loaded from, or by the read router if this instance wasn't loaded from
any database. The using parameter will override the default.
Fields can be used to specify which fields to reload. The fields
should be an iterable of field attnames. If fields is None, then
all non-deferred fields are reloaded.
When accessing deferred fields of an instance, the deferred loading
of the field will call this method.
"""
if fields is not None:
if len(fields) == 0:
return
if any(LOOKUP_SEP in f for f in fields):
raise ValueError(
'Found "%s" in fields argument. Relations and transforms '
'are not allowed in fields.' % LOOKUP_SEP)
db = using if using is not None else self._state.db
if self._deferred:
non_deferred_model = self._meta.proxy_for_model
else:
non_deferred_model = self.__class__
db_instance_qs = non_deferred_model._default_manager.using(db).filter(pk=self.pk)
# Use provided fields, if not set then reload all non-deferred fields.
if fields is not None:
fields = list(fields)
db_instance_qs = db_instance_qs.only(*fields)
elif self._deferred:
deferred_fields = self.get_deferred_fields()
fields = [f.attname for f in self._meta.concrete_fields
if f.attname not in deferred_fields]
db_instance_qs = db_instance_qs.only(*fields)
db_instance = db_instance_qs.get()
non_loaded_fields = db_instance.get_deferred_fields()
for field in self._meta.concrete_fields:
if field.attname in non_loaded_fields:
# This field wasn't refreshed - skip ahead.
continue
setattr(self, field.attname, getattr(db_instance, field.attname))
# Throw away stale foreign key references.
if field.rel and field.get_cache_name() in self.__dict__:
rel_instance = getattr(self, field.get_cache_name())
local_val = getattr(db_instance, field.attname)
related_val = None if rel_instance is None else getattr(rel_instance, field.related_field.attname)
if local_val != related_val:
del self.__dict__[field.get_cache_name()]
self._state.db = db_instance._state.db
class MyModel(RefreshableModel):
# Your Model implementation
pass
obj = MyModel.objects.create(val=1)
obj.refresh_from_db()
Run Code Online (Sandbox Code Playgroud)
DrM*_*ers 13
我假设您必须从类本身内部执行此操作,或者您只需执行以下操作:
def refresh(obj):
""" Reload an object from the database """
return obj.__class__._default_manager.get(pk=obj.pk)
Run Code Online (Sandbox Code Playgroud)
但在内部做这件事并取代self变得丑陋......
我明白你为什么要使用SELECT ... FOR UPDATE,但是一旦你发布了这个,你仍然应该与 进行交互self。
例如,尝试这样做:
@transaction.commit_on_success
def increment(self):
Foo.objects.raw("SELECT id from fooapp_foo WHERE id = %s FOR UPDATE", [self.id])[0]
self.counter += 1
self.save()
Run Code Online (Sandbox Code Playgroud)
该行已锁定,但现在交互正在内存实例上进行,因此更改保持同步。
| 归档时间: |
|
| 查看次数: |
19608 次 |
| 最近记录: |