Skip to content

Make dashboard faster for projects with a lot of subprojects #6873

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions readthedocs/projects/views/mixins.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-

"""Mixin classes for project views."""
from urllib.parse import urlparse

from celery import chain
from django.shortcuts import get_object_or_404

from readthedocs.core.resolver import resolve, resolve_path
from readthedocs.core.utils import prepare_build
from readthedocs.projects.models import Project
from readthedocs.projects.signals import project_import
Expand Down Expand Up @@ -49,6 +49,44 @@ def get_context_data(self, **kwargs):
return context


class ProjectRelationListMixin:

"""Injects ``subprojects_and_urls`` into the context."""

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['subprojects_and_urls'] = self._get_subprojects_and_urls()
return context

def _get_subprojects_and_urls(self):
"""
Get a tuple of subprojects and its absolute URls.

All subprojects share the domain from the parent,
so instead of resolving the domain and path for each subproject,
we resolve only the path of each one.
"""
subprojects_and_urls = []

project = self.get_project()
main_domain = resolve(project)
parsed_main_domain = urlparse(main_domain)

subprojects = project.subprojects.select_related('child')
for subproject in subprojects:
subproject_path = resolve_path(subproject.child)
parsed_subproject_domain = parsed_main_domain._replace(
path=subproject_path,
)
subprojects_and_urls.append(
(
subproject,
parsed_subproject_domain.geturl(),
)
)
return subprojects_and_urls


class ProjectImportMixin:

"""Helpers to import a Project."""
Expand Down
37 changes: 5 additions & 32 deletions readthedocs/projects/views/private.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import csv
import logging
from urllib.parse import urlparse

from allauth.socialaccount.models import SocialAccount
from django.conf import settings
Expand Down Expand Up @@ -45,7 +44,6 @@
LoginRequiredMixin,
PrivateViewMixin,
)
from readthedocs.core.resolver import resolve, resolve_path
from readthedocs.core.utils import broadcast, trigger_build
from readthedocs.core.utils.extend import SettingsOverrideObject
from readthedocs.integrations.models import HttpExchange, Integration
Expand Down Expand Up @@ -81,7 +79,10 @@
from readthedocs.projects.notifications import EmailConfirmNotification
from readthedocs.projects.utils import Echo
from readthedocs.projects.views.base import ProjectAdminMixin, ProjectSpamMixin
from readthedocs.projects.views.mixins import ProjectImportMixin
from readthedocs.projects.views.mixins import (
ProjectImportMixin,
ProjectRelationListMixin,
)
from readthedocs.search.models import SearchQuery

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


class ProjectRelationshipList(ProjectRelationshipMixin, ListView):
class ProjectRelationshipList(ProjectRelationListMixin, ProjectRelationshipMixin, ListView):

def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['superproject'] = self.project.superprojects.first()
ctx['subprojects_and_urls'] = self.get_subprojects_and_urls()
return ctx

def get_subprojects_and_urls(self):
"""
Get a tuple of subprojects and its absolute URls.

All subprojects share the domain from the parent,
so instead of resolving the domain and path for each subproject,
we resolve only the path of each one.
"""
subprojects_and_urls = []

main_domain = resolve(self.project)
parsed_main_domain = urlparse(main_domain)

subprojects = self.object_list.select_related('child')
for subproject in subprojects:
subproject_path = resolve_path(subproject.child)
parsed_subproject_domain = parsed_main_domain._replace(
path=subproject_path,
)
subprojects_and_urls.append(
(
subproject,
parsed_subproject_domain.geturl(),
)
)
return subprojects_and_urls


class ProjectRelationshipCreate(ProjectRelationshipMixin, CreateView):

Expand Down
22 changes: 15 additions & 7 deletions readthedocs/projects/views/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import mimetypes
import operator
import os
from urllib.parse import urlparse
from collections import OrderedDict
from urllib.parse import urlparse

import requests
from django.conf import settings
Expand All @@ -16,13 +16,13 @@
from django.core.files.storage import get_storage_class
from django.db.models import prefetch_related_objects
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render, redirect
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.crypto import constant_time_compare
from django.utils.encoding import force_bytes
from django.views import View
from django.views.decorators.cache import never_cache
from django.views.generic import DetailView, ListView
from django.utils.crypto import constant_time_compare
from django.utils.encoding import force_bytes
from taggit.models import Tag

from readthedocs.analytics.tasks import analytics_event
Expand All @@ -33,12 +33,12 @@
from readthedocs.core.utils.extend import SettingsOverrideObject
from readthedocs.projects.models import Project
from readthedocs.projects.templatetags.projects_tags import sort_version_aware
from readthedocs.projects.views.mixins import ProjectRelationListMixin
from readthedocs.proxito.views.mixins import ServeDocsMixin
from readthedocs.proxito.views.utils import _get_project_data_from_request

from .base import ProjectOnboardMixin
from ..constants import PRIVATE

from .base import ProjectOnboardMixin

log = logging.getLogger(__name__)
search_log = logging.getLogger(__name__ + '.search')
Expand Down Expand Up @@ -81,7 +81,12 @@ def project_redirect(request, invalid_project_slug):
))


class ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView):
class ProjectDetailView(
ProjectRelationListMixin,
BuildTriggerMixin,
ProjectOnboardMixin,
DetailView
):

"""Display project onboard steps."""

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

def get_project(self):
return self.get_object()

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

Expand Down
6 changes: 3 additions & 3 deletions readthedocs/templates/core/project_detail_right.html
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,11 @@ <h3>{% trans "Translations" %}</h3>
{% endblock %}

{% block subprojects %}
{% if project.subprojects.exists %}
{% if subprojects_and_urls %}
<h3>{% trans "Sub Projects" %}</h3>
<ul>
{% for rel in project.subprojects.all %}
<li><a href="{{ rel.get_absolute_url }}">{{ rel.child }}</a></li>
{% for subproject, absolute_url in subprojects_and_urls %}
<li><a href="{{ absolute_url }}">{{ subproject.child }}</a></li>
{% endfor %}
</ul>
{% endif %}
Expand Down