Skip to content

Commit 20e28ca

Browse files
davidfischeragjohnson
authored andcommitted
Serve badges directly from local filesystem (#4561)
* Serve badges directly from local filesystem * Remove call to static * Add a content type test * Autolint fix and small performance optimization * Log an error when the filesystem can't be read
1 parent c670e18 commit 20e28ca

File tree

3 files changed

+52
-37
lines changed

3 files changed

+52
-37
lines changed

readthedocs/projects/views/public.py

+32-18
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from django.conf import settings
1616
from django.contrib import messages
1717
from django.contrib.auth.models import User
18-
from django.contrib.staticfiles.templatetags.staticfiles import static
1918
from django.core.cache import cache
2019
from django.core.urlresolvers import reverse
2120
from django.http import Http404, HttpResponse, HttpResponseRedirect
@@ -115,25 +114,40 @@ def project_badge(request, project_slug):
115114
style = request.GET.get('style', 'flat')
116115
if style not in ("flat", "plastic", "flat-square", "for-the-badge", "social"):
117116
style = "flat"
118-
badge_path = 'projects/badges/%s-' + style + '.svg'
117+
118+
# Get the local path to the badge files
119+
badge_path = os.path.join(
120+
os.path.dirname(__file__),
121+
'..',
122+
'static',
123+
'projects',
124+
'badges',
125+
'%s-' + style + '.svg',
126+
)
127+
119128
version_slug = request.GET.get('version', LATEST)
129+
file_path = badge_path % 'unknown'
130+
131+
version = Version.objects.public(request.user).filter(
132+
project__slug=project_slug, slug=version_slug).first()
133+
134+
if version:
135+
last_build = version.builds.filter(type='html', state='finished').order_by('-date').first()
136+
if last_build:
137+
if last_build.success:
138+
file_path = badge_path % 'passing'
139+
else:
140+
file_path = badge_path % 'failing'
141+
120142
try:
121-
version = Version.objects.public(request.user).get(
122-
project__slug=project_slug, slug=version_slug)
123-
except Version.DoesNotExist:
124-
url = static(badge_path % 'unknown')
125-
return HttpResponseRedirect(url)
126-
version_builds = version.builds.filter(type='html',
127-
state='finished').order_by('-date')
128-
if not version_builds.exists():
129-
url = static(badge_path % 'unknown')
130-
return HttpResponseRedirect(url)
131-
last_build = version_builds[0]
132-
if last_build.success:
133-
url = static(badge_path % 'passing')
134-
else:
135-
url = static(badge_path % 'failing')
136-
return HttpResponseRedirect(url)
143+
with open(file_path) as fd:
144+
return HttpResponse(
145+
fd.read(),
146+
content_type='image/svg+xml',
147+
)
148+
except (IOError, OSError):
149+
log.exception('Failed to read local filesystem while serving a docs badge')
150+
return HttpResponse(status=503)
137151

138152

139153
def project_downloads(request, project_slug):

readthedocs/rtd_tests/tests/test_privacy_urls.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class PublicProjectMixin(ProjectMixin):
177177
response_data = {
178178
# Public
179179
'/projects/pip/downloads/pdf/latest/': {'status_code': 302},
180-
'/projects/pip/badge/': {'status_code': 302},
180+
'/projects/pip/badge/': {'status_code': 200},
181181
}
182182

183183
def test_public_urls(self):

readthedocs/rtd_tests/tests/test_project_views.py

+19-18
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@
44
from mock import patch
55
from django.test import TestCase
66
from django.contrib.auth.models import User
7-
from django.contrib.staticfiles.templatetags.staticfiles import static
87
from django.contrib.messages import constants as message_const
98
from django.core.urlresolvers import reverse
109
from django.http.response import HttpResponseRedirect
1110
from django.views.generic.base import ContextMixin
12-
from django_dynamic_fixture import get
13-
from django_dynamic_fixture import new
11+
from django_dynamic_fixture import get, new
1412

1513
import six
1614

@@ -424,10 +422,6 @@ def get_project_queryset(self):
424422
class TestBadges(TestCase):
425423
"""Test a static badge asset is served for each build."""
426424

427-
# To set `flat` as default style as done in code.
428-
def get_badge_path(self, version, style='flat'):
429-
return static(self.BADGE_PATH % (version, style))
430-
431425
def setUp(self):
432426
self.BADGE_PATH = 'projects/badges/%s-%s.svg'
433427
self.project = get(Project, slug='badgey')
@@ -436,32 +430,39 @@ def setUp(self):
436430

437431
def test_unknown_badge(self):
438432
res = self.client.get(self.badge_url, {'version': self.version.slug})
439-
static_badge = self.get_badge_path('unknown')
440-
self.assertEquals(res.url, static_badge)
433+
self.assertContains(res, 'unknown')
434+
435+
# Unknown project
436+
unknown_project_url = reverse('project_badge', args=['fake-project'])
437+
res = self.client.get(unknown_project_url, {'version': 'latest'})
438+
self.assertContains(res, 'unknown')
441439

442440
def test_passing_badge(self):
443441
get(Build, project=self.project, version=self.version, success=True)
444442
res = self.client.get(self.badge_url, {'version': self.version.slug})
445-
static_badge = self.get_badge_path('passing')
446-
self.assertEquals(res.url, static_badge)
443+
self.assertContains(res, 'passing')
444+
self.assertEqual(res['Content-Type'], 'image/svg+xml')
447445

448446
def test_failing_badge(self):
449447
get(Build, project=self.project, version=self.version, success=False)
450448
res = self.client.get(self.badge_url, {'version': self.version.slug})
451-
static_badge = self.get_badge_path('failing')
452-
self.assertEquals(res.url, static_badge)
449+
self.assertContains(res, 'failing')
453450

454451
def test_plastic_failing_badge(self):
455452
get(Build, project=self.project, version=self.version, success=False)
456453
res = self.client.get(self.badge_url, {'version': self.version.slug, 'style': 'plastic'})
457-
static_badge = self.get_badge_path('failing', 'plastic')
458-
self.assertEquals(res.url, static_badge)
454+
self.assertContains(res, 'failing')
455+
456+
# The plastic badge has slightly more rounding
457+
self.assertContains(res, 'rx="4"')
459458

460459
def test_social_passing_badge(self):
461460
get(Build, project=self.project, version=self.version, success=True)
462-
res = self.client.get(self.badge_url, {'version': self.version.slug , 'style': 'social'})
463-
static_badge = self.get_badge_path('passing', 'social')
464-
self.assertEquals(res.url, static_badge)
461+
res = self.client.get(self.badge_url, {'version': self.version.slug, 'style': 'social'})
462+
self.assertContains(res, 'passing')
463+
464+
# The social badge (but not the other badges) has this element
465+
self.assertContains(res, 'rlink')
465466

466467

467468
class TestTags(TestCase):

0 commit comments

Comments
 (0)