Initial revision.
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*.py[cod]
|
||||||
|
.idea/
|
||||||
|
*.orig
|
||||||
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
- Initial release
|
||||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Scott Vitale
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
2
MANIFEST.in
Normal file
2
MANIFEST.in
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
include *.md
|
||||||
|
recursive-include pwa/templates *
|
||||||
91
README.md
Normal file
91
README.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
django-progressive-web-app
|
||||||
|
=====
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Launching the app from your home screen will display your app [without browser chrome](images/screenshot2.png). As such, it's critical that your application provides all navigation within the HTML (no reliance on the browser back or forward button).
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
=====
|
||||||
|
Progressive Web Apps require HTTPS unless being served from localhost. If you're not already using HTTPS on your site, check out [Let's Encrypt](https://letsencrypt.org/) and [ZeroSSL](https://zerossl.com/).
|
||||||
|
|
||||||
|
Installation
|
||||||
|
=====
|
||||||
|
Install from PyPI:
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install django-progressive-web-app
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=====
|
||||||
|
Add `pwa` to your list of `INSTALLED_APPS` in settings.py:
|
||||||
|
|
||||||
|
```python
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
...
|
||||||
|
'pwa',
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure your app name, description, and icons in settings.py:
|
||||||
|
```python
|
||||||
|
PWA_APP_NAME = 'My Kickass App'
|
||||||
|
PWA_APP_DESCRIPTION = "Do kickass things all day long without that pesky browser chrome"
|
||||||
|
PWA_APP_THEME_COLOR = '#0A0302'
|
||||||
|
PWA_APP_ICONS = [
|
||||||
|
{
|
||||||
|
'src': '/static/images/my_app_icon.png',
|
||||||
|
'sizes': '160x160'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the progressive web app URLs to urls.py:
|
||||||
|
```python
|
||||||
|
from django.conf.urls import url, include
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
...
|
||||||
|
url('', include('pwa.urls')), # You MUST use an empty string as the URL prefix
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Inject the required meta tags in your base.html (or wherever your HTML <head> is defined):
|
||||||
|
```html
|
||||||
|
{% load pwa %}
|
||||||
|
|
||||||
|
<head>
|
||||||
|
...
|
||||||
|
{% progressive_web_app_meta %}
|
||||||
|
...
|
||||||
|
</head>
|
||||||
|
```
|
||||||
|
|
||||||
|
Troubleshooting
|
||||||
|
=====
|
||||||
|
While running the Django test server:
|
||||||
|
1. Verify that `/manifest.json` is being served
|
||||||
|
1. Verify that `/serviceworker.js` 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 "Add to homescreen" link on the Application Tab to verify you can add the app successfully.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```python
|
||||||
|
PWA_SERVICE_WORKER_PATH = os.path.join(BASE_DIR, 'my_app', 'serviceworker.js')
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Feedback
|
||||||
|
=====
|
||||||
|
I welcome your feedback and pull requests. Enjoy!
|
||||||
|
|
||||||
|
License
|
||||||
|
=====
|
||||||
|
All files in this repository are distributed under the MIT license.
|
||||||
BIN
images/screenshot1.png
Normal file
BIN
images/screenshot1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
BIN
images/screenshot2.png
Normal file
BIN
images/screenshot2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
0
pwa/__init__.py
Normal file
0
pwa/__init__.py
Normal file
21
pwa/app_settings.py
Normal file
21
pwa/app_settings.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
""" Settings required by django-progressive-web-app. """
|
||||||
|
from django.conf import settings
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 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'))
|
||||||
|
|
||||||
|
# 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')
|
||||||
|
PWA_APP_ROOT_URL = getattr(settings, 'PWA_APP_ROOT_URL', '/')
|
||||||
|
PWA_APP_THEME_COLOR = getattr(settings, 'PWA_APP_THEME_COLOR', '#000')
|
||||||
|
PWA_APP_ICONS = getattr(settings, 'PWA_APP_ICONS', [
|
||||||
|
{
|
||||||
|
'src': '/',
|
||||||
|
'sizes': '160x160'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
5
pwa/apps.py
Normal file
5
pwa/apps.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PwaConfig(AppConfig):
|
||||||
|
name = 'pwa'
|
||||||
11
pwa/templates/manifest.json
Normal file
11
pwa/templates/manifest.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{% load pwa %}
|
||||||
|
{
|
||||||
|
"name": {{ PWA_APP_NAME|js }},
|
||||||
|
"short_name": {{ PWA_APP_NAME|js }},
|
||||||
|
"description": {{ PWA_APP_DESCRIPTION|js }},
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#fff",
|
||||||
|
"theme_color": {{ PWA_APP_THEME_COLOR|js }},
|
||||||
|
"icons": {{ PWA_APP_ICONS|js }}
|
||||||
|
}
|
||||||
27
pwa/templates/pwa.html
Normal file
27
pwa/templates/pwa.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<!-- Path to manifest.json -->
|
||||||
|
<link rel="manifest" href="/manifest.json">
|
||||||
|
|
||||||
|
<!-- Icons for Apple Devices -->
|
||||||
|
{% for icon in PWA_APP_ICONS %}
|
||||||
|
<link rel="apple-touch-icon" href="{{ icon.src }}" sizes="{{ icon.sizes }}">
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<meta name="theme-color" content="{{ PWA_APP_THEME_COLOR }}">
|
||||||
|
<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-status-bar-style" content="default">
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
// Initialize the service worker
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
navigator.serviceWorker.register('/serviceworker.js').then(function (registration) {
|
||||||
|
// Registration was successful
|
||||||
|
console.log('django-progressive-web-app: ServiceWorker registration successful with scope: ', registration.scope);
|
||||||
|
}).catch(function (err) {
|
||||||
|
// registration failed :(
|
||||||
|
console.log('django-progressive-web-app: ServiceWorker registration failed: ', err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
2
pwa/templates/serviceworker.js
Normal file
2
pwa/templates/serviceworker.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Empty Service Worker implementation. To use your own Service Worker, set the PWA_SERVICE_WORKER_PATH variable in
|
||||||
|
// settings.py
|
||||||
0
pwa/templatetags/__init__.py
Normal file
0
pwa/templatetags/__init__.py
Normal file
25
pwa/templatetags/pwa.py
Normal file
25
pwa/templatetags/pwa.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
from .. import app_settings
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(is_safe=True)
|
||||||
|
def js(obj):
|
||||||
|
""" Transform a python object so it can be safely used in javascript/JSON. """
|
||||||
|
return mark_safe(json.dumps(obj, cls=DjangoJSONEncoder))
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag('pwa.html', takes_context=True)
|
||||||
|
def progressive_web_app_meta(context):
|
||||||
|
# Pass all PWA_* settings into the template
|
||||||
|
return {
|
||||||
|
setting_name: getattr(app_settings, setting_name)
|
||||||
|
for setting_name in dir(app_settings)
|
||||||
|
if setting_name.startswith('PWA_')
|
||||||
|
}
|
||||||
8
pwa/urls.py
Normal file
8
pwa/urls.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from django.conf.urls import url
|
||||||
|
from .views import manifest, service_worker
|
||||||
|
|
||||||
|
# Serve up serviceworker.js and manifest.json at the root
|
||||||
|
urlpatterns = [
|
||||||
|
url('^serviceworker.js$', service_worker),
|
||||||
|
url('^manifest.json$', manifest)
|
||||||
|
]
|
||||||
17
pwa/views.py
Normal file
17
pwa/views.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
from . import app_settings
|
||||||
|
|
||||||
|
|
||||||
|
def service_worker(request):
|
||||||
|
response = HttpResponse(open(app_settings.PWA_SERVICE_WORKER_PATH).read(), content_type='application/javascript')
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def manifest(request):
|
||||||
|
return render(request, 'manifest.json', {
|
||||||
|
setting_name: getattr(app_settings, setting_name)
|
||||||
|
for setting_name in dir(app_settings)
|
||||||
|
if setting_name.startswith('PWA_')
|
||||||
|
})
|
||||||
1
requirements-dev.txt
Normal file
1
requirements-dev.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pypandoc==1.3.3
|
||||||
45
setup.py
Normal file
45
setup.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import os
|
||||||
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
|
short_description = 'A Django app to include a manifest.json and Service Worker instance to enable progressive web ' \
|
||||||
|
'app behavior '
|
||||||
|
|
||||||
|
# noinspection PyBroadException
|
||||||
|
try:
|
||||||
|
# noinspection PyPackageRequirements
|
||||||
|
import pypandoc
|
||||||
|
|
||||||
|
long_description = pypandoc.convert('README.md', 'rst')
|
||||||
|
except:
|
||||||
|
long_description = short_description
|
||||||
|
|
||||||
|
# allow setup.py to be run from any path
|
||||||
|
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='django-progressive-web-app',
|
||||||
|
version='0.1',
|
||||||
|
packages=find_packages(),
|
||||||
|
include_package_data=True,
|
||||||
|
license='MIT License',
|
||||||
|
description=short_description,
|
||||||
|
long_description=long_description,
|
||||||
|
url='http://github.com/svvitale/django-progressive-web-app',
|
||||||
|
author='Scott Vitale',
|
||||||
|
author_email='svvitale@gmail.com',
|
||||||
|
classifiers=[
|
||||||
|
'Environment :: Web Environment',
|
||||||
|
'Framework :: Django',
|
||||||
|
'Framework :: Django :: 1.10',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.4',
|
||||||
|
'Programming Language :: Python :: 3.5',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
|
'Topic :: Internet :: WWW/HTTP',
|
||||||
|
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
||||||
|
],
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user