$ python3 -V
>> Python 3.7.3
$ python3 -m django --version
>> 2.2.13
If Django is installed, you should see the version of your installation. If it isn’t, you’ll get an error telling “No module named django”.
$ django-admin startproject [projectname]
Note: You’ll need to avoid naming projects after built-in Python or Django components. In particular, this means you should avoid using names like
django
(which will conflict with Django itself) ortest
(which conflicts with a built-in Python package).
$ cd [projectname]
$ python3 manage.py runserver
Open localhost:8000
and see the app in action!
troubleshooting for this section
Verify your Heroku CLI installation with the heroku --version
command:
$ heroku --version
>> heroku/7.0.0 (darwin-x64) node-v8.0.0
Next, run the heroku login
command.
Now you’re ready to create your first Heroku app:
heroku create
troubleshooting for this section
Verify your git setup with the git --version
command:
$ git --version
>> git version 2.21.0 (Apple Git-122.2)
troubleshooting for this section
Before we can push our Django app to Heroku, Heroku needs a little more information on how to run the app.
Specifically, Heroku needs 6 changes to our out-of-the-box Django app:
- Gunicorn
- Procfile
- django-heroku
- STATIC_ROOT / PROJECT_ROOT in settings.py
- requirements.txt
- runtime.txt
Gunicorn is an open-source web server for Python. It allows Heroku to deploy our application across various “workers.” In your project’s directory, verify that you have gunicorn:
$ python3
>>> import gunicorn
>>> print(gunicorn.__version__)
20.0.4
>>> quit()
If you don't see it run:
pip3 install gunicorn
In your project’s directory, verify that you have psycopg2:
$ python3
>>> import psycopg2
>>> print(psycopg2.__version__)
2.7.5 (dt dec pq3 ext lo64)
>>> quit()
If you don't see it run:
$ pip3 install psycopg2
If pip3 install psycopg2
isn't working, try pip3 install psycopg2==2.7.5
A Procfile is something unique to Heroku. It’s a file in your project’s root directory that tells Heroku how your app should start and run.
In our case, we’ll use our Procfile to tell Heroku to run a Gunicorn server.
The good news is Django comes with out-of-the-box support for Gunicorn servers, because they both follow the conventions of WSGI.
In the Procfile, we’ll tell Heroku to start a Gunicorn server and then point that server to our Django project’s default WSGI interface.
In [projectname]/
, run the following command to create the Procfile:
echo 'web: gunicorn [projectname].wsgi' > Procfile
You’ll need to replace [projectname] with your project name
Django is a pretty popular framework, so Heroku has created a module called django-heroku that helps with settings, testing, and logging automatically.
To install it, make sure you’re in the same folder as manage.py
then:
$ python3
>>> import django_heroku
>>> quit()
If you see an error like ModuleNotFoundError: No module named 'django_heroku'
, then run:
$ pip3 install django-heroku
With the module successfully installed, we can now add it to our Django project’s settings.py
.
Open settings.py
.
At the top of settings.py
, import the module. Then, at the very bottom, call it:
import django_heroku
...# All of your settings here...
django_heroku.settings(locals())
Save settings.py
, but don’t close it. We have more changes to make.
Search your settings.py
for an environment variable called STATIC_URL
.
Next to that setting, we’ll also need to give Heroku more context about where static files (images, scripts, etc) are stored.
We’ll add two new variables, STATIC_ROOT
and PROJECT_ROOT
, in addition to STATIC_URL
. That section of settings.py
should now look like this:
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static')
WhiteNoise helps Django manage static files (images, scripts, etc) to load your site faster. Edit your settings.py
file and add WhiteNoise to the MIDDLEWARE list. The WhiteNoise middleware should be placed directly after the Django SecurityMiddleware (if you are using it) and before all other middleware:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
# ...
]
That’s it – WhiteNoise will now serve your static files (you can confirm it’s working using the steps below).
Django has a variable that lets it know where to store and retrieve static files. Let’s point it to WhiteNoise.
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
Add it above in the same place as your STATIC_URL
and STATIC_ROOT
variables.
Next, we need to tell Heroku about all the packages we’ve installed:
- dj-database-url
- Django
- django-heroku
- gunicorn
- psycopg2
- psycopg2-binary
- pytz
- whitenoise
Make sure you’re in the [project_name]
directory, then:
pip3 freeze > requirements.txt
Check out requirements.txt
to make sure it looks right:
dj-database-url==0.5.0 (we'll add this later)
Django==2.2.13
django-heroku==0.3.1
gunicorn==20.0.4
psycopg2==2.7.5
psycopg2-binary==2.8.5
python-dotenv==0.13.0 (we'll add this later)
pytz==2019.3
whitenoise==4.1.4
As you build your Django project, chances are you’ll find some other module you need. If so, don’t forget to pip3 freeze > requirements.txt
whenever you deploy those changes.
The final thing we need to do before deploying is tell Heroku what version of Python to use.
$ echo 'python-3.7.3' > runtime.txt
Do this in the same folder as manage.py
:
$ git init
$ git add .
$ touch .gitignore
Now open .gitignore
with your favorite text editor and paste in the following information about files Git should ignore:
### Django ###
*.log
*.pot
*.pyc
__pycache__/
local_settings.py
Now, you won’t bog down the deployment process with your log and cache files. We’ll keep Heroku nice and clean. Below, we’ll add db.sqlite
to this list because we’ll be using a different database on Heroku.
$ git commit -am "init"
$ git push heroku master
You can visit this page. Don't initialize it with a README
$ echo "# test" >> README.md
$ git remote add origin https://github.com/chaudh19/[NAME OF REPO].git
$ git push -u origin master
The key thing we’ll be doing here is setting DATABASE_URL
to the Heroku-provided variable when we’re on Heroku. When we’re working locally, we’ll use a local file — .env
— to set DATABASE_URL
to point to SQLite. That way, any time we use the DATABASE_URL
variable it will point to the correct database based on the environment.
pip3 install python-dotenv
This installs python-dotenv
and also a related module called dj-database-url
.
Add the new modules to your requirements.txt
:
pip3 freeze > requirements.txt
We’ll use a file called .env
to tell Django to use SQLite when running locally. To create .env
and have it point Django to your SQLite database:
echo 'DATABASE_URL=sqlite:///db.sqlite3' > .env
Now, we don’t want .env to make it to Heroku, because .env is the part of our app that points to SQLite and Heroku doesn’t like SQLite.
So, we need git to ignore .env when pushing to Heroku. Run:
Add .env
to .gitignore
Now that we have a .env
file, we need to tell Django to use it. Hence why we downloaded the python-dotenv
plugin earlier.
In your project’s settings.py
make the following changes to get Django to use .env
.
import dj_database_url
import dotenv
# This line should already exist in your settings.py
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# This is new:
dotenv_file = os.path.join(BASE_DIR, ".env")
if os.path.isfile(dotenv_file):
dotenv.load_dotenv(dotenv_file)
Since .env
won’t exist on Heroku, dotenv.load_dotenv(dotenv_file)
will never get called on Heroku and Heroku will proceed to try to find its own database — Postgres.
Currently your settings.py
has this:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
Change it to this:
DATABASES = {}
DATABASES['default'] = dj_database_url.config(conn_max_age=600)
The idea here is to clear the DATABASES
variable and then set the 'default'
key using the dj_database_url
module. This module uses Heroku’s DATABASE_URL
variable if it’s on Heroku, or it uses the DATABASE_URL
we set in the .env
file if we’re working locally.
If you ran the Django application as specified above, you might get an error when working locally because the dj_database_url
module wants to log in with SSL. Heroku Postgres requires SSL, but SQLite doesn’t need or expect it.
So, we’ll use a hacky workaround to get dj_database_url to forget about SSL at the last second.
As the very last line in your settings.py
, add:
# This should already be in your settings.py
django_heroku.settings(locals())
# This is new
del DATABASES['default']['OPTIONS']['sslmode']
Locally:
$ python3 manage.py makemigrations
$ python3 manage.py migrate
$ python3 manage.py createsuperuser
$ python3 manage.py runserver
And on heroku
$ git commit -am "new db changes"
$ git push heroku master
$ heroku run python3 manage.py migrate
$ heroku run python3 manage.py createsuperuser
$ heroku open
python3 manage.py startapp [app_name]
Let’s write the first view. Open the file [app_name]/views.py and put the following Python code in it:
in [app_name]/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the index.")
This is the simplest view possible in Django. To call the view, we need to map it to a URL - and for this we need a URLconf.
To create a URLconf in the polls directory, create a file called urls.py
in the [app_name]
folder.
In [app_name]/urls.py
file include the following code:
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
The next step is to point the root URLconf at the [project_name].urls module. In [project_name]/urls.py, add an import for django.urls.include and insert an include() in the urlpatterns list, so you have:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('[app_name]/', include('[app_name].urls')),
path('admin/', admin.site.urls),
]
Add to installed apps
INSTALLED_APPS = [
'polls.apps.PollsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
main/templates/main/index.html
Shortcuts to make it easier
$ git config --global alias.a '!git add -A && git commit -m'
$ git config alias.pushall '!git push origin master && git push heroku master'
When ready to commit + push
$ git a "commit message"
$ git pushall
$ heroku logs --tail
sources: