我在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()
问题是如果在foo对象上调用increment
,则对象的值不再反映数据库中的值。我需要一种方法来刷新对象中的值,或者至少将它们标记为陈旧,以便在必要时重新获取它们。显然,这是Django refuse to add的开发人员的功能。
我尝试使用以下代码:
for field in self.__class__._meta.get_all_field_names():
setattr(self, field, getattr(offer, field))
不幸的是,我有第二个模型,其定义如下:
class Bar(model.Model):
foo = models.ForeignKey(Foo)
这会导致错误,因为它显示在字段列表中,但您不能getattr
或setattr
。
我有两个问题:
如何刷新对象的值?
我是否需要担心刷新任何引用我的对象的对象,比如外键?
答案 0 :(得分:40)
最后,在 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()
如果您的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()
答案 1 :(得分:13)
我认为你必须在课堂内做这件事,或者你只需要这样做:
def refresh(obj):
""" Reload an object from the database """
return obj.__class__._default_manager.get(pk=obj.pk)
但是在内部执行此操作并替换self
会变得丑陋......
答案 2 :(得分:1)
嗯。在我看来,你永远无法确定你的任何foo.counter实际上是最新的......对于任何类型的模型对象都是如此,而不仅仅是这些类型的计数器......
假设您有以下代码:
f1 = Foo.objects.get()[0]
f2 = Foo.objects.get()[0] #probably somewhere else!
f1.increment() #let's assume this acidly increments counter both in db and in f1
f2.counter # is wrong
最后,f2.counter现在会出错。
为什么刷新这么重要的值 - 为什么不能只在需要的时候找回新的实例?
f1 = Foo.objects.get()[0]
#stuff
f1 = Foo.objects.get(pk=f1.id)
但是如果你真的需要你可以自己创建一个刷新方法...就像你在问题中指出的那样但你需要跳过相关的字段,所以你可以只指定你要迭代的字段名列表(而不是比_meta.get_all_fieldnames
)。或者你可以迭代Foo._meta.fields
它会给你Field对象,你可以检查字段的类 - 我想如果它们是django.db.fields.field.related.RelatedField的实例那么你跳过他们。如果你想要的话,只有在加载模块并将此列表存储在模型类中时才能加快速度(使用class_prepared signal)
答案 3 :(得分:0)
我知道你使用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()
该行已锁定,但现在正在内存实例上进行交互,因此更改仍保持同步。
答案 4 :(得分:0)
您可以使用Django的F expressions来执行此操作。
为了举例说明,我将使用这个模型:
# models.py
from django.db import models
class Something(models.Model):
x = models.IntegerField()
然后,你可以这样做:
from models import Something
from django.db.models import F
blah = Something.objects.create(x=3)
print blah.x # 3
# set property x to itself plus one atomically
blah.x = F('x') + 1
blah.save()
# reload the object back from the DB
blah = Something.objects.get(pk=blah.pk)
print blah.x # 4
答案 5 :(得分:0)
快速,丑陋且未经测试:
from django.db.models.fields.related import RelatedField
for field in self.__class__._meta.fields:
if not isinstance(field, RelatedField):
setattr(self, field.attname, getattr(offer, field))
虽然我认为你可以使用其他_meta
方法对isinstance()
电话进行此操作。
显然,我们都知道如果可能的话应该避免这种情况。也许更好的方法是使用内部模型状态来进行收益?
编辑 - Django 1.4 SELECT FOR UPDATE support是否会解决这个问题?
答案 6 :(得分:0)
我有一些长时间运行的进程并行运行。计算完成后,我想更新值并保存模型,但我不希望整个过程占用事务。所以我的策略就像是
model = Model.objects.get(pk=pk)
# [ do a bunch of stuff here]
# get a fresh model with possibly updated values
with transaction.commit_on_success():
model = model.__class__.objects.get(pk=model.pk)
model.field1 = results
model.save()
答案 7 :(得分:0)
这结合了上述两个答案中的最佳答案,并添加了最新的django语法:
获取最新数据并保证*您的交易保持新鲜:
def refresh_and_lock(obj):
""" Return an fresh copy with a lock."""
return obj.__class__._default_manager.select_for_update().get(pk=obj.pk)
如果 所有 更改了对象,则仅才能通过select_for_update。获取没有锁定的对象的其他进程将在save()而不是get()处挂起,并在第一次事务提交后立即踩踏更改。
答案 8 :(得分:0)
我有类似的需求,虽然您无法有效地刷新现有对象而不会篡改其完整性,但您仍然可以在实施时强制执行最佳实践。对于我所关心的问题,我将该对象标记为陈旧,并使其无法进一步访问它,如下例所示:
class MyModelManager(Manager):
def get_the_token(self, my_obj):
# you need to get that before marking the object stale :-)
pk = my_obj.pk
# I still want to do the update so long a pool_size > 0
row_count = self.filter(pk=pk, pool_size__gt=0).update(pool_size=F('pool_size')-1)
if row_count == 0:
# the pool has been emptied in the meantime, deal with it
raise Whatever
# after this row, one cannot ask anything to the record
my_obj._stale = True
# here you're returning an up-to-date instance of the record
return self.get(pk=pk)
class MyModel(Model):
pool_size = IntegerField()
objects = MyModelManager()
def __getattribute__(self, name):
try:
# checking if the object is marked as stale
is_stale = super(MyModel, self).__getattribute__('_stale'):
# well, it is probably...
if is_stale: raise IAmStale("you should have done obj = obj.get_token()")
except AttributeError:
pass
# it is not stale...
return super(MyModel, self).__getattribute__(name)
def get_token(self):
# since it's about an operation on the DB rather than on the object,
# we'd rather do that at the manager level
# any better way out there to get the manager from an instance?
# self._meta.concrete_model.objects ?
self.__class__.objects.get_the_token(self, my_obj)
(动态写,原谅任何可能的错别字:-))
答案 9 :(得分:0)
您可以使用
refresh_from_db()
例如:
obj.refresh_from_db()
https://docs.djangoproject.com/en/1.11/ref/models/instances/#refreshing-objects-from-database
答案 10 :(得分:0)
我一直在使用这样的方法,因为新的内置refresh_from_db
不会刷新已更改其属性的子级,经常会引起问题。这将清除所有外键的缓存。
def super_refresh_from_db(self):
""" refresh_from_db only reloads local values and any deferred objects whose id has changed.
If the related object has itself changed, we miss that. This attempts to kind of get that back. """
self.refresh_from_db()
db = self._state.db
db_instance_qs = self.__class__._default_manager.using(db).filter(pk=self.pk)
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
if field.is_relation and field.get_cache_name() in self.__dict__:
del self.__dict__[field.get_cache_name()]
答案 11 :(得分:0)
我遇到了同样的问题,这对我有用:
<td></td>