summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorAnssi Kääriäinen <akaariai@gmail.com>2014-07-05 09:03:52 +0300
committerTim Graham <timograham@gmail.com>2014-11-28 06:54:00 -0500
commitc7175fcdfe94be60c04f3b1ceb6d0b2def2b6f09 (patch)
tree409248caf9fe722d53eb1d7654176bb8a5f5c631 /django
parent912ad03226687dae91971ebd7e5cf87521f6b0de (diff)
Fixed #901 -- Added Model.refresh_from_db() method
Thanks to github aliases dbrgn, carljm, slurms, dfunckt, and timgraham for reviews.
Diffstat (limited to 'django')
-rw-r--r--django/db/models/base.py68
-rw-r--r--django/db/models/query_utils.py10
2 files changed, 69 insertions, 9 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index e4f970af05..c2f711ea08 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -13,6 +13,8 @@ from django.core.exceptions import (ObjectDoesNotExist,
MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS)
from django.db import (router, connections, transaction, DatabaseError,
DEFAULT_DB_ALIAS, DJANGO_VERSION_PICKLE_KEY)
+from django.db.models import signals
+from django.db.models.constants import LOOKUP_SEP
from django.db.models.deletion import Collector
from django.db.models.fields import AutoField, FieldDoesNotExist
from django.db.models.fields.related import (ForeignObjectRel, ManyToOneRel,
@@ -21,7 +23,6 @@ from django.db.models.manager import ensure_default_manager
from django.db.models.options import Options
from django.db.models.query import Q
from django.db.models.query_utils import DeferredAttribute, deferred_class_factory
-from django.db.models import signals
from django.utils import six
from django.utils.deprecation import RemovedInDjango19Warning
from django.utils.encoding import force_str, force_text
@@ -552,6 +553,71 @@ class Model(six.with_metaclass(ModelBase)):
pk = property(_get_pk_val, _set_pk_val)
+ 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 = 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
+
def serializable_value(self, field_name):
"""
Returns the value of the field name for this instance. If the field is
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
index 6dbeb855d1..0fbe25cca7 100644
--- a/django/db/models/query_utils.py
+++ b/django/db/models/query_utils.py
@@ -109,14 +109,8 @@ class DeferredAttribute(object):
# might be able to reuse the already loaded value. Refs #18343.
val = self._check_parent_chain(instance, name)
if val is None:
- # We use only() instead of values() here because we want the
- # various data coercion methods (to_python(), etc.) to be
- # called here.
- val = getattr(
- non_deferred_model._base_manager.only(name).using(
- instance._state.db).get(pk=instance.pk),
- self.field_name
- )
+ instance.refresh_from_db(fields=[self.field_name])
+ val = getattr(instance, self.field_name)
data[self.field_name] = val
return data[self.field_name]