Skip to content

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
Tomas Walch committed Dec 10, 2015
0 parents commit 1541f25
Show file tree
Hide file tree
Showing 18 changed files with 1,971 additions and 0 deletions.
31 changes: 31 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.DS_Store

test.*
*.swp
*~
*.py[co]

*.egg
*.egg-info
dist
eggs
sdist
develop-eggs
.installed.cfg

build

pip-log.txt

.idea
.coverage
.tox
.env/

docs/_build
example/style.css
tests/tmp
cover/
*.orig

/README.html
14 changes: 14 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
language: python
python:
- '2.7'
- '3.4'
script: nosetests
deploy:
provider: pypi
user: Tomas.Walch
password:
secure: tS5xcmgEv5tf55PTSGZKRt0kRU0QjcVmQOZ3yN3+80tAM/0AC3YrUBs5AFUjRMzhx/yd9jaTZxYoMO1NYpEDcOeUXGDazTXsd7nb6A7sc6yWzWUCWmHhc1HSf/7GdRoiTHrpthCkMcDZ9cQemuuN8yHpAZteT/XSjBebJOWG0URJe8y7vsVzn9bDP+NRHQyrqaYD2lFKjLIx8i0UiWWdjnB7huAN+LCVTnjoeu85GA1Q97kCt5YuZel/W/PhL4vvDWLNV37DYMrxnjDSYfEvafcYiXSqw9CV1cJF2jnNKP+d+0wzSMK66MA85axR0ZInBuqsygGEN+apojBVp+erOHQ4SE0ODaEpNRyCEIgmSEVSarJKGq3JMc3xkGnq0cle2DdUs7M93Ay4Prk+vMjqNuVWpDLAtCmfTl4Nt29XK6uUiZ0a4zW3GO24qRHzg0vl+8pUJ7LAqChfr4C1TnbF5VuB//2EMzP6R7PcIzt9f4dEhPLlj+U8zBWrcrLqQ+ul9+Qs5fTwFROwEeti+19v45nW4IeumPgck5L/89jOzkJo/xqkqlN7vOO6RVIg0UF8QHHkAXEycGDB4s405ocSkHnSBGJZMlcZy5+jcxE+2MxfzDDQJ3l6v8B/eP+unaQAvyhKa/849CFA0V/E8Kl1nrgqEnZuw0tOsfgqB+WRDBs=
on:
branch: master
tags: true
repo: tjwalch/django-livereload-server
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Changelog
=========

Version 0.1
------------
First release
27 changes: 27 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2015, Tomas Walch

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the author nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include livereload/vendors/livereload.js
include LICENSE
include README.rst
54 changes: 54 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
========================
django-livereload-server
========================

This django app adds a management command that starts a livereload server watching all your static files and templates as well
as a custom ``runserver`` command that issues livereload requests when the development server is ready after a restart.

Installation
------------

Install package: ::

$ pip install django-livereload-server

Add ``'livereload'`` to the ``INSTALLED_APPS``, before ``'django.contrib.staticfiles'`` if this is used::

INSTALLED_APPS = (
...
'livereload',
...
)

Add ``'livereload.middleware.LiveReloadScript'`` to the
``MIDDLEWARE_CLASSES`` (probably at the end)::

MIDDLEWARE_CLASSES = (
...
'livereload.middleware.LiveReloadScript',
)

Configuration
-------------
If you need the livereload server to use a different port than the default 35729,
specify it by setting ``LIVERELOAD_PORT`` in ``settings.py``.

Usage
-----
Start the livereload server with: ::

$ ./manage.py livereload

Extra files and/or paths to watch for changes can be added as positional arguments.

Start the development server as usual with ``./manage.py runserver``. The command now accepts two additional
options:

* ``--nolivereload`` to disable livereload functionality
* ``--livereload-port`` to override both default and settings file specified port

