How to install Django on Hostgator?

12,306

Django on HostGator shared is possible. It is just a little tricky as Django dropped support for FastCGI.

  • Use HostGator's own instructions for setting up Django. http://support.hostgator.com/articles/django-with-fastcgi#shared-reseller

  • But then before it will run you need to patch support for FastCGI back into Django.

    • Luckily Django removed FastCGI all in one commit: https://github.com/django/django/commit/41f0d3d3bc8b0a6831530e1176c6415f9ba45b0b.patch
    • This patch needs to be reversed and ported to point to the release you have. This kind of requires checking out the django source project and playing with git.
    • Don't worry about deconflicting the docs and tests subfolders as these need to be left out of the re-FastCGI-ification patch anyway.
    • Once armed with the patch file for the version of Django installed in HostGator, use git apply to re-inject FastCGI support into the Django code located in your virtual environment ./mydjango/lib/python2.7/site-packages/django
    • At the end is the patch file I ended up with for Django version 1.10.1.
  • Use PyMySQL instead of the standard mysql library. PyMySQL is a pure python mysql client needed as Host Gator doesn't provide gcc.
    • Install the library: pip install PyMySQL
    • Monkey patch it in in manage.py and index.fcgi by prepending the following at the beginning of both files just after the #! python line:
#monkey patch importing pymysql
try:
    import pymysql
    pymysql.install_as_MySQLdb()
except ImportError:
    pass

#Re-fast-cgi-ification-patch
From 51278286d278568dfe89263cad624204c01fa065 Mon Sep 17 00:00:00 2001
From: Joshua 
Date: Tue, 6 Sep 2016 18:19:20 -0400
Subject: django folder only

Revert "Removed FastCGI support per deprecation timeline; refs #20766."

This reverts commit 41f0d3d3bc8b0a6831530e1176c6415f9ba45b0b.

# Conflicts:
#   django/core/management/__init__.py
#   docs/howto/deployment/wsgi/index.txt
#   docs/man/django-admin.1
#   docs/ref/django-admin.txt
#   tests/.coveragerc

# Conflicts:
#   docs/man/django-admin.1
#   docs/ref/django-admin.txt

diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 576ff6b..8d1461f 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -415,8 +415,8 @@ X_FRAME_OPTIONS = 'SAMEORIGIN'
 USE_X_FORWARDED_HOST = False
 USE_X_FORWARDED_PORT = False

-# The Python dotted path to the WSGI application that Django's internal server
-# (runserver) will use. If `None`, the return value of
+# The Python dotted path to the WSGI application that Django's internal servers
+# (runserver, runfcgi) will use. If `None`, the return value of
 # 'django.core.wsgi.get_wsgi_application' is used, thus preserving the same
 # behavior as previous versions of Django. Otherwise this should point to an
 # actual WSGI application object.
diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
index 739dc25..2aa84d7 100644
--- a/django/core/management/__init__.py
+++ b/django/core/management/__init__.py
@@ -251,8 +251,13 @@ class ManagementUtility(object):
         # special case: the 'help' subcommand has no options
         elif cwords[0] in subcommands and cwords[0] != 'help':
             subcommand_cls = self.fetch_command(cwords[0])
+            # special case: 'runfcgi' stores additional options as
+            # 'key=value' pairs
+            if cwords[0] == 'runfcgi':
+                from django.core.servers.fastcgi import FASTCGI_OPTIONS
+                options.extend((k, 1) for k in FASTCGI_OPTIONS)
             # special case: add the names of installed apps to options
-            if cwords[0] in ('dumpdata', 'sqlmigrate', 'sqlsequencereset', 'test'):
+            elif cwords[0] in ('dumpdata', 'sqlmigrate', 'sqlsequencereset', 'test'):
                 try:
                     app_configs = apps.get_app_configs()
                     # Get the last part of the dotted path as the app name.
