Skip to content

Commit 3cca19e

Browse files
authored
Merge pull request #6873 from readthedocs/improve-detail-view
Make dashboard faster for projects with a lot of subprojects
2 parents 5759379 + b8976a0 commit 3cca19e

File tree

4 files changed

+63
-44
lines changed

4 files changed

+63
-44
lines changed

readthedocs/projects/views/mixins.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# -*- coding: utf-8 -*-
2-
31
"""Mixin classes for project views."""
2+
from urllib.parse import urlparse
43

54
from celery import chain
65
from django.shortcuts import get_object_or_404
76

7+
from readthedocs.core.resolver import resolve, resolve_path
88
from readthedocs.core.utils import prepare_build
99
from readthedocs.projects.models import Project
1010
from readthedocs.projects.signals import project_import
@@ -49,6 +49,44 @@ def get_context_data(self, **kwargs):
4949
return context
5050

5151

52+
class ProjectRelationListMixin:
53+
54+
"""Injects ``subprojects_and_urls`` into the context."""
55+
56+
def get_context_data(self, **kwargs):
57+
context = super().get_context_data(**kwargs)
58+
context['subprojects_and_urls'] = self._get_subprojects_and_urls()
59+
return context
60+
61+
def _get_subprojects_and_urls(self):
62+
"""
63+
Get a tuple of subprojects and its absolute URls.
64+
65+
All subprojects share the domain from the parent,
66+
so instead of resolving the domain and path for each subproject,
67+
we resolve only the path of each one.
68+
"""
69+
subprojects_and_urls = []
70+
71+
project = self.get_project()
72+
main_domain = resolve(project)
73+
parsed_main_domain = urlparse(main_domain)
74+
75+
subprojects = project.subprojects.select_related('child')
76+
for subproject in subprojects:
77+
subproject_path = resolve_path(subproject.child)
78+
parsed_subproject_domain = parsed_main_domain._replace(
79+
path=subproject_path,
80+
)
81+
subprojects_and_urls.append(
82+
(
83+
subproject,
84+
parsed_subproject_domain.geturl(),
85+
)
86+
)
87+
return subprojects_and_urls
88+
89+
5290
class ProjectImportMixin:
5391

5492
"""Helpers to import a Project."""

readthedocs/projects/views/private.py

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import csv
44
import logging
5-
from urllib.parse import urlparse
65

76
from allauth.socialaccount.models import SocialAccount
87
from django.conf import settings
@@ -45,7 +44,6 @@
4544
LoginRequiredMixin,
4645
PrivateViewMixin,
4746
)
48-
from readthedocs.core.resolver import resolve, resolve_path
4947
from readthedocs.core.utils import broadcast, trigger_build
5048
from readthedocs.core.utils.extend import SettingsOverrideObject
5149
from readthedocs.integrations.models import HttpExchange, Integration
@@ -81,7 +79,10 @@
8179
from readthedocs.projects.notifications import EmailConfirmNotification
8280
from readthedocs.projects.utils import Echo
8381
from readthedocs.projects.views.base import ProjectAdminMixin, ProjectSpamMixin
84-
from readthedocs.projects.views.mixins import ProjectImportMixin
82+
from readthedocs.projects.views.mixins import (
83+
ProjectImportMixin,
84+
ProjectRelationListMixin,
85+
)
8586
from readthedocs.search.models import SearchQuery
8687

8788
from ..tasks import retry_domain_verification
@@ -459,41 +460,13 @@ def get_success_url(self):
459460
return reverse('projects_subprojects', args=[self.get_project().slug])
460461

461462

462-
class ProjectRelationshipList(ProjectRelationshipMixin, ListView):
463+
class ProjectRelationshipList(ProjectRelationListMixin, ProjectRelationshipMixin, ListView):
463464

464465
def get_context_data(self, **kwargs):
465466
ctx = super().get_context_data(**kwargs)
466467
ctx['superproject'] = self.project.superprojects.first()
467-
ctx['subprojects_and_urls'] = self.get_subprojects_and_urls()
468468
return ctx
469469

