Soru Django'daki bir nesnedeki değerleri nasıl yenileyebilirim?


Django'da bir model objem var. Nesnedeki yöntemlerden biri, değerlerin doğru olmasını sağlamak için satır düzeyinde kilitleme kullanır;

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

Sorun buysa increment Bir foo nesnesinde, nesnenin değerleri veritabanındaki değerleri artık yansıtmaz. Nesnede değerleri yenilemenin veya en azından onları bayat olarak işaretlemenin bir yoluna ihtiyacım var; Anlaşılan, bu Django geliştiricileri işlevselliği eklemeyi reddet.

Aşağıdaki kodu kullanmayı denedim:

for field in self.__class__._meta.get_all_field_names():
    setattr(self, field, getattr(offer, field)) 

Ne yazık ki, aşağıdaki tanımla ikinci bir modelim var:

class Bar(model.Model):
    foo = models.ForeignKey(Foo)

Bu bir hataya neden olur, çünkü alan listelemede görünür ancak siz getattr veya setattr o.

İki sorum var:

  • Nesnemin üzerindeki değerleri nasıl yenileyebilirim?

  • Yabancı anahtarlar gibi, nesneme referansla herhangi bir nesneyi yenilemekten endişe duymalı mıyım?


25
2018-03-20 17:30


Menşei


Nesnelerinizi neden 'ferahlatıyorsunuz? Alanları kasıtlı olarak değiştiriyorsanız, neden onları kaydetmiyorsunuz? Eğer sen değil alanları kasıtlı olarak değiştirerek, neden verileri db'den geri getirmiyorsunuz? - Collin Green
"nesnenin değerleri artık veritabanındaki değerleri yansıtmaz" hangi değerler? Sayaç db'de ve modelinizin örneğinde güncellenecek, değil mi? - AJP
Yukarıda bağladığınız Bilet # 901, çekirdek geliştirici tarafından yeniden açıldı, bu yüzden belki de bir miktar umut var. - keturn
olası kopyası Veritabanından django nesnesini yeniden yükle - Anto
Hala eski değerler alıyorsanız, Django + MySQL, işlemleri (Django 1.5'de varsayılan olarak etkinleştirilmiş) kullanıyorsanız eski değerler alıyor olabilir. Görmek Django'yu önbellekleri yok saymaya ve verileri yeniden yüklemeye nasıl zorlarım - Ryan Rapp


Cevaplar:


Sonunda, içinde Django 1.8Bunu yapmak için özel bir yöntemimiz var. Denir refresh_from_db ve bu sınıfın yeni bir yöntemi django.db.models.Model.

Bir kullanım örneği:

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 sürümünüz 1,8'den azsa ancak bu işlevselliğe sahip olmak istiyorsanız, modelinizi 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()

40
2017-12-20 16:24



django bugtracker'da bir bilet yazdı, çünkü bu bir yabancı anahtarın null olarak ayarlanmış modellerini kırmış gibi görünüyor: code.djangoproject.com/ticket/24418 - Johannes Lerch
Hata düzeltildikçe kod örneğini güncellemeniz gerekir: github.com/django/django/commit/... - Johannes Lerch
Güncellenmiş! Rapor için teşekkürler @JohannesLerch! - blindOSX
Bu 1.4 ile çalışmak için görünmüyor, orada almak için herhangi bir tweaks? Aldığım hata AttributeError: 'Seçenekler' nesnesinin 'concrete_fields' özelliği yok - RaresMan
Yerleşik olduğunun farkında olun refresh_from_db öznitelikleri yeniler ama ilgili nesnelerin niteliklerini DEĞİL. Görmek stackoverflow.com/a/52046068/237091 revize edilmiş bir uygulama için. - Scott Stafford


Bunu, sınıfın kendisinden yapman gerekeceğini varsayalım ya da şöyle bir şey yapmalısınız:

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

Ama bunu dahili olarak yapıyor ve değiştiriyor self çirkinleşir ...


12
2018-03-23 09:20



Evet, bu yüzden Django geliştiricileri bir "yenileme" yöntemi eklemelidir. Garip bir şekilde, bunu yapmayı reddediyorlar, çünkü kendi başınıza yapmanın önemsiz olduğunu düşünüyorlar. Neden kolay olduğunu düşündüklerini anlayamıyorum. - Chris B.


Hmm. Bana öyle geliyor ki, herhangi bir foo.counter'ının aslında güncel olduğundan emin olamazsınız ... Ve bu, her türlü model nesnesi için geçerli, sadece bu tür sayaçların değil ...

Aşağıdaki kodu aldığınızı varsayalım:

    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

Bunun sonunda, f2.counter şimdi yanlış olacak.

Değerleri neden bu kadar önemli hale getiriyor? Neden ihtiyaç duyulduğunda yeni bir örneği geri almak mümkün değil?

    f1 = Foo.objects.get()[0]
    #stuff
    f1 = Foo.objects.get(pk=f1.id)

Ancak gerçekten ihtiyacınız varsa, kendinize bir yenileme yöntemi yaratabilirsiniz ... sorunuzda belirttiğiniz gibi, ancak ilgili alanları atlamanız gerekir, böylece yineleme yapmak istediğiniz alan adlarını listeleyebilirsiniz (yerine _meta.get_all_fieldnames). Ya da tekrar tekrar yapabilirsin Foo._meta.fields Bu size alan nesneleri verecektir ve alanın sınıfını kontrol edebilirsiniz - sanırım onlar django.db.fields.field.related.RelatedField örnekleri ise, onları atlarsınız. Bunu yapmak isterseniz, bunu sadece modülünüzü yükleyerek ve bu listeyi model sınıfınızda saklayarak hızlandırabilirsiniz. class_prepared sinyali)