diff --git a/django/core/management/commands/runfcgi.py b/django/core/management/commands/runfcgi.py
new file mode 100644
index 0000000..98de800
--- /dev/null
+++ b/django/core/management/commands/runfcgi.py
@@ -0,0 +1,32 @@
+import argparse
+import warnings
+
+from django.core.management.base import BaseCommand
+from django.utils.deprecation import RemovedInDjango19Warning
+
+
+class Command(BaseCommand):
+    help = "Runs this project as a FastCGI application. Requires flup."
+
+    def add_arguments(self, parser):
+        parser.add_argument('args', nargs=argparse.REMAINDER,
+            help='Various KEY=val options.')
+
+    def handle(self, *args, **options):
+        warnings.warn(
+            "FastCGI support has been deprecated and will be removed in Django 1.9.",
+            RemovedInDjango19Warning)
+
+        from django.conf import settings
+        from django.utils import translation
+        # Activate the current language, because it won't get activated later.
+        try:
+            translation.activate(settings.LANGUAGE_CODE)
+        except AttributeError:
+            pass
+        from django.core.servers.fastcgi import runfastcgi
+        runfastcgi(args)
+
+    def usage(self, subcommand):
+        from django.core.servers.fastcgi import FASTCGI_HELP
+        return FASTCGI_HELP
diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
index 60fc09a..5560438 100644
--- a/django/core/servers/basehttp.py
+++ b/django/core/servers/basehttp.py
@@ -34,11 +34,13 @@ def get_internal_wsgi_application():
     this will be the ``application`` object in ``projectname/wsgi.py``.

     This function, and the ``WSGI_APPLICATION`` setting itself, are only useful
-    for Django's internal server (runserver); external WSGI servers should just
-    be configured to point to the correct application object directly.
+    for Django's internal servers (runserver, runfcgi); external WSGI servers
+    should just be configured to point to the correct application object
+    directly.

     If settings.WSGI_APPLICATION is not set (is ``None``), we just return
     whatever ``django.core.wsgi.get_wsgi_application`` returns.
