Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ dist/
*.egg-info/
.tox/
.coverage
.DS_Store
.DS_Store
.vscode/
62 changes: 49 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ django-pwa
=====
[![Build Status](https://travis-ci.org/silviolleite/django-pwa.svg)](https://travis-ci.org/silviolleite/django-pwa)
[![Maintainability](https://api.codeclimate.com/v1/badges/246542ea921058c4f76f/maintainability)](https://codeclimate.com/github/silviolleite/django-pwa/maintainability)
[![codecov](https://codecov.io/gh/silviolleite/django-pwa/branch/master/graph/badge.svg)](https://codecov.io/gh/silviolleite/django-pwa)
[![codecov](https://codecov.io/gh/silviolleite/django-pwa/branch/master/graph/badge.svg)](https://codecov.io/gh/silviolleite/django-pwa)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/django-pwa.svg)](https://pypi.org/project/django-pwa/)
[![PyPI - Downloads](https://img.shields.io/pypi/v/django-pwa.svg)](https://pypi.org/project/django-pwa)
[![PyPI - Downloads](https://img.shields.io/pypi/djversions/django-pwa.svg)](https://pypi.org/project/django-pwa)
Expand Down Expand Up @@ -47,7 +47,7 @@ Configure your app name, description, icons and splash screen images in settings
```python

PWA_APP_NAME = 'My App'
PWA_APP_DESCRIPTION = "My app description"
PWA_APP_DESCRIPTION = 'My app description'
PWA_APP_THEME_COLOR = '#0A0302'
PWA_APP_BACKGROUND_COLOR = '#ffffff'
PWA_APP_DISPLAY = 'standalone'
Expand Down Expand Up @@ -78,7 +78,7 @@ PWA_APP_LANG = 'en-US'

```
#### Show console.log
Set the `PWA_APP_DEBUG_MODE = False` to disable the the `console.log` on browser.
Set the `PWA_APP_DEBUG_MODE = False` to disable the the `console.log` on browser.

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`.

Expand Down Expand Up @@ -121,9 +121,12 @@ 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 staticCacheKey = 'django-pwa-';
var staticCacheName = staticCacheKey + 'v' + new Date().getTime();

var offlineUrl = '/offline/';
var filesToCache = [
'/offline',
offlineUrl,
'/css/django-pwa-app.css',
'/images/icons/icon-72x72.png',
'/images/icons/icon-96x96.png',
Expand All @@ -146,14 +149,14 @@ var filesToCache = [
];

// Cache on install
self.addEventListener("install", event => {
self.addEventListener('install', event => {
this.skipWaiting();
event.waitUntil(
caches.open(staticCacheName)
.then(cache => {
return cache.addAll(filesToCache);
})
)
);
});

// Clear cache on activate
Expand All @@ -162,7 +165,7 @@ self.addEventListener('activate', event => {
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => (cacheName.startsWith("django-pwa-")))
.filter(cacheName => (cacheName.startsWith(staticCacheKey)))
.filter(cacheName => (cacheName !== staticCacheName))
.map(cacheName => caches.delete(cacheName))
);
Expand All @@ -171,32 +174,65 @@ self.addEventListener('activate', event => {
});

// Serve from Cache
self.addEventListener("fetch", event => {
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
.catch(() => {
return caches.match('offline');
return caches.match(offlineUrl);
})
)
);
});
```

Adding Your Own Service Worker
=====
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).
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
# Using an absolute path that will be returned as it is
PWA_SERVICE_WORKER_MODE = 'path'
PWA_SERVICE_WORKER_PATH = os.path.join(BASE_DIR, 'my_app', 'serviceworker.js')
```

Adding Your Own Service Worker as template
=====
To add a dynamic service worker functionality, you'll want to create a `serviceworker.tmpl` or similarly named template in a template directory, and then point at it using the `PWA_SERVICE_WORKER_TEMPLATE` variable (`PWA_APP_FETCH_URL` is passed through).

```python
# Using a template that allows dynamic content
PWA_SERVICE_WORKER_MODE = 'template'
PWA_SERVICE_WORKER_TEMPLATE = 'my_app/serviceworker.tmpl'
PWA_SERVICE_WORKER_CACHE_KEY = 'my-app-pwa-'
PWA_SERVICE_WORKER_EXTRA_FILES = [
# include other necessary files
]
```

The `serviceworker.tmpl` template can be easily extended.

Sample of `my_app/serviceworker.tmpl` file:

```tmpl
{% extends "serviceworker.tmpl" %}

{% load static %}

{% block pwa_custom %}
// include favicon
filesToCache.push('{% static "favicon.ico" %}');

// include robots.txt
filesToCache.push('robots.txt');
{% endblock pwa_custom %}
```

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`.
You can overwrite it in a template directory if you continue using the default `serviceworker.js`.


Feedback
Expand Down
12 changes: 10 additions & 2 deletions pwa/app_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,21 @@
# Lazy-evaluate URLs so including pwa.urls in root urlconf works
resolve_url = lazy(resolve_url, str)

_here_ = os.path.abspath(os.path.dirname(__file__))

# Get script prefix for apps not mounted under /
_PWA_SCRIPT_PREFIX = get_script_prefix()

PWA_SERVICE_WORKER_MODE = getattr(settings, 'PWA_SERVICE_WORKER_MODE', 'path')

# Path to the service worker implementation. Default implementation is empty.
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(_here_, 'templates', 'serviceworker.js'))
# Templated service worker
PWA_SERVICE_WORKER_CACHE_KEY = getattr(settings, 'PWA_SERVICE_WORKER_CACHE_KEY', 'django-pwa-')
PWA_SERVICE_WORKER_EXTRA_FILES = getattr(settings, 'PWA_SERVICE_WORKER_EXTRA_FILES', [])
PWA_SERVICE_WORKER_TEMPLATE = getattr(settings, 'PWA_SERVICE_WORKER_TEMPLATE', 'serviceworker.tmpl')

# App parameters to include in manifest.json and appropriate meta tags
PWA_APP_NAME = getattr(settings, 'PWA_APP_NAME', 'MyApp')
PWA_APP_DESCRIPTION = getattr(settings, 'PWA_APP_DESCRIPTION', 'My Progressive Web App')
Expand Down
19 changes: 11 additions & 8 deletions pwa/templates/serviceworker.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// 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 staticCacheKey = 'django-pwa-';
var staticCacheName = staticCacheKey + 'v' + new Date().getTime();

var offlineUrl = '/offline/';
var filesToCache = [
'/offline/',
offlineUrl,
'/static/css/django-pwa-app.css',
'/static/images/icons/icon-72x72.png',
'/static/images/icons/icon-96x96.png',
Expand All @@ -25,14 +28,14 @@ var filesToCache = [
];

// Cache on install
self.addEventListener("install", event => {
self.addEventListener('install', event => {
this.skipWaiting();
event.waitUntil(
caches.open(staticCacheName)
.then(cache => {
return cache.addAll(filesToCache);
})
)
);
});

// Clear cache on activate
Expand All @@ -41,7 +44,7 @@ self.addEventListener('activate', event => {
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => (cacheName.startsWith("django-pwa-")))
.filter(cacheName => (cacheName.startsWith(staticCacheKey)))
.filter(cacheName => (cacheName !== staticCacheName))
.map(cacheName => caches.delete(cacheName))
);
Expand All @@ -50,14 +53,14 @@ self.addEventListener('activate', event => {
});

// Serve from Cache
self.addEventListener("fetch", event => {
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
.catch(() => {
return caches.match('/offline/');
return caches.match(offlineUrl);
})
)
);
});
59 changes: 59 additions & 0 deletions pwa/templates/serviceworker.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{% load static %}
// Base Service Worker implementation. To use your own Service Worker, set the PWA_SERVICE_WORKER_TEMPLATE variable in settings.py

{% block pwa_init %}
var staticCacheKey = '{{ pwa_cache_key }}';
var staticCacheName = staticCacheKey + 'v' + new Date().getTime();

var offlineUrl = '{{ pwa_offline_url }}';
var filesToCache = [
offlineUrl,
'{% static "css/django-pwa-app.css" %}',
{% for f in pwa_cached_files %}'{{ f }}',
{% endfor %}
];
{% endblock pwa_init %}


{% block pwa_custom %}{% endblock pwa_custom %}


{% block pwa_listeners %}
// 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(staticCacheKey)))
.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(offlineUrl);
})
);
});
{% endblock pwa_listeners %}
35 changes: 32 additions & 3 deletions pwa/views.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
from django.http import HttpResponse
from django.shortcuts import render
from django.shortcuts import render, resolve_url

from . import app_settings


def service_worker(request):
response = HttpResponse(open(app_settings.PWA_SERVICE_WORKER_PATH).read(), content_type='application/javascript')
return response
try:
if app_settings.PWA_SERVICE_WORKER_MODE == 'template':
# get the list of "cached" entries
_files = [
# the icons
*[icon['src'] for icon in app_settings.PWA_APP_ICONS],
# the splash screens
*[screen['src'] for screen in app_settings.PWA_APP_SPLASH_SCREEN],
# other files
*app_settings.PWA_SERVICE_WORKER_EXTRA_FILES,
]

return render(
request,
app_settings.PWA_SERVICE_WORKER_TEMPLATE,
{
'pwa_offline_url': resolve_url('offline'),
'pwa_cache_key': app_settings.PWA_SERVICE_WORKER_CACHE_KEY,
'pwa_cached_files': _files,
},
content_type='application/javascript',
)

return HttpResponse(
open(app_settings.PWA_SERVICE_WORKER_PATH).read(),
content_type='application/javascript'
)

except Exception:
# return an empty js file with any error
return HttpResponse('', content_type='application/javascript')


def manifest(request):
Expand Down
6 changes: 5 additions & 1 deletion tests/test_settings_attr.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ def test_has_defined(self):
'PWA_APP_ICONS',
'PWA_APP_DIR',
'PWA_APP_LANG',
'PWA_APP_STATUS_BAR_COLOR'
'PWA_APP_STATUS_BAR_COLOR',
'PWA_SERVICE_WORKER_MODE',
'PWA_SERVICE_WORKER_CACHE_KEY',
'PWA_SERVICE_WORKER_EXTRA_FILES',
'PWA_SERVICE_WORKER_TEMPLATE',
]
for attr in attributes:
with self.subTest():
Expand Down
Loading