Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2326895768 | ||
|
|
053afe1bc0 | ||
|
|
d86732bb9a | ||
|
|
d4d0db5bea | ||
|
|
79c6963219 | ||
|
|
73151d2585 | ||
|
|
93133a3292 | ||
|
|
662cf155d7 | ||
|
|
0d31371002 | ||
|
|
43cb44b5c0 | ||
|
|
c7e9813ac5 | ||
|
|
ed2caecbd7 | ||
|
|
cd144e2ef7 | ||
|
|
625eee756c | ||
|
|
eba9fe5951 | ||
|
|
7c0df3201e | ||
|
|
fc56890d83 | ||
|
|
45d344503f | ||
|
|
86ed4b6504 | ||
|
|
e45b83d54c |
2
.coveragerc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[run]
|
||||||
|
source=pwa
|
||||||
1
.gitignore
vendored
@@ -6,3 +6,4 @@ build/
|
|||||||
dist/
|
dist/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.tox/
|
.tox/
|
||||||
|
.coverage
|
||||||
@@ -7,4 +7,9 @@ install:
|
|||||||
- pip install Django==$DJANGO_VERSION
|
- pip install Django==$DJANGO_VERSION
|
||||||
- pip install tox-travis
|
- pip install tox-travis
|
||||||
- pip install -q -r requirements-dev.txt
|
- pip install -q -r requirements-dev.txt
|
||||||
script: tox
|
- pip install coverage
|
||||||
|
- pip install codecov
|
||||||
|
script:
|
||||||
|
- coverage run runtests.py
|
||||||
|
after_success:
|
||||||
|
- codecov
|
||||||
37
CHANGELOG.md
@@ -6,7 +6,40 @@
|
|||||||
|
|
||||||
## 1.0.0
|
## 1.0.0
|
||||||
|
|
||||||
|
### Added
|
||||||
- Unit tests
|
- Unit tests
|
||||||
- Add Oritentation on manifest.json
|
- Option `Oritentation` on manifest.json
|
||||||
- Add tox
|
- tox.ini
|
||||||
|
- Coverage
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- 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
|
||||||
|
### Changed
|
||||||
|
- Updated the unit tests
|
||||||
|
|
||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix tox.ini to install pypandoc
|
||||||
|
### Added
|
||||||
|
- The support to splash screen for iOS Meta tags `apple-touch-startup-image`
|
||||||
|
- Meta tag `mobile-web-app-capable`
|
||||||
|
- Meta tag `application-name`
|
||||||
|
- Meta tag `msapplication-TileColor` and `msapplication-TileImage` for win8
|
||||||
|
- Meta tag `rel="icon"` with default icon
|
||||||
|
- Images for splash screen
|
||||||
|
- Include the new images to `serviceworker.js`
|
||||||
|
### Changed
|
||||||
|
- Update `CHANGELOG.md`
|
||||||
|
- Update `README.md`
|
||||||
|
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
include *.md
|
include *.md
|
||||||
recursive-include pwa/templates *
|
recursive-include pwa/templates *
|
||||||
|
recursive-include pwa/static *
|
||||||
99
README.md
@@ -1,5 +1,12 @@
|
|||||||
django-pwa
|
django-pwa
|
||||||
=====
|
=====
|
||||||
|
[](https://travis-ci.org/silviolleite/django-pwa)
|
||||||
|
[](https://codeclimate.com/github/silviolleite/django-pwa/maintainability)
|
||||||
|
[](https://codecov.io/gh/silviolleite/django-pwa)
|
||||||
|
[](https://pypi.org/project/django-pwa/)
|
||||||
|
[](https://pypi.org/project/django-pwa)
|
||||||
|
[](https://pypi.org/project/django-pwa)
|
||||||
|
|
||||||
This Django app turns your project into a [progressive web app](https://developers.google.com/web/progressive-web-apps/). Navigating to your site on an Android phone will prompt you to add the app to your home screen.
|
This Django app turns your project into a [progressive web app](https://developers.google.com/web/progressive-web-apps/). Navigating to your site on an Android phone will prompt you to add the app to your home screen.
|
||||||
|
|
||||||

|

|
||||||
@@ -30,8 +37,9 @@ INSTALLED_APPS = [
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure your app name, description, and icons in settings.py:
|
Configure your app name, description, icons and splash screen images in settings.py:
|
||||||
```python
|
```python
|
||||||
|
|
||||||
PWA_APP_NAME = 'My App'
|
PWA_APP_NAME = 'My App'
|
||||||
PWA_APP_DESCRIPTION = "My app description"
|
PWA_APP_DESCRIPTION = "My app description"
|
||||||
PWA_APP_THEME_COLOR = '#0A0302'
|
PWA_APP_THEME_COLOR = '#0A0302'
|
||||||
@@ -45,16 +53,22 @@ PWA_APP_ICONS = [
|
|||||||
'sizes': '160x160'
|
'sizes': '160x160'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
PWA_APP_SPLASH_SCREEN = [
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/splash-640x1136.png',
|
||||||
|
'media': '(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)'
|
||||||
|
}
|
||||||
|
]
|
||||||
PWA_APP_DIR = 'ltr'
|
PWA_APP_DIR = 'ltr'
|
||||||
PWA_APP_LANG = 'en-US'
|
PWA_APP_LANG = 'en-US'
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
All settings are optional, and the app will work fine with its internal defaults. Highly recommend setting at least `PWA_APP_NAME` and `PWA_APP_DESCRIPTION`.
|
All settings are optional, and the app will work fine with its internal defaults. Highly recommend setting at least `PWA_APP_NAME`, `PWA_APP_DESCRIPTION`, `PWA_APP_ICONS` and `PWA_APP_SPLASH_SCREEN`.
|
||||||
|
|
||||||
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 url, include
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
...
|
...
|
||||||
@@ -80,18 +94,95 @@ 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',
|
||||||
|
'/static/images/icons/splash-640x1136.png',
|
||||||
|
'/static/images/icons/splash-750x1334.png',
|
||||||
|
'/static/images/icons/splash-1242x2208.png',
|
||||||
|
'/static/images/icons/splash-1125x2436.png',
|
||||||
|
'/static/images/icons/splash-828x1792.png',
|
||||||
|
'/static/images/icons/splash-1242x2688.png',
|
||||||
|
'/static/images/icons/splash-1536x2048.png',
|
||||||
|
'/static/images/icons/splash-1668x2224.png',
|
||||||
|
'/static/images/icons/splash-1668x2388.png',
|
||||||
|
'/static/images/icons/splash-2048x2732.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.
|
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 = os.path.join(BASE_DIR, '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!
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'pwa.apps.PwaConfig'
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
""" Settings required by django-progressive-web-app. """
|
""" Settings required by django-app. """
|
||||||
from django.conf import settings
|
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',
|
||||||
os.path.join(os.path.abspath(os.path.dirname(__file__)), 'templates', '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')
|
||||||
PWA_APP_DESCRIPTION = getattr(settings, 'PWA_APP_DESCRIPTION', 'My Progressive Web App')
|
PWA_APP_DESCRIPTION = getattr(settings, 'PWA_APP_DESCRIPTION', 'My Progressive Web App')
|
||||||
@@ -15,12 +15,84 @@ 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'
|
'size': '72x72'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/icon-96x96.png',
|
||||||
|
'size': '96x96'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/icon-128x128.png',
|
||||||
|
'size': '128x128'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/icon-144x144.png',
|
||||||
|
'size': '144x144'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/icon-152x152.png',
|
||||||
|
'size': '152x152'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/icon-192x192.png',
|
||||||
|
'size': '192x192'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/icon-384x384.png',
|
||||||
|
'size': '384x384'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/icon-512x512.png',
|
||||||
|
'size': '512x512'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
PWA_APP_SPLASH_SCREEN = getattr(settings, 'PWA_APP_SPLASH_SCREEN', [
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/splash-640x1136.png',
|
||||||
|
'media': '(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/splash-750x1334.png',
|
||||||
|
'media': '(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/splash-1242x2208.png',
|
||||||
|
'media': '(device-width: 621px) and (device-height: 1104px) and (-webkit-device-pixel-ratio: 3)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/splash-1125x2436.png',
|
||||||
|
'media': '(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/splash-828x1792.png',
|
||||||
|
'media': '(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/splash-1242x2688.png',
|
||||||
|
'media': '(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/splash-1536x2048.png',
|
||||||
|
'media': '(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/splash-1668x2224.png',
|
||||||
|
'media': '(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/splash-1668x2388.png',
|
||||||
|
'media': '(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'src': '/static/images/icons/splash-2048x2732.png',
|
||||||
|
'media': '(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2)'
|
||||||
|
}
|
||||||
|
|
||||||
|
])
|
||||||
PWA_APP_DIR = getattr(settings, 'PWA_APP_DIR', 'auto')
|
PWA_APP_DIR = getattr(settings, 'PWA_APP_DIR', 'auto')
|
||||||
PWA_APP_LANG = getattr(settings, 'PWA_APP_LANG', 'en-US')
|
PWA_APP_LANG = getattr(settings, 'PWA_APP_LANG', 'en-US')
|
||||||
|
|
||||||
|
|||||||
5
pwa/static/css/django-pwa-app.css
Normal file
BIN
pwa/static/images/icons/Thumbs.db
Normal file
BIN
pwa/static/images/icons/icon-128x128.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
pwa/static/images/icons/icon-144x144.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
pwa/static/images/icons/icon-152x152.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
pwa/static/images/icons/icon-192x192.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
pwa/static/images/icons/icon-384x384.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
pwa/static/images/icons/icon-512x512.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
pwa/static/images/icons/icon-72x72.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
pwa/static/images/icons/icon-96x96.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
pwa/static/images/icons/splash-1125x2436.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
pwa/static/images/icons/splash-1242x2208.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
pwa/static/images/icons/splash-1242x2688.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
pwa/static/images/icons/splash-1536x2048.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
pwa/static/images/icons/splash-1668x2224.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
pwa/static/images/icons/splash-1668x2388.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
pwa/static/images/icons/splash-2048x2732.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
pwa/static/images/icons/splash-640x1136.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
pwa/static/images/icons/splash-750x1334.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
pwa/static/images/icons/splash-828x1792.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
11
pwa/templates/offline.html
Normal 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>
|
||||||
@@ -1,27 +1,48 @@
|
|||||||
<!-- Path to manifest.json -->
|
<!-- Path to manifest.json -->
|
||||||
<link rel="manifest" href="/manifest.json">
|
<link rel="manifest" href="/manifest.json">
|
||||||
|
|
||||||
<!-- Icons for Apple Devices -->
|
<!-- Add to homescreen for Chrome on Android -->
|
||||||
{% for icon in PWA_APP_ICONS %}
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<link rel="apple-touch-icon" href="{{ icon.src }}" sizes="{{ icon.sizes }}">
|
<meta name="application-name" content="{{ PWA_APP_NAME }}">
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Chrome for Android theme color -->
|
||||||
<meta name="theme-color" content="{{ PWA_APP_THEME_COLOR }}">
|
<meta name="theme-color" content="{{ PWA_APP_THEME_COLOR }}">
|
||||||
|
|
||||||
|
<!-- Add to homescreen for Safari on iOS -->
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-title" content="{{ PWA_APP_NAME }}">
|
<meta name="apple-mobile-web-app-title" content="{{ PWA_APP_NAME }}">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
|
{% for icon in PWA_APP_ICONS %}
|
||||||
|
<link rel="apple-touch-icon" href="{{ icon.src }}" sizes="{{ icon.size }}">
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
{% for splash in PWA_APP_SPLASH_SCREEN%}
|
||||||
|
<link href="{{ splash.src }}" media="{{ splash.media }}" rel="apple-touch-startup-image"/>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Tile for Win8 -->
|
||||||
|
<meta name="msapplication-TileColor" content="{{ PWA_APP_BACKGROUND_COLOR }}">
|
||||||
|
{% with PWA_APP_ICONS|last as icon %}
|
||||||
|
<meta name="msapplication-TileImage" content="{{ icon.src }}">
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="icon" sizes="{{ icon.size }}" href="{{ icon.src }}">
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// Initialize the service worker
|
// Initialize the service worker
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('/serviceworker.js', {
|
navigator.serviceWorker.register('/serviceworker.js', {
|
||||||
scope: '.' // <--- THIS BIT IS REQUIRED
|
scope: '.'
|
||||||
}).then(function (registration) {
|
}).then(function (registration) {
|
||||||
// Registration was successful
|
// Registration was successful
|
||||||
console.log('django-progressive-web-app: ServiceWorker registration successful with scope: ', registration.scope);
|
console.log('django-pwa: ServiceWorker registration successful with scope: ', registration.scope);
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
// registration failed :(
|
// registration failed :(
|
||||||
console.log('django-progressive-web-app: ServiceWorker registration failed: ', err);
|
console.log('django-pwa: ServiceWorker registration failed: ', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,2 +1,63 @@
|
|||||||
// 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',
|
||||||
|
'/static/images/icons/splash-640x1136.png',
|
||||||
|
'/static/images/icons/splash-750x1334.png',
|
||||||
|
'/static/images/icons/splash-1242x2208.png',
|
||||||
|
'/static/images/icons/splash-1125x2436.png',
|
||||||
|
'/static/images/icons/splash-828x1792.png',
|
||||||
|
'/static/images/icons/splash-1242x2688.png',
|
||||||
|
'/static/images/icons/splash-1536x2048.png',
|
||||||
|
'/static/images/icons/splash-1668x2224.png',
|
||||||
|
'/static/images/icons/splash-1668x2388.png',
|
||||||
|
'/static/images/icons/splash-2048x2732.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');
|
||||||
|
})
|
||||||
|
)
|
||||||
|
});
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from .views import manifest, service_worker
|
|
||||||
|
from .views import manifest, service_worker, offline
|
||||||
|
|
||||||
# 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"),
|
url('^serviceworker.js$', service_worker, name='serviceworker'),
|
||||||
url('^manifest.json$', manifest, name="manifest")
|
url('^manifest.json$', manifest, name='manifest'),
|
||||||
|
url('^offline/$', offline, name='offline')
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -15,3 +15,7 @@ def manifest(request):
|
|||||||
for setting_name in dir(app_settings)
|
for setting_name in dir(app_settings)
|
||||||
if setting_name.startswith('PWA_')
|
if setting_name.startswith('PWA_')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def offline(request):
|
||||||
|
return render(request, "offline.html")
|
||||||
|
|||||||
14
setup.py
@@ -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>=1.8",
|
||||||
|
]
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='django-pwa',
|
name='django-pwa',
|
||||||
version='1.0.0',
|
version='1.0.3',
|
||||||
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,12 @@ setup(
|
|||||||
classifiers=[
|
classifiers=[
|
||||||
'Environment :: Web Environment',
|
'Environment :: Web Environment',
|
||||||
'Framework :: Django',
|
'Framework :: Django',
|
||||||
|
'Framework :: Django :: 1.8',
|
||||||
|
'Framework :: Django :: 1.9',
|
||||||
'Framework :: Django :: 1.10',
|
'Framework :: Django :: 1.10',
|
||||||
|
'Framework :: Django :: 1.11',
|
||||||
|
'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',
|
||||||
|
|||||||
@@ -34,3 +34,5 @@ DATABASES = {
|
|||||||
'NAME': 'mydatabase',
|
'NAME': 'mydatabase',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -14,12 +14,34 @@ 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="/static/images/icons/icon-72x72.png" sizes="72x72">',
|
||||||
|
'<link rel="apple-touch-icon" href="/static/images/icons/icon-96x96.png" sizes="96x96">',
|
||||||
|
'<link rel="apple-touch-icon" href="/static/images/icons/icon-128x128.png" sizes="128x128">',
|
||||||
|
'<link rel="apple-touch-icon" href="/static/images/icons/icon-144x144.png" sizes="144x144">',
|
||||||
|
'<link rel="apple-touch-icon" href="/static/images/icons/icon-152x152.png" sizes="152x152">',
|
||||||
|
'<link rel="apple-touch-icon" href="/static/images/icons/icon-192x192.png" sizes="192x192">',
|
||||||
|
'<link rel="apple-touch-icon" href="/static/images/icons/icon-384x384.png" sizes="384x384">',
|
||||||
|
'<link rel="apple-touch-icon" href="/static/images/icons/icon-512x512.png" sizes="512x512">',
|
||||||
'<link rel="manifest" href="/manifest.json">',
|
'<link rel="manifest" href="/manifest.json">',
|
||||||
|
'<meta name="mobile-web-app-capable" content="yes">',
|
||||||
'<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">',
|
||||||
'<meta name="apple-mobile-web-app-title" content="MyApp">',
|
'<meta name="apple-mobile-web-app-title" content="MyApp">',
|
||||||
'<meta name="apple-mobile-web-app-status-bar-style" content="default">'
|
'<meta name="application-name" content="MyApp">',
|
||||||
|
'<meta name="apple-mobile-web-app-status-bar-style" content="default">',
|
||||||
|
'<link rel="icon" sizes="512x512" href="/static/images/icons/icon-512x512.png">',
|
||||||
|
'<link href="/static/images/icons/splash-640x1136.png" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image"/>',
|
||||||
|
'<link href="/static/images/icons/splash-750x1334.png" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image"/>',
|
||||||
|
'<link href="/static/images/icons/splash-1242x2208.png" media="(device-width: 621px) and (device-height: 1104px) and (-webkit-device-pixel-ratio: 3)" rel="apple-touch-startup-image"/>',
|
||||||
|
'<link href="/static/images/icons/splash-1125x2436.png" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)" rel="apple-touch-startup-image"/>',
|
||||||
|
'<link href="/static/images/icons/splash-828x1792.png" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image"/>',
|
||||||
|
'<link href="/static/images/icons/splash-1242x2688.png" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3)" rel="apple-touch-startup-image"/>',
|
||||||
|
'<link href="/static/images/icons/splash-1536x2048.png" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image"/>',
|
||||||
|
'<link href="/static/images/icons/splash-1668x2224.png" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image"/>',
|
||||||
|
'<link href="/static/images/icons/splash-1668x2388.png" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image"/>',
|
||||||
|
'<link href="/static/images/icons/splash-2048x2732.png" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image"/>',
|
||||||
|
'<meta name="msapplication-TileColor" content="#fff">',
|
||||||
|
'<meta name="msapplication-TileImage" content="/static/images/icons/icon-512x512.png">'
|
||||||
]
|
]
|
||||||
for text in tags:
|
for text in tags:
|
||||||
with self.subTest():
|
with self.subTest():
|
||||||
@@ -33,9 +55,9 @@ class CreateMetaTemplateTagTest(TestCase):
|
|||||||
"navigator.serviceWorker.register('/serviceworker.js', {",
|
"navigator.serviceWorker.register('/serviceworker.js', {",
|
||||||
"scope: '.'",
|
"scope: '.'",
|
||||||
"}).then(function (registration) {",
|
"}).then(function (registration) {",
|
||||||
"console.log('django-progressive-web-app: ServiceWorker registration successful with scope: ', registration.scope);",
|
"console.log('django-pwa: ServiceWorker registration successful with scope: ', registration.scope);",
|
||||||
"}, function (err) {",
|
"}, function (err) {",
|
||||||
"console.log('django-progressive-web-app: ServiceWorker registration failed: ', err);",
|
"console.log('django-pwa: ServiceWorker registration failed: ', err);",
|
||||||
"});",
|
"});",
|
||||||
"</script>"
|
"</script>"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
22
tox.ini
@@ -1,10 +1,26 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = py36-django{20}
|
envlist =
|
||||||
|
py35-django{18,19,110,111,20,21}
|
||||||
|
py36-django{20}
|
||||||
|
py36-django{21}
|
||||||
|
py37-django{20}
|
||||||
|
py37-django{21}
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands = python runtests.py
|
commands = python runtests.py
|
||||||
setenv =
|
setenv =
|
||||||
DJANGO_SETTINGS_MODULE=tests.settings
|
DJANGO_SETTINGS_MODULE=tests.settings
|
||||||
PYTHONPATH={toxinidir}
|
PYTHONPATH={toxinidir}
|
||||||
basepython = py36: python3.6
|
basepython =
|
||||||
deps = django20: Django==2.0
|
py35: python3.5
|
||||||
|
py36: python3.6
|
||||||
|
py37: python3.7
|
||||||
|
deps =
|
||||||
|
django18: django==1.8
|
||||||
|
django19: django==1.9
|
||||||
|
django110: django==1.10
|
||||||
|
django111: django==1.11
|
||||||
|
django20: Django==2.0
|
||||||
|
django21: Django==2.1
|
||||||
|
|
||||||
|
pypandoc==1.3.3
|
||||||