diff --git a/reviewboard/accounts/managers.py b/reviewboard/accounts/managers.py
new file mode 100644
index 0000000000000000000000000000000000000000..99d0aab708a94e1f549e20327bf8c9d9e6460f39
--- /dev/null
+++ b/reviewboard/accounts/managers.py
@@ -0,0 +1,14 @@
+from django.db.models import Manager
+
+
+class ProfileManager(Manager):
+    def get_or_create(self, user, *args, **kwargs):
+        if hasattr(user, '_profile'):
+            return user._profile, False
+
+        profile, is_new = \
+            super(ProfileManager, self).get_or_create(user=user, *args,
+                                                      **kwargs)
+        user._profile = profile
+
+        return profile, is_new
diff --git a/reviewboard/accounts/middleware.py b/reviewboard/accounts/middleware.py
index e999bd7f96069a99a8507e52a0ade4a7994dbd51..27cf4e627faa7e12b19e11b65b8b11eb944e624d 100644
--- a/reviewboard/accounts/middleware.py
+++ b/reviewboard/accounts/middleware.py
@@ -9,7 +9,7 @@ class TimezoneMiddleware(object):
     def process_request(self, request):
         if request.user.is_authenticated():
             try:
-                user = Profile.objects.get(user=request.user)
+                user = request.user.get_profile()
                 timezone.activate(pytz.timezone(user.timezone))
             except Profile.DoesNotExist:
                 pass
diff --git a/reviewboard/accounts/models.py b/reviewboard/accounts/models.py
index 519a6f1642b8cebca8ced9fea68d2e917d7f2633..55b113c7e917b559e64d6234c6887a3e8f6ebda1 100644
--- a/reviewboard/accounts/models.py
+++ b/reviewboard/accounts/models.py
@@ -6,6 +6,7 @@ from djblets.util.db import ConcurrencyManager
 from djblets.util.fields import CounterField, JSONField
 from djblets.util.forms import TIMEZONE_CHOICES
 
+from reviewboard.accounts.managers import ProfileManager
 from reviewboard.reviews.models import Group, ReviewRequest
 from reviewboard.site.models import LocalSite
 
@@ -105,6 +106,8 @@ class Profile(models.Model):
 
     extra_data = JSONField(null=True)
 
+    objects = ProfileManager()
+
     def star_review_request(self, review_request):
         """Marks a review request as starred.
 
@@ -217,18 +220,45 @@ class LocalSiteProfile(models.Model):
         return '%s (%s)' % (self.user.username, self.local_site)
 
 
+#
+# The following functions are patched onto the User model.
+#
+
 def _is_user_profile_visible(self, user=None):
-    """Returns whether or not a user's profile is viewable by a given user.
+    """Returns whether or not a User's profile is viewable by a given user.
 
     A profile is viewable if it's not marked as private, or the viewing
     user owns the profile, or the user is a staff member.
     """
     try:
-        profile = Profile.objects.get(user=self)
+        if hasattr(self, 'is_private'):
+            # This is an optimization used by the web API. It will set
+            # is_private on this User instance through a query, saving a
+            # lookup for each instance.
+            #
+            # This must be done because select_related() and
+            # prefetch_related() won't cache reverse foreign key relations.
+            is_private = self.is_private
+        else:
+            is_private = self.get_profile().is_private
+
         return ((user and (user == self or user.is_staff)) or
-                not profile.is_private)
+                not is_private)
     except Profile.DoesNotExist:
         return True
 
+
+def _get_profile(self):
+    """Returns the profile for the User.
+
+    The profile will be cached, preventing queries for future lookups.
+    """
+    if not hasattr(self, '_profile'):
+        self._profile = Profile.objects.get(user=self)
+        self._profile.user = self
+
+    return self._profile
+
 User.is_profile_visible = _is_user_profile_visible
+User.get_profile = _get_profile
 User._meta.ordering = ('username',)
diff --git a/reviewboard/accounts/tests.py b/reviewboard/accounts/tests.py
index 93bc59ea0a85c42bcc98295f6dc43be21f73e1f8..14bd2b394bb02d7dbbe1c2f72932eb856ebf03ad 100644
--- a/reviewboard/accounts/tests.py
+++ b/reviewboard/accounts/tests.py
@@ -1,7 +1,7 @@
 from django.contrib.auth.models import User
 from djblets.testing.decorators import add_fixtures
 
-from reviewboard.accounts.models import LocalSiteProfile, Profile
+from reviewboard.accounts.models import LocalSiteProfile
 from reviewboard.testing import TestCase
 
 
@@ -21,7 +21,7 @@ class ProfileTests(TestCase):
         user1 = User.objects.get(username='admin')
         user2 = User.objects.get(username='doc')
 
-        profile = Profile.objects.get(user=user1)
+        profile = user1.get_profile()
         profile.is_private = True
         profile.save()
 
