Skip to content

Commit 678d360

Browse files
authored
Merge pull request readthedocs#5637 from rtfd/davidfischer/dashboard-latest-build-date
Optimizations and UX improvements to the dashboard screen
2 parents c51e96e + 95948b6 commit 678d360

File tree

5 files changed

+42
-40
lines changed

5 files changed

+42
-40
lines changed

readthedocs/core/templatetags/privacy_tags.py

+1-11
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,11 @@ def is_admin(user, project):
2020

2121
@register.simple_tag(takes_context=True)
2222
def get_public_projects(context, user):
23-
# Creates a Subquery object which returns the latest builds 'id'.
24-
# Used for optimization purpose.
25-
subquery = Subquery(
26-
Build.objects.filter(
27-
project=OuterRef('project_id')).values_list('id', flat=True)[:1]
28-
)
29-
# Filters the latest builds of projects.
30-
latest_build = Prefetch('builds', Build.objects.filter(
31-
pk__in=subquery), to_attr='_latest_build'
32-
)
3323
# 'Exists()' checks if the project has any good builds.
3424
projects = Project.objects.for_user_and_viewer(
3525
user=user,
3626
viewer=context['request'].user,
37-
).prefetch_related('users', latest_build).annotate(
27+
).prefetch_latest_build().annotate(
3828
_good_build=Exists(
3929
Build.objects.filter(success=True, project=OuterRef('pk')))
4030
)

readthedocs/projects/models.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,9 @@ class Project(models.Model):
398398
objects = ProjectQuerySet.as_manager()
399399
all_objects = models.Manager()
400400

401+
# Property used for storing the latest build for a project when prefetching
402+
LATEST_BUILD_CACHE = '_latest_build'
403+
401404
class Meta:
402405
ordering = ('slug',)
403406
permissions = (
@@ -884,7 +887,7 @@ def get_latest_build(self, finished=True):
884887
"""
885888
# Check if there is `_latest_build` attribute in the Queryset.
886889
# Used for Database optimization.
887-
if hasattr(self, '_latest_build'):
890+
if hasattr(self, self.LATEST_BUILD_CACHE):
888891
if self._latest_build:
889892
return self._latest_build[0]
890893
return None

readthedocs/projects/querysets.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Project model QuerySet classes."""
22

33
from django.db import models
4-
from django.db.models import Q
4+
from django.db.models import Q, OuterRef, Subquery, Prefetch
55
from guardian.shortcuts import get_objects_for_user
66

77
from readthedocs.core.utils.extend import SettingsOverrideObject
@@ -75,10 +75,32 @@ def is_active(self, project):
7575

7676
return True
7777

78+
def prefetch_latest_build(self):
79+
"""
80+
For a given queryset of projects, prefetch the latest build for each project
81+
82+
This should come after any filtering.
83+
"""
84+
from readthedocs.builds.models import Build
85+
86+
# Prefetch the latest build for each project.
87+
subquery = Subquery(
88+
Build.objects.filter(
89+
project=OuterRef('project_id')
90+
).order_by('-date').values_list('id', flat=True)[:1]
91+
)
92+
latest_build = Prefetch(
93+
'builds',
94+
Build.objects.filter(pk__in=subquery),
95+
to_attr=self.model.LATEST_BUILD_CACHE,
96+
)
97+
return self.prefetch_related(latest_build)
98+
7899
# Aliases
79100

80101
def dashboard(self, user=None):
81-
return self.for_admin_user(user)
102+
"""Get the projects for this user including the latest build"""
103+
return self.for_admin_user(user).prefetch_latest_build()
82104

83105
def api(self, user=None, detail=True):
84106
if detail:

readthedocs/projects/views/private.py

+1-9
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from django.contrib import messages
88
from django.contrib.auth.decorators import login_required
99
from django.contrib.auth.models import User
10-
from django.db.models import Count, OuterRef, Subquery
1110
from django.http import (
1211
Http404,
1312
HttpResponseBadRequest,
@@ -92,14 +91,7 @@ def validate_primary_email(self, user):
9291
notification.send()
9392

9493
def get_queryset(self):
95-
# Filters the builds for a perticular project.
96-
builds = Build.objects.filter(
97-
project=OuterRef('pk'), type='html', state='finished')
98-
# Creates a Subquery object which returns
99-
# the value of Build.success of the latest build.
100-
sub_query = Subquery(builds.values('success')[:1])
101-
return Project.objects.dashboard(self.request.user).annotate(
102-
build_count=Count('builds'), latest_build_success=sub_query)
94+
return Project.objects.dashboard(self.request.user)
10395

10496
def get(self, request, *args, **kwargs):
10597
self.validate_primary_email(request.user)

readthedocs/templates/projects/project_dashboard_base.html

+12-17
Original file line numberDiff line numberDiff line change
@@ -66,27 +66,22 @@ <h3>{% trans "Projects" %}</h3>
6666
{% block project-name %}
6767
{{ project.name }}
6868
{% endblock %}
69-
{% with builds=project.build_count %}
70-
{% if builds == 0 %}
71-
<span class="right quiet">
72-
{% trans "No builds yet" %}
73-
</span>
74-
{% else %}
75-
<span class="right quiet">
76-
<span class="build-count">
77-
{% blocktrans trimmed count counter=builds %}
78-
1 build
79-
{% plural %}
80-
{{ builds }} builds
81-
{% endblocktrans %}
82-
</span>
83-
{% if project.latest_build_success %}
69+
{% with build=project.get_latest_build %}
70+
<span class="right quiet">
71+
72+
{% if build %}
73+
<time class="build-count" datetime="{{ build.date|date:"c" }}" title="{{ build.date|date:"DATETIME_FORMAT" }}">
74+
<small>{% blocktrans with date=build.date|timesince %}{{ date }} ago{% endblocktrans %}</small>
75+
</time>
76+
{% if build.success %}
8477
<span class="build-state build-state-passing">{% trans "passing" %}</span>
8578
{% else %}
8679
<span class="build-state build-state-failing">{% trans "failing" %}</span>
8780
{% endif %}
88-
</span>
89-
{% endif %}
81+
{% else %}
82+
<span>{% trans "No builds yet" %}</span>
83+
{% endif %}
84+
</span>
9085
{% endwith %}
9186
</a>
9287
</li>

0 commit comments

Comments
 (0)