1
2018-03-21 17:55





Neden kullandığınızı anladım SELECT ... FOR UPDATEama bunu yayınladıktan sonra hala etkileşim kurmalısınız. self.

Örneğin, bunun yerine şunu deneyin:

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

Satır kilitlendi, ancak şimdi etkileşim bellek içi örnekte gerçekleşiyor, bu nedenle değişiklikler senkronizasyonda kalır.


0
2018-03-20 18:09



Bunu düşündüm ama işe yaramıyor. A ve B olmak üzere iki işlemimiz olduğunu varsayalım. Eğer A prosesi nesneyi yüklüyorsa, B prosesi hala veritabanındaki sayaç alanını değiştirebilir. A prosesi artık veritabanı sırasını kilitler, ancak nesnenin bellek içi örneğinde çalışırsa, eski veriler üzerinde çalışır. - Chris B.
A ve B işlemlerinde her ikisi de "güncelleme için ... seç" seçeneğini kullanırsa, çalışır. Proses B, A işlemine ya da geri çekilinceye kadar askıda kalacak, ardından sayacın güncellenmiş kopyasını alacaktır. - Scott Smith


Django'yu kullanabilirsiniz F ifadeleri Bunu yapmak için.

Bir örnek göstermek için bu modeli kullanacağım:

# models.py
from django.db import models
class Something(models.Model):
    x = models.IntegerField()

Sonra, böyle bir şey yapabilirsiniz:

    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

0
2018-03-20 18:12



Bu örnekte işe yarayacaktı. Ancak asıl sorunumuzda, birkaç farklı nesne içeren daha karmaşık işlemler yapmamız gerekiyor ve bu da F ifadelerinin biraz ötesinde. - Chris B.


Çabuk, çirkin ve denenmemiş:

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

sanırım sen bunu başka bir şey kullanarak yapabilirsin _metayaklaşımı isinstance() aramak.

Açıkçası, ikimiz de biliyoruz ki bu mümkün olduğunda kaçınılmalıdır. Belki de iç model devlet ile daha iyi bir yaklaşım olabilir mi?

EDIT - Django 1.4 mi UPDATE desteğine SELECT Bunu çözecek misin?


0
2018-03-21 18:19



UPDATE SEÇİMİ yardımcı olmuyor, çünkü hala nesneyi yenilemenin bir yoluna ihtiyacımız var. Ve çözümünüzün işe yaradığını sanmıyorum çünkü herhangi bir ilgili alan bayat kalıyor. Onları da yenilemeliyiz. - Chris B.
Oh, benim hatam-sadece ilgili olmayan alanlar hakkında endişeli olduğunu düşündüm. - Matt Luongo


Paralel olarak çalışan uzun süreli süreçlerim var. Hesaplamalar yapıldıktan sonra, değerleri güncellemek ve modeli kaydetmek istiyorum, ancak tüm işlemin bir işlem yapmasını istemiyorum. Yani stratejim gibi bir şey

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

0
2017-11-03 00:33