mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2025-01-27 17:54:37 +03:00
Merge pull request #2506 from Andrew-Chen-Wang/async
This commit is contained in:
commit
4e789d8f60
|
@ -45,6 +45,7 @@ Features
|
||||||
* Optimized development and production settings
|
* Optimized development and production settings
|
||||||
* Registration via django-allauth_
|
* Registration via django-allauth_
|
||||||
* Comes with custom user model ready to go
|
* Comes with custom user model ready to go
|
||||||
|
* Optional basic ASGI setup for Websockets
|
||||||
* Optional custom static build using Gulp and livereload
|
* 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)
|
* 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
|
* Media storage using Amazon S3 or Google Cloud Storage
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
"SparkPost",
|
"SparkPost",
|
||||||
"Other SMTP"
|
"Other SMTP"
|
||||||
],
|
],
|
||||||
|
"use_async": "n",
|
||||||
"use_drf": "n",
|
"use_drf": "n",
|
||||||
"custom_bootstrap_compilation": "n",
|
"custom_bootstrap_compilation": "n",
|
||||||
"use_compressor": "n",
|
"use_compressor": "n",
|
||||||
|
|
|
@ -68,10 +68,14 @@ First things first.
|
||||||
|
|
||||||
$ python manage.py migrate
|
$ 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
|
$ 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/
|
.. _PostgreSQL: https://www.postgresql.org/download/
|
||||||
.. _Redis: https://redis.io/download
|
.. _Redis: https://redis.io/download
|
||||||
.. _createdb: https://www.postgresql.org/docs/current/static/app-createdb.html
|
.. _createdb: https://www.postgresql.org/docs/current/static/app-createdb.html
|
||||||
|
|
|
@ -83,6 +83,9 @@ mail_service:
|
||||||
8. SparkPost_
|
8. SparkPost_
|
||||||
9. `Other SMTP`_
|
9. `Other SMTP`_
|
||||||
|
|
||||||
|
use_async:
|
||||||
|
Indicates whether the project should use web sockets with Uvicorn + Gunicorn.
|
||||||
|
|
||||||
use_drf:
|
use_drf:
|
||||||
Indicates whether the project should be configured to use `Django Rest Framework`_.
|
Indicates whether the project should be configured to use `Django Rest Framework`_.
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,15 @@ def remove_celery_files():
|
||||||
os.remove(file_name)
|
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():
|
def remove_dottravisyml_file():
|
||||||
os.remove(".travis.yml")
|
os.remove(".travis.yml")
|
||||||
|
|
||||||
|
@ -372,6 +381,9 @@ def main():
|
||||||
if "{{ cookiecutter.use_drf }}".lower() == "n":
|
if "{{ cookiecutter.use_drf }}".lower() == "n":
|
||||||
remove_drf_starter_files()
|
remove_drf_starter_files()
|
||||||
|
|
||||||
|
if "{{ cookiecutter.use_async }}".lower() == "n":
|
||||||
|
remove_async_files()
|
||||||
|
|
||||||
print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR)
|
print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,8 @@ SUPPORTED_COMBINATIONS = [
|
||||||
{"cloud_provider": "GCP", "mail_service": "SparkPost"},
|
{"cloud_provider": "GCP", "mail_service": "SparkPost"},
|
||||||
{"cloud_provider": "GCP", "mail_service": "Other SMTP"},
|
{"cloud_provider": "GCP", "mail_service": "Other SMTP"},
|
||||||
# Note: cloud_providers GCP and None with mail_service Amazon SES is not supported
|
# 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": "y"},
|
||||||
{"use_drf": "n"},
|
{"use_drf": "n"},
|
||||||
{"js_task_runner": "None"},
|
{"js_task_runner": "None"},
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
release: python manage.py migrate
|
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
|
web: gunicorn config.wsgi:application
|
||||||
|
{%- endif %}
|
||||||
{% if cookiecutter.use_celery == "y" -%}
|
{% if cookiecutter.use_celery == "y" -%}
|
||||||
worker: celery worker --app=config.celery_app --loglevel=info
|
worker: celery worker --app=config.celery_app --loglevel=info
|
||||||
beat: celery beat --app=config.celery_app --loglevel=info
|
beat: celery beat --app=config.celery_app --loglevel=info
|
||||||
|
|
|
@ -6,4 +6,8 @@ set -o nounset
|
||||||
|
|
||||||
|
|
||||||
python manage.py migrate
|
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
|
python manage.py runserver_plus 0.0.0.0:8000
|
||||||
|
{% endif %}
|
||||||
|
|
|
@ -27,4 +27,8 @@ if compress_enabled; then
|
||||||
python /app/manage.py compress
|
python /app/manage.py compress
|
||||||
fi
|
fi
|
||||||
{%- endif %}
|
{%- 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
|
/usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app
|
||||||
|
{%- endif %}
|
||||||
|
|
40
{{cookiecutter.project_slug}}/config/asgi.py
Normal file
40
{{cookiecutter.project_slug}}/config/asgi.py
Normal 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']}")
|
|
@ -1,6 +1,9 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
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.urls import include, path
|
||||||
from django.views import defaults as default_views
|
from django.views import defaults as default_views
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
@ -20,7 +23,12 @@ urlpatterns = [
|
||||||
path("accounts/", include("allauth.urls")),
|
path("accounts/", include("allauth.urls")),
|
||||||
# Your stuff: custom urls includes go here
|
# Your stuff: custom urls includes go here
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + 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
|
# API URLS
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
# API base url
|
# API base url
|
||||||
|
|
13
{{cookiecutter.project_slug}}/config/websocket.py
Normal file
13
{{cookiecutter.project_slug}}/config/websocket.py
Normal 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!"})
|
|
@ -110,6 +110,18 @@ function imgCompression() {
|
||||||
.pipe(dest(paths.images))
|
.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
|
// Run django server
|
||||||
function runServer(cb) {
|
function runServer(cb) {
|
||||||
var cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'})
|
var cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'})
|
||||||
|
@ -118,6 +130,7 @@ function runServer(cb) {
|
||||||
cb(code)
|
cb(code)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
// Browser sync server for live reload
|
// Browser sync server for live reload
|
||||||
function initBrowserSync() {
|
function initBrowserSync() {
|
||||||
|
@ -166,8 +179,12 @@ const generateAssets = parallel(
|
||||||
// Set up dev environment
|
// Set up dev environment
|
||||||
const dev = parallel(
|
const dev = parallel(
|
||||||
{%- if cookiecutter.use_docker == 'n' %}
|
{%- if cookiecutter.use_docker == 'n' %}
|
||||||
|
{%- if cookiecutter.use_async == 'y' %}
|
||||||
|
asyncRunServer,
|
||||||
|
{%- else %}
|
||||||
runServer,
|
runServer,
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
initBrowserSync,
|
initBrowserSync,
|
||||||
watchPaths
|
watchPaths
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
flower==0.9.4 # https://github.com/mher/flower
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- 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
|
# Django
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
-r ./base.txt
|
-r ./base.txt
|
||||||
|
|
||||||
|
{%- if cookiecutter.use_async == 'n' %}
|
||||||
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
|
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
|
||||||
|
{%- endif %}
|
||||||
psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
|
psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
|
||||||
{%- if cookiecutter.use_whitenoise == 'n' %}
|
{%- if cookiecutter.use_whitenoise == 'n' %}
|
||||||
Collectfast==2.1.0 # https://github.com/antonagestam/collectfast
|
Collectfast==2.1.0 # https://github.com/antonagestam/collectfast
|
||||||
|
|
Loading…
Reference in New Issue
Block a user