Skip to content

Commit

Permalink
Initial Django app.
Browse files Browse the repository at this point in the history
  • Loading branch information
amjoconn committed Nov 11, 2016
0 parents commit aeaf9d6
Show file tree
Hide file tree
Showing 15 changed files with 303 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
*.pyc
*.sqlite3
80 changes: 80 additions & 0 deletions README.md
@@ -0,0 +1,80 @@
Async Task Channels Demo
========================

The demo goes with the talk [Async Tasks with Django
Channels](http://albertoconnor.ca/pycon-canada-2016-talk.html) given
at [PyCon Canada 2016](https://2016.pycon.ca/en/schedule/016-albert-oconnor/)

It is split up into a few different step with tags so you can more
easily see how it was built up.

step1
-----

git clone https://github.com/albertoconnor/asyncdemo.git
cd asyncdemo
git checkout step1

At this point, the code is for a basic Django 1.10 web app. Get into your
favorite type of virtual env and then run the following.

pip install -r requirements.text
cd asyncdemo
python manage.py migrate
python manage.py runserver

Now you should have the hello_view being served at
http://127.0.0.1:8000/. You can append "?name=yourname" to change the
name displayed.

Meanwhile, there is a simulated slow network call which writes out the
hello message to the console and takes between 5 and 30 seconds to
do it.

step2
-----

git checkout step2

This has updated requirements.txt and settings.py and created a
routes.py which is even to get Django to run with Channels. The view
remains unchanged, so it will still be slow when you runserver. You can
see though the output Django produces when you runserver has been
changed.

step 3
------

git checkout step3

A "notify" channel has been declared and the view now uses it. When you
runserver the website is more responsive, but if you hammer it those
slow 30 second simulated network calls will gum up the system.

bonusround
----------

git checkout bonusround

This changes channels to use redis as the broker of messages. Even to
runserver you will need to have redis running locally.

When you do you will get the same behavior of step 3. To get around the
slow down under load we will need to run everything in separate
processes, in separate terminals.

daphne asyncdemo.asgi:channel_layer --port 8000

This runs the interface server in it's own process. If you try to
connect now it will time out because there are no workers running.

You can run workers in as many separate terminals as you like:

python manage.py runworker

But the key to trying to maintain throughput is to run a worker which
will never be tied up with a notification task:

python manage.py runworker --exclude-channels=notify

This worker will ensure that hello_view remains responsive.
Empty file added asyncdemo/asyncdemo/__init__.py
Empty file.
121 changes: 121 additions & 0 deletions asyncdemo/asyncdemo/settings.py
@@ -0,0 +1,121 @@
"""
Django settings for asyncdemo project.
Generated by 'django-admin startproject' using Django 1.10.2.
For more information on this file, see
https://docs.djangoproject.com/en/1.10/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.10/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'ud9(^27w%&0j=1oe1kvp@i*1=y0#6#e!4dz^4sncz#pihs_%kt'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'hello',
]

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'asyncdemo.urls'

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

WSGI_APPLICATION = 'asyncdemo.wsgi.application'


# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}


# Password validation
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]


# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/

STATIC_URL = '/static/'
7 changes: 7 additions & 0 deletions asyncdemo/asyncdemo/urls.py
@@ -0,0 +1,7 @@
from django.conf.urls import url

from hello import views

urlpatterns = [
url(r'^$', views.hello_view),
]
16 changes: 16 additions & 0 deletions asyncdemo/asyncdemo/wsgi.py
@@ -0,0 +1,16 @@
"""
WSGI config for asyncdemo project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "asyncdemo.settings")

application = get_wsgi_application()
Empty file added asyncdemo/hello/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions asyncdemo/hello/admin.py
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
5 changes: 5 additions & 0 deletions asyncdemo/hello/apps.py
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class HelloConfig(AppConfig):
name = 'hello'
Empty file.
3 changes: 3 additions & 0 deletions asyncdemo/hello/models.py
@@ -0,0 +1,3 @@
from django.db import models

# Create your models here.
12 changes: 12 additions & 0 deletions asyncdemo/hello/templates/hello.html
@@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Asyc Demo</title>
</head>

<body>
<h1>{{ message }}</h1>
</body>

</html>
3 changes: 3 additions & 0 deletions asyncdemo/hello/tests.py
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
29 changes: 29 additions & 0 deletions asyncdemo/hello/views.py
@@ -0,0 +1,29 @@
import time
from django.shortcuts import render


def delay():
while True:
for i in [5, 5, 5, 30]: # Simulate unreliability
yield i


delay_generator = delay()


def send_notification(message):
time.sleep(next(delay_generator))
print(message) # Simulate sending to slack etc.


def hello_view(request, template="hello.html"):
name = request.GET.get('name', 'World')
message = 'Hello, {}!'.format(name)

send_notification(message)

return render(
request,
template,
dict(message=message),
)
22 changes: 22 additions & 0 deletions asyncdemo/manage.py
@@ -0,0 +1,22 @@
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "asyncdemo.settings")
try:
from django.core.management import execute_from_command_line
except ImportError:
# The above import may fail for some other reason. Ensure that the
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
try:
import django
except ImportError:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
)
raise
execute_from_command_line(sys.argv)

0 comments on commit aeaf9d6

Please sign in to comment.