如何刷新Django中对象的值?

时间:2012-03-20 17:30:40

标签: 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()

问题是如果在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)

这会导致错误,因为它显示在字段列表中,但您不能getattrsetattr

我有两个问题:

  • 如何刷新对象的值?

  • 我是否需要担心刷新任何引用我的对象的对象,比如外键?

12 个答案:

答案 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>