Included the default serviceworker, updated the manifest, Add the default offline page, updated the unit test, Redme

This commit is contained in:
Silvio Leite
2018-11-30 20:33:20 -02:00
parent 73151d2585
commit 79c6963219
25 changed files with 252 additions and 33 deletions

View File

@@ -11,3 +11,16 @@
- Add tox - Add tox
- Add Coverage - Add Coverage
## 1.0.1
- Add django 2 requirement
- Use templateviews instead of own implementations
- Add content_types
- Update `README.md`
- Add `PWA_APP_FETCH_URL`
- Add default_config in `__init__.py`
- Add basic serviceworker
- Add default offline page and default icons
- Updated the unit tests

View File

@@ -1,2 +1,3 @@
include *.md include *.md
recursive-include pwa/templates * recursive-include pwa/templates *
recursive-include pwa/static *

View File

@@ -60,11 +60,11 @@ All settings are optional, and the app will work fine with its internal defaults
Add the progressive web app URLs to urls.py: Add the progressive web app URLs to urls.py:
```python ```python
from django.conf.urls import url, include from django.urls import path, include
urlpatterns = [ urlpatterns = [
... ...
url('', include('pwa.urls')), # You MUST use an empty string as the URL prefix path('', include('pwa.urls')), # You MUST use an empty string as the URL prefix
... ...
] ]
``` ```
@@ -86,18 +86,85 @@ While running the Django test server:
1. Verify that `/manifest.json` is being served 1. Verify that `/manifest.json` is being served
1. Verify that `/serviceworker.js` is being served 1. Verify that `/serviceworker.js` is being served
1. Verify that `/offline` is being served
1. Use the Application tab in the Chrome Developer Tools to verify the progressive web app is configured correctly. 1. Use the Application tab in the Chrome Developer Tools to verify the progressive web app is configured correctly.
1. Use the "Add to homescreen" link on the Application Tab to verify you can add the app successfully. 1. Use the "Add to homescreen" link on the Application Tab to verify you can add the app successfully.
The Service Worker
=====
By default, the service worker implemented by this app is:
```js
// Base Service Worker implementation. To use your own Service Worker, set the PWA_SERVICE_WORKER_PATH variable in settings.py
var staticCacheName = "django-pwa-v" + new Date().getTime();
var filesToCache = [
'/offline',
'/css/django-pwa-app.css',
'/images/icons/icon-72x72.png',
'/images/icons/icon-96x96.png',
'/images/icons/icon-128x128.png',
'/images/icons/icon-144x144.png',
'/images/icons/icon-152x152.png',
'/images/icons/icon-192x192.png',
'/images/icons/icon-384x384.png',
'/images/icons/icon-512x512.png',
];
// Cache on install
self.addEventListener("install", event => {
this.skipWaiting();
event.waitUntil(
caches.open(staticCacheName)
.then(cache => {
return cache.addAll(filesToCache);
})
)
});
// Clear cache on activate
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => (cacheName.startsWith("django-pwa-")))
.filter(cacheName => (cacheName !== staticCacheName))
.map(cacheName => caches.delete(cacheName))
);
})
);
});
// Serve from Cache
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
.catch(() => {
return caches.match('offline');
})
)
});
```
Adding Your Own Service Worker Adding Your Own Service Worker
===== =====
By default, the service worker implemented by this app is empty. To add service worker functionality, you'll want to create a `serviceworker.js` or similarly named file, and then point at it using the PWA_SERVICE_WORKER_PATH variable. By default, the service worker implemented by this app is empty. To add service worker functionality, you'll want to create a `serviceworker.js` or similarly named template in a template directory, and then point at it using the PWA_SERVICE_WORKER_PATH variable (PWA_APP_FETCH_URL is passed through).
```python ```python
PWA_SERVICE_WORKER_PATH = os.path.join(BASE_DIR, 'my_app', 'serviceworker.js') PWA_SERVICE_WORKER_PATH = 'my_app/serviceworker.js'
``` ```
The offline view
=====
By default, the offline view is implemented in `templates/offline.html`
You can overwrite it in a template directory if you continue using the default `serviceworker.js`.
Feedback Feedback
===== =====
I welcome your feedback and pull requests. Enjoy! I welcome your feedback and pull requests. Enjoy!

View File

@@ -0,0 +1 @@
default_app_config = 'pwa.apps.PwaConfig'

View File

