elethan
elethan

Reputation: 16993

Django model reload_from_db() vs. explicitly recalling from db

If I have an object retrieved from a model, for example:

obj = Foo.objects.first()

I know that if I want to reference this object later and make sure that it has the current values from the database, I can call:

obj.refresh_from_db()

My question is, is there any advantage to using the refresh_from_db() method over simply doing?:

obj = Foo.objects.get(id=obj.id)

As far as I know, the result will be the same. refresh_from_db() seems more explicit, but in some cases it means an extra line of code. Lets say I update the value field for obj and later want to test that it has been updated to False. Compare:

obj = Foo.objects.first()
assert obj.value is True
# value of foo obj is updated somewhere to False and I want to test below
obj.refresh_from_db()
assert obj.value is False

with this:

obj = Foo.objects.first()
assert obj.value is True
# value of foo obj is updated somewhere to False and I want to test below
assert Foo.objects.get(id=obj.id).value is False

I am not interested in a discussion of which of the two is more pythonic. Rather, I am wondering if one method has a practical advantage over the other in terms of resources, performance, etc. I have read this bit of documentation, but I was not able to ascertain from that whether there is an advantage to using reload_db(). Thank you!

Upvotes: 6

Views: 6897

Answers (3)

Jimmy Engelbrecht
Jimmy Engelbrecht

Reputation: 713

It seems there is a difference if you use cached properties.

See here:

p.roles[0]
<Role: 16888649>
p.refresh_from_db()
p.roles[0]
<Role: 16888649>
p = Person.objects.get(id=p.id)
p.roles[0]
<Role: 16888650>

Definition from models.py:

@cached_property
def roles(self):
    return Role.objects.filter(employer__person=self).order_by("id")

Upvotes: 0

mfonism
mfonism

Reputation: 573

Just to add to @serg's answer, there's a case where explicitly re-fetching from the db is helpful and refreshing from the db isn't so much useful.

This is the case when you're adding permissions to an object and checking them immediately afterwards, and you need to clear the cached permissions for the object so that your permission checks work as expected.

According to the permission caching section of the django documentation:

The ModelBackend caches permissions on the user object after the first time they need to be fetched for a permissions check. This is typically fine for the request-response cycle since permissions aren’t typically checked immediately after they are added (in the admin, for example). If you are adding permissions and checking them immediately afterward, in a test or view for example, the easiest solution is to re-fetch the user from the database...

For an example, consider this block of code inspired by the one in the documentation cited above:

from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType

from smoothies.models import Smoothie


def force_deblend(user, smoothie):

    # Any permission check will cache the current set of permissions
    if not user.has_perm('smoothies.deblend_smoothie'):

        permission = Permission.objects.get(
            codename='deblend_smoothie',
            content_type=ContentType.objects.get_for_model(Smoothie)
        )
        user.user_permissions.add(permission)

        # Subsequent permission checks hit the cached permission set
        print(user.has_perm('smoothies.deblend_smoothie'))   # False

        # Re-fetch user (explicitly) from db to clear permissions cache
        # Be aware that user.refresh_from_db() won't help here
        user = get_user_model().objects.get(pk=user.pk)

        # Permission cache is now repopulated from the database
        print(user.has_perm('smoothies.deblend_smoothie'))   # True

        ...

    ...

Upvotes: 0

serg
serg

Reputation: 111265

Django sources are usually relatively easy to follow. If we look at the refresh_from_db() implementation, at its core it is still using this same Foo.objects.get(id=obj.id) approach:

db_instance_qs = self.__class__._default_manager.using(db).filter(pk=self.pk)
...
db_instance_qs = db_instance_qs.only(*fields)
...
db_instance = db_instance_qs.get()

Only there are couple extra bells and whistles:

  • deferred fields are ignored
  • stale foreign key references are cleared (according to the comment explanation)

So for everyday usage it is safe to say that they are pretty much the same, use whatever you like.

Upvotes: 8

Related Questions