├── djgunicorn ├── wsgi │ ├── __init__.py │ ├── base.py │ └── staticfiles.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── gunserver.py ├── __init__.py ├── config.py ├── gunicorn.py └── logging.py ├── requirements.txt ├── requirements ├── dev.txt ├── test.txt └── base.txt ├── AUTHORS.rst ├── setup.cfg ├── MANIFEST.in ├── benchmarks ├── simpledjango │ ├── manage.py │ ├── settings.py │ └── __init__.py └── run.py ├── .editorconfig ├── .gitignore ├── HISTORY.rst ├── LICENSE ├── setup.py ├── README.rst └── CONTRIBUTING.rst /djgunicorn/wsgi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djgunicorn/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djgunicorn/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djgunicorn/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.3.0' 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements/base.txt 2 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | -r test.txt 2 | wheel 3 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | -r base.txt 2 | flake8>=2.1.0 3 | -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | django>=1.9.0 2 | gunicorn>=19.0.0 3 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | * Tzu-ping Chung 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | 8 | [bumpversion:file:djgunicorn/__init__.py] 9 | 10 | [wheel] 11 | universal = 1 12 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | include requirements.txt 7 | recursive-include requirements *.txt 8 | recursive-include djgunicorn *.py 9 | -------------------------------------------------------------------------------- /benchmarks/simpledjango/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | 5 | from django.core.management import execute_from_command_line 6 | 7 | 8 | if __name__ == '__main__': 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') 10 | execute_from_command_line() 11 | -------------------------------------------------------------------------------- /djgunicorn/wsgi/base.py: -------------------------------------------------------------------------------- 1 | """WSGI config for Gunicorn. 2 | 3 | This module is designed not to be imported directly, but provided to be 4 | loaded by Gunicorn when it is launched. 5 | """ 6 | 7 | from django.core.servers.basehttp import get_internal_wsgi_application 8 | 9 | application = get_internal_wsgi_application() 10 | -------------------------------------------------------------------------------- /benchmarks/simpledjango/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | # Make sure we can access the simpledjango directory. 5 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 6 | 7 | 8 | DEBUG = True 9 | 10 | ROOT_URLCONF = 'simpledjango' 11 | 12 | INSTALLED_APPS = ['djgunicorn'] 13 | 14 | SECRET_KEY = 'm05k7' 15 | 16 | TIME_ZONE = 'UTC' 17 | -------------------------------------------------------------------------------- /djgunicorn/wsgi/staticfiles.py: -------------------------------------------------------------------------------- 1 | """WSGI config for Gunicorn with staticfiles enabled. 2 | 3 | This module is designed not to be imported directly, but provided to be 4 | loaded by Gunicorn when it is launched. 5 | """ 6 | 7 | from django.contrib.staticfiles.handlers import StaticFilesHandler 8 | from .base import application 9 | 10 | application = StaticFilesHandler(application) 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{py,rst,ini}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{html,css,scss,json,yml}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [Makefile] 23 | indent_style = tab 24 | -------------------------------------------------------------------------------- /benchmarks/simpledjango/__init__.py: -------------------------------------------------------------------------------- 1 | """An ultra-minimal Django setup that we'll use for benchmarking. 2 | 3 | To run:: 4 | 5 | python simpledjango/manage.py runserver 6 | 7 | 8 | """ 9 | 10 | from django.conf.urls import url 11 | from django.core.wsgi import get_wsgi_application 12 | from django.http import HttpResponse 13 | 14 | 15 | def page(request): 16 | return HttpResponse('It works!') 17 | 18 | 19 | urlpatterns = [ 20 | url(r'^$', page), 21 | ] 22 | 23 | 24 | application = get_wsgi_application() 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | __pycache__ 3 | 4 | # C extensions 5 | *.so 6 | 7 | # Packages 8 | *.egg 9 | *.egg-info 10 | dist 11 | build 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | htmlcov 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | 39 | # Complexity 40 | output/*.html 41 | output/*/index.html 42 | 43 | # Sphinx 44 | docs/_build 45 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | History 2 | ------- 3 | 4 | 0.3.0 (2016-04-13) 5 | ++++++++++++++++++ 6 | 7 | * Add Gunicorn config changing directory to where ``manage.py`` to avoid 8 | problems when ``manage.py`` is run in another directory. 9 | * Info message is now displayed when Gunicorn reloads, as ``runserver`` does. 10 | * Enable extended ``runserver`` provided by ``staticfiles`` only if it is 11 | installed (which is the default). 12 | * Gunicorn access logs are now coloured, as ``runserver``'s. 13 | * Get rid of a custom static handler in favour of Django's stock one. 14 | 15 | 16 | 0.2.0 (2016-04-12) 17 | ++++++++++++++++++ 18 | 19 | * Gunicorn invocation is re-implemented with ``subprocess`` to handle reloading 20 | gracefully. (`benoitc/gunicorn#935`_) 21 | 22 | 23 | 0.1.1 (2016-04-11) 24 | ++++++++++++++++++ 25 | 26 | * Lazy-load WSGI handler in Gunicorn application to avoid race conditions. 27 | 28 | 29 | 0.1.0 (2016-04-11) 30 | ++++++++++++++++++ 31 | 32 | * First release on PyPI. 33 | 34 | 35 | .. _`benoitc/gunicorn#935`: 36 | -------------------------------------------------------------------------------- /benchmarks/run.py: -------------------------------------------------------------------------------- 1 | import statistics 2 | import timeit 3 | 4 | 5 | setup_code = """ 6 | import atexit 7 | import requests 8 | import subprocess 9 | import sys 10 | import time 11 | 12 | from __main__ import test_runserver 13 | 14 | p = subprocess.Popen( 15 | [sys.executable, 'simpledjango/manage.py', {cmd!r}], 16 | stdout=subprocess.DEVNULL, 17 | stderr=subprocess.DEVNULL, 18 | ) 19 | atexit.register(lambda: p.terminate()) 20 | time.sleep(1) # Wait for the server to launch. 21 | """ 22 | 23 | 24 | def test_runserver(): 25 | import requests 26 | requests.get('http://localhost:8000/') 27 | 28 | 29 | if __name__ == '__main__': 30 | for cmd in ['runserver', 'gunserver']: 31 | t = timeit.Timer('test_runserver()', setup_code.format(cmd=cmd)) 32 | loop, repeat = 1000, 3 33 | results = t.repeat(repeat, loop) 34 | 35 | print() 36 | print(cmd, 'loop of', loop, 'repeated', repeat, 'times') 37 | print('=============') 38 | print('Average: {mean:.2f}'.format(mean=statistics.mean(results))) 39 | print('Medians: {me:.2f} {lo:.2f} {hi:.2f}'.format( 40 | me=statistics.median(results), 41 | lo=statistics.median_low(results), 42 | hi=statistics.median_high(results), 43 | )) 44 | print('Min/Max: {min:.2f} {max:.2f}'.format( 45 | min=min(results), 46 | max=max(results), 47 | )) 48 | print() 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Tzu-ping Chung 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | * 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. 9 | 10 | * Neither the name of django-gunicorn nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | 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. -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import djgunicorn 5 | 6 | from setuptools import setup, find_packages 7 | 8 | version = djgunicorn.__version__ 9 | 10 | with open('README.rst') as f: 11 | readme = f.read() 12 | 13 | with open('HISTORY.rst') as f: 14 | history = f.read() 15 | 16 | with open('requirements/base.txt') as f: 17 | install_requires = f.read().strip().splitlines() 18 | 19 | setup( 20 | name='djgunicorn', 21 | version=version, 22 | description="""Run Django development server with Gunicorn.""", 23 | long_description=readme + '\n\n' + history, 24 | author='Tzu-ping Chung', 25 | author_email='uranusjr@gmail.com', 26 | url='https://github.com/uranusjr/django-gunicorn', 27 | packages=find_packages(), 28 | include_package_data=True, 29 | install_requires=install_requires, 30 | license='BSD', 31 | zip_safe=False, 32 | keywords='django-gunicorn', 33 | classifiers=[ 34 | 'Development Status :: 3 - Alpha', 35 | 'Framework :: Django', 36 | 'Framework :: Django :: 1.7', 37 | 'Framework :: Django :: 1.8', 38 | 'Intended Audience :: Developers', 39 | 'License :: OSI Approved :: BSD License', 40 | 'Natural Language :: English', 41 | 'Programming Language :: Python :: 2', 42 | 'Programming Language :: Python :: 2.7', 43 | 'Programming Language :: Python :: 3', 44 | 'Programming Language :: Python :: 3.3', 45 | 'Programming Language :: Python :: 3.4', 46 | 'Programming Language :: Python :: 3.5', 47 | ], 48 | ) 49 | -------------------------------------------------------------------------------- /djgunicorn/config.py: -------------------------------------------------------------------------------- 1 | """Gunicorn configuration file used by gunserver's Gunicorn subprocess. 2 | 3 | This module is not designed to be imported directly, but provided as 4 | Gunicorn's configuration file. 5 | """ 6 | 7 | import os 8 | import sys 9 | 10 | import django 11 | import gunicorn 12 | 13 | 14 | # General configs. 15 | logger_class = 'djgunicorn.logging.GunicornLogger' 16 | 17 | 18 | def post_worker_init(worker): 19 | """Hook into Gunicorn to display message after launching. 20 | 21 | This mimics the behaviour of Django's stock runserver command. 22 | """ 23 | quit_command = 'CTRL-BREAK' if sys.platform == 'win32' else 'CONTROL-C' 24 | sys.stdout.write( 25 | "Django version {djangover}, Gunicorn version {gunicornver}, " 26 | "using settings {settings!r}\n" 27 | "Starting development server at {urls}\n" 28 | "Quit the server with {quit_command}.\n".format( 29 | djangover=django.get_version(), 30 | gunicornver=gunicorn.__version__, 31 | settings=os.environ.get('DJANGO_SETTINGS_MODULE'), 32 | urls=', '.join('http://{0}/'.format(b) for b in worker.cfg.bind), 33 | quit_command=quit_command, 34 | ), 35 | ) 36 | 37 | 38 | def worker_int(worker): 39 | """Hook into Gunicorn to display message when worker gets an interruption. 40 | 41 | The purpose of this hook is purely cosmetic: we want a newline when the 42 | worker reloads. This has an unintended side effect to display an extra 43 | newline after the server quits, but it is relatively unimportant. 44 | """ 45 | sys.stdout.write('\n') 46 | -------------------------------------------------------------------------------- /djgunicorn/gunicorn.py: -------------------------------------------------------------------------------- 1 | import os 2 | import signal 3 | import subprocess 4 | import sys 5 | 6 | from django.conf import settings 7 | 8 | 9 | class GunicornRunner(object): 10 | 11 | executable = 'gunicorn' 12 | 13 | def __init__(self, addr, port, options): 14 | self.args = self.build_arguments(addr=addr, port=port, options=options) 15 | 16 | def build_arguments(self, addr, port, options): 17 | try: 18 | handle_statics = ( 19 | options['use_static_handler'] and 20 | (settings.DEBUG or options['insecure_serving']) 21 | ) 22 | except KeyError: 23 | handle_statics = False 24 | module = 'staticfiles' if handle_statics else 'base' 25 | 26 | # Change working directory to where manage.py is. 27 | working_dir = os.path.dirname(os.path.abspath(sys.argv[0])) 28 | 29 | args = [ 30 | self.executable, 31 | 'djgunicorn.wsgi.{module}:application'.format(module=module), 32 | '--bind', '{addr}:{port}'.format(addr=addr, port=int(port)), 33 | '--config', 'python:djgunicorn.config', 34 | '--access-logfile', '-', 35 | '--access-logformat', '%(t)s "%(r)s" %(s)s %(B)s', 36 | '--error-logfile', '-', 37 | '--log-level', 'warning', 38 | '--chdir', working_dir, 39 | ] 40 | if options['use_reloader']: 41 | args.append('--reload') 42 | return args 43 | 44 | def run(self): 45 | # Pass information into the Gunicorn process through environ. 46 | # This seems to be how Django's stock runserver do things as well; 47 | # see `django.core.management.commands.runserver.Command.execute`. 48 | if settings.SETTINGS_MODULE: 49 | os.environ['DJANGO_SETTINGS_MODULE'] = settings.SETTINGS_MODULE 50 | self.proc = subprocess.Popen(self.args, universal_newlines=True) 51 | self.proc.wait() 52 | 53 | def shutdown(self): 54 | self.proc.send_signal(signal.SIGTERM) # Graceful shutdown. 55 | -------------------------------------------------------------------------------- /djgunicorn/management/commands/gunserver.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import datetime 4 | import importlib 5 | import sys 6 | 7 | from django.core.management import get_commands 8 | from django.utils import six 9 | from django.utils.encoding import get_system_encoding 10 | 11 | from djgunicorn.gunicorn import GunicornRunner 12 | 13 | 14 | # Use the active runserver command as base. This is generally provided by 15 | # staticfiles, but can be django.core if it's not installed, or even something 16 | # else if some third-party app overrides it. 17 | def get_command_class(name): 18 | module = importlib.import_module('{app}.management.commands.{name}'.format( 19 | app=get_commands()[name], name=name, 20 | )) 21 | return module.Command 22 | 23 | BaseCommand = get_command_class('runserver') 24 | 25 | 26 | class Command(BaseCommand): 27 | 28 | help = "Starts a lightweight Web server for development with Gunicorn." 29 | 30 | def get_version(self): 31 | import djgunicorn 32 | return djgunicorn.__version__ 33 | 34 | def run(self, **options): 35 | """Override runserver's entry point to bring Gunicorn on. 36 | 37 | A large portion of code in this method is copied from 38 | `django.core.management.commands.runserver`. 39 | """ 40 | shutdown_message = options.get('shutdown_message', '') 41 | 42 | self.stdout.write("Performing system checks...\n\n") 43 | self.check(display_num_errors=True) 44 | self.check_migrations() 45 | now = datetime.datetime.now().strftime(r'%B %d, %Y - %X') 46 | if six.PY2: 47 | now = now.decode(get_system_encoding()) 48 | self.stdout.write(now) 49 | 50 | addr, port = self.addr, self.port 51 | addr = '[{}]'.format(addr) if self._raw_ipv6 else addr 52 | 53 | runner = GunicornRunner(addr, port, options) 54 | try: 55 | runner.run() 56 | except KeyboardInterrupt: 57 | runner.shutdown() 58 | if shutdown_message: 59 | self.stdout.write(shutdown_message) 60 | sys.exit(0) 61 | except: 62 | runner.shutdown() 63 | raise 64 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | django-gunicorn 3 | ============================= 4 | 5 | Run Django development server with Gunicorn. 6 | 7 | 8 | Quickstart 9 | ---------- 10 | 11 | Install django-gunicorn:: 12 | 13 | pip install djgunicorn 14 | 15 | Then add it to your ``INSTALLED_APPS``. You will get a new command 16 | ``gunserver`` (please forgive my little pun-loving self). It runs like 17 | Django's development server, but the HTTP handling is backed by Gunicorn. 18 | 19 | 20 | Features 21 | -------- 22 | 23 | You can find available options with:: 24 | 25 | python manage.py help gunserver 26 | 27 | Most options work as the built-in ``runserver`` command (in 28 | ``django.contrib.staticfiles``). Exceptions: 29 | 30 | * The ``verbosity`` setting is *not* passed to Gunicorn. This seems to be the 31 | case with ``runserver`` anyway, but I'm not sure. It still affects messages 32 | emitted by the command itself. 33 | * The ``nothreading`` option does not do anything. 34 | 35 | 36 | Todo 37 | ---- 38 | 39 | * Unit tests and CI. 40 | * Check how low we can support Django and Gunicorn versions. 41 | * Support for additional Gunicorn configs that may be useful. SSL seems to 42 | be a common need. 43 | * Is it possible to conditionally replace the ``runserver`` command? By 44 | installing an alternative app config, for example? 45 | * We now use ``DJANGO_SETTINGS_MODULE`` to relay where the settings module is 46 | to the Gunicorn subprocess (and let Django loads it automatically). This 47 | causes problems if ``settings.configure()`` is called manually without a 48 | module, and will likely require some hacks to fix. 49 | 50 | 51 | Interesting Links 52 | ----------------- 53 | 54 | * `#21978 (Add optional gunicorn support to runserver) `_ 55 | * `Fixed #21978 -- Added optional gunicorn support to runserver. · django/django `_ 56 | * `dj-static/dj_static.py · kennethreitz/dj-static `_ 57 | * `SSL support for Django-admin runserver `_ 58 | * `Settings — Gunicorn documentation `_ 59 | -------------------------------------------------------------------------------- /djgunicorn/logging.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import os 3 | import time 4 | import traceback 5 | 6 | import gunicorn.glogging 7 | 8 | from django.core.management.color import color_style 9 | 10 | 11 | def colorize(style, msg, resp): 12 | """Taken and modified from `django.utils.log.ServerFormatter.format` 13 | to mimic runserver's styling. 14 | """ 15 | code = resp.status.split(maxsplit=1)[0] 16 | if code[0] == '2': 17 | # Put 2XX first, since it should be the common case 18 | msg = style.HTTP_SUCCESS(msg) 19 | elif code[0] == '1': 20 | msg = style.HTTP_INFO(msg) 21 | elif code == '304': 22 | msg = style.HTTP_NOT_MODIFIED(msg) 23 | elif code[0] == '3': 24 | msg = style.HTTP_REDIRECT(msg) 25 | elif code == '404': 26 | msg = style.HTTP_NOT_FOUND(msg) 27 | elif code[0] == '4': 28 | msg = style.HTTP_BAD_REQUEST(msg) 29 | else: 30 | # Any 5XX, or any other response 31 | msg = style.HTTP_SERVER_ERROR(msg) 32 | return msg 33 | 34 | 35 | class GunicornLogger(gunicorn.glogging.Logger): 36 | """Custom logger class to add styling to access logs. 37 | 38 | Note that this is not a `logging.Logger` instance. 39 | """ 40 | datefmt = r'[%d/%b/%Y %H:%M:%S]' # Same date format as runserver. 41 | 42 | def __init__(self, cfg): 43 | super(GunicornLogger, self).__init__(cfg) 44 | if os.environ.get('DJANGO_COLORS') == 'nocolor': 45 | self.stylize = lambda msg, resp: msg 46 | else: 47 | self.stylize = functools.partial(colorize, color_style()) 48 | 49 | def now(self): 50 | """Override to return date in runserver's format. 51 | """ 52 | return time.strftime(r'[%d/%b/%Y:%H:%M:%S]') 53 | 54 | def make_access_message(self, resp, req, environ, request_time): 55 | safe_atoms = self.atoms_wrapper_class( 56 | self.atoms(resp, req, environ, request_time), 57 | ) 58 | return self.stylize(self.cfg.access_log_format % safe_atoms, resp) 59 | 60 | def access(self, resp, req, environ, request_time): 61 | """Override to apply styling on access logs. 62 | 63 | This duplicates a large portion of `gunicorn.glogging.Logger.access`, 64 | only adding 65 | """ 66 | if not (self.cfg.accesslog or self.cfg.logconfig or self.cfg.syslog): 67 | return 68 | 69 | msg = self.make_access_message(resp, req, environ, request_time) 70 | try: 71 | self.access_log.info(msg) 72 | except: 73 | self.error(traceback.format_exc()) 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | You can contribute in many ways: 9 | 10 | Types of Contributions 11 | ---------------------- 12 | 13 | Report Bugs 14 | ~~~~~~~~~~~ 15 | 16 | Report bugs at https://github.com/uranusjr/django-gunicorn/issues. 17 | 18 | If you are reporting a bug, please include: 19 | 20 | * Your operating system name and version. 21 | * Any details about your local setup that might be helpful in troubleshooting. 22 | * Detailed steps to reproduce the bug. 23 | 24 | Fix Bugs 25 | ~~~~~~~~ 26 | 27 | Look through the GitHub issues for bugs. Anything tagged with "bug" 28 | is open to whoever wants to implement it. 29 | 30 | Implement Features 31 | ~~~~~~~~~~~~~~~~~~ 32 | 33 | Look through the GitHub issues for features. Anything tagged with "feature" 34 | is open to whoever wants to implement it. 35 | 36 | Write Documentation 37 | ~~~~~~~~~~~~~~~~~~~ 38 | 39 | django-gunicorn could always use more documentation, whether as part of the 40 | official django-gunicorn docs, in docstrings, or even on the web in blog posts, 41 | articles, and such. 42 | 43 | Submit Feedback 44 | ~~~~~~~~~~~~~~~ 45 | 46 | The best way to send feedback is to file an issue at https://github.com/uranusjr/django-gunicorn/issues. 47 | 48 | If you are proposing a feature: 49 | 50 | * Explain in detail how it would work. 51 | * Keep the scope as narrow as possible, to make it easier to implement. 52 | * Remember that this is a volunteer-driven project, and that contributions 53 | are welcome :) 54 | 55 | Get Started! 56 | ------------ 57 | 58 | Ready to contribute? Here's how to set up `django-gunicorn` for local development. 59 | 60 | 1. Fork the `django-gunicorn` repo on GitHub. 61 | 2. Clone your fork locally:: 62 | 63 | $ git clone git@github.com:your_name_here/django-gunicorn.git 64 | 65 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 66 | 67 | $ mkvirtualenv django-gunicorn 68 | $ cd django-gunicorn/ 69 | $ python setup.py develop 70 | 71 | 4. Create a branch for local development:: 72 | 73 | $ git checkout -b name-of-your-bugfix-or-feature 74 | 75 | Now you can make your changes locally. 76 | 77 | 5. When you're done making changes, check that your changes pass flake8 and the 78 | tests:: 79 | 80 | $ flake8 djgunicorn tests 81 | 82 | To get flake8 and tox, just pip install them into your virtualenv. 83 | 84 | 6. Commit your changes and push your branch to GitHub:: 85 | 86 | $ git add . 87 | $ git commit -m "Your detailed description of your changes." 88 | $ git push origin name-of-your-bugfix-or-feature 89 | 90 | 7. Submit a pull request through the GitHub website. 91 | 92 | Pull Request Guidelines 93 | ----------------------- 94 | 95 | Before you submit a pull request, check that it meets these guidelines: 96 | 97 | 1. The pull request should include tests. 98 | 2. If the pull request adds functionality, the docs should be updated. Put 99 | your new functionality into a function with a docstring, and add the 100 | feature to the list in README.rst. 101 | 3. The pull request should work for Python 2.7, 3.3+, and for PyPy. 102 | --------------------------------------------------------------------------------