Background
----------
This project is based on a merge of `python-livereload <https://github.com/lepture/python-livereload>`_ and
`django-livereload <https://github.com/Fantomas42/django-livereload>`_, excellent projects both and even better for
smooth django development when combined.
13 changes: 13 additions & 0 deletions livereload/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""django-livereload"""
__version__ = '0.1'
__license__ = 'BSD License'

__author__ = 'Tomas Walch'
__email__ = '[email protected]'

__url__ = 'https://github.com/tjwalch/django-livereload-server'


def livereload_port():
from django.conf import settings
return int(getattr(settings, 'LIVERELOAD_PORT', 35729))
147 changes: 147 additions & 0 deletions livereload/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
"""
livereload.handlers
~~~~~~~~~~~~~~~~~~~
HTTP and WebSocket handlers for livereload.
:copyright: (c) 2013 - 2015 by Hsiaoming Yang
:license: BSD, see LICENSE for more details.
"""

import os
import time
import logging
from pkg_resources import resource_string
from tornado import web
from tornado import ioloop
from tornado import escape
from tornado.websocket import WebSocketHandler
from tornado.util import ObjectDict

logger = logging.getLogger('livereload')


class LiveReloadHandler(WebSocketHandler):
waiters = set()
watcher = None
_last_reload_time = None

def allow_draft76(self):
return True

def check_origin(self, origin):
return True

def on_close(self):
if self in LiveReloadHandler.waiters:
LiveReloadHandler.waiters.remove(self)

def send_message(self, message):
if isinstance(message, dict):
message = escape.json_encode(message)

try:
self.write_message(message)
except:
logger.error('Error sending message', exc_info=True)

@classmethod
def start_tasks(cls):
if cls._last_reload_time:
return

if not cls.watcher._tasks:
logger.info('Watch current working directory')
cls.watcher.watch(os.getcwd())

cls._last_reload_time = time.time()
logger.info('Start watching changes')
if not cls.watcher.start(cls.poll_tasks):
logger.info('Start detecting changes')
ioloop.PeriodicCallback(cls.poll_tasks, 800).start()

@classmethod
def poll_tasks(cls):
filepath, delay = cls.watcher.examine()
if not filepath or delay == 'forever' or not cls.waiters:
return
reload_time = 3

if delay:
reload_time = max(3 - delay, 1)
if filepath == '__livereload__':
reload_time = 0

if time.time() - cls._last_reload_time < reload_time:
# if you changed lot of files in one time
# it will refresh too many times
logger.info('Ignore: %s', filepath)
return
if delay:
loop = ioloop.IOLoop.current()
loop.call_later(delay, cls.reload_waiters)
else:
cls.reload_waiters()

@classmethod
def reload_waiters(cls, path=None):
logger.info(
'Reload %s waiters: %s',
len(cls.waiters),
cls.watcher.filepath,
)

if path is None:
path = cls.watcher.filepath or '*'

msg = {
'command': 'reload',
'path': path,
'liveCSS': True,
'liveImg': True,
}

cls._last_reload_time = time.time()
for waiter in cls.waiters:
try:
waiter.write_message(msg)
except:
logger.error('Error sending message', exc_info=True)
cls.waiters.remove(waiter)

def on_message(self, message):
"""Handshake with livereload.js
1. client send 'hello'
2. server reply 'hello'
3. client send 'info'
"""
message = ObjectDict(escape.json_decode(message))
if message.command == 'hello':
handshake = {
'command': 'hello',
'protocols': [
'http://livereload.com/protocols/official-7',
],
'serverName': 'livereload-tornado',
}
self.send_message(handshake)

if message.command == 'info' and 'url' in message:
logger.info('Browser Connected: %s' % message.url)
LiveReloadHandler.waiters.add(self)


class LiveReloadJSHandler(web.RequestHandler):

def get(self):
self.set_header('Content-Type', 'application/javascript')
self.write(resource_string(__name__, 'vendors/livereload.js'))


class ForceReloadHandler(web.RequestHandler):
def get(self):
path = self.get_argument('path', default=None) or '*'
LiveReloadHandler.reload_waiters(path)
self.write('ok')
1 change: 1 addition & 0 deletions livereload/management/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Management for django-livereload-server"""
1 change: 1 addition & 0 deletions livereload/management/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Management commands for django-livereload"""
41 changes: 41 additions & 0 deletions livereload/management/commands/livereload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import os
from django.conf import settings
from django.apps import apps
from django.core.management.base import BaseCommand
import itertools
from livereload.server import Server
from livereload import livereload_port


class Command(BaseCommand):
help = 'Runs a livereload server watching static files and templates.'

def add_arguments(self, parser):
super(Command, self).add_arguments(parser)
parser.add_argument(
'extra',
nargs='*',
action='store',
help='Extra files or directories to watch',
)

def handle(self, *args, **options):
server = Server()

for dir in itertools.chain(
settings.STATICFILES_DIRS,
getattr(settings, 'TEMPLATE_DIRS', []),
options.get('extra', []),
args):
server.watch(dir)
for template in getattr(settings, 'TEMPLATES', []):
for dir in template['DIRS']:
server.watch(dir)
for app_config in apps.get_app_configs():
server.watch(os.path.join(app_config.path, 'static'))
server.watch(os.path.join(app_config.path, 'templates'))

server.serve(
host='127.0.0.1',
liveport=livereload_port(),
)
Loading

0 comments on commit 1541f25

Please sign in to comment.