Как обновить значения объекта в Django?

У меня есть модельный объект в 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() 

Проблема в том, что если вы вызываете increment объекта foo, значения объекта больше не отражают значения в базе данных. Мне нужен способ обновить значения в объекте или, по крайней мере, пометить их как устаревшие, чтобы они были переназначены, если необходимо. По-видимому, это функциональность, которую разработчики Django отказываются добавлять .

Я попытался использовать следующий код:

 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 .

У меня есть два вопроса:

  • Как обновить значения на моем объекте?

  • Нужно ли беспокоиться об обновлении любых объектов со ссылками на мой объект, например, внешние ключи?

10 Solutions collect form web for “Как обновить значения объекта в Django?”

Наконец, в 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() 

Я предполагаю, что вам нужно сделать это из самого класса, или вы просто сделаете что-то вроде:

 def refresh(obj): """ Reload an object from the database """ return obj.__class__._default_manager.get(pk=obj.pk) 

Но делать это внутренне и заменять self становится уродливым …

Хм. Мне кажется, что вы никогда не можете быть уверены, что ваш любой 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 полей, которые вы хотите _meta.get_all_fieldnames (а не _meta.get_all_fieldnames ). Или вы можете перебирать Foo._meta.fields это даст вам объекты Field, и вы можете просто проверить класс поля – я думаю, что если они являются экземплярами django.db.fields.field.related.RelatedField, то вы пропустите их. Если бы вы захотели, то ускорите это, сделав это только при загрузке вашего модуля и сохранении этого списка в вашем классе модели (используйте сигнал класса_prepared )

Я понимаю, почему вы используете 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() 

Строка заблокирована, но теперь взаимодействие происходит на экземпляре in-memory, поэтому изменения остаются в синхронизации.

Вы можете использовать выражения F для Django для этого.

Чтобы показать пример, я буду использовать эту модель:

 # 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 

Быстрый, уродливый и непроверенный:

 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() .

Очевидно, мы оба знаем, что этого следует избегать, если это возможно. Может быть, лучший подход состоял бы в futz с внутренним модельным состоянием?

EDIT. Будет ли поддержка Django 1.4 SELECT FOR UPDATE решить эту проблему?

У меня есть несколько длительных процессов, которые работают параллельно. После выполнения вычислений я хочу обновить значения и сохранить модель, но я не хочу, чтобы весь процесс связывал транзакцию. Поэтому моя стратегия – это что-то вроде

 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() 

Это сочетает в себе лучшие из двух ответов выше и добавляет современный синтаксис 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 () и топать при изменении сразу после совершения первой транзакции.

У меня была аналогичная потребность, и, хотя вы не можете эффективно обновлять существующий объект без потенциальной подделки его целостности, вы все равно можете применять передовой опыт во время реализации. Что касается меня, я выделяю объект как устаревший и препятствую дальнейшему доступу к нему, как показано в приведенном ниже примере:

 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) 

(написано «на лету», простите все возможные опечатки :-))

Вы можете использовать

 refresh_from_db() 

Например:

 obj.refresh_from_db() 

https://docs.djangoproject.com/en/1.11/ref/models/instances/#refreshing-objects-from-database

  • Расширение профиля пользователя в Django. Создание пользователей пользователями
  • Django admin inline inlines (или, три редактирования модели сразу)
  • Есть ли умный способ получить предыдущий / следующий элемент с помощью Django ORM?
  • Получить имя основного поля модели Django
  • Что такое замена DateModifierNode в новых версиях Django
  • Комплексный запрос с Django (сообщения от всех друзей)
  • Использование нескольких баз данных Django с RedShift
  • Система наследования и администрирования модели Django
  •  
    Interesting Posts for Van-Lav

    Selenium … Программа работает отлично для Firefox, но не Chrome

    Как писать в существующий файл excel без нарушения формул с помощью openpyxl?

    Каков наилучший способ программного входа в веб-сайт для скрипирования экрана? (Желательно в Python)

    Как применять различные функции к объекту groupby?

    Создание нулевого заполненного кадра данных панд

    ValueError: требуется больше, чем 2 значения для распаковки в Python 2.6.6

    Вариант «symlog» Matplotlib: как предотвратить кривые, которые «возвращаются»?

    Как получить статус процесса с помощью pid?

    Загрузка изображений с url в память и создание zip-архива в памяти для отправки в браузер

    Помощь с круговым связанным списком в Python

    Обнаруживать лицо, затем автозахватывать изображения

    Отфильтровать текст, который появляется между двумя метками

    повышение эффективности кода: стандартное отклонение на раздвижных окнах

    Python / Tkinter: Использование языков Tkinter для RTL (справа налево), таких как арабский / иврит?

    Тип Python () или __class__, == или

    Python - лучший язык программирования в мире.