@@ -35,7 +35,7 @@ class ProfileTests(TestCase):
     def test_is_star_unstar_updating_count_correctly(self):
         """Testing if star, unstar affect review request counts correctly."""
         user1 = User.objects.get(username='admin')
-        profile1 = Profile.objects.get(user=user1)
+        profile1 = user1.get_profile()
         review_request = self.create_review_request(publish=True)
 
         site_profile = profile1.site_profiles.get(local_site=None)
diff --git a/reviewboard/diffviewer/diffutils.py b/reviewboard/diffviewer/diffutils.py
index 4f9f2d21ec522324df6b696f0a6d2825543a0402..7cfda50ffc1f3b152562ce1e2de93cd9821a1556 100644
--- a/reviewboard/diffviewer/diffutils.py
+++ b/reviewboard/diffviewer/diffutils.py
@@ -490,7 +490,7 @@ def get_enable_highlighting(user):
 
     if user.is_authenticated():
         try:
-            profile = Profile.objects.get(user=user)
+            profile = user.get_profile()
             user_syntax_highlighting = profile.syntax_highlighting
         except Profile.DoesNotExist:
             pass
diff --git a/reviewboard/reviews/datagrids.py b/reviewboard/reviews/datagrids.py
index 508d2347fad3843bc41e997c223fcfec3177af78..a70c957696447fe1012f7af7037f0db1871f7082 100644
--- a/reviewboard/reviews/datagrids.py
+++ b/reviewboard/reviews/datagrids.py
@@ -61,7 +61,7 @@ class ReviewGroupStarColumn(StarColumn):
             return queryset
 
         try:
-            profile = Profile.objects.get(user=user)
+            profile = user.get_profile()
         except Profile.DoesNotExist:
             return queryset
 
@@ -89,7 +89,7 @@ class ReviewRequestStarColumn(StarColumn):
             return queryset
 
         try:
-            profile = Profile.objects.get(user=user)
+            profile = user.get_profile()
         except Profile.DoesNotExist:
             return queryset
 
diff --git a/reviewboard/reviews/managers.py b/reviewboard/reviews/managers.py
index d87cf9b6aa6da68dea225955e906b9b5c0e7b5f3..b203ee2b304b5afe659a42a696b69c4adb629b52 100644
--- a/reviewboard/reviews/managers.py
+++ b/reviewboard/reviews/managers.py
@@ -192,9 +192,8 @@ class ReviewRequestManager(ConcurrencyManager):
 
         query = Q(target_people=query_user)
 
-        from reviewboard.accounts.models import Profile
         try:
-            profile = Profile.objects.get(user=query_user)
+            profile = query_user.get_profile()
             query = query | Q(starred_by=profile)
         except ObjectDoesNotExist:
             pass
@@ -216,9 +215,8 @@ class ReviewRequestManager(ConcurrencyManager):
 
         query = Q(target_people=query_user) | Q(target_groups__in=groups)
 
-        from reviewboard.accounts.models import Profile
         try:
-            profile = Profile.objects.get(user=query_user)
+            profile = query_user.get_profile()
             query = query | Q(starred_by=profile)
         except ObjectDoesNotExist:
             pass
diff --git a/reviewboard/reviews/templatetags/reviewtags.py b/reviewboard/reviews/templatetags/reviewtags.py
index 80614981c583b8abfdab61176e18f5350c525134..db41945a52f01028aa591d605ecdb59b148e8f58 100644
--- a/reviewboard/reviews/templatetags/reviewtags.py
+++ b/reviewboard/reviews/templatetags/reviewtags.py
@@ -316,7 +316,7 @@ def render_star(user, obj):
 
     if not hasattr(obj, 'starred'):
         try:
-            profile = Profile.objects.get(user=user)
+            profile = user.get_profile()
         except Profile.DoesNotExist:
             return ""
 
diff --git a/reviewboard/reviews/tests.py b/reviewboard/reviews/tests.py
index 246d726494c1dc95277a721281cd02040686af71..99740a7a665b878e4de1ea1792f8b11287296170 100644
--- a/reviewboard/reviews/tests.py
+++ b/reviewboard/reviews/tests.py
@@ -1140,7 +1140,7 @@ class ViewTests(TestCase):
         """Testing dashboard sidebar counts"""
         self.client.login(username='doc', password='doc')
         user = User.objects.get(username='doc')
-        profile = Profile.objects.get(user=user)
+        profile = user.get_profile()
 
         # Create all the test data.
         devgroup = self.create_review_group(name='devgroup')
