diff --git a/perma_web/fabfile/dev.py b/perma_web/fabfile/dev.py
index 873bfff73..0ad1dd9f3 100644
--- a/perma_web/fabfile/dev.py
+++ b/perma_web/fabfile/dev.py
@@ -105,7 +105,7 @@ def sauce_tunnel():
Set up Sauce tunnel before running functional tests targeted at localhost.
"""
if subprocess.call(['which','sc']) == 1: # error return code -- program not found
- sys.exit("Please check that the `sc` program is installed and in your path. To install: https://docs.saucelabs.com/reference/sauce-connect/")
+ sys.exit("Please check that the `sc` program is installed and in your path. To install: https://wiki.saucelabs.com/display/DOCS/Sauce+Connect+Proxy")
local("sc -u %s -k %s" % (settings.SAUCE_USERNAME, settings.SAUCE_ACCESS_KEY))
diff --git a/perma_web/perma/templates/archive/download_to_view_pdf.html b/perma_web/perma/templates/archive/download_to_view_pdf.html
new file mode 100644
index 000000000..63f31ce82
--- /dev/null
+++ b/perma_web/perma/templates/archive/download_to_view_pdf.html
@@ -0,0 +1,5 @@
+
+
Perma.cc can't display this file type on mobile, but you can view or download the archived file by clicking below.
+
File type {{mime_type}}
+
View/Download File
+
\ No newline at end of file
diff --git a/perma_web/perma/templates/archive/single-link.html b/perma_web/perma/templates/archive/single-link.html
index 269f71384..77965dcfb 100755
--- a/perma_web/perma/templates/archive/single-link.html
+++ b/perma_web/perma/templates/archive/single-link.html
@@ -239,6 +239,7 @@
{% endif %}
+
{% endblock %}
{% block mainContent %}
@@ -246,7 +247,11 @@
{% if link.user_deleted %}
{% include "archive/deleted.html" %}
{% elif can_view %}
- {% include "archive/iframe.html" %}
+ {% if redirect_to_download_view %}
+ {% include "archive/download_to_view_pdf.html" %}
+ {% else %}
+ {% include "archive/iframe.html" %}
+ {% endif %}
{% elif link.is_private %}
{% include "archive/dark-archive.html" %}
{% endif %}
diff --git a/perma_web/perma/tests/test_views_common.py b/perma_web/perma/tests/test_views_common.py
index 1ea1f66ea..3f1b14a16 100644
--- a/perma_web/perma/tests/test_views_common.py
+++ b/perma_web/perma/tests/test_views_common.py
@@ -1,7 +1,7 @@
from django.conf import settings
from django.core import mail
from django.core.urlresolvers import reverse
-from django.test import override_settings
+from django.test import override_settings, Client
from perma.urls import urlpatterns
from perma.models import Registrar
@@ -54,6 +54,17 @@ def test_dark_archive(self):
response = self.get('single_linky', reverse_kwargs={'kwargs': {'guid': 'ABCD-0001'}})
self.assertIn("This record is private.", response.content)
+ def test_redirect_to_download(self):
+ # Give user option to download to view pdf if on mobile
+ client = Client(HTTP_USER_AGENT='Mozilla/5.0 (iPhone; CPU iPhone OS 6_1_4 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10B350 Safari/8536.25')
+ response = client.get(reverse('single_linky', kwargs={'guid': '7CF8-SS4G'}))
+ self.assertIn("Perma.cc can\'t display this file type on mobile", response.content)
+
+ # If not on mobile, display link as normal
+ client = Client(HTTP_USER_AGENT='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/601.7.7 (KHTML, like Gecko) Version/9.1.2 Safari/601.7.7')
+ response = client.get(reverse('single_linky', kwargs={'guid': '7CF8-SS4G'}))
+ self.assertNotIn("Perma.cc can\'t display this file type on mobile", response.content)
+
def test_deleted(self):
response = self.get('single_linky', reverse_kwargs={'kwargs': {'guid': 'ABCD-0003'}})
self.assertIn("This record has been deleted.", response.content)
diff --git a/perma_web/perma/utils.py b/perma_web/perma/utils.py
index 8fa5399c3..3b4c1f7ab 100644
--- a/perma_web/perma/utils.py
+++ b/perma_web/perma/utils.py
@@ -9,6 +9,7 @@
from netaddr import IPAddress, IPNetwork
from functools import wraps
import requests
+from ua_parser import user_agent_parser
from django.core.paginator import Paginator
from django.db.models import Q
@@ -252,3 +253,15 @@ def get_lat_long(address):
warn("Error connecting to geocoding API: %s" % r.status_code)
+def parse_user_agent(user_agent_str):
+ return user_agent_parser.ParseUserAgent(user_agent_str)
+
+
+### pdf handling on mobile ###
+
+def redirect_to_download(capture_mime_type, user_agent_str):
+ # redirecting to a page with a download button (and not attempting to display)
+ # if mobile apple device, and the request is a pdf
+ parsed_agent = parse_user_agent(user_agent_str)
+
+ return "Mobile" in parsed_agent["family"] and "pdf" in capture_mime_type
diff --git a/perma_web/perma/views/common.py b/perma_web/perma/views/common.py
index b3ba38277..2a3103b72 100755
--- a/perma_web/perma/views/common.py
+++ b/perma_web/perma/views/common.py
@@ -6,7 +6,6 @@
from ratelimit.decorators import ratelimit
from datetime import timedelta
from wsgiref.handlers import format_date_time
-from ua_parser import user_agent_parser
from urllib import urlencode
from django.contrib.auth.views import redirect_to_login
@@ -24,7 +23,7 @@
from ..models import Link, Registrar, Organization, LinkUser
from ..forms import ContactForm
-from ..utils import if_anonymous, ratelimit_ip_key
+from ..utils import if_anonymous, ratelimit_ip_key, redirect_to_download, parse_user_agent
from ..email import send_admin_email, send_user_email_copy_admins
from warcio.warcwriter import BufferWARCWriter
@@ -109,6 +108,7 @@ def single_linky(request, guid):
"""
Given a Perma ID, serve it up.
"""
+ raw_user_agent = request.META.get('HTTP_USER_AGENT', '')
# Create a canonical version of guid (non-alphanumerics removed, hyphens every 4 characters, uppercase),
# and forward to that if it's different from current guid.
@@ -195,7 +195,7 @@ def make_warcinfo(filename, guid, coll_title, coll_desc, rec_title, pages):
# safari=1 in the query string indicates that the redirect has already happened.
# See http://labs.fundbox.com/third-party-cookies-with-ie-at-2am/
if link.is_private and not request.GET.get('safari'):
- user_agent = user_agent_parser.ParseUserAgent(request.META.get('HTTP_USER_AGENT', ''))
+ user_agent = parse_user_agent(raw_user_agent)
if user_agent.get('family') == 'Safari':
return redirect_to_login(request.build_absolute_uri(),
"//%s%s" % (settings.WARC_HOST, reverse('user_management_set_safari_cookie')))
@@ -210,6 +210,16 @@ def make_warcinfo(filename, guid, coll_title, coll_desc, rec_title, pages):
if (not capture or capture.status != 'success') and link.screenshot_capture and link.screenshot_capture.status == 'success':
return HttpResponseRedirect(reverse('single_linky', args=[guid])+"?type=image")
+ try:
+ capture_mime_type = capture.mime_type()
+ except AttributeError:
+ # If capture is deleted, then mime type does not exist. Catch error.
+ capture_mime_type = None
+
+ # Special handling for mobile pdf viewing because it can be buggy
+ # Redirecting to a download page if on mobile
+ redirect_to_download_view = redirect_to_download(capture_mime_type, raw_user_agent)
+
# If this record was just created by the current user, show them a new record message
new_record = request.user.is_authenticated() and link.created_by_id == request.user.id and not link.user_deleted \
and link.creation_timestamp > timezone.now() - timedelta(seconds=300)
@@ -224,6 +234,8 @@ def make_warcinfo(filename, guid, coll_title, coll_desc, rec_title, pages):
context = {
'link': link,
+ 'redirect_to_download_view': redirect_to_download_view,
+ 'mime_type': capture_mime_type,
'can_view': request.user.can_view(link),
'can_edit': request.user.can_edit(link),
'can_delete': request.user.can_delete(link),
@@ -239,9 +251,9 @@ def make_warcinfo(filename, guid, coll_title, coll_desc, rec_title, pages):
response = render(request, 'archive/single-link.html', context)
date_header = format_date_time(mktime(link.creation_timestamp.timetuple()))
- link_memento = protocol + settings.HOST + '/' + link.guid
+ link_memento = protocol + settings.HOST + '/' + link.guid
link_timegate = protocol + settings.WARC_HOST + settings.TIMEGATE_WARC_ROUTE + '/' + link.safe_url
- link_timemap = protocol + settings.WARC_HOST + settings.WARC_ROUTE + '/timemap/*/' + link.safe_url
+ link_timemap = protocol + settings.WARC_HOST + settings.WARC_ROUTE + '/timemap/*/' + link.safe_url
response['Memento-Datetime'] = date_header
link_memento_headers = '<{0}>; rel="original"; datetime="{1}",<{2}>; rel="memento"; datetime="{1}",<{3}>; rel="timegate",<{4}>; rel="timemap"; type="application/link-format"'