diff --git a/README.rst b/README.rst index d020aa71a..00801b030 100644 --- a/README.rst +++ b/README.rst @@ -46,6 +46,7 @@ Optional Integrations * Integration with Sentry_ for error logging * Integration with NewRelic_ for performance monitoring * Integration with Opbeat_ for performance monitoring +* Experimental Channels_ support .. _alpha: http://blog.getbootstrap.com/2015/08/19/bootstrap-4-alpha/ .. _Hitch: https://github.com/hitchtest/hitchtest @@ -64,7 +65,7 @@ Optional Integrations .. _NewRelic: https://newrelic.com .. _docker-compose: https://www.github.com/docker/compose .. _Opbeat: https://opbeat.com/ - +.. _Channels: https://github.com/andrewgodwin/channels Constraints ----------- diff --git a/cookiecutter.json b/cookiecutter.json index 925a75507..8bcd59cf2 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -16,5 +16,6 @@ "use_newrelic": "n", "use_opbeat": "n", "windows": "n", - "use_python2": "n" + "use_python2": "n", + "use_channels": "n" } diff --git a/docs/channels.rst b/docs/channels.rst new file mode 100644 index 000000000..6c52ab779 --- /dev/null +++ b/docs/channels.rst @@ -0,0 +1,29 @@ +Channels +======== + +Cookiecutter-django comes with (experimental) channels support. A basic skeleton of a Django +project with channels is generated automatically for you if you choose to `use_channels` during +project setup. + +.. note:: If you are using docker, a websocket server and the worker processes are started +automatically for you. Just replace 127.0.0.1 with the IP of your docker-machine you are running. + +There's a basic app called `channelsapp` created automatically for you. It uses the routes from +`conf/routes.py`. The app is based on the `channels getting started guide +`_. + +To see if your websocket server is started, go to http://127.0.0.1:9000/. You should see a greeting +message from autobahn. + +Now, to get started, just open a browser and put the following into the JavaScript console +to test your new code:: + + socket = new WebSocket("ws://127.0.0.1:9000"); + socket.onmessage = function(e) { + alert(e.data); + } + socket.send("hello world"); + + +You should see an alert come back immediately saying "hello world" - your +message has round-tripped through the server and come back to trigger the alert. diff --git a/docs/index.rst b/docs/index.rst index 8cfc45e85..60cc1d33b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,6 +21,7 @@ Contents: settings linters live-reloading-and-sass-compilation + channels deployment-on-heroku deployment-with-docker faq diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 9570e9f46..03f97885f 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -100,6 +100,12 @@ def remove_task_app(project_directory): ) shutil.rmtree(task_app_location) + +def remove_channels_files(project_directory): + """Removes channels files if it isn't going to be used""" + for path in ["config/routing.py", '{{ cookiecutter.repo_name }}/channelsapp']: + shutil.rmtree(path) + # IN PROGRESS # def copy_doc_files(project_directory): # cookiecutters_dir = DEFAULT_CONFIG['cookiecutters_dir'] @@ -125,5 +131,9 @@ make_secret_key(PROJECT_DIRECTORY) if '{{ cookiecutter.use_celery }}'.lower() == 'n': remove_task_app(PROJECT_DIRECTORY) +# 2. Removes the taskapp if celery isn't going to be used +if '{{ cookiecutter.use_channels }}'.lower() == 'n': + remove_channels_files(PROJECT_DIRECTORY) + # 3. Copy files from /docs/ to {{ cookiecutter.repo_name }}/docs/ # copy_doc_files(PROJECT_DIRECTORY) diff --git a/{{cookiecutter.repo_name}}/config/routing.py b/{{cookiecutter.repo_name}}/config/routing.py new file mode 100644 index 000000000..178b826a0 --- /dev/null +++ b/{{cookiecutter.repo_name}}/config/routing.py @@ -0,0 +1,10 @@ +""" +This is an example for a channels routing config using the getting started guide at +https://channels.readthedocs.org/en/latest/getting-started.html +""" +channel_routing = { + "websocket.connect": "{{cookiecutter.repo_name}}.channelsapp.consumers.ws_add", + "websocket.keepalive": "{{cookiecutter.repo_name}}.channelsapp.consumers.ws_add", + "websocket.receive": "{{cookiecutter.repo_name}}.channelsapp.consumers.ws_message", + "websocket.disconnect": "{{cookiecutter.repo_name}}.channelsapp.consumers.ws_disconnect", +} diff --git a/{{cookiecutter.repo_name}}/config/settings/common.py b/{{cookiecutter.repo_name}}/config/settings/common.py index 0a5deebef..1c3741853 100644 --- a/{{cookiecutter.repo_name}}/config/settings/common.py +++ b/{{cookiecutter.repo_name}}/config/settings/common.py @@ -231,6 +231,17 @@ INSTALLED_APPS += ('kombu.transport.django',) BROKER_URL = env("CELERY_BROKER_URL", default='django://') ########## END CELERY {% endif %} +{% if cookiecutter.use_channels == 'y' -%} +########## CHANNELS +INSTALLED_APPS += ('channels',) +CHANNEL_BACKENDS = { + "default": { + "BACKEND": "channels.backends.database.DatabaseChannelBackend", + "ROUTING": "config.routing.channel_routing", + }, +} +########## END CHANNELS +{% endif %} # Location of root django.contrib.admin URL, use {% raw %}{% url 'admin:index' %}{% endraw %} ADMIN_URL = r'^admin/' diff --git a/{{cookiecutter.repo_name}}/config/settings/production.py b/{{cookiecutter.repo_name}}/config/settings/production.py index 09b7ba378..8ac0ad01d 100644 --- a/{{cookiecutter.repo_name}}/config/settings/production.py +++ b/{{cookiecutter.repo_name}}/config/settings/production.py @@ -286,5 +286,12 @@ LOGGING = { {% endif %} # Custom Admin URL, use {% raw %}{% url 'admin:index' %}{% endraw %} ADMIN_URL = env('DJANGO_ADMIN_URL') +{% if cookiecutter.use_channels == 'y' -%} +########## CHANNELS +# the redis channel is broken on channels==0.8. Use the default DB backend in prod for now +# CHANNEL_BACKENDS["default"]["BACKEND"] = "channels.backends.redis_py.RedisChannelBackend" +# CHANNEL_BACKENDS["default"]["HOSTS"] = [("redis-channel", 6379)], +########## END CHANNELS +{% endif %} # Your production stuff: Below this line define 3rd party library settings diff --git a/{{cookiecutter.repo_name}}/dev.yml b/{{cookiecutter.repo_name}}/dev.yml index d018f7cd2..0e3b579a9 100644 --- a/{{cookiecutter.repo_name}}/dev.yml +++ b/{{cookiecutter.repo_name}}/dev.yml @@ -8,10 +8,29 @@ postgres: django: dockerfile: Dockerfile-dev build: . + {% if cookiecutter.use_channels == 'y' -%} + # we need to use runserver when using channels because runserver starts a worker process + # automatically when channels is installed + command: python /app/manage.py runserver 0.0.0.0:8000 + {% else %} command: python /app/manage.py runserver_plus 0.0.0.0:8000 + {% endif %} volumes: - .:/app ports: - "8000:8000" links: - postgres + +{% if cookiecutter.use_channels == 'y' -%} +channelserver: + dockerfile: Dockerfile-dev + build: . + command: python /app/manage.py runwsserver + volumes: + - .:/app + ports: + - "9000:9000" + links: + - postgres +{% endif %} diff --git a/{{cookiecutter.repo_name}}/docker-compose.yml b/{{cookiecutter.repo_name}}/docker-compose.yml index dc6810372..572d8c658 100644 --- a/{{cookiecutter.repo_name}}/docker-compose.yml +++ b/{{cookiecutter.repo_name}}/docker-compose.yml @@ -41,3 +41,25 @@ celerybeat: - redis command: celery -A {{cookiecutter.repo_name}}.taskapp beat -l INFO {% endif %} + +{% if cookiecutter.use_channels == 'y' -%} +channelworker: + build: . + user: django + env_file: .env + command: python /app/manage.py runworker + links: + - postgres + - redis + +channelserver: + build: . + user: django + env_file: .env + command: python /app/manage.py runwsserver + ports: + - "0.0.0.0:9000:9000" + links: + - postgres + - redis +{% endif %} diff --git a/{{cookiecutter.repo_name}}/requirements/base.txt b/{{cookiecutter.repo_name}}/requirements/base.txt index 6d3c39719..d491b384e 100644 --- a/{{cookiecutter.repo_name}}/requirements/base.txt +++ b/{{cookiecutter.repo_name}}/requirements/base.txt @@ -46,5 +46,9 @@ redis>=2.10.0 {% if cookiecutter.use_celery == "y" %} celery==3.1.19 {% endif %} +{% if cookiecutter.use_channels == 'y' -%} +channels==0.8 +autobahn[twisted] +{% endif %} # Your custom requirements go here