diff --git a/reviewboard/reviews/views.py b/reviewboard/reviews/views.py
index 38d81928bc85a0fdc89fc58b2884d4343b41d55f..1fbbe5545069a0708d9075c2b885aec9ad30bfc7 100644
--- a/reviewboard/reviews/views.py
+++ b/reviewboard/reviews/views.py
@@ -384,7 +384,7 @@ def review_detail(request,
             visited.save()
 
         try:
-            profile = Profile.objects.get(user=request.user)
+            profile = request.user.get_profile()
             starred_review_requests = \
                 profile.starred_review_requests.filter(pk=review_request.pk)
             starred = (starred_review_requests.count() > 0)
diff --git a/reviewboard/webapi/resources/base_watched_object.py b/reviewboard/webapi/resources/base_watched_object.py
index 073fd09995f9af2b6b0ddd6d90d8ded7d8854137..5234c6e3ea399f44f7720f53ff41036eb32da1f9 100644
--- a/reviewboard/webapi/resources/base_watched_object.py
+++ b/reviewboard/webapi/resources/base_watched_object.py
@@ -34,7 +34,7 @@ class BaseWatchedObjectResource(WebAPIResource):
             local_site = self._get_local_site(local_site_name)
             if local_site:
                 user = local_site.users.get(username=username)
-                profile = Profile.objects.get(user=user)
+                profile = user.get_profile()
             else:
                 profile = Profile.objects.get(user__username=username)
 
diff --git a/reviewboard/webapi/resources/user.py b/reviewboard/webapi/resources/user.py
index f59d241daad85f1ac08691a4820b251753003891..2920dc0380e071e3f981ecfd90cc14f4243dbab0 100644
--- a/reviewboard/webapi/resources/user.py
+++ b/reviewboard/webapi/resources/user.py
@@ -58,7 +58,10 @@ class UserResource(WebAPIResource, DjbletsUserResource):
 
             query = query.filter(q)
 
-        return query
+        return query.extra(select={
+            'is_private': ('SELECT is_private FROM accounts_profile '
+                           'WHERE accounts_profile.user_id = auth_user.id')
+        })
 
     def serialize_object(self, obj, request=None, *args, **kwargs):
         data = super(UserResource, self).serialize_object(
diff --git a/reviewboard/webapi/tests/test_watched_review_group.py b/reviewboard/webapi/tests/test_watched_review_group.py
index 26c37b70e9bc6cd535a018d167bd0e05581dadae..fc321fcf869297d163e37a162a24ecc25f8b278e 100644
--- a/reviewboard/webapi/tests/test_watched_review_group.py
+++ b/reviewboard/webapi/tests/test_watched_review_group.py
@@ -1,7 +1,6 @@
 from djblets.testing.decorators import add_fixtures
 from djblets.webapi.errors import DOES_NOT_EXIST, PERMISSION_DENIED
 
-from reviewboard.accounts.models import Profile
 from reviewboard.webapi.resources import resources
 from reviewboard.webapi.tests.base import BaseWebAPITestCase
 from reviewboard.webapi.tests.mimetypes import (
@@ -35,7 +34,7 @@ class ResourceListTests(BaseWebAPITestCase):
                              populate_items):
         if populate_items:
             group = self.create_review_group(with_local_site=with_local_site)
-            profile = Profile.objects.get(user=user)
+            profile = user.get_profile()
             profile.starred_groups.add(group)
             items = [group]
         else:
@@ -68,7 +67,7 @@ class ResourceListTests(BaseWebAPITestCase):
                 [group])
 
     def check_post_result(self, user, rsp, group):
-        profile = Profile.objects.get(user=user)
+        profile = user.get_profile()
         self.assertTrue(group in profile.starred_groups.all())
 
     def test_post_with_does_not_exist_error(self):
diff --git a/reviewboard/webapi/tests/test_watched_review_request.py b/reviewboard/webapi/tests/test_watched_review_request.py
index fae1d05d534d4b33c7c697c9a09db44ab1027ee8..2d257a55d20fb60020b42e16b92705f6a0cda374 100644
--- a/reviewboard/webapi/tests/test_watched_review_request.py
+++ b/reviewboard/webapi/tests/test_watched_review_request.py
@@ -1,7 +1,6 @@
 from djblets.testing.decorators import add_fixtures
 from djblets.webapi.errors import DOES_NOT_EXIST, PERMISSION_DENIED
 
-from reviewboard.accounts.models import Profile
 from reviewboard.webapi.resources import resources
 from reviewboard.webapi.tests.base import BaseWebAPITestCase
 from reviewboard.webapi.tests.mimetypes import (
@@ -39,7 +38,7 @@ class ResourceListTests(BaseWebAPITestCase):
                 with_local_site=with_local_site,
                 publish=True)
 
-            profile = Profile.objects.get(user=user)
+            profile = user.get_profile()
             profile.starred_review_requests.add(review_request)
             items = [review_request]
         else:
@@ -87,7 +86,7 @@ class ResourceListTests(BaseWebAPITestCase):
                 [review_request])
 
     def check_post_result(self, user, rsp, review_request):
-        profile = Profile.objects.get(user=user)
+        profile = user.get_profile()
         self.assertTrue(review_request in
                         profile.starred_review_requests.all())
 