470-
def get_subprojects_and_urls(self):
471-
"""
472-
Get a tuple of subprojects and its absolute URls.
473-
474-
All subprojects share the domain from the parent,
475-
so instead of resolving the domain and path for each subproject,
476-
we resolve only the path of each one.
477-
"""
478-
subprojects_and_urls = []
479-
480-
main_domain = resolve(self.project)
481-
parsed_main_domain = urlparse(main_domain)
482-
483-
subprojects = self.object_list.select_related('child')
484-
for subproject in subprojects:
485-
subproject_path = resolve_path(subproject.child)
486-
parsed_subproject_domain = parsed_main_domain._replace(
487-
path=subproject_path,
488-
)
489-
subprojects_and_urls.append(
490-
(
491-
subproject,
492-
parsed_subproject_domain.geturl(),
493-
)
494-
)
495-
return subprojects_and_urls
496-
497470

498471
class ProjectRelationshipCreate(ProjectRelationshipMixin, CreateView):
499472

readthedocs/projects/views/public.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import mimetypes
77
import operator
88
import os
9-
from urllib.parse import urlparse
109
from collections import OrderedDict
10+
from urllib.parse import urlparse
1111

1212
import requests
1313
from django.conf import settings
@@ -16,13 +16,13 @@
1616
from django.core.files.storage import get_storage_class
1717
from django.db.models import prefetch_related_objects
1818
from django.http import HttpResponse
19-
from django.shortcuts import get_object_or_404, render, redirect
19+
from django.shortcuts import get_object_or_404, redirect, render
2020
from django.urls import reverse
21+
from django.utils.crypto import constant_time_compare
22+
from django.utils.encoding import force_bytes
2123
from django.views import View
2224
from django.views.decorators.cache import never_cache
2325
from django.views.generic import DetailView, ListView
24-
from django.utils.crypto import constant_time_compare
25-
from django.utils.encoding import force_bytes
2626
from taggit.models import Tag
2727

2828
from readthedocs.analytics.tasks import analytics_event
@@ -33,12 +33,12 @@
3333
from readthedocs.core.utils.extend import SettingsOverrideObject
3434
from readthedocs.projects.models import Project
3535
from readthedocs.projects.templatetags.projects_tags import sort_version_aware
36+
from readthedocs.projects.views.mixins import ProjectRelationListMixin
3637
from readthedocs.proxito.views.mixins import ServeDocsMixin
3738
from readthedocs.proxito.views.utils import _get_project_data_from_request
3839

39-
from .base import ProjectOnboardMixin
4040
from ..constants import PRIVATE
41-
41+
from .base import ProjectOnboardMixin
4242

4343
log = logging.getLogger(__name__)
4444
search_log = logging.getLogger(__name__ + '.search')
@@ -81,7 +81,12 @@ def project_redirect(request, invalid_project_slug):
8181
))
8282

8383

84-
class ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView):
84+
class ProjectDetailView(
85+
ProjectRelationListMixin,
86+
BuildTriggerMixin,
87+
ProjectOnboardMixin,
88+
DetailView
89+
):
8590

8691
"""Display project onboard steps."""
8792

@@ -91,6 +96,9 @@ class ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView):
9196
def get_queryset(self):
9297
return Project.objects.protected(self.request.user)
9398

99+
def get_project(self):
100+
return self.get_object()
101+
94102
def get_context_data(self, **kwargs):
95103
context = super().get_context_data(**kwargs)
96104

readthedocs/templates/core/project_detail_right.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ <h3>{% trans "Translations" %}</h3>
128128
{% endblock %}
129129

130130
{% block subprojects %}
131-
{% if project.subprojects.exists %}
131+
{% if subprojects_and_urls %}
132132
<h3>{% trans "Sub Projects" %}</h3>
133133
<ul>
134-
{% for rel in project.subprojects.all %}
135-
<li><a href="{{ rel.get_absolute_url }}">{{ rel.child }}</a></li>
134+
{% for subproject, absolute_url in subprojects_and_urls %}
135+
<li><a href="{{ absolute_url }}">{{ subproject.child }}</a></li>
136136
{% endfor %}
137137
</ul>
138138
{% endif %}

0 commit comments

Comments
 (0)