101对双生儿's Blog

Not pleased by external gains. Not saddened by personal losses.

0%

Deploying PyCharm-Created Django Project on Heroku

I previously hosted two of my Django projects, NoteShare and Anime, on IBM Cloud. Now, I have decided to switch to Heroku, but the migration is not that straightforward. This article introduces how to deploy a Django project, created by PyCharm, on Heroku.

Why I Made This Switch

IBM Cloud, previously called IBM Bluemix, is a Platform-as-a-Service (PaaS) based on Cloud Foundry. It provides free Lite accounts, but the limitations of those free accounts are super inconvenient:

  • After 10 days of no development activity, your apps go to sleep.
  • After 30 days of no development activity, your service instances with Lite plans are deleted.

Having these rules means that I have to frequently update my projects even if I don't need to. More interestingly, once an app goes to sleep, I must either push new code or log into the console to re-activate it. Therefore, I decided to switch to Heroku's free plan. The free plan on Heroku also has the following limitation:

  • If an app has a free web dyno, and that dyno receives no web traffic in a 30-minute period, it will sleep.

However, I can simply re-activate the app by visiting the URL of it. If you really want your app being online during most of the time in a day, you always have other options available. (By the way, it is not possible to make the free dynos always online.)

Differences Between IBM Cloud and Heroku

To me, there are two major differences.

  • Cloud Foundry uses .cfignore to determine which files should be excluded from upload. This file can be different from .gitignore which determines which files should be excluded from the Git repository. This gives me more freedom of maintaining files in a project. For example, I don't want to ruin my repository by adding super large SQLite database file or numerous subtitle files of anime episodes. I don't want to push the credentials of the project to GitHub, too, but I want them available in the cloud. To deal with these cases, I could simply include those files in .gitignore but not in .cfignore in the past. However, on Heroku I don't have that level of control.
  • Heroku's ephemeral filesystem makes it impossible to use file-based SQLite database, as I just explained that SQLite database will not and should not be added to the repository. Therefore, I must switch to some database systems such as Postgres or MySQL, and actually Heroku provides bunch of database add-ons. In addition, I cannot have any dynamically-generated files in the projects.

Therefore, I cannot directly push the projects to Heroku directly. Many changes must be made before doing that.

Setting Up a New Django Project Using PyCharm

Since there are lots of changes to my out-dated projects (which haven't been maintained for a long time since my app on IBM Cloud was deleted) and I forgot the process of setting up Django projects, I decided to deploy a new one using PyCharm. The following tutorial assumes that:

  • A Python environment for Django has been set up;
  • Heroku CLI has been installed and configured;
  • Postgres has been installed locally and a clean database has been created;
  • A clean Django project has been created in PyCharm and the Git repository has been initialized;
  • A Heroku app has been created.

Preparing the Project

We need at least three files in the top-level project folder.

runtime.txt specifies the Python runtime the Heroku app uses. See a list of supported runtimes here.

Procfile declares what command to execute to start the app. Since I uses Gunicorn as the server, here I just have one line:

1
web: gunicorn django_site.wsgi --log-file -

where django_site is Django project's name and --log-file - means log to stderr.

requirements.txt defines app dependencies. It is recommended to run pip freeze > requirements.txt to create it but at least those are necessary:

1
2
3
4
5
Django>=3.0.0
django-heroku>=0.3.0
gunicorn>=20.0.0
requests>=2.22.0
whitenoise>=4.1.0

Then run pip install -r requirements.txt to install all the dependencies locally. I would maintain packages using conda though.

In addition to those files, a .gitignore is also necessary. It can be easily created on gitignore.io. You can also use mine.

Modifying Project's settings.py

settings.py is located inside django_site folder. It is generated by PyCharm to configure the project. Here are the modifications:

Set SECRET_KEY because it should not be exposed to the public. This is critical if the repository will be pushed to some public code bases such as GitHub. So first, mark down the secret key. Then run heroku config:set SECRET_KEY=<secret_key> to set the environment variable SECRET_KEY on Heroku. Also, add this environment variable to any PyCharm configurations. Finally, change the code to SECRET_KEY = os.environ.get('SECRET_KEY') so that the app will read the key from the environment variable.

Set DEBUG = False and DEBUG_PROPAGATE_EXCEPTIONS = True.

Set ALLOWED_HOSTS where django-site is the name of the Heroku app:

1
2
3
4
5
6
7
ALLOWED_HOSTS = [
'0.0.0.0',
'127.0.0.1',
'[::1]',
'.localhost',
'django-site.herokuapp.com',
]

Add middleware WhiteNoise by

1
2
3
4
5
6
7
8
9
10
11
12
13
INSTALLED_APPS = [
'whitenoise.runserver_nostatic',
'django.contrib.staticfiles',
# ...
]

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
# ...
]

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

Set database connection. In order not to expose database URLs in the code, first add import dj_database_url to the very beginning of the file, then after DATABASES = { ... }, add the following code:

1
2
3
if 'DATABASE_URL' in os.environ:
# Configure Django for DATABASE_URL environment variable.
DATABASES['default'] = dj_database_url.config(conn_max_age=600)

And don't forget to add DATABASE_URL environment variable to any PyCharm configurations with value postgresql://localhost:5432/django_site if django_site is the name of the local Postgres database.

Set static asset variables. For example:

1
2
3
4
5
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]

Make sure that staticfiles and static folders exist in the top-level folder and static folder is not empty (i.e., containing at least one file). Also run heroku config:set DEBUG_COLLECTSTATIC=1 to simplify debugging collectstatic on Heroku.

Set media file variables:

1
2
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

Run python manage.py migrate to apply migrations.

Setting Up a New Django Application

A Django project can have multiple Django applications. When PyCharm creates the project, an application can be created at the same time. The key things here are coding app/urls.py, app/models,py and app/views.py, designing templates and adding static files to app/static/app folder. Don't forget to add the application to django_site/settings.py and django_site/urls.py.

When done, run the following commands to migrate schema changes and load initial data into the local database if the fixture is located at app/fixtures/app.json:

1
2
3
4
python manage.py makemigrations
python manage.py migrate
python manage.py loaddata app
python manage.py collectstatic

Pushing the Project to Heroku

We need to push both the local database and the local code repository to Heroku. I use Heroku Postgres add-on and have attached it to the Heroku app, so I just need to do:

1
2
3
heroku git:remote -a django-site
heroku pg:push postgresql://localhost:5432/django_site <postgresql_addon>
git push heroku master

References

  1. Harman Deep Singh, "Deploying Django App on Heroku with Postgres as Backend," Medium. [Online]. Available: https://medium.com/%40hdsingh13/b2f3194e8a43.