Python 我们需要执行涉及两个不同对象的更复杂的事务,而这仅仅是F表达式无法实现的。选择更新没有帮助,因为我们仍然需要一种方法来刷新对象。我认为您的解决方案不起作用,因为任何相关字段都会过时。我们也需要刷新它们。哦,我的错误-我以为你只关心不相关的字段。是的,这就是
Python 我们需要执行涉及两个不同对象的更复杂的事务,而这仅仅是F表达式无法实现的。选择更新没有帮助,因为我们仍然需要一种方法来刷新对象。我认为您的解决方案不起作用,因为任何相关字段都会过时。我们也需要刷新它们。哦,我的错误-我以为你只关心不相关的字段。是的,这就是,python,django,django-models,Python,Django,Django Models,我们需要执行涉及两个不同对象的更复杂的事务,而这仅仅是F表达式无法实现的。选择更新没有帮助,因为我们仍然需要一种方法来刷新对象。我认为您的解决方案不起作用,因为任何相关字段都会过时。我们也需要刷新它们。哦,我的错误-我以为你只关心不相关的字段。是的,这就是Django开发人员应该添加“刷新”方法的原因。奇怪的是,他们拒绝做这件事,因为他们觉得自己一个人做这件事微不足道。我不明白他们为什么认为这很容易。只是在django bugtracker中写了一张罚单,因为这似乎在foreignkey设置为n
我们需要执行涉及两个不同对象的更复杂的事务,而这仅仅是F表达式无法实现的。选择更新没有帮助,因为我们仍然需要一种方法来刷新对象。我认为您的解决方案不起作用,因为任何相关字段都会过时。我们也需要刷新它们。哦,我的错误-我以为你只关心不相关的字段。是的,这就是Django开发人员应该添加“刷新”方法的原因。奇怪的是,他们拒绝做这件事,因为他们觉得自己一个人做这件事微不足道。我不明白他们为什么认为这很容易。只是在django bugtracker中写了一张罚单,因为这似乎在foreignkey设置为null的模型上出现了问题:您应该在错误修复后更新代码示例:更新!感谢@JohannesLerch的报道!这在1.4中似乎不起作用,有什么改进吗?我得到的错误是AttributeError:“Options”对象没有属性“concrete\u fields”请注意内置的
refresh\u from\u db
刷新属性,但不刷新相关对象的属性。请参阅,以了解修改后的实现。
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()
for field in self.__class__._meta.get_all_field_names():
setattr(self, field, getattr(offer, field))
class Bar(model.Model):
foo = models.ForeignKey(Foo)
@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()
# 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
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
f1 = Foo.objects.get()[0]
#stuff
f1 = Foo.objects.get(pk=f1.id)
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))
def refresh(obj):
""" Reload an object from the database """
return obj.__class__._default_manager.get(pk=obj.pk)
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()
def refresh_and_lock(obj):
""" Return an fresh copy with a lock."""
return obj.__class__._default_manager.select_for_update().get(pk=obj.pk)
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)
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()
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()
refresh_from_db()
obj.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()]
obj.reload()