Merge pull request #2506 from Andrew-Chen-Wang/async

This commit is contained in:
Bruno Alla 2020-04-16 09:51:03 +01:00 committed by GitHub
commit 4e789d8f60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 121 additions and 2 deletions

View File

@ -45,6 +45,7 @@ Features
* Optimized development and production settings
* Registration via django-allauth_
* Comes with custom user model ready to go
* Optional basic ASGI setup for Websockets
* Optional custom static build using Gulp and livereload
* Send emails via Anymail_ (using Mailgun_ by default or Amazon SES if AWS is selected cloud provider, but switchable)
* Media storage using Amazon S3 or Google Cloud Storage

View File

@ -44,6 +44,7 @@
"SparkPost",
"Other SMTP"
],
"use_async": "n",
"use_drf": "n",
"custom_bootstrap_compilation": "n",
"use_compressor": "n",

View File

@ -68,10 +68,14 @@ First things first.
$ python manage.py migrate
#. See the application being served through Django development server: ::
#. If you're running synchronously, see the application being served through Django development server: ::
$ python manage.py runserver 0.0.0.0:8000
or if you're running asynchronously: ::
$ gunicorn config.asgi --bind 0.0.0.0:8000 -k uvicorn.workers.UvicornWorker --reload
.. _PostgreSQL: https://www.postgresql.org/download/
.. _Redis: https://redis.io/download
.. _createdb: https://www.postgresql.org/docs/current/static/app-createdb.html

View File

@ -83,6 +83,9 @@ mail_service:
8. SparkPost_
9. `Other SMTP`_
use_async:
Indicates whether the project should use web sockets with Uvicorn + Gunicorn.
use_drf:
Indicates whether the project should be configured to use `Django Rest Framework`_.

View File

@ -101,6 +101,15 @@ def remove_celery_files():
os.remove(file_name)
def remove_async_files():
file_names = [
os.path.join("config", "asgi.py"),
os.path.join("config", "websocket.py"),
]
for file_name in file_names:
os.remove(file_name)
def remove_dottravisyml_file():
os.remove(".travis.yml")
@ -372,6 +381,9 @@ def main():
if "{{ cookiecutter.use_drf }}".lower() == "n":
remove_drf_starter_files()
if "{{ cookiecutter.use_async }}".lower() == "n":
remove_async_files()
print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR)

View File

