Getting Started

Part 1, Chapter 3


Start by creating a new project directory called "taxi-app" to hold both the client and server applications.

$ mkdir taxi-app && cd taxi-app

Then, within "taxi-app", create a new virtual environment to isolate our project's dependencies:

$ mkdir server && cd server
$ python3.7 -m venv env
$ source env/bin/activate
(env)$

The above commands may differ depending on your OS as well as your Python virtual environment tool (i.e., venv, virtualenvwrapper, Pipenv).

Install Django, Django REST Framework, Django Channels, channel_redis, pytest-asyncio, pytest-django, and Pillow, and then create a new Django project and app:

(env)$ pip install \
       channels==2.3.1 \
       channels-redis==2.4.1 \
       Django==2.2.8 \
       djangorestframework==3.10.3 \
       djangorestframework-simplejwt==4.4.0 \
       Pillow==6.2.1 \
       psycopg2-binary==2.8.4 \
       pytest-asyncio==0.10.0 \
       pytest-django==3.7.0
(env)$ django-admin.py startproject taxi
(env)$ cd taxi
(env)$ python manage.py startapp trips

Next, download and install PostgreSQL using the method that works best for your system. Depending on your operating system and preferred installation, starting PostgreSQL may involve running a command in your terminal or starting an application. Make sure that PostgreSQL is running on its default port, 5432. We'll use this port to connect Django to the database.

Next, download and install Redis. In a new terminal window start the Redis server and make sure that it is running on its default port, 6379. The port number will be important when we tell Django how to communicate with Redis.

$ redis-server

If you're on a Mac, we recommend using Homebrew for both PostgreSQL and Redis:

$ brew install postgresql
$ brew services start postgresql
$ brew install redis
$ brew services start redis

Connect to Postgres using the psql client.

$(env) psql -U postgres

Create a new database and user with the following commands:

postgres=# CREATE USER taxi WITH SUPERUSER CREATEDB CREATEROLE PASSWORD 'taxi';
postgres=# CREATE DATABASE taxi OWNER taxi;

Exit psql by typing \q and hitting the RETURN button.

Then switch back to your original terminal window. Complete our project's setup by updating INSTALLED_APPS in the settings.py file within your code editor of choice:

# taxi/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.postgres', # new
    'django.contrib.staticfiles',
    'rest_framework', # new
    'trips', # new
]

# changed
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.getenv('PGDATABASE'),
        'USER': os.getenv('PGUSER'),
        'PASSWORD': os.getenv('PGPASSWORD'),
        'HOST': os.getenv('PGHOST', 'localhost'),
        'PORT': os.getenv('PGPORT', '5432'),
    }
}

AUTH_USER_MODEL = 'trips.User' # new

Here, alongside the boilerplate Django apps, we've added Django's PostgreSQL package, Django REST Framework, and our own trips app.

We've replaced the default SQLite3 database with PostgreSQL. We're getting the database settings from the environment variables. We'll need to make sure that we set those variables in the same environment where we run the Django server. They can be anything you'd like, but for the purposes of this demo, I've decided to use the following:

export PGDATABASE=taxi
export PGUSER=taxi
export PGPASSWORD=taxi

I've chosen to use the default values for PGHOST and PGPORT.

We've also added an AUTH_USER_MODEL setting to make Django reference a user model of our design instead of the built-in one since we'll need to store more user data than what the standard fields allow.

Since we're creating this project from scratch, defining a custom user model is the right move. If we had made this change later in the project, we would have had to create a supplementary model and link it to the existing default user model.

Create a basic custom user model in the trips/models.py file.

# trips/models.py

from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass

Using this custom User model allows us to add fields to it later.

Then make our first migration:

(env)$ python manage.py makemigrations
Migrations for 'trips':
  trips/migrations/0001_initial.py
    - Create model User

Now we can run the Django management migrate command, which will properly set up our app to use our custom user model. All database tables will be created as well.

(env)$ python manage.py migrate

You should see something similar to:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, trips
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying trips.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying sessions.0001_initial... OK

Ensure all is well by running the server:

(env)$ python manage.py runserver

Then, navigate to http://localhost:8000/ within your browser of choice. You should see:

django landing page

Kill the server by typing Control+C (the Control and "C" key at the same time).

Let's set up the Django admin page next. Create a new superuser account with the createsuperuser management command. Choose a username, an email, and a password when prompted. (If Django deems your password "too common", it will warn you.)

(env)$ python manage.py createsuperuser

Open your trips/admin.py file and replace the contents with the following code.

# trips/admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as DefaultUserAdmin

from .models import User


@admin.register(User)
class UserAdmin(DefaultUserAdmin):
    pass

Visit http://localhost:8000/admin/ in your browser and log in with your superuser credentials. Click on the "Users" link to see your admin user's record. (I chose [email protected] as my username.)

django admin page

Next, configure the CHANNEL_LAYERS by setting a default Redis backend and routing in the settings.py file. This can go on the bottom of the file.

# taxi/settings.py

REDIS_URL = os.getenv('REDIS_URL', 'redis://localhost:6379')

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            'hosts': [REDIS_URL],
        },
    },
}

Then, add Django Channels to the INSTALLED_APPS:

# taxi/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.postgres',
    'django.contrib.staticfiles',
    'channels', # new
    'rest_framework',
    'trips',
]

Try running the server again with python manage.py runserver. You should see the following error:

(env) $ python manage.py runserver
CommandError: You have not set ASGI_APPLICATION, which is needed to run the server.

Create a new file called routing.py within our taxi app:

# taxi/routing.py

from channels.routing import ProtocolTypeRouter

application = ProtocolTypeRouter({})

Are you wondering why we didn't pass any parameters to the ProtocolTypeRouter? According to the documentation, the app initializes an HTTP router by default if one isn't explicitly specified.

If an http argument is not provided, it will default to the Django view system’s ASGI interface, channels.http.AsgiHandler, which means that for most projects that aren’t doing custom long-poll HTTP handling, you can simply not specify an http option and leave it to work the "normal" Django way.

Open your taxi/settings.py file one last time. Find the WSGI_APPLICATION setting and below that line add the following.

# taxi/settings.py

ASGI_APPLICATION = 'taxi.routing.application'

Also, add a new asgi.py file alongside the wsgi.py file.

# taxi/asgi.py

import os
import django

from channels.routing import get_default_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'taxi.settings')
django.setup()
application = get_default_application()

Run with this for now. We'll look at what's happening here in an upcoming lesson.

Make sure the server now runs error-free.

(env)$ python manage.py runserver

There should no longer be any error messages.

Your directory structure should look like the following. (Virtual environment and other system files are excluded.)

.
└── server
    └── taxi
        ├── manage.py
        ├── taxi
        │   ├── __init__.py
        │   ├── asgi.py
        │   ├── routing.py
        │   ├── settings.py
        │   ├── urls.py
        │   └── wsgi.py
        └── trips
            ├── __init__.py
            ├── admin.py
            ├── apps.py
            ├── migrations
            │   ├── 0001_initial.py
            │   └── __init__.py
            ├── models.py
            ├── tests.py
            └── views.py

Before moving on, take a moment to review all that we've done thus far. Try to answer the "why" along with the "what" and "how". For example, why did we use Redis over an in-memory layer for Django Channels?




Mark as Completed