@@ -3,8 +3,7 @@ from django.conf import settings
import os import os
# Path to the service worker implementation. Default implementation is empty. # Path to the service worker implementation. Default implementation is empty.
PWA_SERVICE_WORKER_PATH = getattr(settings, 'PWA_SERVICE_WORKER_PATH', PWA_SERVICE_WORKER_PATH = getattr(settings, 'PWA_SERVICE_WORKER_PATH', 'serviceworker.js')
os.path.join(os.path.abspath(os.path.dirname(__file__)), 'templates', 'serviceworker.js'))
# App parameters to include in manifest.json and appropriate meta tags # App parameters to include in manifest.json and appropriate meta tags
PWA_APP_NAME = getattr(settings, 'PWA_APP_NAME', 'MyApp') PWA_APP_NAME = getattr(settings, 'PWA_APP_NAME', 'MyApp')
@@ -15,10 +14,39 @@ PWA_APP_BACKGROUND_COLOR = getattr(settings, 'PWA_APP_BACKGROUND_COLOR', '#fff')
PWA_APP_DISPLAY = getattr(settings, 'PWA_APP_DISPLAY', 'standalone') PWA_APP_DISPLAY = getattr(settings, 'PWA_APP_DISPLAY', 'standalone')
PWA_APP_ORIENTATION = getattr(settings, 'PWA_APP_ORIENTATION', 'any') PWA_APP_ORIENTATION = getattr(settings, 'PWA_APP_ORIENTATION', 'any')
PWA_APP_START_URL = getattr(settings, 'PWA_APP_START_URL', '/') PWA_APP_START_URL = getattr(settings, 'PWA_APP_START_URL', '/')
PWA_APP_FETCH_URL = getattr(settings, 'PWA_APP_FETCH_URL', '/')
PWA_APP_ICONS = getattr(settings, 'PWA_APP_ICONS', [ PWA_APP_ICONS = getattr(settings, 'PWA_APP_ICONS', [
{ {
'src': '/', 'src': '/static/images/icons/icon-72x72.png',
'sizes': '160x160' 'sizes': '72x72'
},
{
'src': '/static/images/icons/icon-96x96.png',
'sizes': '96x96'
},
{
'src': '/static/images/icons/icon-128x128.png',
'sizes': '128x128'
},
{
'src': '/static/images/icons/icon-144x144.png',
'sizes': '144x144'
},
{
'src': '/static/images/icons/icon-152x152.png',
'sizes': '152x152'
},
{
'src': '/static/images/icons/icon-192x192.png',
'sizes': '192x192'
},
{
'src': '/static/images/icons/icon-384x384.png',
'sizes': '384x384'
},
{
'src': '/static/images/icons/icon-512x512.png',
'sizes': '512x512'
} }
]) ])
PWA_APP_DIR = getattr(settings, 'PWA_APP_DIR', 'auto') PWA_APP_DIR = getattr(settings, 'PWA_APP_DIR', 'auto')

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -0,0 +1,11 @@
{% load static %}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Default offline template</title>
<link href="{% static '/css/django-pwa-app.css' %}" rel="stylesheet">
</head>
<body>
<h1>You are currently not connected to any networks.</h1>
</body>
</html>

View File

@@ -1,2 +1,53 @@
// Empty Service Worker implementation. To use your own Service Worker, set the PWA_SERVICE_WORKER_PATH variable in // Base Service Worker implementation. To use your own Service Worker, set the PWA_SERVICE_WORKER_PATH variable in settings.py
// settings.py
var staticCacheName = "django-pwa-v" + new Date().getTime();
var filesToCache = [
'/offline',
'/static/css/django-pwa-app.css',
'/static/images/icons/icon-72x72.png',
'/static/images/icons/icon-96x96.png',
'/static/images/icons/icon-128x128.png',
'/static/images/icons/icon-144x144.png',
'/static/images/icons/icon-152x152.png',
'/static/images/icons/icon-192x192.png',
'/static/images/icons/icon-384x384.png',
'/static/images/icons/icon-512x512.png',
];
// Cache on install
self.addEventListener("install", event => {
this.skipWaiting();
event.waitUntil(
caches.open(staticCacheName)
.then(cache => {
return cache.addAll(filesToCache);
})
)
});
// Clear cache on activate
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => (cacheName.startsWith("django-pwa-")))
.filter(cacheName => (cacheName !== staticCacheName))
.map(cacheName => caches.delete(cacheName))
);
})
);
});
// Serve from Cache
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
.catch(() => {
return caches.match('offline');
})
)
});

View File

@@ -1,8 +1,10 @@
from django.conf.urls import url from django.urls import path
from .views import manifest, service_worker
from .views import Manifest, ServiceWorker, OfflineView
# Serve up serviceworker.js and manifest.json at the root # Serve up serviceworker.js and manifest.json at the root
urlpatterns = [ urlpatterns = [
url('^serviceworker.js$', service_worker, name="serviceworker"), path('serviceworker.js', ServiceWorker.as_view(), name='serviceworker'),
url('^manifest.json$', manifest, name="manifest") path('manifest.json', Manifest.as_view(), name='manifest'),
path('offline', OfflineView.as_view(), name='offline')
] ]

View File

