Skip to content

Commit 5dc774d

Browse files
authored
Versions: enforce using project.versions instead of Version.filter(project=project) (#12411)
When using Version.filter(project=project), django will re-fetch the project for each version when accessed, but when using `project.versions` django will share the same project instance on each version, so fewer queries and less memory is used. I checked and all querysets in public make use of `self.filter`, so everything should work properly. There are probably more querysets that can benefit from a similar change, but this is just a start.
1 parent 59c95b6 commit 5dc774d

File tree

13 files changed

+38
-43
lines changed

13 files changed

+38
-43
lines changed

readthedocs/api/v2/permissions.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from rest_framework_api_key.permissions import KeyParser
66

77
from readthedocs.api.v2.models import BuildAPIKey
8-
from readthedocs.builds.models import Version
98

109

1110
class IsOwner(permissions.BasePermission):
@@ -39,9 +38,8 @@ def has_permission(self, request, view):
3938
project = view._get_project()
4039
version = view._get_version()
4140
has_access = (
42-
Version.objects.public(
41+
project.versions.public(
4342
user=request.user,
44-
project=project,
4543
only_active=False,
4644
)
4745
.filter(pk=version.pk)

readthedocs/api/v2/views/core_views.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
from readthedocs.api.v2.permissions import HasBuildAPIKey
1212
from readthedocs.builds.constants import LATEST
13-
from readthedocs.builds.models import Version
1413
from readthedocs.core.templatetags.core_tags import make_document_url
1514
from readthedocs.projects.models import Project
1615

@@ -61,7 +60,7 @@ def docurl(request):
6160

6261
project = get_object_or_404(Project, slug=project)
6362
version = get_object_or_404(
64-
Version.objects.public(request.user, project=project, only_active=False),
63+
project.versions.public(request.user, only_active=False),
6564
slug=version,
6665
)
6766
return Response(

readthedocs/builds/filters.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from readthedocs.builds.constants import BUILD_FINAL_STATES
88
from readthedocs.builds.constants import BUILD_STATE_FINISHED
99
from readthedocs.builds.constants import EXTERNAL
10-
from readthedocs.builds.models import Version
10+
from readthedocs.builds.constants import INTERNAL
1111
from readthedocs.core.filters import FilteredModelChoiceFilter
1212
from readthedocs.core.filters import ModelFilterSet
1313

@@ -67,9 +67,8 @@ def get_version_queryset(self):
6767
# Copied from the version listing view. We need this here as this is
6868
# what allows the build version list to populate. Otherwise the
6969
# ``all()`` queryset method is used.
70-
return Version.internal.public(
70+
return self.project.versions(manager=INTERNAL).public(
7171
user=self.request.user,
72-
project=self.project,
7372
)
7473

7574
def get_state(self, queryset, _, value):

readthedocs/builds/querysets.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ def _public_only(self):
8282
def public(
8383
self,
8484
user=None,
85-
project=None,
8685
only_active=True,
8786
include_hidden=True,
8887
only_built=False,
@@ -94,15 +93,19 @@ def public(
9493
9594
External versions use the `Project.external_builds_privacy_level`
9695
field instead of its `privacy_level` field.
96+
97+
.. note::
98+
99+
Avoid filtering by reverse relationships in this method (like project),
100+
and instead use project.builds or similar, so the same object is shared
101+
between the results.
97102
"""
98103
queryset = self._public_only()
99104
if user:
100105
if user.is_superuser:
101106
queryset = self.all()
102107
else:
103108
queryset = self._add_from_user_projects(queryset, user, admin=True, member=True)
104-
if project:
105-
queryset = queryset.filter(project=project)
106109
if only_active:
107110
queryset = queryset.filter(active=True)
108111
if only_built:

readthedocs/builds/views.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
from requests.utils import quote
1616

1717
from readthedocs.builds.constants import BUILD_FINAL_STATES
18+
from readthedocs.builds.constants import INTERNAL
1819
from readthedocs.builds.filters import BuildListFilter
1920
from readthedocs.builds.models import Build
20-
from readthedocs.builds.models import Version
2121
from readthedocs.core.filters import FilterContextMixin
2222
from readthedocs.core.permissions import AdminPermission
2323
from readthedocs.core.utils import cancel_build
@@ -55,9 +55,8 @@ class BuildList(
5555
filterset_class = BuildListFilter
5656

5757
def _get_versions(self, project):
58-
return Version.internal.public(
58+
project.versions(manager=INTERNAL).public(
5959
user=self.request.user,
60-
project=project,
6160
)
6261

6362
def get_project(self):

readthedocs/projects/models.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,9 +1124,7 @@ def latest_internal_build(self):
11241124
return self.builds(manager=INTERNAL).select_related("version").first()
11251125

11261126
def active_versions(self):
1127-
from readthedocs.builds.models import Version
1128-
1129-
versions = Version.internal.public(project=self, only_active=True)
1127+
versions = self.versions(manager=INTERNAL).public(only_active=True)
11301128
return versions.filter(built=True, active=True) | versions.filter(
11311129
active=True, uploaded=True
11321130
)

readthedocs/projects/views/private.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from vanilla import UpdateView
2929

3030
from readthedocs.analytics.models import PageView
31+
from readthedocs.builds.constants import INTERNAL
3132
from readthedocs.builds.forms import RegexAutomationRuleForm
3233
from readthedocs.builds.forms import VersionForm
3334
from readthedocs.builds.models import AutomationRuleMatch
@@ -234,10 +235,13 @@ def get_success_url(self):
234235

235236
class ProjectVersionEditMixin(ProjectVersionMixin):
236237
def get_queryset(self):
237-
return Version.internal.public(
238-
user=self.request.user,
239-
project=self.get_project(),
240-
only_active=False,
238+
return (
239+
self.get_project()
240+
.versions(manager=INTERNAL)
241+
.public(
242+
user=self.request.user,
243+
only_active=False,
244+
)
241245
)
242246

243247
def form_valid(self, form):

readthedocs/projects/views/public.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,8 @@ class ProjectDetailViewBase(
106106
filterset_class = ProjectVersionListFilterSet
107107

108108
def _get_versions(self, project):
109-
return Version.internal.public(
109+
return project.versions(manager=INTERNAL).public(
110110
user=self.request.user,
111-
project=project,
112111
)
113112

114113
def get_queryset(self):
@@ -423,9 +422,8 @@ def project_versions(request, project_slug):
423422
slug=project_slug,
424423
)
425424

426-
versions = Version.internal.public(
425+
versions = project.versions(manager=INTERNAL).public(
427426
user=request.user,
428-
project=project,
429427
only_active=False,
430428
)
431429
active_versions = versions.filter(active=True)

readthedocs/proxito/views/hosting.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,7 @@ def _get_versions(self, request, project):
311311
- They are active
312312
- They are not hidden
313313
"""
314-
# NOTE: Use project.versions, not Version.objects,
315-
# so all results share the same instance of project.
316314
return project.versions(manager=INTERNAL).public(
317-
project=project,
318315
user=request.user,
319316
only_active=True,
320317
only_built=True,

readthedocs/proxito/views/serve.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
from readthedocs.api.mixins import CDNCacheTagsMixin
1616
from readthedocs.builds.constants import EXTERNAL
17+
from readthedocs.builds.constants import INTERNAL
1718
from readthedocs.builds.constants import LATEST
1819
from readthedocs.builds.constants import STABLE
19-
from readthedocs.builds.models import Version
2020
from readthedocs.core.mixins import CDNCacheControlMixin
2121
from readthedocs.core.resolver import Resolver
2222
from readthedocs.core.unresolver import InvalidExternalVersionError
@@ -713,7 +713,7 @@ def get(self, request):
713713

714714
def _get_hidden_paths(self, project):
715715
"""Get the absolute paths of the public hidden versions of `project`."""
716-
hidden_versions = Version.internal.public(project=project).filter(hidden=True)
716+
hidden_versions = project.versions(manager=INTERNAL).public().filter(hidden=True)
717717
resolver = Resolver()
718718
hidden_paths = [
719719
resolver.resolve_path(project, version_slug=version.slug) for version in hidden_versions
@@ -803,8 +803,7 @@ def changefreqs_generator():
803803
yield from itertools.chain(changefreqs, itertools.repeat("monthly"))
804804

805805
project = request.unresolved_domain.project
806-
public_versions = Version.internal.public(
807-
project=project,
806+
public_versions = project.versions(manager=INTERNAL).public(
808807
only_active=True,
809808
include_hidden=False,
810809
)
@@ -851,7 +850,8 @@ def changefreqs_generator():
851850
if project.translations.exists():
852851
for translation in project.translations.all():
853852
translated_version = (
854-
Version.internal.public(project=translation)
853+
translation.versions(manager=INTERNAL)
854+
.public()
855855
.filter(slug=version.slug)
856856
.first()
857857
)

0 commit comments

Comments
 (0)