Załaduj ponownie obiekt django z bazy danych

160

Czy można odświeżyć stan obiektu django z bazy danych? Mam na myśli zachowanie mniej więcej równoważne z:

new_self = self.__class__.objects.get(pk=self.pk)
for each field of the record:
    setattr(self, field, getattr(new_self, field))

AKTUALIZACJA: Znaleziono ponownie otwartą / wontfix wojnę w trackerze: http://code.djangoproject.com/ticket/901 . Nadal nie rozumiem, dlaczego opiekunom to się nie podoba.

grep
źródło
W zwykłym kontekście SQL nie ma to sensu. Obiekt bazy danych można zmienić dopiero po zakończeniu transakcji i wykonaniu commmit. Gdy już to zrobisz, będziesz musiał poczekać na zatwierdzenie następnej transakcji SQL. Dlaczego to robisz? Jak długo będziesz czekać na następną transakcję?
S.Lott,
Wydaje się, że jest to niepotrzebna funkcja; jest już możliwe ponowne wyszukanie obiektu w bazie danych.
Stephan
Chciałbym to za dobrze, ale został zamknięty wielokrotnie tutaj
eruciform
2
Nie jest to właściwe, ponieważ obiekty modelu Django są proxy. Jeśli dostaniesz ten sam wiersz tabeli do dwóch obiektów - x1 = X.objects.get (id = 1); x2 = X.objects.get (id = 1), będą testowane jako równe, ale są to różne obiekty i stan nie jest udostępniany. Możesz zmienić zarówno samodzielnie, jak i zapisać - ostatni zapisany określa stan wiersza w bazie danych. Dlatego poprawne jest ponowne ładowanie z prostym przypisaniem - x1 = X.objects.get (id = 1). Zastosowanie metody przeładowania doprowadziłoby do tego, że wiele osób błędnie wywnioskowałoby, że x1.f = 'nowa wartość'; (x1.f == x2.f) jest True.
Paul Whipp,

Odpowiedzi:

259

Od Django 1.8 odświeżanie obiektów jest wbudowane. Link do dokumentacji .

def test_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()
    self.assertEqual(obj.val, 2)
Tim Fletcher
źródło
@ fcracker79 Tak, zostało zaimplementowane dopiero w 1.8. W przypadku wcześniejszych wersji Django najlepiej wybrać jedną z pozostałych odpowiedzi.
Tim Fletcher
1
Nie wiesz, co oznacza „Aktualizacja wszystkich nieodroczonych pól” wspomniana w dokumentach?
Yunti
1
@Yunti Możesz odroczyć pola lub wyraźnie poprosić o podanie tylko podzbioru pól, a wynikowy obiekt zostanie wypełniony tylko częściowo. refresh_from_dbzaktualizuje tylko takie już wypełnione pola.
301_Moved_Permanently
Nie można znaleźć szczegółów w dokumentacji, ale poprawnie zgłasza DoesNotExistwyjątek, jeśli podstawowy obiekt został usunięty podczas wywoływania refresh_from_db. FYI.
Tim Tisdall
28

Zauważyłem, że relatywnie łatwo jest przeładować obiekt z bazy danych w następujący sposób:

x = X.objects.get(id=x.id)
Rory
źródło
19
Tak, ale ... po tym musisz zaktualizować wszystkie odniesienia do tego obiektu. Niezbyt poręczny i podatny na błędy.
grep
2
Okazało się, że jest to konieczne, gdy Celery zaktualizował mój obiekt w bazie danych poza django, django najwyraźniej zachował pamięć podręczną obiektu, ponieważ nie miał pojęcia, że ​​się zmienił.
Bob Spryn,
3
z django.db.models.loading import get_model; instance = get_model (instance) .objects.get (pk = instance.pk)
Erik
1
@grep właśnie stracił 2 godziny na pisaniu testu dla tego przypadku użycia: 1: Zainicjuj model; 2: Zaktualizuj model za pomocą formularza; 3: Sprawdź, czy nowa wartość została zaktualizowana .... Więc tak, podatne na błędy.
vlad-ardelean
3
Myślę, że refresh_from_dbrozwiązuje wszystkie te problemy.
Flimm
16

W odniesieniu do komentarza @ grep, czy nie powinno być możliwe:

# Put this on your base model (or monkey patch it onto django's Model if that's your thing)
def reload(self):
    new_self = self.__class__.objects.get(pk=self.pk)
    # You may want to clear out the old dict first or perform a selective merge
    self.__dict__.update(new_self.__dict__)

# Use it like this
bar.foo = foo
assert bar.foo.pk is None
foo.save()
foo.reload()
assert bar.foo is foo and bar.foo.pk is not None
Eloff
źródło
Dzięki za rozwiązanie. Gdyby tylko TAK pozwolił na wiele głosów pozytywnych!
user590028
11
Django udostępnia teraz refresh_from_dbmetodę.
Flimm
9

Jak zauważył @Flimm, jest to naprawdę świetne rozwiązanie:

foo.refresh_from_db()

Spowoduje to ponowne załadowanie wszystkich danych z bazy danych do obiektu.

Ron
źródło