@@ -1,17 +1,27 @@
from django.http import HttpResponse from django.views.generic.base import TemplateView
from django.shortcuts import render
from . import app_settings from . import app_settings
def service_worker(request): class ServiceWorker(TemplateView):
response = HttpResponse(open(app_settings.PWA_SERVICE_WORKER_PATH).read(), content_type='application/javascript') content_type = 'application/javascript'
return response template_name = app_settings.PWA_SERVICE_WORKER_PATH
def get_context_data(self, **kwargs):
kwargs['PWA_APP_FETCH_URL'] = app_settings.PWA_APP_FETCH_URL
return super().get_context_data(**kwargs)
def manifest(request): class Manifest(TemplateView):
return render(request, 'manifest.json', { content_type = 'application/json'
setting_name: getattr(app_settings, setting_name) template_name = 'manifest.json'
for setting_name in dir(app_settings)
if setting_name.startswith('PWA_') def get_context_data(self, **kwargs):
}) for setting_name in dir(app_settings):
if setting_name.startswith('PWA_'):
kwargs[setting_name] = getattr(app_settings, setting_name)
return super().get_context_data(**kwargs)
class OfflineView(TemplateView):
template_name = "offline.html"

View File

@@ -10,16 +10,21 @@ try:
import pypandoc import pypandoc
long_description = pypandoc.convert('README.md', 'rst') long_description = pypandoc.convert('README.md', 'rst')
except: except RuntimeError:
long_description = short_description long_description = short_description
# allow setup.py to be run from any path # allow setup.py to be run from any path
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
install_requirements = [
"django>=2",
]
setup( setup(
name='django-pwa', name='django-pwa',
version='1.0.0', version='1.0.1',
packages=find_packages(), packages=find_packages(),
install_requires=install_requirements,
include_package_data=True, include_package_data=True,
license='MIT License', license='MIT License',
description=short_description, description=short_description,
@@ -30,7 +35,8 @@ setup(
classifiers=[ classifiers=[
'Environment :: Web Environment', 'Environment :: Web Environment',
'Framework :: Django', 'Framework :: Django',
'Framework :: Django :: 1.10', 'Framework :: Django :: 2.0',
'Framework :: Django :: 2.1',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent', 'Operating System :: OS Independent',

View File

@@ -34,3 +34,5 @@ DATABASES = {
'NAME': 'mydatabase', 'NAME': 'mydatabase',
} }
} }
STATIC_URL = '/static/'

View File

@@ -15,6 +15,7 @@ class AppSettingsTest(TestCase):
'PWA_APP_DISPLAY', 'PWA_APP_DISPLAY',
'PWA_APP_ORIENTATION', 'PWA_APP_ORIENTATION',
'PWA_APP_START_URL', 'PWA_APP_START_URL',
'PWA_APP_FETCH_URL',
'PWA_APP_ICONS', 'PWA_APP_ICONS',
'PWA_APP_DIR', 'PWA_APP_DIR',
'PWA_APP_LANG' 'PWA_APP_LANG'

View File

@@ -14,7 +14,14 @@ class CreateMetaTemplateTagTest(TestCase):
def test_has_tags(self): def test_has_tags(self):
"""Must contains the tags in HTML""" """Must contains the tags in HTML"""
tags = [ tags = [
'<link rel="apple-touch-icon" href="/" sizes="160x160">', '<link rel="apple-touch-icon" href="/images/icons/icon-72x72.png" sizes="72x72">',
'<link rel="apple-touch-icon" href="/images/icons/icon-96x96.png" sizes="96x96">',
'<link rel="apple-touch-icon" href="/images/icons/icon-128x128.png" sizes="128x128">',
'<link rel="apple-touch-icon" href="/images/icons/icon-144x144.png" sizes="144x144">',
'<link rel="apple-touch-icon" href="/images/icons/icon-152x152.png" sizes="152x152">',
'<link rel="apple-touch-icon" href="/images/icons/icon-192x192.png" sizes="192x192">',
'<link rel="apple-touch-icon" href="/images/icons/icon-384x384.png" sizes="384x384">',
'<link rel="apple-touch-icon" href="/images/icons/icon-512x512.png" sizes="512x512">',
'<link rel="manifest" href="/manifest.json">', '<link rel="manifest" href="/manifest.json">',
'<meta name="theme-color" content="#000">', '<meta name="theme-color" content="#000">',
'<meta name="apple-mobile-web-app-capable" content="yes">', '<meta name="apple-mobile-web-app-capable" content="yes">',

View File

@@ -41,3 +41,13 @@ class ManifestTest(TestCase):
for expected in contents: for expected in contents:
with self.subTest(): with self.subTest():
self.assertContains(self.response, expected) self.assertContains(self.response, expected)
class OfflineTest(TestCase):
def setUp(self):
self.response = self.client.get(r('offline'))
def test_get(self):
"""GET /offline Should return status code 200"""
self.assertEqual(200, self.response.status_code)

View File

@@ -1,5 +1,7 @@
[tox] [tox]
envlist = py36-django{20} envlist =
py36-django{20}
py36-django{21}
[testenv] [testenv]
commands = python runtests.py commands = python runtests.py
@@ -7,4 +9,6 @@ setenv =
DJANGO_SETTINGS_MODULE=tests.settings DJANGO_SETTINGS_MODULE=tests.settings
PYTHONPATH={toxinidir} PYTHONPATH={toxinidir}
basepython = py36: python3.6 basepython = py36: python3.6
deps = django20: Django==2.0 deps =
django20: Django==2.0
django21: Django==2.1