commit a66325af7bb62da4d6e621a09ef65b0c45017d05 Author: Scott Date: Sat Feb 4 22:20:40 2017 -0700 Initial revision. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4977a25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.py[cod] +.idea/ +*.orig diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ebbe20a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.1.0 + + - Initial release diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..aeeef25 --- /dev/null +++ b/LICENSE.md @@ -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. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..30a7de1 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include *.md +recursive-include pwa/templates * \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3499bc4 --- /dev/null +++ b/README.md @@ -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. + +![Prompt for install](images/screenshot1.png) + +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 %} + + + ... + {% progressive_web_app_meta %} + ... + +``` + +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. diff --git a/images/screenshot1.png b/images/screenshot1.png new file mode 100644 index 0000000..b975b44 Binary files /dev/null and b/images/screenshot1.png differ diff --git a/images/screenshot2.png b/images/screenshot2.png new file mode 100644 index 0000000..fa29f9b Binary files /dev/null and b/images/screenshot2.png differ diff --git a/pwa/__init__.py b/pwa/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pwa/app_settings.py b/pwa/app_settings.py new file mode 100644 index 0000000..513bc2b --- /dev/null +++ b/pwa/app_settings.py @@ -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' + } +]) + + diff --git a/pwa/apps.py b/pwa/apps.py new file mode 100644 index 0000000..ea35e80 --- /dev/null +++ b/pwa/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class PwaConfig(AppConfig): + name = 'pwa' diff --git a/pwa/templates/manifest.json b/pwa/templates/manifest.json new file mode 100644 index 0000000..95bddcf --- /dev/null +++ b/pwa/templates/manifest.json @@ -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 }} +} diff --git a/pwa/templates/pwa.html b/pwa/templates/pwa.html new file mode 100644 index 0000000..9207a4e --- /dev/null +++ b/pwa/templates/pwa.html @@ -0,0 +1,27 @@ + + + + +{% for icon in PWA_APP_ICONS %} + +{% endfor %} + + + + + + + diff --git a/pwa/templates/serviceworker.js b/pwa/templates/serviceworker.js new file mode 100644 index 0000000..91e75b8 --- /dev/null +++ b/pwa/templates/serviceworker.js @@ -0,0 +1,2 @@ +// Empty Service Worker implementation. To use your own Service Worker, set the PWA_SERVICE_WORKER_PATH variable in +// settings.py diff --git a/pwa/templatetags/__init__.py b/pwa/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pwa/templatetags/pwa.py b/pwa/templatetags/pwa.py new file mode 100644 index 0000000..39f7168 --- /dev/null +++ b/pwa/templatetags/pwa.py @@ -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_') + } diff --git a/pwa/urls.py b/pwa/urls.py new file mode 100644 index 0000000..fe26e7d --- /dev/null +++ b/pwa/urls.py @@ -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) +] diff --git a/pwa/views.py b/pwa/views.py new file mode 100644 index 0000000..cc77fca --- /dev/null +++ b/pwa/views.py @@ -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_') + }) diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..e3a0394 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +pypandoc==1.3.3 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e982de4 --- /dev/null +++ b/setup.py @@ -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', + ], +) \ No newline at end of file