@ -73,6 +73,8 @@ SUPPORTED_COMBINATIONS = [
{"cloud_provider": "GCP", "mail_service": "SparkPost"},
{"cloud_provider": "GCP", "mail_service": "Other SMTP"},
# Note: cloud_providers GCP and None with mail_service Amazon SES is not supported
{"use_async": "y"},
{"use_async": "n"},
{"use_drf": "y"},
{"use_drf": "n"},
{"js_task_runner": "None"},

View File

@ -1,5 +1,9 @@
release: python manage.py migrate
{% if cookiecutter.use_async == "y" -%}
web: gunicorn config.asgi:application -k uvicorn.workers.UvicornWorker
{%- else %}
web: gunicorn config.wsgi:application
{%- endif %}
{% if cookiecutter.use_celery == "y" -%}
worker: celery worker --app=config.celery_app --loglevel=info
beat: celery beat --app=config.celery_app --loglevel=info

View File

@ -6,4 +6,8 @@ set -o nounset
python manage.py migrate
{%- if cookiecutter.use_async == 'y' %}
/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:8000 --chdir=/app -k uvicorn.workers.UvicornWorker --reload
{%- else %}
python manage.py runserver_plus 0.0.0.0:8000
{% endif %}

View File

@ -27,4 +27,8 @@ if compress_enabled; then
python /app/manage.py compress
fi
{%- endif %}
{% if cookiecutter.use_async == 'y' %}
/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:5000 --chdir=/app -k uvicorn.workers.UvicornWorker
{% else %}
/usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app
{%- endif %}

View File

@ -0,0 +1,40 @@
"""
ASGI config for {{ cookiecutter.project_name }} project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/dev/howto/deployment/asgi/
"""
import os
import sys
from pathlib import Path
from django.core.asgi import get_asgi_application
# This allows easy placement of apps within the interior
# {{ cookiecutter.project_slug }} directory.
app_path = Path(__file__).parents[1].resolve()
sys.path.append(str(app_path / "{{ cookiecutter.project_slug }}"))
# If DJANGO_SETTINGS_MODULE is unset, default to the local settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
# This application object is used by any ASGI server configured to use this file.
django_application = get_asgi_application()
# Apply ASGI middleware here.
# from helloworld.asgi import HelloWorldApplication
# application = HelloWorldApplication(application)
# Import websocket application here, so apps from django_application are loaded first
from config.websocket import websocket_application # noqa isort:skip
async def application(scope, receive, send):
if scope["type"] == "http":
await django_application(scope, receive, send)
elif scope["type"] == "websocket":
await websocket_application(scope, receive, send)
else:
raise NotImplementedError(f"Unknown scope type {scope['type']}")

View File

@ -1,6 +1,9 @@
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
{%- if cookiecutter.use_async == 'y' %}
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
{%- endif %}
from django.urls import include, path
from django.views import defaults as default_views
from django.views.generic import TemplateView
@ -20,7 +23,12 @@ urlpatterns = [
path("accounts/", include("allauth.urls")),
# Your stuff: custom urls includes go here
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
{% if cookiecutter.use_drf == 'y' -%}
{%- if cookiecutter.use_async == 'y' %}
if settings.DEBUG:
# Static file serving when using Gunicorn + Uvicorn for local web socket development
urlpatterns += staticfiles_urlpatterns()
{%- endif %}
{% if cookiecutter.use_drf == 'y' %}
# API URLS
urlpatterns += [
# API base url

View File

@ -0,0 +1,13 @@
async def websocket_application(scope, receive, send):
while True:
event = await receive()
if event["type"] == "websocket.connect":
await send({"type": "websocket.accept"})
if event["type"] == "websocket.disconnect":
break
if event["type"] == "websocket.receive":
if event["text"] == "ping":
await send({"type": "websocket.send", "text": "pong!"})

View File

@ -110,6 +110,18 @@ function imgCompression() {
.pipe(dest(paths.images))
}
{% if cookiecutter.use_async == 'y' -%}
// Run django server
function asyncRunServer() {
var cmd = spawn('gunicorn', [
'config.asgi', '-k', 'uvicorn.workers.UvicornWorker', '--reload'
], {stdio: 'inherit'}
)
cmd.on('close', function(code) {
console.log('gunicorn exited with code ' + code)
})
}
{%- else %}
// Run django server
function runServer(cb) {
var cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'})
@ -118,6 +130,7 @@ function runServer(cb) {
cb(code)
})
}
{%- endif %}
// Browser sync server for live reload
function initBrowserSync() {
@ -166,8 +179,12 @@ const generateAssets = parallel(
// Set up dev environment
const dev = parallel(
{%- if cookiecutter.use_docker == 'n' %}
{%- if cookiecutter.use_async == 'y' %}
asyncRunServer,
{%- else %}
runServer,
{%- endif %}
{%- endif %}
initBrowserSync,
watchPaths
)

View File

@ -16,6 +16,10 @@ django-celery-beat==2.0.0 # https://github.com/celery/django-celery-beat
flower==0.9.4 # https://github.com/mher/flower
{%- endif %}
{%- endif %}
{%- if cookiecutter.use_async == 'y' %}
uvicorn==0.11.3 # https://github.com/encode/uvicorn
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
{%- endif %}
# Django
# ------------------------------------------------------------------------------

View File

@ -2,7 +2,9 @@
-r ./base.txt
{%- if cookiecutter.use_async == 'n' %}
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
{%- endif %}
psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
{%- if cookiecutter.use_whitenoise == 'n' %}
Collectfast==2.1.0 # https://github.com/antonagestam/collectfast