+
     """
     from django.conf import settings
     app_path = getattr(settings, 'WSGI_APPLICATION')
diff --git a/django/core/servers/fastcgi.py b/django/core/servers/fastcgi.py
new file mode 100644
index 0000000..b44473b
--- /dev/null
+++ b/django/core/servers/fastcgi.py
@@ -0,0 +1,187 @@
+"""
+FastCGI (or SCGI, or AJP1.3 ...) server that implements the WSGI protocol.
+
+Uses the flup python package: http://www.saddi.com/software/flup/
+
+This is an adaptation of the flup package to add FastCGI server support
+to run Django apps from Web servers that support the FastCGI protocol.
+This module can be run standalone or from the django-admin / manage.py
+scripts using the "runfcgi" directive.
+
+Run with the extra option "help" for a list of additional options you can
+pass to this server.
+"""
+
+import importlib
+import os
+import sys
+
+__version__ = "0.1"
+__all__ = ["runfastcgi"]
+
+FASTCGI_OPTIONS = {
+    'protocol': 'fcgi',
+    'host': None,
+    'port': None,
+    'socket': None,
+    'method': 'fork',
+    'daemonize': None,
+    'workdir': '/',
+    'pidfile': None,
+    'maxspare': 5,
+    'minspare': 2,
+    'maxchildren': 50,
+    'maxrequests': 0,
+    'debug': None,
+    'outlog': None,
+    'errlog': None,
+    'umask': None,
+}
+
+FASTCGI_HELP = r"""
+  Run this project as a fastcgi (or some other protocol supported
+  by flup) application. To do this, the flup package from
+  http://www.saddi.com/software/flup/ is required.
+
+   runfcgi [options] [fcgi settings]
+
+Optional Fcgi settings: (setting=value)
+  protocol=PROTOCOL    fcgi, scgi, ajp, ... (default %(protocol)s)
+  host=HOSTNAME        hostname to listen on.
+  port=PORTNUM         port to listen on.
+  socket=FILE          UNIX socket to listen on.
+  method=IMPL          prefork or threaded (default %(method)s).
+  maxrequests=NUMBER   number of requests a child handles before it is
+                       killed and a new child is forked (0 = no limit).
+  maxspare=NUMBER      max number of spare processes / threads (default %(maxspare)s).
+  minspare=NUMBER      min number of spare processes / threads (default %(minspare)s).
+  maxchildren=NUMBER   hard limit number of processes / threads (default %(maxchildren)s).
+  daemonize=BOOL       whether to detach from terminal.
+  pidfile=FILE         write the spawned process-id to this file.
+  workdir=DIRECTORY    change to this directory when daemonizing (default %(workdir)s).
+  debug=BOOL           set to true to enable flup tracebacks.
+  outlog=FILE          write stdout to this file.
+  errlog=FILE          write stderr to this file.
+  umask=UMASK          umask to use when daemonizing, in octal notation (default 022).
+
+Examples:
+  Run a "standard" fastcgi process on a file-descriptor
+  (for Web servers which spawn your processes for you)
+    $ manage.py runfcgi method=threaded
+
+  Run a scgi server on a TCP host/port
+    $ manage.py runfcgi protocol=scgi method=prefork host=127.0.0.1 port=8025
+
+  Run a fastcgi server on a UNIX domain socket (posix platforms only)
+    $ manage.py runfcgi method=prefork socket=/tmp/fcgi.sock
+
+  Run a fastCGI as a daemon and write the spawned PID in a file
+    $ manage.py runfcgi socket=/tmp/fcgi.sock method=prefork \
+        daemonize=true pidfile=/var/run/django-fcgi.pid
+
+""" % FASTCGI_OPTIONS
+
+
+def fastcgi_help(message=None):
+    print(FASTCGI_HELP)
+    if message:
+        print(message)
+    return False
+
+
+def runfastcgi(argset=[], **kwargs):
+    options = FASTCGI_OPTIONS.copy()
+    options.update(kwargs)
+    for x in argset:
+        if "=" in x:
+            k, v = x.split('=', 1)
+        else:
+            k, v = x, True
+        options[k.lower()] = v
+
+    if "help" in options:
+        return fastcgi_help()
+
+    try:
+        import flup  # NOQA
+    except ImportError as e:
+        sys.stderr.write("ERROR: %s\n" % e)
+        sys.stderr.write("  Unable to load the flup package.  In order to run django\n")
+        sys.stderr.write("  as a FastCGI application, you will need to get flup from\n")
+        sys.stderr.write("  http://www.saddi.com/software/flup/   If you've already\n")
+        sys.stderr.write("  installed flup, then make sure you have it in your PYTHONPATH.\n")
+        return False
+
+    flup_module = 'server.' + options['protocol']
+
+    if options['method'] in ('prefork', 'fork'):
+        wsgi_opts = {
+            'maxSpare': int(options["maxspare"]),
+            'minSpare': int(options["minspare"]),
+            'maxChildren': int(options["maxchildren"]),
+            'maxRequests': int(options["maxrequests"]),
+        }
+        flup_module += '_fork'
+    elif options['method'] in ('thread', 'threaded'):
+        wsgi_opts = {
+            'maxSpare': int(options["maxspare"]),
+            'minSpare': int(options["minspare"]),
+            'maxThreads': int(options["maxchildren"]),
+        }
+    else:
+        return fastcgi_help("ERROR: Implementation must be one of prefork or "
+                            "thread.")
+
+    wsgi_opts['debug'] = options['debug'] is not None
+
+    try:
+        module = importlib.import_module('.%s' % flup_module, 'flup')
+        WSGIServer = module.WSGIServer
+    except Exception:
+        print("Can't import flup." + flup_module)
+        return False
+
+    # Prep up and go
+    from django.core.servers.basehttp import get_internal_wsgi_application
+
+    if options["host"] and options["port"] and not options["socket"]:
+        wsgi_opts['bindAddress'] = (options["host"], int(options["port"]))
+    elif options["socket"] and not options["host"] and not options["port"]:
+        wsgi_opts['bindAddress'] = options["socket"]
+    elif not options["socket"] and not options["host"] and not options["port"]:
+        wsgi_opts['bindAddress'] = None
+    else:
+        return fastcgi_help("Invalid combination of host, port, socket.")
+
+    if options["daemonize"] is None:
+        # Default to daemonizing if we're running on a socket/named pipe.
+        daemonize = (wsgi_opts['bindAddress'] is not None)
+    else:
+        if options["daemonize"].lower() in ('true', 'yes', 't'):
+            daemonize = True
+        elif options["daemonize"].lower() in ('false', 'no', 'f'):
+            daemonize = False
+        else:
+            return fastcgi_help("ERROR: Invalid option for daemonize "
+                                "parameter.")
+
+    daemon_kwargs = {}
+    if options['outlog']:
+        daemon_kwargs['out_log'] = options['outlog']
+    if options['errlog']:
+        daemon_kwargs['err_log'] = options['errlog']
+    if options['umask']:
+        daemon_kwargs['umask'] = int(options['umask'], 8)
+
+    if daemonize:
+        from django.utils.daemonize import become_daemon
+        become_daemon(our_home_dir=options["workdir"], **daemon_kwargs)
+
+    if options["pidfile"]:
+        with open(options["pidfile"], "w") as fp:
+            fp.write("%d\n" % os.getpid())
+
+    WSGIServer(get_internal_wsgi_application(), **wsgi_opts).run()
+
+if __name__ == '__main__':
+    runfastcgi(sys.argv[1:])
diff --git a/django/utils/daemonize.py b/django/utils/daemonize.py
new file mode 100644
index 0000000..76f4d5d
--- /dev/null
+++ b/django/utils/daemonize.py
@@ -0,0 +1,62 @@
+import os
+import sys
+
+from . import six
+
+buffering = int(six.PY3)        # No unbuffered text I/O on Python 3 (#20815).
+
+if os.name == 'posix':
+    def become_daemon(our_home_dir='.', out_log='/dev/null',
+                      err_log='/dev/null', umask=0o022):
+        "Robustly turn into a UNIX daemon, running in our_home_dir."
+        # First fork
+        try:
+            if os.fork() > 0:
+                sys.exit(0)     # kill off parent
+        except OSError as e:
+            sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
+            sys.exit(1)
+        os.setsid()
+        os.chdir(our_home_dir)
+        os.umask(umask)
+
+        # Second fork
+        try:
+            if os.fork() > 0:
+                os._exit(0)
+        except OSError as e:
+            sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
+            os._exit(1)
+
+        si = open('/dev/null', 'r')
+        so = open(out_log, 'a+', buffering)
+        se = open(err_log, 'a+', buffering)
+        os.dup2(si.fileno(), sys.stdin.fileno())
+        os.dup2(so.fileno(), sys.stdout.fileno())
+        os.dup2(se.fileno(), sys.stderr.fileno())
+        # Set custom file descriptors so that they get proper buffering.
+        sys.stdout, sys.stderr = so, se
+else:
+    def become_daemon(our_home_dir='.', out_log=None, err_log=None, umask=0o022):
+        """
+        If we're not running under a POSIX system, just simulate the daemon
+        mode by doing redirections and directory changing.
+        """
+        os.chdir(our_home_dir)
+        os.umask(umask)
+        sys.stdin.close()
+        sys.stdout.close()
+        sys.stderr.close()
+        if err_log:
+            sys.stderr = open(err_log, 'a', buffering)
+        else:
+            sys.stderr = NullDevice()
+        if out_log:
+            sys.stdout = open(out_log, 'a', buffering)
+        else:
+            sys.stdout = NullDevice()
+
+    class NullDevice:
+        "A writeable object that writes to nowhere -- like /dev/null."
+        def write(self, s):
+            pass

Share:
12,306
Hiroyuki Nuri
Author by

Hiroyuki Nuri

Updated on June 14, 2022

Comments

  • Hiroyuki Nuri
    Hiroyuki Nuri almost 2 years

    I read on Hostgator that it is possible to install Django on their hosting service, I already contacted the live support and replied that they cannot help with that.

    Has somebody already done it?

  • Hektor
    Hektor over 2 years
    As of 30.9.21 you do not need any specific account to host Django on Hostgator though you do need, as the first answer details, to apply a patch, and to administer the site yourself since Hostgator expressly do not provide support or assistance for Django installs to CGI. As far as on a deciated/VPS platform goes, I am not contradicting your answer since I was simply saying that it IS possible to host Django on their basic plans, but that it might be tricky.